From 98c6c88391e524d6ceca4b827156feaff1e20fdf Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 29 Oct 2021 18:32:13 +0200 Subject: [PATCH 001/200] Completed GetSetComplement --- Moose Development/Moose/Core/Set.lua | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 44194929f..5230b20fe 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -308,17 +308,14 @@ do -- SET_BASE -- @return Core.Set#SET_BASE The set of objects that are in set *B* but **not** in this set *A*. function SET_BASE:GetSetComplement(SetB) - local complement=SET_BASE:New() + local complement = self:GetSetUnion(SetB) + local intersection = self:GetSetIntersection(SetB) - 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 + for _,Object in pairs(intersection.Set) do + complement:Remove(Object.ObjectName,true) end - return intersection + return complement end From 18745158a38701b64c48ca984974845463d64270 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 30 Oct 2021 16:33:14 +0200 Subject: [PATCH 002/200] Speedmax returning 0 not nil --- Moose Development/Moose/Wrapper/Group.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index f1443b2d0..72c9215ab 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -568,12 +568,12 @@ function GROUP:GetSpeedMax() local Units=self:GetUnits() - local speedmax=nil + local speedmax=0 for _,unit in pairs(Units) do local unit=unit --Wrapper.Unit#UNIT local speed=unit:GetSpeedMax() - if speedmax==nil then + if speedmax==0 then speedmax=speed elseif speed Date: Sat, 30 Oct 2021 16:33:18 +0200 Subject: [PATCH 003/200] Speedmax returning 0 not nil --- Moose Development/Moose/Wrapper/Unit.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 3fb557504..098508f56 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -442,7 +442,7 @@ function UNIT:GetSpeedMax() return SpeedMax*3.6 end - return nil + return 0 end --- Returns the unit's max range in meters derived from the DCS descriptors. From c94275cb8b63b759ba6f0a74c06c5101e627f5c2 Mon Sep 17 00:00:00 2001 From: Penecruz <73371761+Penecruz@users.noreply.github.com> Date: Sun, 31 Oct 2021 06:41:58 +1100 Subject: [PATCH 004/200] Airboss V/STOL updates -Add additional Airboss V/STOL carrier HMAS Canberra L02 -Add Waveoff for AV-8B -Add Cut Pass if Land without LSO clearance -Changes to V/STOL groove timings -Stabilise call when over the V/STOL landing spot -larger abeam landing spot margin to allow decelerating to stable abeam and still be cleared to land -Abeam area now extends further aft to allow LSO clearance 45-90 as per NATOPS -Minor document changes --- Moose Development/Moose/Ops/Airboss.lua | 179 ++++++++++++++++-------- 1 file changed, 122 insertions(+), 57 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 6b7045538..515dd4ca7 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -51,7 +51,7 @@ -- -- At the moment, optimized parameters are available for the F/A-18C Hornet (Lot 20) and A-4E community mod as aircraft and the USS John C. Stennis as carrier. -- --- The AV-8B Harrier, the USS Tarawa, USS America and Juan Carlos I are WIP. The AV-8B harrier and the LHA's and LHD can only be used together, i.e. these ships are the only carriers the harrier is supposed to land on and +-- The AV-8B Harrier, the USS Tarawa, USS America, HMAS Canberra and Juan Carlos I are WIP. The AV-8B harrier and the LHA's and LHD can only be used together, i.e. these ships are the only carriers the harrier is supposed to land on and -- no other fixed wing aircraft (human or AI controlled) are supposed to land on these ships. Currently only Case I is supported. Case II/III take slightly different steps from the CVN carrier. -- However, the two Case II/III pattern are very similar so this is not a big drawback. -- @@ -108,9 +108,10 @@ -- * [DCS: F/A-18C Hornet – Episode 16: CASE III Introduction](https://www.youtube.com/watch?v=DvlMHnLjbDQ) -- * [DCS: F/A-18C Hornet Case I Carrier Landing Training Lesson Recording](https://www.youtube.com/watch?v=D33uM9q4xgA) -- --- ### AV-8B Harrier at USS Tarawa +-- ### AV-8B Harrier and V/STOL Operations: -- -- * [Harrier Ship Landing Mission with Auto LSO!](https://www.youtube.com/watch?v=lqmVvpunk2c) +-- * [Updated Airboss V/STOL Features USS Tarawa](https://youtu.be/K7I4pU6j718) -- * [Harrier Practice pattern USS America](https://youtu.be/99NigITYmcI) -- -- === @@ -300,7 +301,7 @@ -- -- Once the aircraft reaches the Initial, the landing pattern begins. The important steps of the pattern are shown in the image above. -- The AV-8B Harrier pattern is very similar, the only differences are as there is no angled deck there is no wake check. from the ninety you wil fly a straight approach offset 26 ft to port (left) of the tram line. --- The aim is to arrive abeam the landing spot in a stable hover at 120 ft with forward speed matched to the boat. From there the LSO will call "cleared to land". You then level cross to the tram line at the designated landing spot at land vertcally. +-- The aim is to arrive abeam the landing spot in a stable hover at 120 ft with forward speed matched to the boat. From there the LSO will call "cleared to land". You then level cross to the tram line at the designated landing spot at land vertcally. When you stabalise over the landing spot LSO will call Stabalise to indicate you are centered at the correct spot. -- -- -- ## CASE III @@ -634,12 +635,12 @@ -- -- Furthermore, we have the cases: -- --- * 2.5 Points **B**: "Bolder", when the player landed but did not catch a wire. +-- * 2.5 Points **B**: "Bolter", when the player landed but did not catch a wire. -- * 2.0 Points **WOP**: "Pattern Wave-Off", when pilot was far away from where he should be in the pattern. -- * 2.0 Points **OWO**: "Own Wave-Off**, when pilot flies past the deck without touching it. -- * 1.0 Points **WO**: "Technique Wave-Off": Player got waved off in the final parts of the groove. -- * 1.0 Points **LIG**: "Long In the Groove", when pilot extents the downwind leg too far and screws up the timing for the following aircraft. --- * 0.0 Points **CUT**: "Cut pass", when player was waved off but landed anyway. +-- * 0.0 Points **CUT**: "Cut pass", when player was waved off but landed anyway. In addition if a V/STOL lands without having been Cleared to Land. -- -- ## Foul Deck Waveoff -- @@ -1298,9 +1299,10 @@ AIRBOSS.AircraftCarrier={ -- @field #string TRUMAN USS Harry S. Truman (CVN-75) [Super Carrier Module] -- @field #string FORRESTAL USS Forrestal (CV-59) [Heatblur Carrier Module] -- @field #string VINSON USS Carl Vinson (CVN-70) [Obsolete] --- @field #string TARAWA USS Tarawa (LHA-1) --- @field #string AMERICA USS America (LHA-6) --- @field #string JCARLOS Juan Carlos I (L61) +-- @field #string TARAWA USS Tarawa (LHA-1) [V/STOL Carrier] +-- @field #string AMERICA USS America (LHA-6) [V/STOL Carrier] +-- @field #string JCARLOS Juan Carlos I (L61) [V/STOL Carrier] +-- @field #string HMAS Canberra (L02) [V/STOL Carrier] -- @field #string KUZNETSOV Admiral Kuznetsov (CV 1143.5) AIRBOSS.CarrierType={ ROOSEVELT="CVN_71", @@ -1313,6 +1315,7 @@ AIRBOSS.CarrierType={ TARAWA="LHA_Tarawa", AMERICA="USS America LHA-6", JCARLOS="L61", + CANBERRA="L02", KUZNETSOV="KUZNECOW", } @@ -1726,7 +1729,7 @@ AIRBOSS.MenuF10Root=nil --- Airboss class version. -- @field #string version -AIRBOSS.version="1.2.0" +AIRBOSS.version="1.2.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -1991,6 +1994,9 @@ function AIRBOSS:New(carriername, alias) elseif self.carriertype==AIRBOSS.CarrierType.JCARLOS then -- Use Juan Carlos parameters. self:_InitJcarlos() + elseif self.carriertype==AIRBOSS.CarrierType.CANBERRA then + -- Use Juan Carlos parameters at this stage --TODO Check primary Landing spot. + self:_InitJcarlos() elseif self.carriertype==AIRBOSS.CarrierType.KUZNETSOV then -- Kusnetsov parameters - maybe... self:_InitStennis() @@ -2082,7 +2088,7 @@ function AIRBOSS:New(carriername, alias) -- Carrier specific. - if self.carrier:GetTypeName()~=AIRBOSS.CarrierType.TARAWA or self.carrier:GetTypeName()~=AIRBOSS.CarrierType.AMERICA or self.carrier:GetTypeName()~=AIRBOSS.CarrierType.JCARLOS then + if self.carrier:GetTypeName()~=AIRBOSS.CarrierType.TARAWA or self.carrier:GetTypeName()~=AIRBOSS.CarrierType.AMERICA or self.carrier:GetTypeName()~=AIRBOSS.CarrierType.JCARLOS or self.carrier:GetTypeName()~=AIRBOSS.CarrierType.CANBERRA then -- Flare wires. local w1=stern:Translate(self.carrierparam.wire1, FB, true) @@ -6886,7 +6892,7 @@ function AIRBOSS:_GetMarshalAltitude(stack, case) p2=Carrier:Translate(UTILS.NMToMeters(1.5), hdg) -- Tarawa,LHA,LHD Delta patterns. - if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS then + if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then -- Pattern is directly overhead the carrier. p1=Carrier:Translate(UTILS.NMToMeters(1.0), hdg+90) @@ -7714,6 +7720,8 @@ function AIRBOSS:_InitPlayer(playerData, step) playerData.wofd=false playerData.owo=false playerData.boltered=false + playerData.hover=false + playerData.stable=false playerData.landed=false playerData.Tlso=timer.getTime() playerData.Tgroove=nil @@ -8731,7 +8739,7 @@ function AIRBOSS:OnEventLand(EventData) self:T(self.lid..text) -- Check carrier type. - if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS then + if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then -- Power "Idle". self:RadioTransmission(self.LSORadio, self.LSOCall.IDLE, false, 1, nil, true) @@ -8766,7 +8774,7 @@ function AIRBOSS:OnEventLand(EventData) -- AI unit landed -- -------------------- - if self.carriertype~=AIRBOSS.CarrierType.TARAWA or self.carriertype~=AIRBOSS.CarrierType.AMERICA or self.carriertype~=AIRBOSS.CarrierType.JCARLOS then + if self.carriertype~=AIRBOSS.CarrierType.TARAWA or self.carriertype~=AIRBOSS.CarrierType.AMERICA or self.carriertype~=AIRBOSS.CarrierType.JCARLOS or self.carriertype~=AIRBOSS.CarrierType.CANBERRA then -- Coordinate at landing event local coord=EventData.IniUnit:GetCoordinate() @@ -9675,6 +9683,8 @@ function AIRBOSS:_Bullseye(playerData) -- LSO expect spot 5 or 7.5 call if playerData.actype==AIRBOSS.AircraftCarrier.AV8B and self.carriertype==AIRBOSS.CarrierType.JCARLOS then + self:RadioTransmission(self.LSORadio, self.LSOCall.EXPECTSPOT5, nil, nil, nil, true) + elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B and self.carriertype==AIRBOSS.CarrierType.CANBERRA then self:RadioTransmission(self.LSORadio, self.LSOCall.EXPECTSPOT5, nil, nil, nil, true) elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B then self:RadioTransmission(self.LSORadio, self.LSOCall.EXPECTSPOT75, nil, nil, nil, true) @@ -9813,7 +9823,7 @@ function AIRBOSS:_CheckForLongDownwind(playerData) local limit=UTILS.NMToMeters(-1.6) -- For the tarawa, other LHA and LHD we give a bit more space. - if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS then + if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then limit=UTILS.NMToMeters(-2.0) end @@ -9861,7 +9871,9 @@ function AIRBOSS:_Abeam(playerData) -- LSO expect spot 5 or 7.5 call if playerData.actype==AIRBOSS.AircraftCarrier.AV8B and self.carriertype==AIRBOSS.CarrierType.JCARLOS then self:RadioTransmission(self.LSORadio, self.LSOCall.EXPECTSPOT5, false, 5, nil, true) - elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B then + elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B and self.carriertype==AIRBOSS.CarrierType.CANBERRA then + self:RadioTransmission(self.LSORadio, self.LSOCall.EXPECTSPOT5, false, 5, nil, true) + elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B then self:RadioTransmission(self.LSORadio, self.LSOCall.EXPECTSPOT75, false, 5, nil, true) end @@ -9898,7 +9910,7 @@ function AIRBOSS:_Ninety(playerData) self:_PlayerHint(playerData) -- Next step: wake. - if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS then + if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then -- Harrier has no wake stop. It stays port of the boat. self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.FINAL) else @@ -10155,8 +10167,8 @@ function AIRBOSS:_Groove(playerData) -- Speed difference. local dv=math.abs(vplayer-vcarrier) - -- Stable when speed difference < 10 km/h. - local stable=dv<10 + -- Stable when speed difference < 20 km/h. + local stable=dv<20 -- Check if player is inside the zone. if playerData.unit:IsInZone(ZoneALS) and stable then @@ -10166,6 +10178,9 @@ function AIRBOSS:_Groove(playerData) -- Next step: Level cross. self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.GROOVE_LC) + -- Set Stable Hover + playerData.stable=true + playerData.hover=true end elseif rho<=RAR and playerData.step==AIRBOSS.PatternStep.GROOVE_LC then @@ -10185,13 +10200,14 @@ function AIRBOSS:_Groove(playerData) -- Speed difference. local dv=math.abs(vplayer-vcarrier) - -- Stable when v<7.5 km/h. - local stable=dv<7.5 + -- Stable when v<10 km/h. + local stable=dv<10 - -- Radio Transmission "Cleared to land" once the aircraft is inside the zone. - if playerData.unit:IsInZone(ZoneLS) and stable and playerData.warning==false then - self:RadioTransmission(self.LSORadio, self.LSOCall.STABILIZED, nil, nil, nil, true) - playerData.warning=true + -- Radio Transmission "Stabilized" once the aircraft has been cleared to cross and is over the Landing Spot and stable. + if playerData.unit:IsInZone(ZoneLS) and stable and playerData.stable==true then + self:RadioTransmission(self.LSORadio, self.LSOCall.STABILIZED, nil, nil, nil, false) + playerData.stable=false + playerData.warning=true end -- We keep it in this step until landed. @@ -10224,8 +10240,25 @@ function AIRBOSS:_Groove(playerData) -- Nothing else necessary. return end - - end + end + + -- Long V/STOL groove time Wave Off over 75 seconds to IC - TOPGUN level Only. --pene testing (WIP) + + --if rho>=RAR and rho<=RIC and not playerData.waveoff and playerData.difficulty==AIRBOSS.Difficulty.HARD and playerData.actype== AIRBOSS.AircraftCarrier.AV8B then + -- Get groove time + --local vSlow=groovedata.time + -- If too slow wave off. + --if vSlow >75 then + + -- LSO Wave off! + --self:RadioTransmission(self.LSORadio, self.LSOCall.WAVEOFF, nil, nil, nil, true) + --playerData.Tlso=timer.getTime() + + -- Player was waved Off + --playerData.waveoff=true + --return + --end + --end -- Groovedata step. groovedata.Step=playerData.step @@ -10395,11 +10428,10 @@ function AIRBOSS:_CheckWaveOff(glideslopeError, lineupError, AoA, playerData) -- For the harrier, we allow a bit more room. if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then - glMax= 4.0 - glMin=-3.0 - luAbs= 5.0 - -- No waveoff for harrier pilots at the moment. - return false + glMax= 2.6 + glMin=-2.0 + luAbs= 4.1 -- Testing Pene (WIP) needs feedback to tighten up tolerences. + end -- Too high or too low? @@ -10423,9 +10455,10 @@ function AIRBOSS:_CheckWaveOff(glideslopeError, lineupError, AoA, playerData) waveoff=true end - -- Too slow or too fast? Only for pros. - if playerData.difficulty==AIRBOSS.Difficulty.HARD then - -- Get aircraft specific AoA values + -- Too slow or too fast? Only for pros. + + if playerData.difficulty==AIRBOSS.Difficulty.HARD and playerData.actype~=AIRBOSS.AircraftCarrier.AV8B then + -- Get aircraft specific AoA values. Not for AV-8B due to transition to Stable Hover. local aoaac=self:_GetAircraftAoA(playerData) -- Check too slow or too fast. if AoA 91 Seconds: SLOW V/STOL (Early hover stop selection) +-- * < 55 seconds: Fast V/STOL +-- * < 75 seconds: OK V/STOL +-- * > 76 Seconds: SLOW V/STOL (Early hover stop selection) +-- +-- If you manage to be between 60.0 and 65.0 seconds in the AV-8B, you will even get and okay underline "\_OK\_" -- -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data table. @@ -12269,9 +12324,9 @@ function AIRBOSS:_EvalGrooveTime(playerData) -- Time in groove for AV-8B elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B and t<55 then -- VSTOL Late Hover stop selection too fast to Abeam LDG Spot AV-8B. grade="FAST V/STOL Groove" - elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B and t<90 then -- VSTOL Operations with AV-8B. + elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B and t<75 then -- VSTOL Operations with AV-8B. grade="OK V/STOL Groove" - elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B and t>=91 then -- VSTOL Early Hover stop selection slow to Abeam LDG Spot AV-8B. + elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B and t>=76 then -- VSTOL Early Hover stop selection slow to Abeam LDG Spot AV-8B. grade="SLOW V/STOL Groove" else grade="LIG" @@ -12283,7 +12338,7 @@ function AIRBOSS:_EvalGrooveTime(playerData) end -- V/STOL Unicorn! - if playerData.actype==AIRBOSS.AircraftCarrier.AV8B and (t>=65.0 and t<=75.0) then + if playerData.actype==AIRBOSS.AircraftCarrier.AV8B and (t>=60.0 and t<=65.0) then grade="_OK_ V/STOL" end @@ -12312,7 +12367,7 @@ function AIRBOSS:_LSOgrade(playerData) -- Put everything together. local G=GXX.." "..GIM.." ".." "..GIC.." "..GAR - -- Count number of minor, normal and major deviations. TODO - work on Harrier counts due slower approach speed. + -- Count number of minor, normal and major deviations. local N=nXX+nIM+nIC+nAR local nL=count(G, '_')/2 local nS=count(G, '%(') @@ -12321,7 +12376,7 @@ function AIRBOSS:_LSOgrade(playerData) -- Groove time 15-18.99 sec for a unicorn. Or 65-70 for V/STOL unicorn. local Tgroove=playerData.Tgroove local TgrooveUnicorn=Tgroove and (Tgroove>=15.0 and Tgroove<=18.99) or false - local TgrooveVstolUnicorn=Tgroove and (Tgroove>=65.0 and Tgroove<=70.0)and playerData.actype==AIRBOSS.AircraftCarrier.AV8B or false + local TgrooveVstolUnicorn=Tgroove and (Tgroove>=60.0 and Tgroove<=65.0)and playerData.actype==AIRBOSS.AircraftCarrier.AV8B or false local grade local points @@ -12371,7 +12426,7 @@ end text=text.."# of normal deviations = "..nN.."\n" text=text.."# of small deviations ( = "..nS.."\n" self:T2(self.lid..text) - + -- Special cases. if playerData.wop then --------------------- @@ -12427,8 +12482,18 @@ end -- Bolter grade="-- (BOLTER)" points=2.5 - end - + + elseif not playerData.hover and playerData.actype==AIRBOSS.AircraftCarrier.AV8B then + ------------------------------- + -- AV-8B not cleared to land -- -- Landing clearence is carrier from LC to Landing + ------------------------------- + if playerData.landed then + -- AIRBOSS wants your balls! + grade="CUT" + points=0.0 + end + + end return grade, points, G end @@ -12652,7 +12717,7 @@ function AIRBOSS:_GS(step, n) if n==-1 then gp=AIRBOSS.GroovePos.IC elseif n==1 then - if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS then + if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then gp=AIRBOSS.GroovePos.AL else gp=AIRBOSS.GroovePos.IW @@ -14544,7 +14609,7 @@ function AIRBOSS:_IsCarrierAircraft(unit) -- Special case for Harrier which can only land on Tarawa, LHA and LHD. if aircrafttype==AIRBOSS.AircraftCarrier.AV8B then - if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS then + if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then return true else return false @@ -14552,7 +14617,7 @@ function AIRBOSS:_IsCarrierAircraft(unit) end -- Also only Harriers can land on the Tarawa, LHA and LHD. - if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS then + if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then if aircrafttype~=AIRBOSS.AircraftCarrier.AV8B then return false end @@ -17922,7 +17987,7 @@ function AIRBOSS:_MarkCaseZones(_unitName, flare) end -- Tarawa, LHA and LHD landing spots. - if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS then + if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then text=text.."\n* abeam landing stop with RED flares" -- Abeam landing spot zone. local ALSPT=self:_GetZoneAbeamLandingSpot() From 19d5cb8ecbe84ef3182ff5accfac8ffc6a1bf117 Mon Sep 17 00:00:00 2001 From: Rolln Date: Sat, 30 Oct 2021 23:35:27 -0600 Subject: [PATCH 005/200] Added a command line option that will enable SSML support when using Google text-to-speech. --- Moose Development/Moose/Sound/SRS.lua | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 9be975a78..8f27c6dbf 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -88,10 +88,19 @@ -- -- Use a specific "culture" with the @{#MSRS.SetCulture} function, e.g. `:SetCulture("en-US")` or `:SetCulture("de-DE")`. -- +-- ## Set Google +-- +-- Use Google's text-to-speech engine with the @{#MSRS.SetGoogle} function, e.g. ':SetGoogle()'. +-- By enabling this it also allows you to utilize SSML in your text for added flexibilty. +-- For more information on setting up a cloud account, visit: https://cloud.google.com/text-to-speech +-- Google's supported SSML reference: https://cloud.google.com/text-to-speech/docs/ssml +-- -- ## Set Voice -- -- Use a specifc voice with the @{#MSRS.SetVoice} function, e.g, `:SetVoice("Microsoft Hedda Desktop")`. -- Note that this must be installed on your windows system. +-- If enabling SetGoogle(), you can use voices provided by Google +-- Google's supported voices: https://cloud.google.com/text-to-speech/docs/voices -- -- ## Set Coordinate -- @@ -678,7 +687,7 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp -- Set google. if self.google then - command=command..string.format(' -G "%s"', self.google) + command=command..string.format(' --ssml -G "%s"', self.google) end -- Debug output. From ab6cd2b751c990a28c12f693d2089dfd0c4c7de9 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 31 Oct 2021 11:51:31 +0100 Subject: [PATCH 006/200] Fix cleanup exisiting crates --- Moose Development/Moose/Ops/CTLD.lua | 55 +++++++++++++++------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 8cdfbcdd6..6abfd8477 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -2145,24 +2145,39 @@ function CTLD:_LoadCratesNearby(Group, Unit) self.Loaded_Cargo[unitname] = loaded self:_UpdateUnitCargoMass(Unit) -- clean up real world crates - local existingcrates = self.Spawned_Cargo -- #table - local newexcrates = {} - for _,_crate in pairs(existingcrates) do - local excrate = _crate -- #CTLD_CARGO - local ID = excrate:GetID() - for _,_ID in pairs(crateidsloaded) do - if ID ~= _ID then - table.insert(newexcrates,_crate) - end - end - end - self.Spawned_Cargo = nil - self.Spawned_Cargo = newexcrates + self:_CleanupTrackedCrates(crateidsloaded) end end return self end +--- (Internal) Function to clean up tracked cargo crates +function CTLD:_CleanupTrackedCrates(crateIdsToRemove) + local existingcrates = self.Spawned_Cargo -- #table + local newexcrates = {} + for _,_crate in pairs(existingcrates) do + local excrate = _crate -- #CTLD_CARGO + local ID = excrate:GetID() + local keep = true + for _,_ID in pairs(crateIdsToRemove) do + if ID == _ID then + keep = false + end + end + -- remove destroyed crates here too + local static = _crate:GetPositionable() -- Wrapper.Static#STATIC -- crates + if not static or not static:IsAlive() then + keep = false + end + if keep then + table.insert(newexcrates,_crate) + end + end + self.Spawned_Cargo = nil + self.Spawned_Cargo = newexcrates + return self +end + --- (Internal) Function to get current loaded mass -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit @@ -2849,19 +2864,7 @@ function CTLD:_CleanUpCrates(Crates,Build,Number) if found == numberdest then break end -- got enough end -- loop and remove from real world representation - for _,_crate in pairs(existingcrates) do - local excrate = _crate -- #CTLD_CARGO - local ID = excrate:GetID() - for _,_ID in pairs(destIDs) do - if ID ~= _ID then - table.insert(newexcrates,_crate) - end - end - end - - -- reset Spawned_Cargo - self.Spawned_Cargo = nil - self.Spawned_Cargo = newexcrates + self:_CleanupTrackedCrates(destIDs) return self end From 8e776cb3ab3eca4d9ca2150cde7a095d20fb2a26 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sat, 6 Nov 2021 15:29:09 +0100 Subject: [PATCH 007/200] Update CSAR.lua Adding CASEVAC option by Shagrat --- Moose Development/Moose/Ops/CSAR.lua | 149 ++++++++++++++++++++++----- 1 file changed, 124 insertions(+), 25 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index b166009df..bd874e608 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -45,6 +45,7 @@ -- * Object oriented refactoring of Ciribob\'s fantastic CSAR script. -- * No need for extra MIST loading. -- * Additional events to tailor your mission. +-- * Optional SpawnCASEVAC to create casualties without beacon (e.g. handling dead ground vehicles and create CASVAC requests). -- -- ## 0. Prerequisites -- @@ -105,7 +106,7 @@ -- self.countryblue= country.id.USA -- self.countryred = country.id.RUSSIA -- self.countryneutral = country.id.UN_PEACEKEEPERS --- +-- -- ## 2.1 Experimental Features -- -- WARNING - Here\'ll be dragons! @@ -115,7 +116,9 @@ -- self.SRSPath = "E:\\Progra~1\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your SRS installation -- server(!) -- self.SRSchannel = 300 -- radio channel -- self.SRSModulation = radio.modulation.AM -- modulation --- +-- -- +-- self.csarUsePara = false -- If set to true, will use the LandingAfterEjection Event instead of Ejection --shagrat +-- -- ## 3. Results -- -- Number of successful landings with save pilots and aggregated number of saved pilots is stored in these variables in the object: @@ -175,6 +178,8 @@ -- -- Create downed "Pilot Wagner" in #ZONE "CSAR_Start_1" at a random point for the blue coalition -- my_csar:SpawnCSARAtZone( "CSAR_Start_1", coalition.side.BLUE, "Pilot Wagner", true ) -- +-- --Create a casualty and CASEVAC request from a "Point" (VEC2) for the blue coalition --shagrat +-- my_csar:SpawnCASEVAC(Point, coalition.side.BLUE) -- -- @field #CSAR CSAR = { @@ -238,11 +243,11 @@ CSAR.AircraftType["Mi-8MTV2"] = 12 CSAR.AircraftType["Mi-8MT"] = 12 CSAR.AircraftType["Mi-24P"] = 8 CSAR.AircraftType["Mi-24V"] = 8 -CSAR.AircraftType["Bell-47"] = 2 +CSAR.AircraftType["Bell-47"] = 2 --- CSAR class version. -- @field #string version -CSAR.version="0.1.11r2" +CSAR.version="0.1.12r2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -250,7 +255,7 @@ CSAR.version="0.1.11r2" -- DONE: SRS Integration (to be tested) -- TODO: Maybe - add option to smoke/flare closest MASH - +-- TODO: shagrat Add cargoWeight to helicopter when pilot boarded ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -380,7 +385,10 @@ function CSAR:New(Coalition, Template, Alias) self.countryblue= country.id.USA self.countryred = country.id.RUSSIA self.countryneutral = country.id.UN_PEACEKEEPERS - + + -- added 0.1.3 + self.csarUsePara = true -- shagrat set to true, will use the LandingAfterEjection Event instead of Ejection + -- WARNING - here\'ll be dragons -- for this to work you need to de-sanitize your mission environment in \Scripts\MissionScripting.lua -- needs SRS => 1.9.6 to work (works on the *server* side) @@ -645,10 +653,14 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla local _typeName = _typeName or "Pilot" if not noMessage then - self:_DisplayToAllSAR("MAYDAY MAYDAY! " .. _typeName .. " is down. ", self.coalition, self.messageTime) + if _freq ~= 0 then --shagrat different CASEVAC msg + self:_DisplayToAllSAR("MAYDAY MAYDAY! " .. _typeName .. " is down. ", self.coalition, self.messageTime) + else + self:_DisplayToAllSAR("Troops In Contact. " .. _typeName .. " requests CASEVAC. ", self.coalition, self.messageTime) + end end - if _freq then + if (_freq and _freq ~= 0) then --shagrat only add beacon if _freq is NOT 0 self:_AddBeaconToGroup(_spawnedGroup, _freq) end @@ -657,10 +669,18 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla local _text = _description if not forcedesc then if _playerName ~= nil then - _text = "Pilot " .. _playerName + if _freq ~= 0 then --shagrat + _text = "Pilot " .. _playerName + else + _text = "TIC - " .. _playerName + end elseif _unitName ~= nil then - _text = "AI Pilot of " .. _unitName - end + if _freq ~= 0 then --shagrat + _text = "AI Pilot of " .. _unitName + else + _text = "TIC - " .. _unitName + end + end end self:T({_spawnedGroup, _alias}) @@ -668,7 +688,7 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla self:_CreateDownedPilotTrack(_spawnedGroup,_GroupName,_coalition,_unitName,_text,_typeName,_freq,_playerName) - self:_InitSARForPilot(_spawnedGroup, _GroupName, _freq, noMessage) + self:_InitSARForPilot(_spawnedGroup, _unitName, _freq, noMessage) --shagrat use unitName to have the aircraft callsign / descriptive "name" etc. return self end @@ -737,6 +757,58 @@ function CSAR:SpawnCSARAtZone(Zone, Coalition, Description, RandomPoint, Nomessa return self end +--- (Internal) Function to add a CSAR object into the scene at a Point coordinate (VEC_2). For mission designers wanting to add e.g. casualties to the scene, that don't use beacons. +-- @param #CSAR self +-- @param #string _Point a POINT_VEC2. +-- @param #number _coalition Coalition. +-- @param #string _description (optional) Description. +-- @param #boolean _nomessage (optional) If true, don\'t send a message to SAR. +-- @param #string unitname (optional) Name of the lost unit. +-- @param #string typename (optional) Type of plane. +-- @param #boolean forcedesc (optional) Force to use the description passed only for the pilot track entry. Use to have fully custom names. +function CSAR:_SpawnCASEVAC( _Point, _coalition, _description, _nomessage, unitname, typename, forcedesc) --shagrat added internal Function _SpawnCASEVAC + self:T(self.lid .. " _SpawnCASEVAC") + + local _description = _description or "CASEVAC" + local unitname = unitname or "CASEVAC" + local typename = typename or "Ground Commander" + + local pos = {} + pos = _Point + + local _country = 0 + if _coalition == coalition.side.BLUE then + _country = self.countryblue + elseif _coalition == coalition.side.RED then + _country = self.countryred + else + _country = self.countryneutral + end + --shagrat set frequency to 0 as "flag" for no beacon + self:_AddCsar(_coalition, _country, pos, typename, unitname, _description, 0, _nomessage, _description, forcedesc) + + return self +end + +--- Function to add a CSAR object into the scene at a zone coordinate. For mission designers wanting to add e.g. PoWs to the scene. +-- @param #CSAR self +-- @param #string Point a POINT_VEC2. +-- @param #number Coalition Coalition. +-- @param #string Description (optional) Description. +-- @param #boolean addBeacon (optional) yes or no. +-- @param #boolean Nomessage (optional) If true, don\'t send a message to SAR. +-- @param #string Unitname (optional) Name of the lost unit. +-- @param #string Typename (optional) Type of plane. +-- @param #boolean Forcedesc (optional) Force to use the **description passed only** for the pilot track entry. Use to have fully custom names. +-- @usage If missions designers want to spawn downed pilots into the field, e.g. at mission begin, to give the helicopter guys work, they can do this like so: +-- +-- -- Create casualty "CASEVAC" at Point #POINT_VEC2 for the blue coalition. +-- my_csar:SpawnCASEVAC( POINT_VEC2, coalition.side.BLUE ) +function CSAR:SpawnCASEVAC(Point, Coalition, Description, Nomessage, Unitname, Typename, Forcedesc) + self:_SpawnCASEVAC(Point, Coalition, Description, Nomessage, Unitname, Typename, Forcedesc) + return self +end --shagrat end added CASEVAC + --- (Internal) Event handler. -- @param #CSAR self function CSAR:_EventHandler(EventData) @@ -748,7 +820,7 @@ function CSAR:_EventHandler(EventData) -- no event if _event == nil or _event.initiator == nil then return false - + -- take off elseif _event.id == EVENTS.Takeoff then -- taken off self:T(self.lid .. " Event unit - Takeoff") @@ -824,12 +896,12 @@ function CSAR:_EventHandler(EventData) local _unit = _event.IniUnit local _unitname = _event.IniUnitName local _group = _event.IniGroup - + if _unit == nil then return -- error! end - - local _coalition = _unit:GetCoalition() + + local _coalition = _unit:GetCoalition() if _coalition ~= self.coalition then return --ignore! end @@ -852,11 +924,27 @@ function CSAR:_EventHandler(EventData) return end - -- all checks passed, get going. - local _freq = self:_GenerateADFFrequency() - self:_AddCsar(_coalition, _unit:GetCountry(), _unit:GetCoordinate() , _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, false, "none") - - return true + -- all checks passed, get going. + if self.csarUsePara == false then --shagrat check parameter LandingAfterEjection, if true don't spawn a Pilot from EJECTION event, wait for the Chute to land + local _freq = self:_GenerateADFFrequency() + self:_AddCsar(_coalition, _unit:GetCountry(), _unit:GetCoordinate() , _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, false, "none") + return true + end + ---- shagrat on event LANDING_AFTER_EJECTION spawn pilot at parachute location + elseif (_event.id == EVENTS.LandingAfterEjection and self.csarUsePara == true) then + self:I({EVENT=_event}) + local _LandingPos = COORDINATE:NewFromVec3(_event.initiator:getPosition().p) + local _unitname = "Aircraft" --_event.initiator:getName() or "Aircraft" --shagrat Optional use of Object name which is unfortunately 'f15_Pilot_Parachute' + local _typename = "Ejected Pilot" --_event.Initiator.getTypeName() or "Ejected Pilot" + local _country = _event.initiator:getCountry() + local _coalition = coalition.getCountryCoalition( _country ) + local _freq = self:_GenerateADFFrequency() + self:I({coalition=_coalition,country= _country, coord=_LandingPos, name=_unitname, player=_event.IniPlayerName, freq=_freq}) + self:_AddCsar(_coalition, _country, _LandingPos, nil, _unitname, _event.IniPlayerName, _freq, false, "none")--shagrat add CSAR at Parachute location. + + Unit.destroy(_event.initiator) -- shagrat remove static Pilot model + + return true elseif _event.id == EVENTS.Land then self:T(self.lid .. " Landing") @@ -921,8 +1009,13 @@ function CSAR:_InitSARForPilot(_downedGroup, _GroupName, _freq, _nomessage) local _leadername = _leader:GetName() if not _nomessage then - local _text = string.format("%s requests SAR at %s, beacon at %.2f KHz", _leadername, _coordinatesText, _freqk) - self:_DisplayToAllSAR(_text,self.coalition,self.messageTime) + if _freq ~= 0 then --shagrat + local _text = string.format("%s requests SAR at %s, beacon at %.2f KHz", _groupName, _coordinatesText, _freqk)--shagrat _groupName to prevent 'f15_Pilot_Parachute' + self:_DisplayToAllSAR(_text,self.coalition,self.messageTime) + else --shagrat CASEVAC msg + local _text = string.format("Pickup Zone at %s.", _coordinatesText ) + self:_DisplayToAllSAR(_text,self.coalition,self.messageTime) + end end for _,_heliName in pairs(self.csarUnits) do @@ -1060,7 +1153,7 @@ function CSAR:_PopSmokeForGroup(_woundedGroupName, _woundedLeader) if _lastSmoke == nil or timer.getTime() > _lastSmoke then local _smokecolor = self.smokecolor - local _smokecoord = _woundedLeader:GetCoordinate() + local _smokecoord = _woundedLeader:GetCoordinate():Translate( 6, math.random( 1, 360) ) --shagrat place smoke at a random 6 m distance, so smoke does not obscure the pilot _smokecoord:Smoke(_smokecolor) self.smokeMarkers[_woundedGroupName] = timer.getTime() + 300 -- next smoke time end @@ -1435,7 +1528,11 @@ function CSAR:_DisplayActiveSAR(_unitName) else distancetext = string.format("%.1fkm", _distance/1000.0) end - table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %s ", _value.desc, _coordinatesText, _value.frequency / 1000, distancetext) }) + if _value.frequency == 0 then--shagrat insert CASEVAC without Frequency + table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %s ", _value.desc, _coordinatesText, distancetext) }) + else + table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %s ", _value.desc, _coordinatesText, _value.frequency / 1000, distancetext) }) + end end end @@ -1876,6 +1973,7 @@ function CSAR:onafterStart(From, Event, To) self:HandleEvent(EVENTS.Takeoff, self._EventHandler) self:HandleEvent(EVENTS.Land, self._EventHandler) self:HandleEvent(EVENTS.Ejection, self._EventHandler) + self:HandleEvent(EVENTS.LandingAfterEjection, self._EventHandler) --shagrat self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) self:HandleEvent(EVENTS.PilotDead, self._EventHandler) @@ -1986,6 +2084,7 @@ function CSAR:onafterStop(From, Event, To) self:UnHandleEvent(EVENTS.Takeoff) self:UnHandleEvent(EVENTS.Land) self:UnHandleEvent(EVENTS.Ejection) + self:UnHandleEvent(EVENTS.LandingAfterEjection) -- shagrat self:UnHandleEvent(EVENTS.PlayerEnterUnit) self:UnHandleEvent(EVENTS.PlayerEnterAircraft) self:UnHandleEvent(EVENTS.PilotDead) From 74cd5e338795eb8e944d5a3082d7627afbff1195 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 9 Nov 2021 10:31:12 +0100 Subject: [PATCH 008/200] MANTIS - added docu and addition os SEAD events --- Moose Development/Moose/Functional/Mantis.lua | 510 +++++++++++------- 1 file changed, 308 insertions(+), 202 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 3843df13d..f9cea279f 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -1,26 +1,26 @@ --- **Functional** -- Modular, Automatic and Network capable Targeting and Interception System for Air Defenses --- +-- -- === --- +-- -- **MANTIS** - Moose derived Modular, Automatic and Network capable Targeting and Interception System -- Controls a network of SAM sites. Use detection to switch on the AA site closest to the enemy -- Leverage evasiveness from SEAD -- Leverage attack range setup added by DCS in 11/20 --- +-- -- === --- +-- -- ## Missions: -- -- ### [MANTIS - Modular, Automatic and Network capable Targeting and Interception System](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/MTS%20-%20Mantis/MTS-010%20-%20Basic%20Mantis%20Demo) --- +-- -- === --- +-- -- ### Author : **applevangelist ** --- +-- -- @module Functional.Mantis -- @image Functional.Mantis.jpg - --- Date: July 2021 +-- +-- Date: Nov 2021 ------------------------------------------------------------------------- --- **MANTIS** class, extends Core.Base#BASE @@ -59,10 +59,10 @@ -- @extends Core.Base#BASE ---- *The worst thing that can happen to a good cause is, not to be skillfully attacked, but to be ineptly defended.* - Frédéric Bastiat --- +--- *The worst thing that can happen to a good cause is, not to be skillfully attacked, but to be ineptly defended.* - Frédéric Bastiat +-- -- Simple Class for a more intelligent Air Defense System --- +-- -- #MANTIS -- Moose derived Modular, Automatic and Network capable Targeting and Interception System. -- Controls a network of SAM sites. Use detection to switch on the AA site closest to the enemy. @@ -72,62 +72,62 @@ -- Set up your SAM sites in the mission editor. Name the groups with common prefix like "Red SAM". -- Set up your EWR system in the mission editor. Name the groups with common prefix like "Red EWR". Can be e.g. AWACS or a combination of AWACS and Search Radars like e.g. EWR 1L13 etc. -- [optional] Set up your HQ. Can be any group, e.g. a command vehicle. --- +-- -- # 1. Basic tactical considerations when setting up your SAM sites --- +-- -- ## 1.1 Radar systems and AWACS --- +-- -- Typically, your setup should consist of EWR (early warning) radars to detect and track targets, accompanied by AWACS if your scenario forsees that. Ensure that your EWR radars have a good coverage of the area you want to track. -- **Location** is of highest importantance here. Whilst AWACS in DCS has almost the "all seeing eye", EWR don't have that. Choose your location wisely, against a mountain backdrop or inside a valley even the best EWR system -- doesn't work well. Prefer higher-up locations with a good view; use F7 in-game to check where you actually placed your EWR and have a look around. Apart from the obvious choice, do also consider other radar units --- for this role, most have "SR" (search radar) or "STR" (search and track radar) in their names, use the encyclopedia to see what they actually do. --- +-- for this role, most have "SR" (search radar) or "STR" (search and track radar) in their names, use the encyclopedia to see what they actually do. +-- -- ## 1.2 SAM sites --- --- Typically your SAM should cover all attack ranges. The closer the enemy gets, the more systems you will need to deploy to defend your location. Use a combination of long-range systems like the SA-10/11, midrange like SA-6 and short-range like --- SA-2 for defense (Patriot, Hawk, Gepard, Blindfire for the blue side). For close-up defense and defense against HARMs or low-flying aircraft, helicopters it is also advisable to deploy SA-15 TOR systems, Shilka, Strela and Tunguska units, as well as manpads (Think Gepard, Avenger, Chaparral, +-- +-- Typically your SAM should cover all attack ranges. The closer the enemy gets, the more systems you will need to deploy to defend your location. Use a combination of long-range systems like the SA-10/11, midrange like SA-6 and short-range like +-- SA-2 for defense (Patriot, Hawk, Gepard, Blindfire for the blue side). For close-up defense and defense against HARMs or low-flying aircraft, helicopters it is also advisable to deploy SA-15 TOR systems, Shilka, Strela and Tunguska units, as well as manpads (Think Gepard, Avenger, Chaparral, -- Linebacker, Roland systems for the blue side). If possible, overlap ranges for mutual coverage. --- +-- -- ## 1.3 Typical problems --- --- Often times, people complain because the detection cannot "see" oncoming targets and/or Mantis switches on too late. Three typial problems here are --- --- * bad placement of radar units, --- * overestimation how far units can "see" and --- * not taking into account that a SAM site will take (e.g for a SA-6) 30-40 seconds between switching to RED, acquiring the target and firing. --- +-- +-- Often times, people complain because the detection cannot "see" oncoming targets and/or Mantis switches on too late. Three typial problems here are +-- +-- * bad placement of radar units, +-- * overestimation how far units can "see" and +-- * not taking into account that a SAM site will take (e.g for a SA-6) 30-40 seconds between switching to RED, acquiring the target and firing. +-- -- An attacker doing 350knots will cover ca 180meters/second or thus more than 6km until the SA-6 fires. Use triggers zones and the ruler in the missione editor to understand distances and zones. Take into account that the ranges given by the circles -- in the mission editor are absolute maximum ranges; in-game this is rather 50-75% of that depending on the system. Fiddle with placement and options to see what works best for your scenario, and remember **everything in here is in meters**. --- +-- -- # 2. Start up your MANTIS with a basic setting --- --- `myredmantis = MANTIS:New("myredmantis","Red SAM","Red EWR",nil,"red",false)` --- `myredmantis:Start()` --- --- [optional] Use --- --- * `MANTIS:SetEWRGrouping(radius)` --- * `MANTIS:SetEWRRange(radius)` --- * `MANTIS:SetSAMRadius(radius)` --- * `MANTIS:SetDetectInterval(interval)` --- * `MANTIS:SetAutoRelocate(hq, ewr)` --- +-- +-- myredmantis = MANTIS:New("myredmantis","Red SAM","Red EWR",nil,"red",false) +-- myredmantis:Start() +-- +-- [optional] Use +-- +-- * MANTIS:SetEWRGrouping(radius) +-- * MANTIS:SetEWRRange(radius) +-- * MANTIS:SetSAMRadius(radius) +-- * MANTIS:SetDetectInterval(interval) +-- * MANTIS:SetAutoRelocate(hq, ewr) +-- -- before starting #MANTIS to fine-tune your setup. --- +-- -- If you want to use a separate AWACS unit (default detection range: 250km) to support your EWR system, use e.g. the following setup: --- --- `mybluemantis = MANTIS:New("bluemantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")` --- `mybluemantis:Start()` +-- +-- mybluemantis = MANTIS:New("bluemantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs") +-- mybluemantis:Start() -- -- # 3. Default settings --- +-- -- By default, the following settings are active: -- -- * SAM_Templates_Prefix = "Red SAM" - SAM site group names in the mission editor begin with "Red SAM" -- * EWR_Templates_Prefix = "Red EWR" - EWR group names in the mission editor begin with "Red EWR" - can also be combined with an AWACS unit -- * checkradius = 25000 (meters) - SAMs will engage enemy flights, if they are within a 25km around each SAM site - `MANTIS:SetSAMRadius(radius)` -- * grouping = 5000 (meters) - Detection (EWR) will group enemy flights to areas of 5km for tracking - `MANTIS:SetEWRGrouping(radius)` --- * acceptrange = 80000 (meters) - Detection (EWR) will on consider flights inside a 80km radius - `MANTIS:SetEWRRange(radius)` +-- * acceptrange = 80000 (meters) - Detection (EWR) will on consider flights inside a 80km radius - `MANTIS:SetEWRRange(radius)` -- * detectinterval = 30 (seconds) - MANTIS will decide every 30 seconds which SAM to activate - `MANTIS:SetDetectInterval(interval)` -- * engagerange = 85 (percent) - SAMs will only fire if flights are inside of a 85% radius of their max firerange - `MANTIS:SetSAMRange(range)` -- * dynamic = false - Group filtering is set to once, i.e. newly added groups will not be part of the setup by default - `MANTIS:New(name,samprefix,ewrprefix,hq,coaltion,dynamic)` @@ -135,29 +135,52 @@ -- * debug = false - Debugging reports on screen are set to off - `MANTIS:Debug(onoff)` -- -- # 4. Advanced Mode --- --- Advanced mode will *decrease* reactivity of MANTIS, if HQ and/or EWR network dies. Awacs is counted as one EWR unit. It will set SAMs to RED state if both are dead. Requires usage of an **HQ** object and the **dynamic** option. --- --- E.g. `mymantis:SetAdvancedMode( true, 90 )` --- --- Use this option if you want to make use of or allow advanced SEAD tactics. --- +-- +-- Advanced mode will *decrease* reactivity of MANTIS, if HQ and/or EWR network dies. Awacs is counted as one EWR unit. It will set SAMs to RED state if both are dead. Requires usage of an **HQ** object and the **dynamic** option. +-- +-- E.g. mymantis:SetAdvancedMode( true, 90 ) +-- +-- Use this option if you want to make use of or allow advanced SEAD tactics. +-- -- # 5. Integrate SHORAD --- +-- -- You can also choose to integrate Mantis with @{Functional.Shorad#SHORAD} for protection against HARMs and AGMs. When SHORAD detects a missile fired at one of MANTIS' SAM sites, it will activate SHORAD systems in -- the given defense checkradius around that SAM site. Create a SHORAD object first, then integrate with MANTIS like so: --- --- `local SamSet = SET_GROUP:New():FilterPrefixes("Blue SAM"):FilterCoalitions("blue"):FilterStart()` --- `myshorad = SHORAD:New("BlueShorad", "Blue SHORAD", SamSet, 22000, 600, "blue")` --- `-- now set up MANTIS` --- `mymantis = MANTIS:New("BlueMantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")` --- `mymantis:AddShorad(myshorad,720)` --- `mymantis:Start()` --- --- and (optionally) remove the link later on with --- --- `mymantis:RemoveShorad()` -- +-- local SamSet = SET_GROUP:New():FilterPrefixes("Blue SAM"):FilterCoalitions("blue"):FilterStart() +-- myshorad = SHORAD:New("BlueShorad", "Blue SHORAD", SamSet, 22000, 600, "blue") +-- -- now set up MANTIS +-- mymantis = MANTIS:New("BlueMantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs") +-- mymantis:AddShorad(myshorad,720) +-- mymantis:Start() +-- +-- and (optionally) remove the link later on with +-- +-- mymantis:RemoveShorad() +-- +-- # 6. Integrated SEAD +-- +-- MANTIS is using @{Functional.Sead#SEAD} internally to both detect and evade HARM attacks. No extra efforts needed to set this up! +-- Once a HARM attack is detected, MANTIS (via SEAD) will shut down the radars of the attacked SAM site and take evasive action by moving the SAM +-- vehicles around (*if they are __drivable__*, that is). There's a component of randomness in detection and evasion, which is based on the +-- skill set of the SAM set (the higher the skill, the more likely). When a missile is fired from far away, the SAM will stay active for a +-- period of time to stay defensive, before it takes evasive actions. +-- +-- You can link into the SEAD driven events of MANTIS like so: +-- +-- function mymantis:OnAfterSeadSuppressionPlanned(From, Event, To, Group, Name, SuppressionStartTime, SuppressionEndTime) +-- -- your code here - SAM site shutdown and evasion planned, but not yet executed +-- -- Time entries relate to timer.getTime() - see https://wiki.hoggitworld.com/view/DCS_func_getTime +-- end +-- +-- function mymantis:OnAfterSeadSuppressionStart(From, Event, To, Group, Name) +-- -- your code here - SAM site is emissions off and possibly moving +-- end +-- +-- function mymantis:OnAfterSeadSuppressionEnd(From, Event, To, Group, Name) +-- -- your code here - SAM site is back online +-- end +-- -- @field #MANTIS MANTIS = { ClassName = "MANTIS", @@ -198,6 +221,7 @@ MANTIS = { DLink = false, DLTimeStamp = 0, Padding = 10, + SuppressedGroups = {}, } --- Advanced state enumerator @@ -222,31 +246,31 @@ do --@param #string coaltion Coalition side of your setup, e.g. "blue", "red" or "neutral" --@param #boolean dynamic Use constant (true) filtering or just filter once (false, default) (optional) --@param #string awacs Group name of your Awacs (optional) - --@param #boolean EmOnOff Make MANTIS switch Emissions on and off instead of changing the alarm state between RED and GREEN (optional) - --@param #number Padding For #SEAD - Extra number of seconds to add to radar switch-back-on time (optional) + --@param #boolean EmOnOff Make MANTIS switch Emissions on and off instead of changing the alarm state between RED and GREEN (optional) + --@param #number Padding For #SEAD - Extra number of seconds to add to radar switch-back-on time (optional) --@return #MANTIS self --@usage Start up your MANTIS with a basic setting -- - -- `myredmantis = MANTIS:New("myredmantis","Red SAM","Red EWR",nil,"red",false)` - -- `myredmantis:Start()` - -- - -- [optional] Use - -- - -- * `MANTIS:SetEWRGrouping(radius)` - -- * `MANTIS:SetEWRRange(radius)` - -- * `MANTIS:SetSAMRadius(radius)` - -- * `MANTIS:SetDetectInterval(interval)` - -- * `MANTIS:SetAutoRelocate(hq, ewr)` - -- + -- myredmantis = MANTIS:New("myredmantis","Red SAM","Red EWR",nil,"red",false) + -- myredmantis:Start() + -- + -- [optional] Use + -- + -- * MANTIS:SetEWRGrouping(radius) + -- * MANTIS:SetEWRRange(radius) + -- * MANTIS:SetSAMRadius(radius) + -- * MANTIS:SetDetectInterval(interval) + -- * MANTIS:SetAutoRelocate(hq, ewr) + -- -- before starting #MANTIS to fine-tune your setup. - -- + -- -- If you want to use a separate AWACS unit (default detection range: 250km) to support your EWR system, use e.g. the following setup: - -- - -- `mybluemantis = MANTIS:New("bluemantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")` - -- `mybluemantis:Start()` - -- + -- + -- mybluemantis = MANTIS:New("bluemantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs") + -- mybluemantis:Start() + -- function MANTIS:New(name,samprefix,ewrprefix,hq,coaltion,dynamic,awacs, EmOnOff, Padding) - + -- DONE: Create some user functions for these -- DONE: Make HQ useful -- DONE: Set SAMs to auto if EWR dies @@ -269,7 +293,7 @@ do self.autorelocateunits = { HQ = false, EWR = false} self.advanced = false self.adv_ratio = 100 - self.adv_state = 0 + self.adv_state = 0 self.verbose = false self.Adv_EWR_Group = nil self.AWACS_Prefix = awacs or nil @@ -284,27 +308,28 @@ do self.SamStateTracker = {} -- table to hold alert states, so we don't trigger state changes twice in adv mode self.DLink = false self.Padding = Padding or 10 - + self.SuppressedGroups = {} + if EmOnOff then if EmOnOff == false then self.UseEmOnOff = false else self.UseEmOnOff = true end - end - + end + if type(awacs) == "string" then self.advAwacs = true else self.advAwacs = false end - + -- Inherit everything from BASE class. local self = BASE:Inherit(self, FSM:New()) -- #MANTIS - + -- Set the string id for output to DCS.log file. self.lid=string.format("MANTIS %s | ", self.name) - + -- Debug trace. if self.debug then BASE:TraceOnOff(true) @@ -312,7 +337,7 @@ do --BASE:TraceClass("SEAD") BASE:TraceLevel(1) end - + if self.dynamic then -- Set SAM SET_GROUP self.SAM_Group = SET_GROUP:New():FilterPrefixes(self.SAM_Templates_Prefix):FilterCoalitions(self.Coalition):FilterStart() @@ -324,36 +349,39 @@ do -- Set EWR SET_GROUP self.EWR_Group = SET_GROUP:New():FilterPrefixes({self.SAM_Templates_Prefix,self.EWR_Templates_Prefix}):FilterCoalitions(self.Coalition):FilterOnce() end - + -- set up CC if self.HQ_Template_CC then self.HQ_CC = GROUP:FindByName(self.HQ_Template_CC) end - + -- @field #string version - self.version="0.6.2" + self.version="0.7.1" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) - + --- FSM Functions --- - + -- Start State. self:SetStartState("Stopped") -- Add FSM transitions. - -- From State --> Event --> To State - self:AddTransition("Stopped", "Start", "Running") -- Start FSM. - self:AddTransition("*", "Status", "*") -- MANTIS status update. - self:AddTransition("*", "Relocating", "*") -- MANTIS HQ and EWR are relocating. - self:AddTransition("*", "GreenState", "*") -- MANTIS A SAM switching to GREEN state. - self:AddTransition("*", "RedState", "*") -- MANTIS A SAM switching to RED state. - self:AddTransition("*", "AdvStateChange", "*") -- MANTIS advanced mode state change. - self:AddTransition("*", "ShoradActivated", "*") -- MANTIS woke up a connected SHORAD. - self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. - + -- From State --> Event --> To State + self:AddTransition("Stopped", "Start", "Running") -- Start FSM. + self:AddTransition("*", "Status", "*") -- MANTIS status update. + self:AddTransition("*", "Relocating", "*") -- MANTIS HQ and EWR are relocating. + self:AddTransition("*", "GreenState", "*") -- MANTIS A SAM switching to GREEN state. + self:AddTransition("*", "RedState", "*") -- MANTIS A SAM switching to RED state. + self:AddTransition("*", "AdvStateChange", "*") -- MANTIS advanced mode state change. + self:AddTransition("*", "ShoradActivated", "*") -- MANTIS woke up a connected SHORAD. + self:AddTransition("*", "SeadSuppressionStart", "*") -- SEAD has switched off one group. + self:AddTransition("*", "SeadSuppressionEnd", "*") -- SEAD has switched on one group. + self:AddTransition("*", "SeadSuppressionPlanned", "*") -- SEAD has planned a suppression. + self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. + ------------------------ --- Pseudo Functions --- ------------------------ - + --- Triggers the FSM event "Start". Starts the MANTIS. Initializes parameters and starts event handlers. -- @function [parent=#MANTIS] Start -- @param #MANTIS self @@ -379,7 +407,7 @@ do -- @function [parent=#MANTIS] __Status -- @param #MANTIS self -- @param #number delay Delay in seconds. - + --- On After "Relocating" event. HQ and/or EWR moved. -- @function [parent=#MANTIS] OnAfterRelocating -- @param #MANTIS self @@ -387,7 +415,7 @@ do -- @param #string Event The Event -- @param #string To The To State -- @return #MANTIS self - + --- On After "GreenState" event. A SAM group was switched to GREEN alert. -- @function [parent=#MANTIS] OnAfterGreenState -- @param #MANTIS self @@ -396,7 +424,7 @@ do -- @param #string To The To State -- @param Wrapper.Group#GROUP Group The GROUP object whose state was changed -- @return #MANTIS self - + --- On After "RedState" event. A SAM group was switched to RED alert. -- @function [parent=#MANTIS] OnAfterRedState -- @param #MANTIS self @@ -405,7 +433,7 @@ do -- @param #string To The To State -- @param Wrapper.Group#GROUP Group The GROUP object whose state was changed -- @return #MANTIS self - + --- On After "AdvStateChange" event. Advanced state changed, influencing detection speed. -- @function [parent=#MANTIS] OnAfterAdvStateChange -- @param #MANTIS self @@ -416,7 +444,7 @@ do -- @param #number Newstate New state - 0 = green, 1 = amber, 2 = red -- @param #number Interval Calculated detection interval based on state and advanced feature setting -- @return #MANTIS self - + --- On After "ShoradActivated" event. Mantis has activated a SHORAD. -- @function [parent=#MANTIS] OnAfterShoradActivated -- @param #MANTIS self @@ -427,21 +455,50 @@ do -- @param #number Radius Radius around the named group to find SHORAD groups -- @param #number Ontime Seconds the SHORAD will stay active - return self + --- On After "SeadSuppressionPlanned" event. Mantis has planned to switch off a site to defend SEAD attack. + -- @function [parent=#MANTIS] OnAfterSeadSuppressionPlanned + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param Wrapper.Group#GROUP Group The suppressed GROUP object + -- @param #string Name Name of the suppressed group + -- @param #number SuppressionStartTime Model start time of the suppression from `timer.getTime()` + -- @param #number SuppressionEndTime Model end time of the suppression from `timer.getTime()` + + --- On After "SeadSuppressionStart" event. Mantis has switched off a site to defend a SEAD attack. + -- @function [parent=#MANTIS] OnAfterSeadSuppressionStart + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param Wrapper.Group#GROUP Group The suppressed GROUP object + -- @param #string Name Name of the suppressed groupe + + --- On After "SeadSuppressionEnd" event. Mantis has switched on a site after a SEAD attack. + -- @function [parent=#MANTIS] OnAfterSeadSuppressionEnd + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param Wrapper.Group#GROUP Group The suppressed GROUP object + -- @param #string Name Name of the suppressed group + + return self end ----------------------------------------------------------------------- -- MANTIS helper functions ------------------------------------------------------------------------ - +----------------------------------------------------------------------- + --- [Internal] Function to get the self.SAM_Table -- @param #MANTIS self - -- @return #table table + -- @return #table table function MANTIS:_GetSAMTable() self:T(self.lid .. "GetSAMTable") return self.SAM_Table end - + --- [Internal] Function to set the self.SAM_Table -- @param #MANTIS self -- @return #MANTIS self @@ -450,7 +507,7 @@ do self.SAM_Table = table return self end - + --- Function to set the grouping radius of the detection in meters -- @param #MANTIS self -- @param #number radius Radius upon which detected objects will be grouped @@ -470,17 +527,17 @@ do self.acceptrange = radius return self end - + --- Function to set switch-on/off zone for the SAM sites in meters -- @param #MANTIS self - -- @param #number radius Radius of the firing zone + -- @param #number radius Radius of the firing zone function MANTIS:SetSAMRadius(radius) self:T(self.lid .. "SetSAMRadius") local radius = radius or 25000 self.checkradius = radius return self end - + --- Function to set SAM firing engage range, 0-100 percent, e.g. 75 -- @param #MANTIS self -- @param #number range Percent of the max fire range @@ -493,7 +550,7 @@ do self.engagerange = range return self end - + --- Function to set a new SAM firing engage range, use this method to adjust range while running MANTIS, e.g. for different setups day and night -- @param #MANTIS self -- @param #number range Percent of the max fire range @@ -508,7 +565,7 @@ do self.mysead.EngagementRange = range return self end - + --- Function to set switch-on/off the debug state -- @param #MANTIS self -- @param #boolean onoff Set true to switch on @@ -526,7 +583,7 @@ do end return self end - + --- Function to get the HQ object for further use -- @param #MANTIS self -- @return Wrapper.GROUP#GROUP The HQ #GROUP object or *nil* if it doesn't exist @@ -535,10 +592,10 @@ do if self.HQ_CC then return self.HQ_CC else - return nil - end + return nil + end end - + --- Function to set separate AWACS detection instance -- @param #MANTIS self -- @param #string prefix Name of the AWACS group in the mission editor @@ -562,7 +619,7 @@ do self.awacsrange = range return self end - + --- Function to set the HQ object for further use -- @param #MANTIS self -- @param Wrapper.GROUP#GROUP group The #GROUP object to be set as HQ @@ -580,7 +637,7 @@ do end return self end - + --- Function to set the detection interval -- @param #MANTIS self -- @param #number interval The interval in seconds @@ -589,8 +646,8 @@ do local interval = interval or 30 self.detectinterval = interval return self - end - + end + --- Function to set Advanded Mode -- @param #MANTIS self -- @param #boolean onoff If true, will activate Advanced Mode @@ -615,7 +672,7 @@ do end return self end - + --- Set using Emissions on/off instead of changing alarm state -- @param #MANTIS self -- @param #boolean switch Decide if we are changing alarm state or Emission state @@ -624,8 +681,8 @@ do self.UseEmOnOff = switch or false return self end - - --- Set using an #INTEL_DLINK object instead of #DETECTION. Requires Develop branch of Moose.lua. + + --- Set using an #INTEL_DLINK object instead of #DETECTION -- @param #MANTIS self -- @param Ops.Intelligence#INTEL_DLINK DLink The data link object to be used. function MANTIS:SetUsingDLink(DLink) @@ -635,7 +692,7 @@ do self.DLTimeStamp = timer.getAbsTime() return self end - + --- [Internal] Function to check if HQ is alive -- @param #MANTIS self -- @return #boolean True if HQ is alive, else false @@ -654,11 +711,11 @@ do return true else --self:T(self.lid.." HQ is dead!") - return false + return false end end end - return self + return self end --- [Internal] Function to check if EWR is (at least partially) alive @@ -690,7 +747,7 @@ do return false end end - return self + return self end --- [Internal] Function to determine state of the advanced mode @@ -726,7 +783,7 @@ do end return newinterval, currstate end - + --- Function to set autorelocation for HQ and EWR objects. Note: Units must be actually mobile in DCS! -- @param #MANTIS self -- @param #boolean hq If true, will relocate HQ object @@ -742,8 +799,8 @@ do --self:T({self.autorelocate, self.autorelocateunits}) end return self - end - + end + --- [Internal] Function to execute the relocation -- @param #MANTIS self function MANTIS:_RelocateGroups() @@ -780,7 +837,7 @@ do end return self end - + --- [Internal] Function to check if any object is in the given SAM zone -- @param #MANTIS self -- @param #table dectset Table of coordinates of detected items @@ -796,12 +853,12 @@ do local coord = _coord -- get current coord to check -- output for cross-check local targetdistance = samcoordinate:DistanceFromPointVec2(coord) - if self.verbose or self.debug then + if self.verbose or self.debug then local dectstring = coord:ToStringLLDMS() local samstring = samcoordinate:ToStringLLDMS() 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) - self:I(self.lid..text) + self:I(self.lid..text) end -- end output to cross-check if targetdistance <= radius then @@ -816,20 +873,20 @@ do -- @return Functional.Detection #DETECTION_AREAS The running detection set function MANTIS:StartDetection() self:T(self.lid.."Starting Detection") - + -- start detection local groupset = self.EWR_Group local grouping = self.grouping or 5000 local acceptrange = self.acceptrange or 80000 local interval = self.detectinterval or 60 - + --@param Functional.Detection #DETECTION_AREAS _MANTISdetection [Internal] The MANTIS detection object local MANTISdetection = DETECTION_AREAS:New( groupset, grouping ) --[Internal] Grouping detected objects to 5000m zones MANTISdetection:FilterCategories({ Unit.Category.AIRPLANE, Unit.Category.HELICOPTER }) MANTISdetection:SetAcceptRange(acceptrange) MANTISdetection:SetRefreshTimeInterval(interval) MANTISdetection:Start() - + function MANTISdetection:OnAfterDetectedItem(From,Event,To,DetectedItem) --BASE:I( { From, Event, To, DetectedItem }) local debug = false @@ -838,30 +895,30 @@ do local text = "MANTIS: Detection at "..Coordinate:ToStringLLDMS() local m = MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) end - end + end return MANTISdetection end - + --- [Internal] Function to start the detection via AWACS if defined as separate -- @param #MANTIS self -- @return Functional.Detection #DETECTION_AREAS The running detection set function MANTIS:StartAwacsDetection() self:T(self.lid.."Starting Awacs Detection") - + -- start detection local group = self.AWACS_Prefix local groupset = SET_GROUP:New():FilterPrefixes(group):FilterCoalitions(self.Coalition):FilterStart() local grouping = self.grouping or 5000 --local acceptrange = self.acceptrange or 80000 local interval = self.detectinterval or 60 - + --@param Functional.Detection #DETECTION_AREAS _MANTISdetection [Internal] The MANTIS detection object local MANTISAwacs = DETECTION_AREAS:New( groupset, grouping ) --[Internal] Grouping detected objects to 5000m zones MANTISAwacs:FilterCategories({ Unit.Category.AIRPLANE, Unit.Category.HELICOPTER }) MANTISAwacs:SetAcceptRange(self.awacsrange) --250km MANTISAwacs:SetRefreshTimeInterval(interval) MANTISAwacs:Start() - + function MANTISAwacs:OnAfterDetectedItem(From,Event,To,DetectedItem) --BASE:I( { From, Event, To, DetectedItem }) local debug = false @@ -870,10 +927,10 @@ do local text = "Awacs Detection at "..Coordinate:ToStringLLDMS() local m = MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) end - end + end return MANTISAwacs end - + --- [Internal] Function to set the SAM start state -- @param #MANTIS self -- @return #MANTIS self @@ -891,6 +948,7 @@ do local group = _group -- Wrapper.Group#GROUP -- TODO: add emissions on/off if self.UseEmOnOff then + group:OptionAlarmStateRed() group:EnableEmission(false) --group:SetAIOff() else @@ -909,10 +967,14 @@ do -- make SAMs evasive local mysead = SEAD:New( SEAD_Grps, self.Padding ) -- Functional.Sead#SEAD mysead:SetEngagementRange(engagerange) + mysead:AddCallBack(self) + if self.UseEmOnOff then + mysead:SwitchEmissions(true) + end self.mysead = mysead return self end - + --- [Internal] Function to update SAM table and SEAD state -- @param #MANTIS self -- @return #MANTIS self @@ -944,7 +1006,7 @@ do end return self end - + --- Function to link up #MANTIS with a #SHORAD installation -- @param #MANTIS self -- @param Functional.Shorad#SHORAD Shorad The #SHORAD object @@ -961,7 +1023,7 @@ do end return self end - + --- Function to unlink #MANTIS from a #SHORAD installation -- @param #MANTIS self function MANTIS:RemoveShorad() @@ -969,11 +1031,11 @@ do self.ShoradLink = false return self end - + ----------------------------------------------------------------------- -- MANTIS main functions ------------------------------------------------------------------------ - +----------------------------------------------------------------------- + --- [Internal] Check detection function -- @param #MANTIS self -- @param Functional.Detection#DETECTION_AREAS detection Detection object @@ -995,22 +1057,24 @@ do local name = _data[1] local samgroup = GROUP:FindByName(name) local IsInZone, Distance = self:CheckObjectInZone(detset, samcoordinate) - if IsInZone then --check any target in zone + local suppressed = self.SuppressedGroups[name] or false + if IsInZone then --check any target in zone and not curr managed by SEAD if samgroup:IsAlive() then -- switch on SAM - if self.UseEmOnOff then - -- TODO: add emissions on/off + if self.UseEmOnOff and not suppressed then + -- DONE: add emissions on/off --samgroup:SetAIOn() samgroup:EnableEmission(true) + elseif not self.UseEmOnOff and not suppressed then + samgroup:OptionAlarmStateRed() end - samgroup:OptionAlarmStateRed() - if self.SamStateTracker[name] ~= "RED" then + if self.SamStateTracker[name] ~= "RED" and not suppressed then self:__RedState(1,samgroup) self.SamStateTracker[name] = "RED" end -- link in to SHORAD if available -- DONE: Test integration fully - if self.ShoradLink and Distance < self.ShoradActDistance then -- don't give SHORAD position away too early + if self.ShoradLink and (Distance < self.ShoradActDistance or suppressed) then -- don't give SHORAD position away too early local Shorad = self.Shorad local radius = self.checkradius local ontime = self.ShoradTime @@ -1018,24 +1082,25 @@ do self:__ShoradActivated(1,name, radius, ontime) end -- debug output - if self.debug or self.verbose then + if self.debug or self.verbose and not suppressed then local text = string.format("SAM %s switched to alarm state RED!", name) local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) if self.verbose then self:I(self.lid..text) end end end --end alive - else + else if samgroup:IsAlive() then -- switch off SAM - if self.UseEmOnOff then + if self.UseEmOnOff and not suppressed then samgroup:EnableEmission(false) - end + elseif not self.UseEmOnOff and not suppressed then samgroup:OptionAlarmStateGreen() - if self.SamStateTracker[name] ~= "GREEN" then - self:__GreenState(1,samgroup) - self.SamStateTracker[name] = "GREEN" - end - if self.debug or self.verbose then + end + if self.SamStateTracker[name] ~= "GREEN" and not suppressed then + self:__GreenState(1,samgroup) + self.SamStateTracker[name] = "GREEN" + end + if self.debug or self.verbose and not suppressed then local text = string.format("SAM %s switched to alarm state GREEN!", name) local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) if self.verbose then self:I(self.lid..text) end @@ -1044,8 +1109,8 @@ do end --end check end --for for loop return self - end - + end + --- [Internal] Relocation relay function -- @param #MANTIS self -- @return #MANTIS self @@ -1054,7 +1119,7 @@ do self:_RelocateGroups() return self end - + --- [Internal] Check advanced state -- @param #MANTIS self -- @return #MANTIS self @@ -1089,7 +1154,7 @@ do end -- end newstate vs oldstate return self end - + --- [Internal] Check DLink state -- @param #MANTIS self -- @return #MANTIS self @@ -1103,7 +1168,7 @@ do self:I(self.lid .. "Intel DLink not running - switching back to single detection!") end end - + --- [Internal] Function to set start state -- @param #MANTIS self -- @param #string From The From State @@ -1120,10 +1185,10 @@ do if self.advAwacs then self.AWACS_Detection = self:StartAwacsDetection() end - self:__Status(-math.random(1,10)) + self:__Status(-math.random(1,10)) return self end - + --- [Internal] Before status function for MANTIS -- @param #MANTIS self -- @param #string From The From State @@ -1141,7 +1206,7 @@ do if self.advAwacs and not self.state2flag then self:_Check(self.AWACS_Detection) end - + -- relocate HQ and EWR if self.autorelocate then local relointerval = self.relointerval @@ -1149,26 +1214,26 @@ do local timepassed = thistime - self.TimeStamp local halfintv = math.floor(timepassed / relointerval) - + --self:T({timepassed=timepassed, halfintv=halfintv}) - + if halfintv >= 1 then self.TimeStamp = timer.getAbsTime() self:_Relocate() self:__Relocating(1) end end - + -- advanced state check if self.advanced then self:_CheckAdvState() end - + -- check DLink state if self.DLink then self:_CheckDLinkState() end - + return self end @@ -1191,7 +1256,7 @@ do self:__Status(interval) return self end - + --- [Internal] Function to stop MANTIS -- @param #MANTIS self -- @param #string From The From State @@ -1200,9 +1265,9 @@ do -- @return #MANTIS self function MANTIS:onafterStop(From, Event, To) self:T({From, Event, To}) - return self + return self end - + --- [Internal] Function triggered by Event Relocating -- @param #MANTIS self -- @param #string From The From State @@ -1211,9 +1276,9 @@ do -- @return #MANTIS self function MANTIS:onafterRelocating(From, Event, To) self:T({From, Event, To}) - return self + return self end - + --- [Internal] Function triggered by Event GreenState -- @param #MANTIS self -- @param #string From The From State @@ -1223,9 +1288,9 @@ do -- @return #MANTIS self function MANTIS:onafterGreenState(From, Event, To, Group) self:T({From, Event, To, Group}) - return self + return self end - + --- [Internal] Function triggered by Event RedState -- @param #MANTIS self -- @param #string From The From State @@ -1235,9 +1300,9 @@ do -- @return #MANTIS self function MANTIS:onafterRedState(From, Event, To, Group) self:T({From, Event, To, Group}) - return self + return self end - + --- [Internal] Function triggered by Event AdvStateChange -- @param #MANTIS self -- @param #string From The From State @@ -1249,9 +1314,9 @@ do -- @return #MANTIS self function MANTIS:onafterAdvStateChange(From, Event, To, Oldstate, Newstate, Interval) self:T({From, Event, To, Oldstate, Newstate, Interval}) - return self + return self end - + --- [Internal] Function triggered by Event ShoradActivated -- @param #MANTIS self -- @param #string From The From State @@ -1262,8 +1327,49 @@ do -- @param #number Ontime Seconds the SHORAD will stay active function MANTIS:onafterShoradActivated(From, Event, To, Name, Radius, Ontime) self:T({From, Event, To, Name, Radius, Ontime}) - return self + return self end + + --- [Internal] Function triggered by Event SeadSuppressionStart + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param Wrapper.Group#GROUP Group The suppressed GROUP object + -- @param #string Name Name of the suppressed group + function MANTIS:onafterSeadSuppressionStart(From, Event, To, Group, Name) + self:T({From, Event, To, Name}) + self.SuppressedGroups[Name] = true + return self + end + + --- [Internal] Function triggered by Event SeadSuppressionEnd + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param Wrapper.Group#GROUP Group The suppressed GROUP object + -- @param #string Name Name of the suppressed group + function MANTIS:onafterSeadSuppressionEnd(From, Event, To, Group, Name) + self:T({From, Event, To, Name}) + self.SuppressedGroups[Name] = false + return self + end + + --- [Internal] Function triggered by Event SeadSuppressionPlanned + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param Wrapper.Group#GROUP Group The suppressed GROUP object + -- @param #string Name Name of the suppressed group + -- @param #number SuppressionStartTime Model start time of the suppression from `timer.getTime()` + -- @param #number SuppressionEndTime Model end time of the suppression from `timer.getTime()` + function MANTIS:onafterSeadSuppressionPlanned(From, Event, To, Group, Name, SuppressionStartTime, SuppressionEndTime) + self:T({From, Event, To, Name}) + return self + end + end ----------------------------------------------------------------------- -- MANTIS end From 0e9076efa33ddb615d3cb928af85665f3bebf593 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 9 Nov 2021 10:32:37 +0100 Subject: [PATCH 009/200] SEAD - align to dev changes, allow callback on SEAD events --- Moose Development/Moose/Functional/Sead.lua | 236 ++++++++++++-------- 1 file changed, 147 insertions(+), 89 deletions(-) diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index e149fb697..8c65f0ba7 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -1,56 +1,64 @@ --- **Functional** -- Make SAM sites execute evasive and defensive behaviour when being fired upon. --- +-- -- === --- +-- -- ## Features: --- +-- -- * When SAM sites are being fired upon, the SAMs will take evasive action will reposition themselves when possible. -- * When SAM sites are being fired upon, the SAMs will take defensive action by shutting down their radars. --- +-- * SEAD calculates the time it takes for a HARM to reach the target - and will attempt to minimize the shut-down time. +-- * Detection and evasion of shots has a random component based on the skill level of the SAM groups. +-- -- === --- +-- -- ## Missions: --- +-- -- [SEV - SEAD Evasion](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SEV%20-%20SEAD%20Evasion) --- +-- -- === --- +-- -- ### Authors: **FlightControl**, **applevangelist** --- --- Last Update: Aug 2021 --- +-- +-- Last Update: Nov 2021 +-- -- === --- +-- -- @module Functional.Sead -- @image SEAD.JPG ---- +--- -- @type SEAD -- @extends Core.Base#BASE --- Make SAM sites execute evasive and defensive behaviour when being fired upon. --- +-- -- This class is very easy to use. Just setup a SEAD object by using @{#SEAD.New}() and SAMs will evade and take defensive action when being fired upon. +-- Once a HARM attack is detected, SEAD will shut down the radars of the attacked SAM site and take evasive action by moving the SAM +-- vehicles around (*if* they are drivable, that is). There's a component of randomness in detection and evasion, which is based on the +-- skill set of the SAM set (the higher the skill, the more likely). When a missile is fired from far away, the SAM will stay active for a +-- period of time to stay defensive, before it takes evasive actions. -- -- # Constructor: --- +-- -- Use the @{#SEAD.New}() constructor to create a new SEAD object. --- +-- -- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } ) --- +-- -- @field #SEAD SEAD = { - ClassName = "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 } } - }, + Excellent = { Evade = 10, DelayOn = { 10, 30 } } + }, SEADGroupPrefixes = {}, SuppressedGroups = {}, EngagementRange = 75, -- default 75% engagement range Feature Request #1355 Padding = 10, + CallBack = nil, + UseCallBack = false, } --- Missile enumerators @@ -69,7 +77,7 @@ SEAD = { ["X_31"] = "X_31", ["Kh25"] = "Kh25", } - + --- Missile enumerators - from DCS ME and Wikipedia -- @field HarmData SEAD.HarmData = { @@ -86,14 +94,14 @@ SEAD = { ["X_31"] = {150, 3}, ["Kh25"] = {25, 0.8}, } - + --- Creates the main object which is handling defensive actions for SA sites or moving SA vehicles. -- When an anti radiation missile is fired (KH-58, KH-31P, KH-31A, KH-25MPU, HARM missiles), the SA will shut down their radars and will take evasive actions... -- Chances are big that the missile will miss. -- @param #SEAD self -- @param #table SEADGroupPrefixes Table of #string entries or single #string, which is a table of Prefixes of the SA Groups in the DCS mission editor on which evasive actions need to be taken. -- @param #number Padding (Optional) Extra number of seconds to add to radar switch-back-on time --- @return SEAD +-- @return #SEAD self -- @usage -- -- CCCP SEAD Defenses -- -- Defends the Russian SA installations from SEAD attacks. @@ -102,7 +110,7 @@ function SEAD:New( SEADGroupPrefixes, Padding ) 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 @@ -110,25 +118,29 @@ function SEAD:New( SEADGroupPrefixes, Padding ) else self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes end - + local padding = Padding or 10 if padding < 10 then padding = 10 end self.Padding = padding + self.UseEmissionsOnOff = false + + self.CallBack = nil + self.UseCallBack = false self:HandleEvent( EVENTS.Shot, self.HandleEventShot ) - - self:I("*** SEAD - Started Version 0.3.1") + + self:I("*** SEAD - Started Version 0.3.3") return self end ---- Update the active SEAD Set +--- Update the active SEAD Set (while running) -- @param #SEAD self -- @param #table SEADGroupPrefixes The prefixes to add, note: can also be a single #string -- @return #SEAD self function SEAD:UpdateSet( SEADGroupPrefixes ) self:T( SEADGroupPrefixes ) - + if type( SEADGroupPrefixes ) == 'table' then for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix @@ -142,8 +154,8 @@ end --- Sets the engagement range of the SAMs. Defaults to 75% to make it more deadly. Feature Request #1355 -- @param #SEAD self --- @param #number range Set the engagement range in percent, e.g. 50 --- @return self +-- @param #number range Set the engagement range in percent, e.g. 55 (default 75) +-- @return #SEAD self function SEAD:SetEngagementRange(range) self:T( { range } ) range = range or 75 @@ -157,7 +169,8 @@ end --- Set the padding in seconds, which extends the radar off time calculated by SEAD -- @param #SEAD self --- @param #number Padding Extra number of seconds to add for the switch-on +-- @param #number Padding Extra number of seconds to add for the switch-on (default 10 seconds) +-- @return #SEAD self function SEAD:SetPadding(Padding) self:T( { Padding } ) local padding = Padding or 10 @@ -166,56 +179,80 @@ function SEAD:SetPadding(Padding) return self end - --- Check if a known HARM was fired - -- @param #SEAD self - -- @param #string WeaponName - -- @return #boolean Returns true for a match - -- @return #string name Name of hit in table - function SEAD:_CheckHarms(WeaponName) - self:T( { WeaponName } ) - local hit = false - local name = "" - for _,_name in pairs (SEAD.Harms) do - if string.find(WeaponName,_name,1) then - hit = true - name = _name - break - end - end - return hit, name - end +--- Set SEAD to use emissions on/off in addition to alarm state. +-- @param #SEAD self +-- @param #boolean Switch True for on, false for off. +-- @return #SEAD self +function SEAD:SwitchEmissions(Switch) + self:T({Switch}) + self.UseEmissionsOnOff = Switch + return self +end - --- (Internal) Return distance in meters between two coordinates or -1 on error. - -- @param #SEAD self - -- @param Core.Point#COORDINATE _point1 Coordinate one - -- @param Core.Point#COORDINATE _point2 Coordinate two - -- @return #number Distance in meters - function SEAD:_GetDistance(_point1, _point2) - self:T("_GetDistance") - if _point1 and _point2 then - local distance1 = _point1:Get2DDistance(_point2) - local distance2 = _point1:DistanceFromPointVec2(_point2) - --self:T({dist1=distance1, dist2=distance2}) - if distance1 and type(distance1) == "number" then - return distance1 - elseif distance2 and type(distance2) == "number" then - return distance2 - else - self:E("*****Cannot calculate distance!") - self:E({_point1,_point2}) - return -1 +--- Add an object to call back when going evasive. +-- @param #SEAD self +-- @param #table Object The object to call. Needs to have object functions as follows: +-- `:SeadSuppressionPlanned(Group, Name, SuppressionStartTime, SuppressionEndTime)` +-- `:SeadSuppressionStart(Group, Name)`, +-- `:SeadSuppressionEnd(Group, Name)`, +-- @return #SEAD self +function SEAD:AddCallBack(Object) + self:T({Class=Object.ClassName}) + self.CallBack = Object + self.UseCallBack = true + return self +end + +--- (Internal) Check if a known HARM was fired +-- @param #SEAD self +-- @param #string WeaponName +-- @return #boolean Returns true for a match +-- @return #string name Name of hit in table +function SEAD:_CheckHarms(WeaponName) + self:T( { WeaponName } ) + local hit = false + local name = "" + for _,_name in pairs (SEAD.Harms) do + if string.find(WeaponName,_name,1) then + hit = true + name = _name + break end + end + return hit, name +end + +--- (Internal) Return distance in meters between two coordinates or -1 on error. +-- @param #SEAD self +-- @param Core.Point#COORDINATE _point1 Coordinate one +-- @param Core.Point#COORDINATE _point2 Coordinate two +-- @return #number Distance in meters +function SEAD:_GetDistance(_point1, _point2) + self:T("_GetDistance") + if _point1 and _point2 then + local distance1 = _point1:Get2DDistance(_point2) + local distance2 = _point1:DistanceFromPointVec2(_point2) + --self:T({dist1=distance1, dist2=distance2}) + if distance1 and type(distance1) == "number" then + return distance1 + elseif distance2 and type(distance2) == "number" then + return distance2 else - self:E("******Cannot calculate distance!") + self:E("*****Cannot calculate distance!") self:E({_point1,_point2}) return -1 end + else + self:E("******Cannot calculate distance!") + self:E({_point1,_point2}) + return -1 end - ---- Detects if an SAM site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME. --- @see SEAD --- @param #SEAD +end + +--- (Internal) Detects if an SAM site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME. +-- @param #SEAD self -- @param Core.Event#EVENTDATA EventData +-- @return #SEAD self function SEAD:HandleEventShot( EventData ) self:T( { EventData.id } ) local SEADPlane = EventData.IniUnit -- Wrapper.Unit#UNIT @@ -227,7 +264,7 @@ function SEAD:HandleEventShot( EventData ) self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName) --self:T({ SEADWeapon }) - + if self:_CheckHarms(SEADWeaponName) then self:T( '*** SEAD - Weapon Match' ) local _targetskill = "Random" @@ -245,13 +282,13 @@ function SEAD:HandleEventShot( EventData ) -- see if we are shot at local SEADGroupFound = false for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do - self:T( SEADGroupPrefix ) + self:T( _targetgroupname, SEADGroupPrefix ) if string.find( _targetgroupname, SEADGroupPrefix, 1, true ) then SEADGroupFound = true self:T( '*** SEAD - Group Match Found' ) break end - end + end if SEADGroupFound == true then -- yes we are being attacked if _targetskill == "Random" then -- when skill is random, choose a skill local Skills = { "Average", "Good", "High", "Excellent" } @@ -282,45 +319,66 @@ function SEAD:HandleEventShot( EventData ) else _distance = 0 end - + self:T( string.format("*** SEAD - target skill %s, distance %dkm, reach %dkm, tti %dsec", _targetskill, _distance,reach,_tti )) - + if reach >= _distance then self:T("*** SEAD - Shot in Reach") - + local function SuppressionStart(args) self:T(string.format("*** SEAD - %s Radar Off & Relocating",args[2])) local grp = args[1] -- Wrapper.Group#GROUP - grp:OptionAlarmStateGreen() + local name = args[2] -- #string Group Name + if self.UseEmissionsOnOff then + grp:EnableEmission(false) + end + grp:OptionAlarmStateGreen() -- needed else we cannot move around grp:RelocateGroundRandomInRadius(20,300,false,false,"Diamond") + if self.UseCallBack then + local object = self.CallBack + object:SeadSuppressionStart(grp,name) + end end - + local function SuppressionStop(args) self:T(string.format("*** SEAD - %s Radar On",args[2])) local grp = args[1] -- Wrapper.Group#GROUP - grp:OptionAlarmStateRed() + local name = args[2] -- #string Group Nam + if self.UseEmissionsOnOff then + grp:EnableEmission(true) + end + grp:OptionAlarmStateAuto() grp:OptionEngageRange(self.EngagementRange) - self.SuppressedGroups[args[2]] = false + self.SuppressedGroups[name] = false + if self.UseCallBack then + local object = self.CallBack + object:SeadSuppressionEnd(grp,name) + end end - + -- randomize switch-on time local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2]) if delay > _tti then delay = delay / 2 end -- speed up if _tti > (3*delay) then delay = (_tti / 2) * 0.9 end -- shot from afar - - local SuppressionStartTime = timer.getTime() + delay + + local SuppressionStartTime = timer.getTime() + delay local SuppressionEndTime = timer.getTime() + _tti + self.Padding - + if not self.SuppressedGroups[_targetgroupname] then self:T(string.format("*** SEAD - %s | Parameters TTI %ds | Switch-Off in %ds",_targetgroupname,_tti,delay)) timer.scheduleFunction(SuppressionStart,{_targetgroup,_targetgroupname},SuppressionStartTime) timer.scheduleFunction(SuppressionStop,{_targetgroup,_targetgroupname},SuppressionEndTime) self.SuppressedGroups[_targetgroupname] = true + if self.UseCallBack then + local object = self.CallBack + object:SeadSuppressionPlanned(_targetgroup,_targetgroupname,SuppressionStartTime,SuppressionEndTime) + end end - + end end end end end + return self end From c520de00872c5492c708a29dec68b618a6d11464 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sun, 14 Nov 2021 12:57:00 +0100 Subject: [PATCH 010/200] Wrapper Unit - fix for missile count Wrapper Unit - fix for missile count, issue #1624 --- Moose Development/Moose/Wrapper/Unit.lua | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 098508f56..d878c3b72 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -626,8 +626,8 @@ function UNIT:GetAmmunition() -- Type name of current weapon. local Tammo=ammotable[w]["desc"]["typeName"] - local _weaponString = UTILS.Split(Tammo,"%.") - local _weaponName = _weaponString[#_weaponString] + --local _weaponString = UTILS.Split(Tammo,"%.") + --local _weaponName = _weaponString[#_weaponString] -- Get the weapon category: shell=0, missile=1, rocket=2, bomb=3 local Category=ammotable[w].desc.category @@ -655,8 +655,9 @@ function UNIT:GetAmmunition() nbombs=nbombs+Nammo elseif Category==Weapon.Category.MISSILE then - - -- Add up all cruise missiles (category 5) + + + -- Add up all missiles (category 5) if MissileCategory==Weapon.MissileCategory.AAM then nmissiles=nmissiles+Nammo elseif MissileCategory==Weapon.MissileCategory.ANTI_SHIP then @@ -665,6 +666,10 @@ function UNIT:GetAmmunition() nmissiles=nmissiles+Nammo elseif MissileCategory==Weapon.MissileCategory.OTHER then nmissiles=nmissiles+Nammo + elseif MissileCategory==Weapon.MissileCategory.SAM then + nmissiles=nmissiles+Nammo + elseif MissileCategory==Weapon.MissileCategory.CRUISE then + nmissiles=nmissiles+Nammo end end From 9c5b5d4633e292a0d4225b4678d71fdf630623d6 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 14 Nov 2021 13:34:23 +0100 Subject: [PATCH 011/200] CSAR - added changes by Shagrat for Casevac CSAR - don't make usePara default. Added coalition check if using the parachute landing event --- Moose Development/Moose/Ops/CSAR.lua | 125 ++++++++++++++------------- 1 file changed, 64 insertions(+), 61 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index bd874e608..fb19b288e 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -45,7 +45,7 @@ -- * Object oriented refactoring of Ciribob\'s fantastic CSAR script. -- * No need for extra MIST loading. -- * Additional events to tailor your mission. --- * Optional SpawnCASEVAC to create casualties without beacon (e.g. handling dead ground vehicles and create CASVAC requests). +-- * Optional SpawnCASEVAC to create casualties without beacon (e.g. handling dead ground vehicles and create CASVAC requests). -- -- ## 0. Prerequisites -- @@ -117,7 +117,7 @@ -- self.SRSchannel = 300 -- radio channel -- self.SRSModulation = radio.modulation.AM -- modulation -- -- --- self.csarUsePara = false -- If set to true, will use the LandingAfterEjection Event instead of Ejection --shagrat +-- self.csarUsePara = false -- If set to true, will use the LandingAfterEjection Event instead of Ejection --shagrat -- -- ## 3. Results -- @@ -178,8 +178,8 @@ -- -- Create downed "Pilot Wagner" in #ZONE "CSAR_Start_1" at a random point for the blue coalition -- my_csar:SpawnCSARAtZone( "CSAR_Start_1", coalition.side.BLUE, "Pilot Wagner", true ) -- --- --Create a casualty and CASEVAC request from a "Point" (VEC2) for the blue coalition --shagrat --- my_csar:SpawnCASEVAC(Point, coalition.side.BLUE) +-- --Create a casualty and CASEVAC request from a "Point" (VEC2) for the blue coalition --shagrat +-- my_csar:SpawnCASEVAC(Point, coalition.side.BLUE) -- -- @field #CSAR CSAR = { @@ -243,11 +243,11 @@ CSAR.AircraftType["Mi-8MTV2"] = 12 CSAR.AircraftType["Mi-8MT"] = 12 CSAR.AircraftType["Mi-24P"] = 8 CSAR.AircraftType["Mi-24V"] = 8 -CSAR.AircraftType["Bell-47"] = 2 +CSAR.AircraftType["Bell-47"] = 2 --- CSAR class version. -- @field #string version -CSAR.version="0.1.12r2" +CSAR.version="0.1.12r3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -387,7 +387,7 @@ function CSAR:New(Coalition, Template, Alias) self.countryneutral = country.id.UN_PEACEKEEPERS -- added 0.1.3 - self.csarUsePara = true -- shagrat set to true, will use the LandingAfterEjection Event instead of Ejection + self.csarUsePara = false -- shagrat set to true, will use the LandingAfterEjection Event instead of Ejection -- WARNING - here\'ll be dragons -- for this to work you need to de-sanitize your mission environment in \Scripts\MissionScripting.lua @@ -654,10 +654,10 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla if not noMessage then if _freq ~= 0 then --shagrat different CASEVAC msg - self:_DisplayToAllSAR("MAYDAY MAYDAY! " .. _typeName .. " is down. ", self.coalition, self.messageTime) - else - self:_DisplayToAllSAR("Troops In Contact. " .. _typeName .. " requests CASEVAC. ", self.coalition, self.messageTime) - end + self:_DisplayToAllSAR("MAYDAY MAYDAY! " .. _typeName .. " is down. ", self.coalition, self.messageTime) + else + self:_DisplayToAllSAR("Troops In Contact. " .. _typeName .. " requests CASEVAC. ", self.coalition, self.messageTime) + end end if (_freq and _freq ~= 0) then --shagrat only add beacon if _freq is NOT 0 @@ -669,18 +669,18 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla local _text = _description if not forcedesc then if _playerName ~= nil then - if _freq ~= 0 then --shagrat - _text = "Pilot " .. _playerName - else - _text = "TIC - " .. _playerName - end + if _freq ~= 0 then --shagrat + _text = "Pilot " .. _playerName + else + _text = "TIC - " .. _playerName + end elseif _unitName ~= nil then if _freq ~= 0 then --shagrat - _text = "AI Pilot of " .. _unitName - else - _text = "TIC - " .. _unitName - end - end + _text = "AI Pilot of " .. _unitName + else + _text = "TIC - " .. _unitName + end + end end self:T({_spawnedGroup, _alias}) @@ -817,10 +817,15 @@ function CSAR:_EventHandler(EventData) local _event = EventData -- Core.Event#EVENTDATA + -- no Player + if self.enableForAI == false and _event.IniPlayerName == nil then + return + end + -- no event if _event == nil or _event.initiator == nil then return false - + -- take off elseif _event.id == EVENTS.Takeoff then -- taken off self:T(self.lid .. " Event unit - Takeoff") @@ -896,19 +901,15 @@ function CSAR:_EventHandler(EventData) local _unit = _event.IniUnit local _unitname = _event.IniUnitName local _group = _event.IniGroup - + if _unit == nil then return -- error! end - - local _coalition = _unit:GetCoalition() + + local _coalition = _unit:GetCoalition() if _coalition ~= self.coalition then return --ignore! end - - if self.enableForAI == false and _event.IniPlayerName == nil then - return - end if not self.takenOff[_event.IniUnitName] and not _group:IsAirborne() then self:T(self.lid .. " Pilot has not taken off, ignore") @@ -925,26 +926,28 @@ function CSAR:_EventHandler(EventData) end -- all checks passed, get going. - if self.csarUsePara == false then --shagrat check parameter LandingAfterEjection, if true don't spawn a Pilot from EJECTION event, wait for the Chute to land - local _freq = self:_GenerateADFFrequency() - self:_AddCsar(_coalition, _unit:GetCountry(), _unit:GetCoordinate() , _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, false, "none") - return true - end - ---- shagrat on event LANDING_AFTER_EJECTION spawn pilot at parachute location - elseif (_event.id == EVENTS.LandingAfterEjection and self.csarUsePara == true) then - self:I({EVENT=_event}) - local _LandingPos = COORDINATE:NewFromVec3(_event.initiator:getPosition().p) - local _unitname = "Aircraft" --_event.initiator:getName() or "Aircraft" --shagrat Optional use of Object name which is unfortunately 'f15_Pilot_Parachute' - local _typename = "Ejected Pilot" --_event.Initiator.getTypeName() or "Ejected Pilot" - local _country = _event.initiator:getCountry() - local _coalition = coalition.getCountryCoalition( _country ) - local _freq = self:_GenerateADFFrequency() - self:I({coalition=_coalition,country= _country, coord=_LandingPos, name=_unitname, player=_event.IniPlayerName, freq=_freq}) - self:_AddCsar(_coalition, _country, _LandingPos, nil, _unitname, _event.IniPlayerName, _freq, false, "none")--shagrat add CSAR at Parachute location. - - Unit.destroy(_event.initiator) -- shagrat remove static Pilot model - - return true + if self.csarUsePara == false then --shagrat check parameter LandingAfterEjection, if true don't spawn a Pilot from EJECTION event, wait for the Chute to land + local _freq = self:_GenerateADFFrequency() + self:_AddCsar(_coalition, _unit:GetCountry(), _unit:GetCoordinate() , _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, false, "none") + return true + end + + ---- shagrat on event LANDING_AFTER_EJECTION spawn pilot at parachute location + elseif (_event.id == EVENTS.LandingAfterEjection and self.csarUsePara == true) then + self:I({EVENT=_event}) + local _LandingPos = COORDINATE:NewFromVec3(_event.initiator:getPosition().p) + local _unitname = "Aircraft" --_event.initiator:getName() or "Aircraft" --shagrat Optional use of Object name which is unfortunately 'f15_Pilot_Parachute' + local _typename = "Ejected Pilot" --_event.Initiator.getTypeName() or "Ejected Pilot" + local _country = _event.initiator:getCountry() + local _coalition = coalition.getCountryCoalition( _country ) + if _coalition == self.coalition then + local _freq = self:_GenerateADFFrequency() + self:I({coalition=_coalition,country= _country, coord=_LandingPos, name=_unitname, player=_event.IniPlayerName, freq=_freq}) + self:_AddCsar(_coalition, _country, _LandingPos, nil, _unitname, _event.IniPlayerName, _freq, false, "none")--shagrat add CSAR at Parachute location. + + Unit.destroy(_event.initiator) -- shagrat remove static Pilot model + end + return true elseif _event.id == EVENTS.Land then self:T(self.lid .. " Landing") @@ -1009,13 +1012,13 @@ function CSAR:_InitSARForPilot(_downedGroup, _GroupName, _freq, _nomessage) local _leadername = _leader:GetName() if not _nomessage then - if _freq ~= 0 then --shagrat - local _text = string.format("%s requests SAR at %s, beacon at %.2f KHz", _groupName, _coordinatesText, _freqk)--shagrat _groupName to prevent 'f15_Pilot_Parachute' - self:_DisplayToAllSAR(_text,self.coalition,self.messageTime) - else --shagrat CASEVAC msg - local _text = string.format("Pickup Zone at %s.", _coordinatesText ) - self:_DisplayToAllSAR(_text,self.coalition,self.messageTime) - end + if _freq ~= 0 then --shagrat + local _text = string.format("%s requests SAR at %s, beacon at %.2f KHz", _groupName, _coordinatesText, _freqk)--shagrat _groupName to prevent 'f15_Pilot_Parachute' + self:_DisplayToAllSAR(_text,self.coalition,self.messageTime) + else --shagrat CASEVAC msg + local _text = string.format("Pickup Zone at %s.", _coordinatesText ) + self:_DisplayToAllSAR(_text,self.coalition,self.messageTime) + end end for _,_heliName in pairs(self.csarUnits) do @@ -1528,11 +1531,11 @@ function CSAR:_DisplayActiveSAR(_unitName) else distancetext = string.format("%.1fkm", _distance/1000.0) end - if _value.frequency == 0 then--shagrat insert CASEVAC without Frequency - table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %s ", _value.desc, _coordinatesText, distancetext) }) - else - table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %s ", _value.desc, _coordinatesText, _value.frequency / 1000, distancetext) }) - end + if _value.frequency == 0 then--shagrat insert CASEVAC without Frequency + table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %s ", _value.desc, _coordinatesText, distancetext) }) + else + table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %s ", _value.desc, _coordinatesText, _value.frequency / 1000, distancetext) }) + end end end From 570e8388fc84c1517b61e2a7bfc44ce74dcd81aa Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 27 Nov 2021 17:30:25 +0100 Subject: [PATCH 012/200] Bug fixes --- Moose Development/Moose/Ops/CSAR.lua | 12 +- Moose Development/Moose/Utilities/Utils.lua | 487 +++++++++++++++++++- 2 files changed, 490 insertions(+), 9 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index fb19b288e..3e602cd43 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -247,7 +247,7 @@ CSAR.AircraftType["Bell-47"] = 2 --- CSAR class version. -- @field #string version -CSAR.version="0.1.12r3" +CSAR.version="0.1.12r4" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -1122,9 +1122,9 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) local dist = UTILS.MetersToNM(self.autosmokedistance) disttext = string.format("%.0fnm",dist) end - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud!\nI'll pop a smoke when you are %s away.\nLand or hover by the smoke.", _heliName, _pilotName, disttext), self.messageTime,false,true) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Finally, that is music in my ears!\nI'll pop a smoke when you are %s away.\nLand or hover by the smoke.", _heliName, _pilotName, disttext), self.messageTime,false,true) else - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud!\nRequest a flare or smoke if you need.", _heliName, _pilotName), self.messageTime,false,true) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Finally, that is music in my ears!\nRequest a flare or smoke if you need.", _heliName, _pilotName), self.messageTime,false,true) end --mark as shown for THIS heli and THIS group self.heliVisibleMessage[_lookupKeyHeli] = true @@ -1607,7 +1607,7 @@ function CSAR:_SignalFlare(_unitName) local _closest = self:_GetClosestDownedPilot(_heli) local smokedist = 8000 if self.approachdist_far > smokedist then smokedist = self.approachdist_far end - if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < smokedist then + if _closest ~= nil and _closest.pilot ~= nil and _closest.distance > 0 and _closest.distance < smokedist then local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) local _distance = 0 @@ -1662,7 +1662,7 @@ function CSAR:_Reqsmoke( _unitName ) local smokedist = 8000 if smokedist < self.approachdist_far then smokedist = self.approachdist_far end local _closest = self:_GetClosestDownedPilot(_heli) - if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < smokedist then + if _closest ~= nil and _closest.pilot ~= nil and _closest.distance > 0 and _closest.distance < smokedist then local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) local _distance = 0 if _SETTINGS:IsImperial() then @@ -1670,7 +1670,7 @@ function CSAR:_Reqsmoke( _unitName ) else _distance = string.format("%.1fkm",_closest.distance) end - local _msg = string.format("%s - Popping signal smoke at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance) + local _msg = string.format("%s - Popping smoke at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance) self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true) local _coord = _closest.pilot:GetCoordinate() local color = self.smokecolor diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index f99053ee1..c9bbe6856 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -970,6 +970,15 @@ function UTILS.VecDot(a, b) return a.x*b.x + a.y*b.y + a.z*b.z end +--- Calculate the [dot product](https://en.wikipedia.org/wiki/Dot_product) of two 2D vectors. The result is a number. +-- @param DCS#Vec2 a Vector in 2D with x, y components. +-- @param DCS#Vec2 b Vector in 2D with x, y components. +-- @return #number Scalar product of the two vectors a*b. +function UTILS.Vec2Dot(a, b) + return a.x*b.x + a.y*b.y +end + + --- Calculate the [euclidean norm](https://en.wikipedia.org/wiki/Euclidean_distance) (length) of a 3D vector. -- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @return #number Norm of the vector. @@ -977,6 +986,13 @@ function UTILS.VecNorm(a) return math.sqrt(UTILS.VecDot(a, a)) end +--- Calculate the [euclidean norm](https://en.wikipedia.org/wiki/Euclidean_distance) (length) of a 2D vector. +-- @param DCS#Vec2 a Vector in 2D with x, y components. +-- @return #number Norm of the vector. +function UTILS.Vec2Norm(a) + return math.sqrt(UTILS.Vec2Dot(a, a)) +end + --- Calculate the distance between two 2D vectors. -- @param DCS#Vec2 a Vector in 3D with x, y components. -- @param DCS#Vec2 b Vector in 3D with x, y components. @@ -1059,6 +1075,17 @@ function UTILS.VecHdg(a) return h end +--- Calculate "heading" of a 2D vector in the X-Y plane. +-- @param DCS#Vec2 a Vector in "D with x, y components. +-- @return #number Heading in degrees in [0,360). +function UTILS.Vec2Hdg(a) + local h=math.deg(math.atan2(a.y, a.x)) + if h<0 then + h=h+360 + end + return h +end + --- Calculate the difference between two "heading", i.e. angles in [0,360) deg. -- @param #number h1 Heading one. -- @param #number h2 Heading two. @@ -1095,6 +1122,22 @@ function UTILS.VecTranslate(a, distance, angle) return {x=TX, y=a.y, z=TY} end +--- Translate 2D vector in the 2D (x,z) plane. +-- @param DCS#Vec2 a Vector in 2D with x, y components. +-- @param #number distance The distance to translate. +-- @param #number angle Rotation angle in degrees. +-- @return DCS#Vec2 Translated vector. +function UTILS.Vec2Translate(a, distance, angle) + + local SX = a.x + local SY = a.y + local Radians=math.rad(angle or 0) + local TX=distance*math.cos(Radians)+SX + local TY=distance*math.sin(Radians)+SY + + return {x=TX, y=TY} +end + --- Rotate 3D vector in the 2D (x,z) plane. y-component (usually altitude) unchanged. -- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @param #number angle Rotation angle in degrees. @@ -1115,6 +1158,25 @@ function UTILS.Rotate2D(a, angle) return A end +--- Rotate 2D vector in the 2D (x,z) plane. +-- @param DCS#Vec2 a Vector in 2D with x, y components. +-- @param #number angle Rotation angle in degrees. +-- @return DCS#Vec2 Vector rotated in the (x,y) plane. +function UTILS.Vec2Rotate2D(a, angle) + + local phi=math.rad(angle) + + local x=a.x + local y=a.y + + local X=x*math.cos(phi)-y*math.sin(phi) + local Y=x*math.sin(phi)+y*math.cos(phi) + + local A={x=X, y=Y} + + return A +end + --- Converts a TACAN Channel/Mode couple into a frequency in Hz. -- @param #number TACANChannel The TACAN channel, i.e. the 10 in "10X". @@ -1549,7 +1611,7 @@ function UTILS.GetOSTime() end --- Shuffle a table accoring to Fisher Yeates algorithm ---@param #table table to be shuffled +--@param #table t Table to be shuffled --@return #table function UTILS.ShuffleTable(t) if t == nil or type(t) ~= "table" then @@ -1612,12 +1674,12 @@ function UTILS.IsLoadingDoorOpen( unit_name ) BASE:T(unit_name .. " side door is open") ret_val = true end - + if string.find(type_name, "Bell-47") then -- bell aint got no doors so always ready to load injured soldiers BASE:T(unit_name .. " door is open") ret_val = true end - + if ret_val == false then BASE:T(unit_name .. " all doors are closed") end @@ -1772,3 +1834,422 @@ function UTILS.GenerateLaserCodes() end return jtacGeneratedLaserCodes end + +--- Function to save an object to a file +-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems. +-- @param #string Filename The name of the file. Existing file will be overwritten. +-- @param #table Data The LUA data structure to save. This will be e.g. a table of text lines with an \\n at the end of each line. +-- @return #boolean outcome True if saving is possible, else false. +function UTILS.SaveToFile(Path,Filename,Data) + -- Thanks to @FunkyFranky + -- Check io module is available. + if not io then + BASE:E("ERROR: io not desanitized. Can't save current file.") + return false + end + + -- Check default path. + if Path==nil and not lfs then + BASE:E("WARNING: lfs not desanitized. File will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") + end + + -- Set path or default. + local path = nil + if lfs then + path=Path or lfs.writedir() + end + + -- Set file name. + local filename=Filename + if path~=nil then + filename=path.."\\"..filename + end + + -- write + local f = assert(io.open(filename, "wb")) + f:write(Data) + f:close() + return true +end + +--- Function to save an object to a file +-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems. +-- @param #string Filename The name of the file. +-- @return #boolean outcome True if reading is possible and successful, else false. +-- @return #table data The data read from the filesystem (table of lines of text). Each line is one single #string! +function UTILS.LoadFromFile(Path,Filename) + -- Thanks to @FunkyFranky + -- Check io module is available. + if not io then + BASE:E("ERROR: io not desanitized. Can't save current state.") + return false + end + + -- Check default path. + if Path==nil and not lfs then + BASE:E("WARNING: lfs not desanitized. Loading will look into your DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") + end + + -- Set path or default. + local path = nil + if lfs then + path=Path or lfs.writedir() + end + + -- Set file name. + local filename=Filename + if path~=nil then + filename=path.."\\"..filename + end + + -- Check if file exists. + local exists=UTILS.CheckFileExists(Path,Filename) + if not exists then + BASE:E(string.format("ERROR: File %s does not exist!",filename)) + return false + end + + -- read + local file=assert(io.open(filename, "rb")) + local loadeddata = {} + for line in file:lines() do + loadeddata[#loadeddata+1] = line + end + file:close() + return true, loadeddata +end + +--- Function to check if a file exists. +-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems. +-- @param #string Filename The name of the file. +-- @return #boolean outcome True if reading is possible, else false. +function UTILS.CheckFileExists(Path,Filename) + -- Thanks to @FunkyFranky + -- Function that check if a file exists. + local function _fileexists(name) + local f=io.open(name,"r") + if f~=nil then + io.close(f) + return true + else + return false + end + end + + -- Check io module is available. + if not io then + BASE:E("ERROR: io not desanitized. Can't save current state.") + return false + end + + -- Check default path. + if Path==nil and not lfs then + BASE:E("WARNING: lfs not desanitized. Loading will look into your DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") + end + + -- Set path or default. + local path = nil + if lfs then + path=Path or lfs.writedir() + end + + -- Set file name. + local filename=Filename + if path~=nil then + filename=path.."\\"..filename + end + + -- Check if file exists. + local exists=_fileexists(filename) + if not exists then + BASE:E(string.format("ERROR: File %s does not exist!",filename)) + return false + else + return true + end +end + +--- Function to save the state of a list of groups found by name +-- @param #table List Table of strings with groupnames +-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems. +-- @param #string Filename The name of the file. +-- @return #boolean outcome True if saving is successful, else false. +-- @usage +-- We will go through the list and find the corresponding group and save the current group size (0 when dead). +-- These groups are supposed to be put on the map in the ME and have *not* moved (e.g. stationary SAM sites). +-- Position is still saved for your usage. +-- The idea is to reduce the number of units when reloading the data again to restart the saved mission. +-- The data will be a simple comma separated list of groupname and size, with one header line. +function UTILS.SaveStationaryListOfGroups(List,Path,Filename) + local filename = Filename or "StateListofGroups" + local data = "--Save Stationary List of Groups: "..Filename .."\n" + for _,_group in pairs (List) do + local group = GROUP:FindByName(_group) -- Wrapper.Group#GROUP + if group and group:IsAlive() then + local units = group:CountAliveUnits() + local position = group:GetVec3() + data = string.format("%s%s,%d,%d,%d,%d\n",data,_group,units,position.x,position.y,position.z) + else + data = string.format("%s%s,0,0,0,0\n",data,_group) + end + end + -- save the data + local outcome = UTILS.SaveToFile(Path,Filename,data) + return outcome +end + +--- Function to save the state of a set of Wrapper.Group#GROUP objects. +-- @param Core.Set#SET_BASE Set of objects to save +-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems. +-- @param #string Filename The name of the file. +-- @return #boolean outcome True if saving is successful, else false. +-- @usage +-- We will go through the set and find the corresponding group and save the current group size and current position. +-- The idea is to respawn the groups **spawned during an earlier run of the mission** at the given location and reduce +-- the number of units in the group when reloading the data again to restart the saved mission. Note that *dead* groups +-- cannot be covered with this. +-- **Note** Do NOT use dashes or hashes in group template names (-,#)! +-- The data will be a simple comma separated list of groupname and size, with one header line. +-- The current task/waypoint/etc cannot be restored. +function UTILS.SaveSetOfGroups(Set,Path,Filename) + local filename = Filename or "SetOfGroups" + local data = "--Save SET of groups: "..Filename .."\n" + local List = Set:GetSetObjects() + for _,_group in pairs (List) do + local group = _group -- Wrapper.Group#GROUP + if group and group:IsAlive() then + local name = group:GetName() + local template = string.gsub(name,"-(.+)$","") + if string.find(template,"#") then + template = string.gsub(name,"#(%d+)$","") + end + local units = group:CountAliveUnits() + local position = group:GetVec3() + data = string.format("%s%s,%s,%d,%d,%d,%d\n",data,name,template,units,position.x,position.y,position.z) + end + end + -- save the data + local outcome = UTILS.SaveToFile(Path,Filename,data) + return outcome +end + +--- Function to save the state of a set of Wrapper.Static#STATIC objects. +-- @param Core.Set#SET_BASE Set of objects to save +-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems. +-- @param #string Filename The name of the file. +-- @return #boolean outcome True if saving is successful, else false. +-- @usage +-- We will go through the set and find the corresponding static and save the current name and postion when alive. +-- The data will be a simple comma separated list of name and state etc, with one header line. +function UTILS.SaveSetOfStatics(Set,Path,Filename) + local filename = Filename or "SetOfStatics" + local data = "--Save SET of statics: "..Filename .."\n" + local List = Set:GetSetObjects() + for _,_group in pairs (List) do + local group = _group -- Wrapper.Static#STATIC + if group and group:IsAlive() then + local name = group:GetName() + local position = group:GetVec3() + data = string.format("%s%s,%d,%d,%d\n",data,name,position.x,position.y,position.z) + end + end + -- save the data + local outcome = UTILS.SaveToFile(Path,Filename,data) + return outcome +end + +--- Function to save the state of a list of statics found by name +-- @param #table List Table of strings with statics names +-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems. +-- @param #string Filename The name of the file. +-- @return #boolean outcome True if saving is successful, else false. +-- @usage +-- We will go through the list and find the corresponding static and save the current alive state as 1 (0 when dead). +-- Position is saved for your usage. **Note** this works on UNIT-name level. +-- The idea is to reduce the number of units when reloading the data again to restart the saved mission. +-- The data will be a simple comma separated list of name and state etc, with one header line. +function UTILS.SaveStationaryListOfStatics(List,Path,Filename) + local filename = Filename or "StateListofStatics" + local data = "--Save Stationary List of Statics: "..Filename .."\n" + for _,_group in pairs (List) do + local group = STATIC:FindByName(_group,false) -- Wrapper.Static#STATIC + if group and group:IsAlive() then + local position = group:GetVec3() + data = string.format("%s%s,1,%d,%d,%d\n",data,_group,position.x,position.y,position.z) + else + data = string.format("%s%s,0,0,0,0\n",data,_group) + end + end + -- save the data + local outcome = UTILS.SaveToFile(Path,Filename,data) + return outcome +end + +--- Load back a stationary list of groups from file. +-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems. +-- @param #string Filename The name of the file. +-- @param #boolean Reduce If false, existing loaded groups will not be reduced to fit the saved number. +-- @return #table Table of data objects (tables) containing groupname, coordinate and group object. Returns nil when file cannot be read. +function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce) + local reduce = Reduce==false and false or true + local filename = Filename or "StateListofGroups" + local datatable = {} + if UTILS.CheckFileExists(Path,filename) then + local outcome,loadeddata = UTILS.LoadFromFile(Path,Filename) + -- remove header + table.remove(loadeddata, 1) + for _id,_entry in pairs (loadeddata) do + local dataset = UTILS.Split(_entry,",") + -- groupname,units,position.x,position.y,position.z + local groupname = dataset[1] + local size = tonumber(dataset[2]) + local posx = tonumber(dataset[3]) + local posy = tonumber(dataset[4]) + local posz = tonumber(dataset[5]) + local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz}) + local data = { groupname=groupname, size=size, coordinate=coordinate, group=GROUP:FindByName(groupname) } + if reduce then + local actualgroup = GROUP:FindByName(groupname) + local actualsize = actualgroup:CountAliveUnits() + if actualsize > size then + local reduction = actualsize-size + BASE:I("Reducing groupsize by ".. reduction .. " units!") + -- reduce existing group + local units = actualgroup:GetUnits() + local units2 = UTILS.ShuffleTable(units) -- randomize table + for i=1,reduction do + units2[i]:Destroy(false) + end + end + end + table.insert(datatable,data) + end + else + return nil + end + return datatable +end + +--- Load back a SET of groups from file. +-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems. +-- @param #string Filename The name of the file. +-- @param #boolean Spawn If set to false, do not re-spawn the groups loaded in location and reduce to size. +-- @return Core.Set#SET_GROUP Set of GROUP objects. +-- Returns nil when file cannot be read. Returns a table of data entries if Spawn is false: `{ groupname=groupname, size=size, coordinate=coordinate }` +function UTILS.LoadSetOfGroups(Path,Filename,Spawn) + local spawn = SPAWN==false and false or true + local filename = Filename or "SetOfGroups" + local setdata = SET_GROUP:New() + local datatable = {} + if UTILS.CheckFileExists(Path,filename) then + local outcome,loadeddata = UTILS.LoadFromFile(Path,Filename) + -- remove header + table.remove(loadeddata, 1) + for _id,_entry in pairs (loadeddata) do + local dataset = UTILS.Split(_entry,",") + -- groupname,template,units,position.x,position.y,position.z + local groupname = dataset[1] + local template = dataset[2] + local size = tonumber(dataset[3]) + local posx = tonumber(dataset[4]) + local posy = tonumber(dataset[5]) + local posz = tonumber(dataset[6]) + local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz}) + local group=nil + local data = { groupname=groupname, size=size, coordinate=coordinate } + table.insert(datatable,data) + if spawn then + local group = SPAWN:New(groupname) + :InitDelayOff() + :OnSpawnGroup( + function(spwndgrp) + setdata:AddObject(spwndgrp) + local actualsize = spwndgrp:CountAliveUnits() + if actualsize > size then + local reduction = actualsize-size + -- reduce existing group + local units = spwndgrp:GetUnits() + local units2 = UTILS.ShuffleTable(units) -- randomize table + for i=1,reduction do + units2[i]:Destroy(false) + end + end + end + ) + :SpawnFromCoordinate(coordinate) + end + end + else + return nil + end + if spawn then + return setdata + else + return datatable + end +end + +--- Load back a SET of statics from file. +-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems. +-- @param #string Filename The name of the file. +-- @return Core.Set#SET_STATIC Set SET_STATIC containing the static objects. +function UTILS.LoadSetOfStatics(Path,Filename) + local filename = Filename or "SetOfStatics" + local datatable = SET_STATIC:New() + if UTILS.CheckFileExists(Path,filename) then + local outcome,loadeddata = UTILS.LoadFromFile(Path,Filename) + -- remove header + table.remove(loadeddata, 1) + for _id,_entry in pairs (loadeddata) do + local dataset = UTILS.Split(_entry,",") + -- staticname,position.x,position.y,position.z + local staticname = dataset[1] + local posx = tonumber(dataset[2]) + local posy = tonumber(dataset[3]) + local posz = tonumber(dataset[4]) + local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz}) + datatable:AddObject(STATIC:FindByName(staticname,false)) + end + else + return nil + end + return datatable +end + +--- Load back a stationary list of statics from file. +-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems. +-- @param #string Filename The name of the file. +-- @param #boolean Reduce If false, do not destroy the units with size=0. +-- @return #table Table of data objects (tables) containing staticname, size (0=dead else 1), coordinate and the static object. +-- Returns nil when file cannot be read. +function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce) + local reduce = Reduce==false and false or true + local filename = Filename or "StateListofStatics" + local datatable = {} + if UTILS.CheckFileExists(Path,filename) then + local outcome,loadeddata = UTILS.LoadFromFile(Path,Filename) + -- remove header + table.remove(loadeddata, 1) + for _id,_entry in pairs (loadeddata) do + local dataset = UTILS.Split(_entry,",") + -- staticname,units(1/0),position.x,position.y,position.z) + local staticname = dataset[1] + local size = tonumber(dataset[2]) + local posx = tonumber(dataset[3]) + local posy = tonumber(dataset[4]) + local posz = tonumber(dataset[5]) + local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz}) + local data = { staticname=staticname, size=size, coordinate=coordinate, static=STATIC:FindByName(staticname,false) } + table.insert(datatable,data) + if size==0 and reduce then + local static = STATIC:FindByName(staticname,false) + if static then + static:Destroy(false) + end + end + end + else + return nil + end + return datatable +end From 01a707ae0a6959f350f48cf523635e1017b8986f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 29 Nov 2021 07:57:07 +0100 Subject: [PATCH 013/200] Small changes in GROUP --- Moose Development/Moose/Wrapper/Group.lua | 50 +++++++++++------------ 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 72c9215ab..2708b8ca3 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -310,8 +310,7 @@ end --- Returns the @{DCS#Position3} position vectors indicating the point and direction vectors in 3D of the POSITIONABLE within the mission. -- @param Wrapper.Positionable#POSITIONABLE self --- @return DCS#Position The 3D position vectors of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. +-- @return DCS#Position The 3D position vectors of the POSITIONABLE or #nil if the groups not existing or alive. function GROUP:GetPositionVec3() -- Overridden from POSITIONABLE:GetPositionVec3() self:F2( self.PositionableName ) @@ -339,9 +338,7 @@ end -- If the first @{Wrapper.Unit} of the group is inactive, it will return false. -- -- @param #GROUP self --- @return #boolean true if the group is alive and active. --- @return #boolean false if the group is alive but inactive. --- @return #nil if the group does not exist anymore. +-- @return #boolean `true` if the group is alive *and* active, `false` if the group is alive but inactive or `#nil` if the group does not exist anymore. function GROUP:IsAlive() self:F2( self.GroupName ) @@ -363,8 +360,7 @@ end --- Returns if the group is activated. -- @param #GROUP self --- @return #boolean true if group is activated. --- @return #nil The group is not existing or alive. +-- @return #boolean `true` if group is activated or `#nil` The group is not existing or alive. function GROUP:IsActive() self:F2( self.GroupName ) @@ -412,7 +408,6 @@ function GROUP:Destroy( GenerateEvent, delay ) self:F2( self.GroupName ) if delay and delay>0 then - --SCHEDULER:New(nil, GROUP.Destroy, {self, GenerateEvent}, delay) self:ScheduleOnce(delay, GROUP.Destroy, self, GenerateEvent) else @@ -2326,41 +2321,44 @@ function GROUP:GetAttribute() local unarmedship=self:HasAttribute("Unarmed ships") - -- Define attribute. Order is important. - if transportplane then - attribute=GROUP.Attribute.AIR_TRANSPORTPLANE - elseif awacs then - attribute=GROUP.Attribute.AIR_AWACS - elseif fighter then + -- Define attribute. Order of attack is important. + if fighter then attribute=GROUP.Attribute.AIR_FIGHTER elseif bomber then attribute=GROUP.Attribute.AIR_BOMBER + elseif awacs then + attribute=GROUP.Attribute.AIR_AWACS + elseif transportplane then + attribute=GROUP.Attribute.AIR_TRANSPORTPLANE elseif tanker then attribute=GROUP.Attribute.AIR_TANKER + -- helos + elseif attackhelicopter then + attribute=GROUP.Attribute.AIR_ATTACKHELO 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 + -- ground - order of attack elseif ewr then attribute=GROUP.Attribute.GROUND_EWR elseif sam then attribute=GROUP.Attribute.GROUND_SAM + elseif aaa then + attribute=GROUP.Attribute.GROUND_AAA + elseif artillery then + attribute=GROUP.Attribute.GROUND_ARTILLERY + elseif tank then + attribute=GROUP.Attribute.GROUND_TANK + elseif apc then + attribute=GROUP.Attribute.GROUND_APC + elseif infantry then + attribute=GROUP.Attribute.GROUND_INFANTRY elseif truck then attribute=GROUP.Attribute.GROUND_TRUCK elseif train then attribute=GROUP.Attribute.GROUND_TRAIN + -- ships elseif aircraftcarrier then attribute=GROUP.Attribute.NAVAL_AIRCRAFTCARRIER elseif warship then From 9998c86c1f5e48995aa03f14eba018a904e81d14 Mon Sep 17 00:00:00 2001 From: Tommy Carlsson Date: Tue, 30 Nov 2021 19:37:26 +0400 Subject: [PATCH 014/200] Update AI_A2G_Dispatcher.lua Fix typos and incorrect references (leftovers) to A2A/CAP etc. --- .../Moose/AI/AI_A2G_Dispatcher.lua | 92 +++++++++---------- 1 file changed, 42 insertions(+), 50 deletions(-) diff --git a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua index 3db79d137..3c257bb2a 100644 --- a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua @@ -1,4 +1,4 @@ ---- **AI** - Create an automated A2G defense system based on a detection network of reconnaissance vehicles and air units, coordinating SEAD, BAI and CAP operations. +--- **AI** - Create an automated A2G defense system based on a detection network of reconnaissance vehicles and air units, coordinating SEAD, BAI and CAS operations. -- -- === -- @@ -30,7 +30,7 @@ -- -- ## YouTube Channel: -- --- [DCS WORLD - MOOSE - A2G GCICAP - Build an automatic A2G Defense System](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl0S4KMNUUJpaUs6zZHjLKNx) +-- [DCS WORLD - MOOSE - A2G DISPATCHER - Build an automatic A2G Defense System - Introduction](https://www.youtube.com/watch?v=zwSxWRAGVH8) -- -- === -- @@ -157,7 +157,7 @@ -- The A2G defense system will select from the given templates a random template to spawn a new plane (group). -- -- --- ## 10. How to squadrons engage in a defensive action? +-- ## 10. How do squadrons engage in a defensive action? -- -- There are two ways how squadrons engage and execute your A2G defenses. -- Squadrons can start the defense directly from the airbase, farp or carrier. When a squadron launches a defensive group, that group @@ -183,23 +183,23 @@ -- The COORDINATE_UNIT will help you to specify a defense coodinate that is attached to a moving unit. -- -- --- ## 13. How much defense coordinates do I need to create? +-- ## 13. How many defense coordinates do I need to create? -- -- It depends, but the idea is to define only the necessary defense points that drive your mission. --- If you define too much defense points, the performance of your mission may decrease. Per defense point defined, --- all the possible enemies are evaluated. Note that each defense coordinate has a reach depending on the size of the defense radius. --- The default defense radius is about 60km, and depending on the defense reactivity, defenses will be launched when the enemy is at --- close or greater distance from the defense coordinate. +-- If you define too many defense coordinates, the performance of your mission may decrease. For each defined defense coordinate, +-- all the possible enemies are evaluated. Note that each defense coordinate has a reach depending on the size of the associated defense radius. +-- The default defense radius is about 60km. Depending on the defense reactivity, defenses will be launched when the enemy is at a +-- closer distance from the defense coordinate than the defense radius. -- -- -- ## 14. For each Squadron doing patrols, what are the time intervals and patrol amounts to be performed? -- -- For each patrol: -- --- * **How many** patrol you want to have airborne at the same time? +-- * **How many** patrols you want to have airborne at the same time? -- * **How frequent** you want the defense mechanism to check whether to start a new patrol? -- --- other considerations: +-- Other considerations: -- -- * **How far** is the patrol area from the engagement "hot zone". You want to ensure that the enemy is reached on time! -- * **How safe** is the patrol area taking into account air superiority. Is it well defended, are there nearby A2A bases? @@ -214,9 +214,8 @@ -- * From a parking spot with running engines -- * From a parking spot with cold engines -- --- **The default takeoff method is staight in the air.** --- This takeoff method is the most useful if you want to avoid airplane clutter at airbases! --- But it is the least realistic one! +-- **The default takeoff method is straight in the air.** +-- This takeoff method is the most useful if you want to avoid airplane clutter at airbases, but it is the least realistic one. -- -- -- ## 16. For each Squadron, which landing method will I use? @@ -227,9 +226,8 @@ -- * Despawn after landing on the runway -- * Despawn after engine shutdown after landing -- --- **The default landing method is despawn when near the airbase when returning.** --- This landing method is the most useful if you want to avoid airplane clutter at airbases! --- But it is the least realistic one! +-- **The default landing method is to despawn when near the airbase when returning.** +-- This landing method is the most useful if you want to avoid airplane clutter at airbases, but it is the least realistic one. -- -- -- ## 19. For each Squadron, which **defense overhead** will I use? @@ -270,7 +268,7 @@ do -- AI_A2G_DISPATCHER -- @type AI_A2G_DISPATCHER -- @extends Tasking.DetectionManager#DETECTION_MANAGER - --- Create an automated A2G defense system based on a detection network of reconnaissance vehicles and air units, coordinating SEAD, BAI and CAP operations. + --- Create an automated A2G defense system based on a detection network of reconnaissance vehicles and air units, coordinating SEAD, BAI and CAS operations. -- -- === -- @@ -345,7 +343,7 @@ do -- AI_A2G_DISPATCHER -- -- It uses the DetectionSetGroup, which defines the set of reconnaissance groups to detect for enemy ground targets. -- Detection = DETECTION_AREAS:New( DetectionSetGroup, 1000 ) -- - -- -- Setup the A2A dispatcher, and initialize it. + -- -- Setup the A2G dispatcher, and initialize it. -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) -- -- @@ -589,14 +587,14 @@ do -- AI_A2G_DISPATCHER -- -- Setup the Takeoff methods -- -- -- The default takeoff - -- A2ADispatcher:SetDefaultTakeOffFromRunway() + -- A2GDispatcher:SetDefaultTakeOffFromRunway() -- -- -- The individual takeoff per squadron - -- A2ADispatcher:SetSquadronTakeoff( "Mineralnye", AI_A2G_DISPATCHER.Takeoff.Air ) - -- A2ADispatcher:SetSquadronTakeoffInAir( "Sochi" ) - -- A2ADispatcher:SetSquadronTakeoffFromRunway( "Mozdok" ) - -- A2ADispatcher:SetSquadronTakeoffFromParkingCold( "Maykop" ) - -- A2ADispatcher:SetSquadronTakeoffFromParkingHot( "Novo" ) + -- A2GDispatcher:SetSquadronTakeoff( "Mineralnye", AI_A2G_DISPATCHER.Takeoff.Air ) + -- A2GDispatcher:SetSquadronTakeoffInAir( "Sochi" ) + -- A2GDispatcher:SetSquadronTakeoffFromRunway( "Mozdok" ) + -- A2GDispatcher:SetSquadronTakeoffFromParkingCold( "Maykop" ) + -- A2GDispatcher:SetSquadronTakeoffFromParkingHot( "Novo" ) -- -- -- ### 3.5.1. Set Squadron takeoff altitude when spawning new aircraft in the air. @@ -635,7 +633,7 @@ do -- AI_A2G_DISPATCHER -- -- Aircraft will cold start from the FARP, and thus, a maximum of 4 aircraft can be launched at the same time. -- -- Additionally, depending on the group composition of the aircraft, defending units will be ordered for takeoff together. -- -- It takes about 3 to 4 minutes to takeoff helicopters from FARPs in cold start. - -- A2ADispatcher:SetSquadronTakeOffInterval( "Mineralnye", 60 * 4 ) + -- A2GDispatcher:SetSquadronTakeOffInterval( "Mineralnye", 60 * 4 ) -- -- -- ### 3.6. Set squadron landing methods @@ -649,7 +647,7 @@ do -- AI_A2G_DISPATCHER -- -- You can use these methods to minimize the airbase coodination overhead and to increase the airbase efficiency. -- When there are lots of aircraft returning for landing, at the same airbase, the takeoff process will be halted, which can cause a complete failure of the - -- A2A defense system, as no new CAP or GCI planes can takeoff. + -- A2G defense system, as no new SEAD, BAI or CAS planes can takeoff. -- Note that the method @{#AI_A2G_DISPATCHER.SetSquadronLandingNearAirbase}() will only work for returning aircraft, not for damaged or out of fuel aircraft. -- Damaged or out-of-fuel aircraft are returning to the nearest friendly airbase and will land, and are out of control from ground control. -- @@ -659,14 +657,14 @@ do -- AI_A2G_DISPATCHER -- -- Setup the Landing methods -- -- -- The default landing method - -- A2ADispatcher:SetDefaultLandingAtRunway() + -- A2GDispatcher:SetDefaultLandingAtRunway() -- -- -- The individual landing per squadron - -- A2ADispatcher:SetSquadronLandingAtRunway( "Mineralnye" ) - -- A2ADispatcher:SetSquadronLandingNearAirbase( "Sochi" ) - -- A2ADispatcher:SetSquadronLandingAtEngineShutdown( "Mozdok" ) - -- A2ADispatcher:SetSquadronLandingNearAirbase( "Maykop" ) - -- A2ADispatcher:SetSquadronLanding( "Novo", AI_A2G_DISPATCHER.Landing.AtRunway ) + -- A2GDispatcher:SetSquadronLandingAtRunway( "Mineralnye" ) + -- A2GDispatcher:SetSquadronLandingNearAirbase( "Sochi" ) + -- A2GDispatcher:SetSquadronLandingAtEngineShutdown( "Mozdok" ) + -- A2GDispatcher:SetSquadronLandingNearAirbase( "Maykop" ) + -- A2GDispatcher:SetSquadronLanding( "Novo", AI_A2G_DISPATCHER.Landing.AtRunway ) -- -- -- ### 3.7. Set squadron **grouping**. @@ -822,10 +820,10 @@ do -- AI_A2G_DISPATCHER -- Note that you can still change the patrol limit and patrol time intervals for each patrol individually using -- the @{#AI_A2G_DISPATCHER.SetSquadronPatrolTimeInterval}() method. -- - -- ## 10.7.3. Default tanker for refuelling when executing CAP. + -- ## 10.7.3. Default tanker for refuelling when executing SEAD, BAI and CAS. -- - -- Instead of sending CAP to RTB when out of fuel, you can let CAP refuel in mid air using a tanker. - -- This greatly increases the efficiency of your CAP operations. + -- Instead of sending SEAD, BAI and CAS to RTB when out of fuel, you can let SEAD, BAI and CAS refuel in mid air using a tanker. + -- This greatly increases the efficiency of your SEAD, BAI and CAS operations. -- -- In the mission editor, setup a group with task Refuelling. A tanker unit of the correct coalition will be automatically selected. -- Then, use the method @{#AI_A2G_DISPATCHER.SetDefaultTanker}() to set the tanker for the dispatcher. @@ -837,12 +835,6 @@ do -- AI_A2G_DISPATCHER -- -- ![Banner Image](..\Presentations\AI_A2G_DISPATCHER\AI_A2G_DISPATCHER-ME_11.JPG) -- - -- -- Define the CAP - -- A2ADispatcher:SetSquadron( "Sochi", AIRBASE.Caucasus.Sochi_Adler, { "SQ CCCP SU-34" }, 20 ) - -- A2ADispatcher:SetSquadronCap( "Sochi", ZONE:New( "PatrolZone" ), 4000, 8000, 600, 800, 1000, 1300 ) - -- A2ADispatcher:SetSquadronCapInterval("Sochi", 2, 30, 600, 1 ) - -- A2ADispatcher:SetSquadronGci( "Sochi", 900, 1200 ) - -- -- -- Set the default tanker for refuelling to "Tanker", when the default fuel treshold has reached 90% fuel left. -- A2ADispatcher:SetDefaultFuelThreshold( 0.9 ) -- A2ADispatcher:SetDefaultTanker( "Tanker" ) @@ -878,8 +870,8 @@ do -- AI_A2G_DISPATCHER -- ## 11. Airbase capture: -- -- Different squadrons can be located at one airbase. - -- If the airbase gets captured, that is, when there is an enemy unit near the airbase, and there aren't anymore friendlies at the airbase, the airbase will change coalition ownership. - -- As a result, the GCI and CAP will stop! + -- If the airbase gets captured, that is when there is an enemy unit near the airbase and there are no friendlies at the airbase, the airbase will change coalition ownership. + -- As a result, the SEAD, BAI and CAS will stop. -- However, the squadron will still stay alive. Any airplane that is airborne will continue its operations until all airborne airplanes -- of the squadron will be destroyed. This to keep consistency of air operations not to confuse the players. -- @@ -900,8 +892,8 @@ do -- AI_A2G_DISPATCHER -- @field Core.Spawn#SPAWN Spawn The spawning object. -- @field #number ResourceCount The number of resources available. -- @field #list<#string> TemplatePrefixes The list of template prefixes. - -- @field #boolean Captured true if the squadron is captured. - -- @field #number Overhead The overhead for the squadron. + -- @field #boolean Captured true if the squadron is captured. + -- @field #number Overhead The overhead for the squadron. --- List of defense coordinates. @@ -911,14 +903,14 @@ do -- AI_A2G_DISPATCHER --- @field #AI_A2G_DISPATCHER.DefenseCoordinates DefenseCoordinates AI_A2G_DISPATCHER.DefenseCoordinates = {} - --- Enumerator for spawns at airbases + --- Enumerator for spawns at airbases. -- @type AI_A2G_DISPATCHER.Takeoff -- @extends Wrapper.Group#GROUP.Takeoff --- @field #AI_A2G_DISPATCHER.Takeoff Takeoff AI_A2G_DISPATCHER.Takeoff = GROUP.Takeoff - --- Defnes Landing location. + --- Defines Landing location. -- @field #AI_A2G_DISPATCHER.Landing AI_A2G_DISPATCHER.Landing = { NearAirbase = 1, @@ -926,7 +918,7 @@ do -- AI_A2G_DISPATCHER AtEngineShutdown = 3, } - --- A defense queue item description + --- A defense queue item description. -- @type AI_A2G_DISPATCHER.DefenseQueueItem -- @field Squadron -- @field #AI_A2G_DISPATCHER.Squadron DefenderSquadron The squadron in the queue. @@ -938,7 +930,7 @@ do -- AI_A2G_DISPATCHER -- @field #string SquadronName The name of the squadron. --- Queue of planned defenses to be launched. - -- This queue exists because defenses must be launched on FARPS, or in the air, or on an airbase, or on carriers. + -- This queue exists because defenses must be launched from FARPS, in the air, from airbases, or from carriers. -- And some of these platforms have very limited amount of "launching" platforms. -- Therefore, this queue concept is introduced that queues each defender request. -- Depending on the location of the launching site, the queued defenders will be launched at varying time intervals. @@ -950,7 +942,7 @@ do -- AI_A2G_DISPATCHER AI_A2G_DISPATCHER.DefenseQueue = {} - --- Defense approach types + --- Defense approach types. -- @type #AI_A2G_DISPATCHER.DefenseApproach AI_A2G_DISPATCHER.DefenseApproach = { Random = 1, From 48e8b1a9b38e25a7f84b876d12d60adbcfc16eb0 Mon Sep 17 00:00:00 2001 From: Tommy Carlsson Date: Wed, 1 Dec 2021 11:22:36 +0400 Subject: [PATCH 015/200] Update AI_A2G_Dispatcher.lua More updates and fixes. Update AI_A2G_Dispatcher.lua General documentation updates. --- .../Moose/AI/AI_A2G_Dispatcher.lua | 369 +++++++++--------- 1 file changed, 190 insertions(+), 179 deletions(-) diff --git a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua index 3c257bb2a..22e6f56e9 100644 --- a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua @@ -7,14 +7,14 @@ -- * Setup quickly an A2G defense system for a coalition. -- * Setup multiple defense zones to defend specific coordinates in your battlefield. -- * Setup (SEAD) Suppression of Air Defense squadrons, to gain control in the air of enemy grounds. --- * Setup (CAS) Controlled Air Support squadrons, to attack closeby enemy ground units near friendly installations. -- * Setup (BAI) Battleground Air Interdiction squadrons to attack remote enemy ground units and targets. +-- * Setup (CAS) Controlled Air Support squadrons, to attack close by enemy ground units near friendly installations. -- * Define and use a detection network controlled by recce. --- * Define A2G defense squadrons at airbases, farps and carriers. +-- * Define A2G defense squadrons at airbases, FARPs and carriers. -- * Enable airbases for A2G defenses. -- * Add different planes and helicopter templates to squadrons. -- * Assign squadrons to execute a specific engagement type depending on threat level of the detected ground enemy unit composition. --- * Add multiple squadrons to different airbases, farps or carriers. +-- * Add multiple squadrons to different airbases, FARPs or carriers. -- * Define different ranges to engage upon. -- * Establish an automatic in air refuel process for planes using refuel tankers. -- * Setup default settings for all squadrons and A2G defenses. @@ -40,10 +40,10 @@ -- -- AI_A2G_DISPATCHER is the main A2G defense class that models the A2G defense system. -- --- Before you start using the AI_A2G_DISPATCHER, ask youself the following questions. +-- Before you start using the AI_A2G_DISPATCHER, ask yourself the following questions: -- -- --- ## 1. Which coalition am I modeling an A2G defense system for? blue or red? +-- ## 1. Which coalition am I modeling an A2G defense system for? Blue or red? -- -- One AI_A2G_DISPATCHER object can create a defense system for **one coalition**, which is blue or red. -- If you want to create a **mutual defense system**, for both blue and red, then you need to create **two** AI_A2G_DISPATCHER **objects**, @@ -52,7 +52,7 @@ -- -- ## 2. Which type of detection will I setup? Grouping based per AREA, per TYPE or per UNIT? (Later others will follow). -- --- The MOOSE framework leverages the @{Functional.Detection} classes to perform the reconnaissance, detecting enemy units +-- The MOOSE framework leverages the @{Functional.Detection} classes to perform the reconnaissance, detecting enemy units -- and reporting them to the head quarters. -- Several types of @{Functional.Detection} classes exist, and the most common characteristics of these classes is that they: -- @@ -73,10 +73,10 @@ -- the recce will be very effective at detecting approaching enemy targets. Therefore, always use the terrain very carefully! -- -- Airborne recce (AFAC) are also very effective. The are capable of patrolling at a functional detection altitude, --- having an overview of the whole battlefield. However, airborne recce can be vulnerable to air to ground attacks, +-- having an overview of the whole battlefield. However, airborne recce can be vulnerable to air to ground attacks, -- so you need air superiority to make them effective. -- Airborne recce will also have varying ground detection technology, which plays a big role in the effectiveness of the reconnaissance. --- Certain helicopter or plane types have ground searching radars or advanced ground scanning technology, and are very effective +-- Certain helicopter or plane types have ground searching radars or advanced ground scanning technology, and are very effective -- compared to air units having only visual detection capabilities. -- For example, for the red coalition, the Mi-28N and the Su-34; and for the blue side, the reaper, are such effective airborne recce units. -- @@ -88,7 +88,7 @@ -- -- ## 4. How do the defenses decide **when and where to engage** on approaching enemy units? -- --- The A2G dispacher needs you to setup (various) defense coordinates, which are strategic positions in the battle field to be defended. +-- The A2G dispatcher needs you to setup (various) defense coordinates, which are strategic positions in the battle field to be defended. -- Any ground based enemy approaching within the proximity of such a defense point, may trigger for a defensive action by friendly air units. -- -- There are 2 important parameters that play a role in the defensive decision making: defensiveness and reactivity. @@ -108,7 +108,7 @@ -- ## 5. Are defense coordinates and defense reactivity the only parameters? -- -- No, depending on the target type, and the threat level of the target, the probability of defense will be higher. --- In other words, when a SAM-10 radar emitter is detected, its probabilty for defense will be much higher than when a BMP-1 vehicle is +-- In other words, when a SAM-10 radar emitter is detected, its probability for defense will be much higher than when a BMP-1 vehicle is -- detected, even when both enemies are at the same distance from a defense coordinate. -- This will ensure optimal defenses, SEAD tasks will be launched much more quicker against engaging radar emitters, to ensure air superiority. -- Approaching main battle tanks will be engaged much faster, than a group of approaching trucks. @@ -117,25 +117,26 @@ -- ## 6. Which Squadrons will I create and which name will I give each Squadron? -- -- The A2G defense system works with **Squadrons**. Each Squadron must be given a unique name, that forms the **key** to the squadron. --- Several options and activities can be set per Squadron. A free format name can be given, but always ensure that the name is meaningfull +-- Several options and activities can be set per Squadron. A free format name can be given, but always ensure that the name is meaningful -- for your mission, and remember that squadron names are used for communication to the players of your mission. -- --- There are mainly 3 types of defenses: **SEAD**, **CAS** and **BAI**. +-- There are mainly 3 types of defenses: **SEAD**, **BAI**, and **CAS**. -- --- Suppression of Air Defenses (SEAD) are effective agains radar emitters. Close Air Support (CAS) is launched when the enemy is close near friendly units. +-- Suppression of Air Defenses (SEAD) are effective against radar emitters. -- Battleground Air Interdiction (BAI) tasks are launched when there are no friendlies around. +-- Close Air Support (CAS) is launched when the enemy is close near friendly units. -- -- Depending on the defense type, different payloads will be needed. See further points on squadron definition. -- -- --- ## 7. Where will the Squadrons be located? On Airbases? On Carrier Ships? On Farps? +-- ## 7. Where will the Squadrons be located? On Airbases? On Carriers? On FARPs? -- --- Squadrons are placed at the **home base** on an **airfield**, **carrier** or **farp**. +-- Squadrons are placed at the **home base** on an **airfield**, **carrier** or **FARP**. -- Carefully plan where each Squadron will be located as part of the defense system required for mission effective defenses. -- If the home base of the squadron is too far from assumed enemy positions, then the defenses will be too late. -- The home bases must be **behind** enemy lines, you want to prevent your home bases to be engaged by enemies! -- Depending on the units applied for defenses, the home base can be further or closer to the enemies. --- Any airbase, farp or carrier can act as the launching platform for A2G defenses. +-- Any airbase, FARP, or carrier can act as the launching platform for A2G defenses. -- Carefully plan which airbases will take part in the coalition. Color each airbase **in the color of the coalition**, using the mission editor, -- or your air units will not return for landing at the airbase! -- @@ -146,7 +147,7 @@ -- These are late activated groups with one airplane or helicopter that start with a specific name, called the **template prefix**. -- The A2G defense system will select from the given templates a random template to spawn a new plane (group). -- --- A squadron will perform specific task types (SEAD, CAS or BAI). So, squadrons will require specific templates for the +-- A squadron will perform specific task types (SEAD, BAI or CAS). So, squadrons will require specific templates for the -- task types it will perform. A squadron executing SEAD defenses, will require a payload with long range anti-radar seeking missiles. -- -- @@ -160,7 +161,7 @@ -- ## 10. How do squadrons engage in a defensive action? -- -- There are two ways how squadrons engage and execute your A2G defenses. --- Squadrons can start the defense directly from the airbase, farp or carrier. When a squadron launches a defensive group, that group +-- Squadrons can start the defense directly from the airbase, FARP or carrier. When a squadron launches a defensive group, that group -- will start directly from the airbase. The other way is to launch early on in the mission a patrolling mechanism. -- Squadrons will launch air units to patrol in specific zone(s), so that when ground enemy targets are detected, that the airborne -- A2G defenses can come immediately into action. @@ -180,7 +181,7 @@ -- ## 12. Are moving defense coordinates possible? -- -- Yes, different COORDINATE types are possible to be used. --- The COORDINATE_UNIT will help you to specify a defense coodinate that is attached to a moving unit. +-- The COORDINATE_UNIT will help you to specify a defense coordinate that is attached to a moving unit. -- -- -- ## 13. How many defense coordinates do I need to create? @@ -199,7 +200,7 @@ -- * **How many** patrols you want to have airborne at the same time? -- * **How frequent** you want the defense mechanism to check whether to start a new patrol? -- --- Other considerations: +-- Other considerations: -- -- * **How far** is the patrol area from the engagement "hot zone". You want to ensure that the enemy is reached on time! -- * **How safe** is the patrol area taking into account air superiority. Is it well defended, are there nearby A2A bases? @@ -227,18 +228,18 @@ -- * Despawn after engine shutdown after landing -- -- **The default landing method is to despawn when near the airbase when returning.** --- This landing method is the most useful if you want to avoid airplane clutter at airbases, but it is the least realistic one. +-- This landing method is the most useful if you want to avoid aircraft clutter at airbases, but it is the least realistic one. -- -- -- ## 19. For each Squadron, which **defense overhead** will I use? -- -- For each Squadron, depending on the helicopter or airplane type (modern, old) and payload, which overhead is required to provide any defense? -- --- In other words, if **X** enemy ground units are detected, how many **Y** defense helicpters or airplanes need to engage (per squadron)? --- The **Y** is dependent on the type of airplane (era), payload, fuel levels, skills etc. +-- In other words, if **X** enemy ground units are detected, how many **Y** defense helicopters or airplanes need to engage (per squadron)? +-- The **Y** is dependent on the type of aircraft (era), payload, fuel levels, skills etc. -- But the most important factor is the payload, which is the amount of A2G weapons the defense can carry to attack the enemy ground units. --- For example, a Ka-50 can carry 16 vikrs, that means, that it potentially can destroy at least 8 ground units without a reload of ammunication. --- That means, that one defender can destroy more enemy ground units. +-- For example, a Ka-50 can carry 16 Vikhrs, this means that it potentially can destroy at least 8 ground units without a reload of ammunition. +-- That means, that one defender can destroy more enemy ground units. -- Thus, the overhead is a **factor** that will calculate dynamically how many **Y** defenses will be required based on **X** attackers detected. -- -- **The default overhead is 1. A smaller value than 1, like 0.25 will decrease the overhead to a 1 / 4 ratio, meaning, @@ -277,7 +278,7 @@ do -- AI_A2G_DISPATCHER -- Multiple defense coordinates can be setup. Defense coordinates can be strategic or tactical positions or references to strategic units or scenery. -- The A2G dispatcher will evaluate every x seconds the tactical situation around each defense coordinate. When a defense coordinate -- is under threat, it will communicate through the command center that defensive actions need to be taken and will launch groups of air units for defense. - -- The level of threat to the defense coordinate varyies upon the strength and types of the enemy units, the distance to the defense point, and the defensiveness parameters. + -- The level of threat to the defense coordinate varies upon the strength and types of the enemy units, the distance to the defense point, and the defensiveness parameters. -- Defensive actions are taken through probability, but the closer and the more threat the enemy poses to the defense coordinate, the faster it will be attacked by friendly A2G units. -- -- Please study carefully the underlying explanations how to setup and use this module, as it has many features. @@ -288,6 +289,7 @@ do -- AI_A2G_DISPATCHER -- -- # USAGE GUIDE -- + -- -- ## 1. AI\_A2G\_DISPATCHER constructor: -- -- ![Banner Image](..\Presentations\AI_A2G_DISPATCHER\AI_A2G_DISPATCHER-ME_1.JPG) @@ -295,6 +297,7 @@ do -- AI_A2G_DISPATCHER -- -- The @{#AI_A2G_DISPATCHER.New}() method creates a new AI_A2G_DISPATCHER instance. -- + -- -- ### 1.1. Define the **reconnaissance network**: -- -- As part of the AI_A2G_DISPATCHER :New() constructor, a reconnaissance network must be given as the first parameter. @@ -326,18 +329,18 @@ do -- AI_A2G_DISPATCHER -- By spawning in dynamically additional recce, you can ensure that there is sufficient reconnaissance coverage so the defense mechanism is continuously -- alerted of new enemy ground targets. -- - -- The following example defens a new reconnaissance network using a @{Functional.Detection#DETECTION_AREAS} object. + -- The following is an example defense of a new reconnaissance network using a @{Functional.Detection#DETECTION_AREAS} object. -- -- -- Define a SET_GROUP object that builds a collection of groups that define the recce network. -- -- Here we build the network with all the groups that have a name starting with CCCP Recce. - -- DetectionSetGroup = SET_GROUP:New() -- Defene a set of group objects, caled DetectionSetGroup. - -- + -- DetectionSetGroup = SET_GROUP:New() -- Define a set of group objects, called DetectionSetGroup. + -- -- DetectionSetGroup:FilterPrefixes( { "CCCP Recce" } ) -- The DetectionSetGroup will search for groups that start with the name "CCCP Recce". - -- - -- -- This command will start the dynamic filtering, so when groups spawn in or are destroyed, + -- + -- -- This command will start the dynamic filtering, so when groups spawn in or are destroyed, -- -- which have a group name starting with "CCCP Recce", then these will be automatically added or removed from the set. - -- DetectionSetGroup:FilterStart() - -- + -- DetectionSetGroup:FilterStart() + -- -- -- This command defines the reconnaissance network. -- -- It will group any detected ground enemy targets within a radius of 1km. -- -- It uses the DetectionSetGroup, which defines the set of reconnaissance groups to detect for enemy ground targets. @@ -345,18 +348,19 @@ do -- AI_A2G_DISPATCHER -- -- -- Setup the A2G dispatcher, and initialize it. -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) - -- - -- + -- + -- -- The above example creates a SET_GROUP instance, and stores this in the variable (object) **DetectionSetGroup**. -- **DetectionSetGroup** is then being configured to filter all active groups with a group name starting with `"CCCP Recce"` to be included in the set. - -- **DetectionSetGroup** is then calling `FilterStart()`, which is starting the dynamic filtering or inclusion of these groups. + -- **DetectionSetGroup** is then calling `FilterStart()`, which is starting the dynamic filtering or inclusion of these groups. -- Note that any destroy or new spawn of a group having a name, starting with the above prefix, will be removed or added to the set. -- - -- Then a new detection object is created from the class `DETECTION_AREAS`. A grouping radius of 1000 meters (1km) is choosen. + -- Then a new detection object is created from the class `DETECTION_AREAS`. A grouping radius of 1000 meters (1km) is chosen. -- - -- The `Detection` object is then passed to the @{#AI_A2G_DISPATCHER.New}() method to indicate the reconnaissance network + -- The `Detection` object is then passed to the @{#AI_A2G_DISPATCHER.New}() method to indicate the reconnaissance network -- configuration and setup the A2G defense detection mechanism. -- + -- -- ### 1.2. Setup the A2G dispatcher for both a red and blue coalition. -- -- Following the above described procedure, you'll need to create for each coalition an separate detection network, and a separate A2G dispatcher. @@ -372,19 +376,20 @@ do -- AI_A2G_DISPATCHER -- DetectionBlue = DETECTION_AREAS:New( DetectionSetGroupBlue, 1000 ) -- A2GDispatcherBlue = AI_A2G_DISPATCHER:New( DetectionBlue ) -- - -- -- Note: Also the SET_GROUP objects should be created for each coalition separately, containing each red and blue recce respectively! -- + -- -- ### 1.3. Define the enemy ground target **grouping radius**, in case you use DETECTION_AREAS: -- - -- The target grouping radius is a property of the DETECTION_AREAS class, that was passed to the AI_A2G_DISPATCHER:New() method, + -- The target grouping radius is a property of the DETECTION_AREAS class, that was passed to the AI_A2G_DISPATCHER:New() method -- but can be changed. The grouping radius should not be too small, but also depends on the types of ground forces and the way you want your mission to evolve. -- A large radius will mean large groups of enemy ground targets, while making smaller groups will result in a more fragmented defense system. -- Typically I suggest a grouping radius of 1km. This is the right balance to create efficient defenses. -- - -- Note that detected targets are constantly re-grouped, that is, when certain detected enemy ground units are moving further than the group radius, - -- then these units will become a separate area being detected. This may result in additional defenses being started by the dispatcher! - -- So don't make this value too small! Again, I advise about 1km or 1000 meters. + -- Note that detected targets are constantly re-grouped, that is, when certain detected enemy ground units are moving further than the group radius + -- then these units will become a separate area being detected. This may result in additional defenses being started by the dispatcher, + -- so don't make this value too small! Again, about 1km, or 1000 meters, is recommended. + -- -- -- ## 2. Setup (a) **Defense Coordinate(s)**. -- @@ -404,7 +409,7 @@ do -- AI_A2G_DISPATCHER -- -- Later, a COORDINATE_UNIT will be added to the framework, which can be used to assign "moving" coordinates to an A2G dispatcher. -- - -- **REMEMBER!** + -- **REMEMBER!** -- -- - **Defense coordinates are the center of the A2G dispatcher defense system!** -- - **You can define more defense coordinates to defend a larger area.** @@ -427,15 +432,16 @@ do -- AI_A2G_DISPATCHER -- This defines an A2G dispatcher which will engage on enemy ground targets within 30km radius around the defense coordinate. -- Note that the defense radius **applies to all defense coordinates** defined within the A2G dispatcher. -- + -- -- ### 2.2. The **Defense Reactivity**. -- - -- There are three levels that can be configured to tweak the defense reactivity. As explained above, the threat to a defense coordinate is + -- There are three levels that can be configured to tweak the defense reactivity. As explained above, the threat to a defense coordinate is -- also determined by the distance of the enemy ground target to the defense coordinate. -- If you want to have a **low** defense reactivity, that is, the probability that an A2G defense will engage to the enemy ground target, then - -- use the @{#AI_A2G_DISPATCHER.SetDefenseReactivityLow}() method. For medium and high reactivity, use the methods + -- use the @{#AI_A2G_DISPATCHER.SetDefenseReactivityLow}() method. For medium and high reactivity, use the methods -- @{#AI_A2G_DISPATCHER.SetDefenseReactivityMedium}() and @{#AI_A2G_DISPATCHER.SetDefenseReactivityHigh}() respectively. -- - -- Note that the reactivity of defenses is always in relation to the Defense Radius! the shorter the distance, + -- Note that the reactivity of defenses is always in relation to the Defense Radius! the shorter the distance, -- the less reactive the defenses will be in terms of distance to enemy ground targets! -- -- For example: @@ -444,11 +450,12 @@ do -- AI_A2G_DISPATCHER -- -- This defines an A2G dispatcher with high defense reactivity. -- + -- -- ## 3. **Squadrons**. -- -- The A2G dispatcher works with **Squadrons**, that need to be defined using the different methods available. -- - -- Use the method @{#AI_A2G_DISPATCHER.SetSquadron}() to **setup a new squadron** active at an airfield, farp or carrier, + -- Use the method @{#AI_A2G_DISPATCHER.SetSquadron}() to **setup a new squadron** active at an airfield, FARP or carrier, -- while defining which helicopter or plane **templates** are being used by the squadron and how many **resources** are available. -- -- **Multiple squadrons** can be defined within one A2G dispatcher, each having specific defense tasks and defense parameter settings! @@ -467,11 +474,11 @@ do -- AI_A2G_DISPATCHER -- * Control how new helicopters or aircraft are taking off from the airfield, farp or carrier (in the air, cold, hot, at the runway). -- * Control how returning helicopters or aircraft are landing at the airfield, farp or carrier (in the air near the airbase, after landing, after engine shutdown). -- * Control the **grouping** of new helicopters or aircraft spawned at the airfield, farp or carrier. If there is more than one helicopter or aircraft to be spawned, these may be grouped. - -- * Control the **overhead** or defensive strength of the squadron. Depending on the types of helicopters, planes, amount of resources and payload (weapon configuration) chosen, + -- * Control the **overhead** or defensive strength of the squadron. Depending on the types of helicopters, planes, amount of resources and payload (weapon configuration) chosen, -- the mission designer can choose to increase or reduce the amount of planes spawned. -- -- The method @{#AI_A2G_DISPATCHER.SetSquadron}() defines for you a new squadron. - -- The provided parameters are the squadron name, airbase name and a list of template prefixe, and a number that indicates the amount of resources. + -- The provided parameters are the squadron name, airbase name and a list of template prefixes, and a number that indicates the amount of resources. -- -- For example, this defines 3 new squadrons: -- @@ -487,16 +494,17 @@ do -- AI_A2G_DISPATCHER -- Squadrons can be commanded to execute 3 types of tasks, as explained above: -- -- - SEAD: Suppression of Air Defenses, which are ground targets that have medium or long range radar emitters. + -- - BAI : Battlefield Air Interdiction, which are targets further away from the front-line. -- - CAS : Close Air Support, when there are enemy ground targets close to friendly units. - -- - BAI : Battlefield Air Interdiction, which are targets further away from the frond-line. -- -- You need to configure each squadron which task types you want it to perform. Read on ... -- + -- -- ### 3.2. Squadrons enemy ground target **engagement types**. -- - -- There are two ways how targets can be engaged: directly **on call** from the airfield, farp or carrier, or through a **patrol**. + -- There are two ways how targets can be engaged: directly **on call** from the airfield, FARP or carrier, or through a **patrol**. -- - -- Patrols are extremely handy, as these will airborne your helicopters or airplanes in advance. They will patrol in defined zones outlined, + -- Patrols are extremely handy, as these will get your helicopters or airplanes airborne in advance. They will patrol in defined zones outlined, -- and will engage with the targets once commanded. If the patrol zone is close enough to the enemy ground targets, then the time required -- to engage is heavily minimized! -- @@ -504,13 +512,14 @@ do -- AI_A2G_DISPATCHER -- -- The mission designer needs to carefully balance the need for patrols or the need for engagement on call from the airfields. -- + -- -- ### 3.3. Squadron **on call** engagement. -- -- So to make squadrons engage targets from the airfields, use the following methods: -- -- - For SEAD, use the @{#AI_A2G_DISPATCHER.SetSquadronSead}() method. - -- - For CAS, use the @{#AI_A2G_DISPATCHER.SetSquadronCas}() method. -- - For BAI, use the @{#AI_A2G_DISPATCHER.SetSquadronBai}() method. + -- - For CAS, use the @{#AI_A2G_DISPATCHER.SetSquadronCas}() method. -- -- Note that for the tasks, specific helicopter or airplane templates are required to be used, which you can configure using your mission editor. -- Especially the payload (weapons configuration) is important to get right. @@ -520,11 +529,12 @@ do -- AI_A2G_DISPATCHER -- A2GDispatcher:SetSquadron( "Maykop SEAD", AIRBASE.Caucasus.Maykop_Khanskaya, { "CCCP KA-50 SEAD" }, 10 ) -- A2GDispatcher:SetSquadronSead( "Maykop SEAD", 120, 250 ) -- + -- A2GDispatcher:SetSquadron( "Maykop BAI", "BAI", { "CCCP KA-50 BAI" }, 10 ) + -- A2GDispatcher:SetSquadronBai( "Maykop BAI", 120, 250 ) + -- -- A2GDispatcher:SetSquadron( "Maykop CAS", "CAS", { "CCCP KA-50 CAS" }, 10 ) -- A2GDispatcher:SetSquadronCas( "Maykop CAS", 120, 250 ) -- - -- A2GDispatcher:SetSquadron( "Maykop BAI", "BAI", { "CCCP KA-50 BAI" }, 10 ) - -- A2GDispatcher:SetSquadronBai( "Maykop BAI", 120, 250 ) -- -- ### 3.4. Squadron **on patrol engagement**. -- @@ -534,14 +544,14 @@ do -- AI_A2G_DISPATCHER -- So to make squadrons engage targets from a patrol zone, use the following methods: -- -- - For SEAD, use the @{#AI_A2G_DISPATCHER.SetSquadronSeadPatrol}() method. - -- - For CAS, use the @{#AI_A2G_DISPATCHER.SetSquadronCasPatrol}() method. -- - For BAI, use the @{#AI_A2G_DISPATCHER.SetSquadronBaiPatrol}() method. + -- - For CAS, use the @{#AI_A2G_DISPATCHER.SetSquadronCasPatrol}() method. -- -- Because a patrol requires more parameters, the following methods must be used to fine-tune the patrols for each squadron. -- -- - For SEAD, use the @{#AI_A2G_DISPATCHER.SetSquadronSeadPatrolInterval}() method. - -- - For CAS, use the @{#AI_A2G_DISPATCHER.SetSquadronCasPatrolInterval}() method. -- - For BAI, use the @{#AI_A2G_DISPATCHER.SetSquadronBaiPatrolInterval}() method. + -- - For CAS, use the @{#AI_A2G_DISPATCHER.SetSquadronCasPatrolInterval}() method. -- -- Here an example to setup patrols of various task types: -- @@ -549,16 +559,16 @@ do -- AI_A2G_DISPATCHER -- A2GDispatcher:SetSquadronSeadPatrol( "Maykop SEAD", PatrolZone, 300, 500, 50, 80, 250, 300 ) -- A2GDispatcher:SetSquadronPatrolInterval( "Maykop SEAD", 2, 30, 60, 1, "SEAD" ) -- - -- A2GDispatcher:SetSquadron( "Maykop CAS", "CAS", { "CCCP KA-50 CAS" }, 10 ) - -- A2GDispatcher:SetSquadronCasPatrol( "Maykop CAS", PatrolZone, 600, 700, 50, 80, 250, 300 ) - -- A2GDispatcher:SetSquadronPatrolInterval( "Maykop CAS", 2, 30, 60, 1, "CAS" ) - -- -- A2GDispatcher:SetSquadron( "Maykop BAI", "BAI", { "CCCP KA-50 BAI" }, 10 ) -- A2GDispatcher:SetSquadronBaiPatrol( "Maykop BAI", PatrolZone, 800, 900, 50, 80, 250, 300 ) -- A2GDispatcher:SetSquadronPatrolInterval( "Maykop BAI", 2, 30, 60, 1, "BAI" ) -- + -- A2GDispatcher:SetSquadron( "Maykop CAS", "CAS", { "CCCP KA-50 CAS" }, 10 ) + -- A2GDispatcher:SetSquadronCasPatrol( "Maykop CAS", PatrolZone, 600, 700, 50, 80, 250, 300 ) + -- A2GDispatcher:SetSquadronPatrolInterval( "Maykop CAS", 2, 30, 60, 1, "CAS" ) + -- -- - -- ### 3.5. Set squadron take-off methods + -- ### 3.5. Set squadron takeoff methods -- -- Use the various SetSquadronTakeoff... methods to control how squadrons are taking-off from the home airfield, FARP or ship. -- @@ -577,24 +587,24 @@ do -- AI_A2G_DISPATCHER -- * aircraft may get into a "dead-lock" situation, where two aircraft are blocking each other. -- * aircraft may collide at the airbase. -- * aircraft may be awaiting the landing of a plane currently in the air, but never lands ... - -- + -- -- Currently within the DCS engine, the airfield traffic coordination is erroneous and contains a lot of bugs. - -- If you experience while testing problems with aircraft take-off or landing, please use one of the above methods as a solution to workaround these issues! + -- If you experience while testing problems with aircraft takeoff or landing, please use one of the above methods as a solution to workaround these issues! -- -- This example sets the default takeoff method to be from the runway. -- And for a couple of squadrons overrides this default method. -- - -- -- Setup the Takeoff methods - -- - -- -- The default takeoff - -- A2GDispatcher:SetDefaultTakeOffFromRunway() - -- - -- -- The individual takeoff per squadron + -- -- Setup the takeoff methods + -- + -- -- Set the default takeoff method + -- A2GDispatcher:SetDefaultTakeoffFromRunway() + -- + -- -- Set the individual squadrons takeoff method -- A2GDispatcher:SetSquadronTakeoff( "Mineralnye", AI_A2G_DISPATCHER.Takeoff.Air ) -- A2GDispatcher:SetSquadronTakeoffInAir( "Sochi" ) -- A2GDispatcher:SetSquadronTakeoffFromRunway( "Mozdok" ) -- A2GDispatcher:SetSquadronTakeoffFromParkingCold( "Maykop" ) - -- A2GDispatcher:SetSquadronTakeoffFromParkingHot( "Novo" ) + -- A2GDispatcher:SetSquadronTakeoffFromParkingHot( "Novo" ) -- -- -- ### 3.5.1. Set Squadron takeoff altitude when spawning new aircraft in the air. @@ -606,6 +616,7 @@ do -- AI_A2G_DISPATCHER -- As part of the method @{#AI_A2G_DISPATCHER.SetSquadronTakeoffInAir}() a parameter can be specified to set the takeoff altitude. -- If this parameter is not specified, then the default altitude will be used for the squadron. -- + -- -- ### 3.5.2. Set Squadron takeoff interval. -- -- The different types of available airfields have different amounts of available launching platforms: @@ -613,13 +624,13 @@ do -- AI_A2G_DISPATCHER -- - Airbases typically have a lot of platforms. -- - FARPs have 4 platforms. -- - Ships have 2 to 4 platforms. - -- + -- -- Depending on the demand of requested takeoffs by the A2G dispatcher, an airfield can become overloaded. Too many aircraft need to be taken -- off at the same time, which will result in clutter as described above. In order to better control this behaviour, a takeoff scheduler is implemented, -- which can be used to control how many aircraft are ordered for takeoff between specific time intervals. - -- The takeff intervals can be specified per squadron, which make sense, as each squadron have a "home" airfield. + -- The takeoff intervals can be specified per squadron, which make sense, as each squadron have a "home" airfield. -- - -- For this purpose, the method @{#AI_A2G_DISPATCHER.SetSquadronTakeOffInterval}() can be used to specify the takeoff intervals of + -- For this purpose, the method @{#AI_A2G_DISPATCHER.SetSquadronTakeoffInterval}() can be used to specify the takeoff intervals of -- aircraft groups per squadron to avoid cluttering of aircraft at airbases. -- This is especially useful for FARPs and ships. Each takeoff dispatch is queued by the dispatcher and when the interval time -- has been reached, a new group will be spawned or activated for takeoff. @@ -632,8 +643,8 @@ do -- AI_A2G_DISPATCHER -- -- Imagine a squadron launched from a FARP, with a grouping of 4. -- -- Aircraft will cold start from the FARP, and thus, a maximum of 4 aircraft can be launched at the same time. -- -- Additionally, depending on the group composition of the aircraft, defending units will be ordered for takeoff together. - -- -- It takes about 3 to 4 minutes to takeoff helicopters from FARPs in cold start. - -- A2GDispatcher:SetSquadronTakeOffInterval( "Mineralnye", 60 * 4 ) + -- -- It takes about 3 to 4 minutes for helicopters to takeoff from FARPs in cold start. + -- A2GDispatcher:SetSquadronTakeoffInterval( "Mineralnye", 60 * 4 ) -- -- -- ### 3.6. Set squadron landing methods @@ -645,7 +656,7 @@ do -- AI_A2G_DISPATCHER -- * @{#AI_A2G_DISPATCHER.SetSquadronLandingAtRunway}() will despawn the returning aircraft directly after landing at the runway. -- * @{#AI_A2G_DISPATCHER.SetSquadronLandingAtEngineShutdown}() will despawn the returning aircraft when the aircraft has returned to its parking spot and has turned off its engines. -- - -- You can use these methods to minimize the airbase coodination overhead and to increase the airbase efficiency. + -- You can use these methods to minimize the airbase coordination overhead and to increase the airbase efficiency. -- When there are lots of aircraft returning for landing, at the same airbase, the takeoff process will be halted, which can cause a complete failure of the -- A2G defense system, as no new SEAD, BAI or CAS planes can takeoff. -- Note that the method @{#AI_A2G_DISPATCHER.SetSquadronLandingNearAirbase}() will only work for returning aircraft, not for damaged or out of fuel aircraft. @@ -673,7 +684,7 @@ do -- AI_A2G_DISPATCHER -- -- ![Banner Image](..\Presentations\AI_A2G_DISPATCHER\Dia12.JPG) -- - -- In the case of **on call** engagement, the @{#AI_A2G_DISPATCHER.SetSquadronGrouping}() method has additional behaviour. + -- In the case of **on call** engagement, the @{#AI_A2G_DISPATCHER.SetSquadronGrouping}() method has additional behaviour. -- When there aren't enough patrol flights airborne, a on call will be initiated for the remaining -- targets to be engaged. Depending on the grouping parameter, the spawned flights for on call aircraft are grouped into this setting. -- For example with a group setting of 2, if 3 targets are detected and cannot be engaged by the available patrols or any airborne flight, @@ -695,13 +706,13 @@ do -- AI_A2G_DISPATCHER -- -- For example, a A-10C with full long-distance A2G missiles payload, may still be less effective than a Su-23 with short range A2G missiles... -- So in this case, one may want to use the @{#AI_A2G_DISPATCHER.SetOverhead}() method to allocate more defending planes as the amount of detected attacking ground units. - -- The overhead must be given as a decimal value with 1 as the neutral value, which means that overhead values: + -- The overhead must be given as a decimal value with 1 as the neutral value, which means that overhead values: -- -- * Higher than 1.0, for example 1.5, will increase the defense unit amounts. For 4 attacking ground units detected, 6 aircraft will be spawned. -- * Lower than 1, for example 0.75, will decrease the defense unit amounts. For 4 attacking ground units detected, only 3 aircraft will be spawned. -- - -- The amount of defending units is calculated by multiplying the amount of detected attacking ground units as part of the detected group - -- multiplied by the overhead parameter, and rounded up to the smallest integer. + -- The amount of defending units is calculated by multiplying the amount of detected attacking ground units as part of the detected group + -- multiplied by the overhead parameter, and rounded up to the smallest integer. -- -- Typically, for A2G defenses, values small than 1 will be used. Here are some good values for a couple of aircraft to support CAS operations: -- @@ -710,7 +721,7 @@ do -- AI_A2G_DISPATCHER -- - A-10A: 0.25 -- - SU-25T: 0.10 -- - -- So generically, the amount of missiles that an aircraft can take will determine its attacking effectiveness. The longer the range of the missiles, + -- So generically, the amount of missiles that an aircraft can take will determine its attacking effectiveness. The longer the range of the missiles, -- the less risk that the defender may be destroyed by the enemy, thus, the less aircraft needs to be activated in a defense. -- -- The **overhead value is set for a Squadron**, and can be **dynamically adjusted** during mission execution, so to adjust the defense overhead when the tactical situation changes. @@ -722,13 +733,13 @@ do -- AI_A2G_DISPATCHER -- -- Use the method @{#AI_A2G_DISPATCHER.SetSquadronEngageLimit}() to limit the amount of aircraft that will engage with the enemy, per squadron. -- - -- ## 4. Set the **fuel treshold**. + -- ## 4. Set the **fuel threshold**. -- - -- When aircraft get **out of fuel** to a certain %-tage, which is by default **15% (0.15)**, there are two possible actions that can be taken: + -- When an aircraft gets **out of fuel** with only a certain % of fuel left, which is **15% (0.15)** by default, there are two possible actions that can be taken: -- - The aircraft will go RTB, and will be replaced with a new aircraft if possible. -- - The aircraft will refuel at a tanker, if a tanker has been specified for the squadron. -- - -- Use the method @{#AI_A2G_DISPATCHER.SetSquadronFuelThreshold}() to set the **squadron fuel treshold** of the aircraft for all squadrons. + -- Use the method @{#AI_A2G_DISPATCHER.SetSquadronFuelThreshold}() to set the **squadron fuel threshold** of the aircraft for all squadrons. -- -- ## 6. Other configuration options -- @@ -740,7 +751,7 @@ do -- AI_A2G_DISPATCHER -- -- ## 10. Default settings. -- - -- Default settings configure the standard behaviour of the squadrons. + -- Default settings configure the standard behaviour of the squadrons. -- This section a good overview of the different parameters that setup the behaviour of **ALL** the squadrons by default. -- Note that default behaviour can be tweaked, and thus, this will change the behaviour of all the squadrons. -- Unless there is a specific behaviour set for a specific squadron, the default configured behaviour will be followed. @@ -759,7 +770,7 @@ do -- AI_A2G_DISPATCHER -- -- ## 10.2. Default landing behaviour. -- - -- The default landing behaviour is set to **near the airbase**, which means that returning airplanes will be despawned directly in the air by default. + -- The default landing behaviour is set to **near the airbase**, which means that returning aircraft will be despawned directly in the air by default. -- -- The default landing method can be set for ALL squadrons that don't have an individual landing method configured. -- @@ -778,23 +789,23 @@ do -- AI_A2G_DISPATCHER -- -- ## 10.4. Default **grouping**. -- - -- The default grouping is set to **one airplane**. That essentially means that there won't be any grouping applied by default. + -- The default grouping is set to **one aircraft**. That essentially means that there won't be any grouping applied by default. -- -- The default grouping value can be set for ALL squadrons that don't have an individual grouping value configured. -- - -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultGrouping}() to set the **default grouping** of spawned airplanes for all squadrons. + -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultGrouping}() to set the **default grouping** of spawned aircraft for all squadrons. -- - -- ## 10.5. Default RTB fuel treshold. + -- ## 10.5. Default RTB fuel threshold. -- - -- When an airplane gets **out of fuel** to a certain %-tage, which is **15% (0.15)**, it will go RTB, and will be replaced with a new airplane when applicable. + -- When an aircraft gets **out of fuel** with only a certain % of fuel left, which is **15% (0.15)** by default, it will go RTB, and will be replaced with a new aircraft when applicable. -- - -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultFuelThreshold}() to set the **default fuel treshold** of spawned airplanes for all squadrons. + -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultFuelThreshold}() to set the **default fuel threshold** of spawned aircraft for all squadrons. -- - -- ## 10.6. Default RTB damage treshold. + -- ## 10.6. Default RTB damage threshold. -- - -- When an airplane is **damaged** to a certain %-tage, which is **40% (0.40)**, it will go RTB, and will be replaced with a new airplane when applicable. + -- When an aircraft is **damaged** to a certain %, which is **40% (0.40)** by default, it will go RTB, and will be replaced with a new aircraft when applicable. -- - -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultDamageThreshold}() to set the **default damage treshold** of spawned airplanes for all squadrons. + -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultDamageThreshold}() to set the **default damage threshold** of spawned aircraft for all squadrons. -- -- ## 10.7. Default settings for **patrol**. -- @@ -820,14 +831,14 @@ do -- AI_A2G_DISPATCHER -- Note that you can still change the patrol limit and patrol time intervals for each patrol individually using -- the @{#AI_A2G_DISPATCHER.SetSquadronPatrolTimeInterval}() method. -- - -- ## 10.7.3. Default tanker for refuelling when executing SEAD, BAI and CAS. + -- ## 10.7.3. Default tanker for refuelling when executing SEAD, BAI and CAS operations. -- - -- Instead of sending SEAD, BAI and CAS to RTB when out of fuel, you can let SEAD, BAI and CAS refuel in mid air using a tanker. + -- Instead of sending SEAD, BAI and CAS aircraft to RTB when out of fuel, you can let SEAD, BAI and CAS aircraft refuel in mid air using a tanker. -- This greatly increases the efficiency of your SEAD, BAI and CAS operations. -- -- In the mission editor, setup a group with task Refuelling. A tanker unit of the correct coalition will be automatically selected. -- Then, use the method @{#AI_A2G_DISPATCHER.SetDefaultTanker}() to set the tanker for the dispatcher. - -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultFuelThreshold}() to set the %-tage left in the defender airplane tanks when a refuel action is needed. + -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultFuelThreshold}() to set the % left in the defender aircraft tanks when a refuel action is needed. -- -- When the tanker specified is alive and in the air, the tanker will be used for refuelling. -- @@ -835,9 +846,9 @@ do -- AI_A2G_DISPATCHER -- -- ![Banner Image](..\Presentations\AI_A2G_DISPATCHER\AI_A2G_DISPATCHER-ME_11.JPG) -- - -- -- Set the default tanker for refuelling to "Tanker", when the default fuel treshold has reached 90% fuel left. - -- A2ADispatcher:SetDefaultFuelThreshold( 0.9 ) - -- A2ADispatcher:SetDefaultTanker( "Tanker" ) + -- -- Set the default tanker for refuelling to "Tanker", when the default fuel threshold has reached 90% fuel left. + -- A2GDispatcher:SetDefaultFuelThreshold( 0.9 ) + -- A2GDispatcher:SetDefaultTanker( "Tanker" ) -- -- ## 10.8. Default settings for GCI. -- @@ -871,9 +882,9 @@ do -- AI_A2G_DISPATCHER -- -- Different squadrons can be located at one airbase. -- If the airbase gets captured, that is when there is an enemy unit near the airbase and there are no friendlies at the airbase, the airbase will change coalition ownership. - -- As a result, the SEAD, BAI and CAS will stop. - -- However, the squadron will still stay alive. Any airplane that is airborne will continue its operations until all airborne airplanes - -- of the squadron will be destroyed. This to keep consistency of air operations not to confuse the players. + -- As a result, further SEAD, BAI, and CAS operations from that airbase will stop. + -- However, the squadron will still stay alive. Any aircraft that is airborne will continue its operations until all airborne aircraft + -- of the squadron are destroyed. This is to keep consistency of air operations and avoid confusing players. -- -- -- @@ -930,7 +941,7 @@ do -- AI_A2G_DISPATCHER -- @field #string SquadronName The name of the squadron. --- Queue of planned defenses to be launched. - -- This queue exists because defenses must be launched from FARPS, in the air, from airbases, or from carriers. + -- This queue exists because defenses must be launched from FARPs, in the air, from airbases, or from carriers. -- And some of these platforms have very limited amount of "launching" platforms. -- Therefore, this queue concept is introduced that queues each defender request. -- Depending on the location of the launching site, the queued defenders will be launched at varying time intervals. @@ -950,9 +961,9 @@ do -- AI_A2G_DISPATCHER } --- AI_A2G_DISPATCHER constructor. - -- This is defining the A2G DISPATCHER for one coaliton. + -- This is defining the A2G DISPATCHER for one coalition. -- The Dispatcher works with a @{Functional.Detection#DETECTION_BASE} object that is taking of the detection of targets using the EWR units. - -- The Detection object is polymorphic, depending on the type of detection object choosen, the detection will work differently. + -- The Detection object is polymorphic, depending on the type of detection object chosen, the detection will work differently. -- @param #AI_A2G_DISPATCHER self -- @param Functional.Detection#DETECTION_BASE Detection The DETECTION object that will detects targets using the the Early Warning Radar network. -- @return #AI_A2G_DISPATCHER self @@ -999,11 +1010,11 @@ do -- AI_A2G_DISPATCHER self:SetDisengageRadius( 300000 ) -- The default Disengage Radius is 300 km. self:SetDefaultTakeoff( AI_A2G_DISPATCHER.Takeoff.Air ) - self:SetDefaultTakeoffInAirAltitude( 500 ) -- Default takeoff is 500 meters above the ground. + self:SetDefaultTakeoffInAirAltitude( 500 ) -- Default takeoff is 500 meters above ground level (AGL). self:SetDefaultLanding( AI_A2G_DISPATCHER.Landing.NearAirbase ) self:SetDefaultOverhead( 1 ) self:SetDefaultGrouping( 1 ) - self:SetDefaultFuelThreshold( 0.15, 0 ) -- 15% of fuel remaining in the tank will trigger the airplane to return to base or refuel. + self:SetDefaultFuelThreshold( 0.15, 0 ) -- 15% of fuel remaining in the tank will trigger the aircraft to return to base or refuel. self:SetDefaultDamageThreshold( 0.4 ) -- When 40% of damage, go RTB. self:SetDefaultPatrolTimeInterval( 180, 600 ) -- Between 180 and 600 seconds. self:SetDefaultPatrolLimit( 1 ) -- Maximum one Patrol per squadron. @@ -1220,7 +1231,7 @@ do -- AI_A2G_DISPATCHER self:I( "Captured " .. AirbaseName ) - -- Now search for all squadrons located at the airbase, and sanatize them. + -- Now search for all squadrons located at the airbase, and sanitize them. for SquadronName, Squadron in pairs( self.DefenderSquadrons ) do if Squadron.AirbaseName == AirbaseName then Squadron.ResourceCount = -999 -- The base has been captured, and the resources are eliminated. No more spawning. @@ -1322,7 +1333,7 @@ do -- AI_A2G_DISPATCHER -- A2GDispatcher:SetDisengageRadius( 50000 ) -- -- -- Set 100km as the Disengage Radius. - -- A2GDispatcher:SetDisngageRadius() -- 300000 is the default value. + -- A2GDispatcher:SetDisengageRadius() -- 300000 is the default value. -- function AI_A2G_DISPATCHER:SetDisengageRadius( DisengageRadius ) @@ -1332,7 +1343,7 @@ do -- AI_A2G_DISPATCHER end - --- Define the defense radius to check if a target can be engaged by a squadron group for SEAD, CAS or BAI for defense. + --- Define the defense radius to check if a target can be engaged by a squadron group for SEAD, BAI, or CAS for defense. -- When targets are detected that are still really far off, you don't want the AI_A2G_DISPATCHER to launch defenders, as they might need to travel too far. -- You want it to wait until a certain defend radius is reached, which is calculated as: -- 1. the **distance of the closest airbase to target**, being smaller than the **Defend Radius**. @@ -1427,18 +1438,18 @@ do -- AI_A2G_DISPATCHER end - --- Set the default damage treshold when defenders will RTB. - -- The default damage treshold is by default set to 40%, which means that when the airplane is 40% damaged, it will go RTB. + --- Set the default damage threshold when defenders will RTB. + -- The default damage threshold is by default set to 40%, which means that when the aircraft is 40% damaged, it will go RTB. -- @param #AI_A2G_DISPATCHER self - -- @param #number DamageThreshold A decimal number between 0 and 1, that expresses the %-tage of the damage treshold before going RTB. + -- @param #number DamageThreshold A decimal number between 0 and 1, that expresses the % of damage when the aircraft will go RTB. -- @return #AI_A2G_DISPATCHER -- @usage -- -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) -- - -- -- Now Setup the default damage treshold. - -- A2GDispatcher:SetDefaultDamageThreshold( 0.90 ) -- Go RTB when the airplane 90% damaged. + -- -- Now Setup the default damage threshold. + -- A2GDispatcher:SetDefaultDamageThreshold( 0.90 ) -- Go RTB when the aircraft is 90% damaged. -- function AI_A2G_DISPATCHER:SetDefaultDamageThreshold( DamageThreshold ) @@ -1865,7 +1876,7 @@ do -- AI_A2G_DISPATCHER -- -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The squadron name. - -- @param #number PatrolLimit (optional) The maximum amount of Patrol groups to be spawned. Note that a Patrol is a group, so can consist out of 1 to 4 airplanes. The default is 1 Patrol group. + -- @param #number PatrolLimit (optional) The maximum amount of Patrol groups to be spawned. Note that each Patrol is a group, and can consist of 1 to 4 aircraft. The default is 1 Patrol group. -- @param #number LowInterval (optional) The minimum time boundary in seconds when a new Patrol will be spawned. The default is 180 seconds. -- @param #number HighInterval (optional) The maximum time boundary in seconds when a new Patrol will be spawned. The default is 600 seconds. -- @param #number Probability Is not in use, you can skip this parameter. @@ -1912,9 +1923,9 @@ do -- AI_A2G_DISPATCHER --- Set the squadron Patrol parameters for SEAD tasks. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The squadron name. - -- @param #number PatrolLimit (optional) The maximum amount of Patrol groups to be spawned. Note that a Patrol is a group, so can consist out of 1 to 4 airplanes. The default is 1 Patrol group. - -- @param #number LowInterval (optional) The minimum time boundary in seconds when a new Patrol will be spawned. The default is 180 seconds. - -- @param #number HighInterval (optional) The maximum time boundary in seconds when a new Patrol will be spawned. The default is 600 seconds. + -- @param #number PatrolLimit (optional) The maximum amount of Patrol groups to be spawned. Each Patrol group can consist of 1 to 4 aircraft. The default is 1 Patrol group. + -- @param #number LowInterval (optional) The minimum time in seconds between new Patrols being spawned. The default is 180 seconds. + -- @param #number HighInterval (optional) The maximum ttime in seconds between new Patrols being spawned. The default is 600 seconds. -- @param #number Probability Is not in use, you can skip this parameter. -- @return #AI_A2G_DISPATCHER -- @usage @@ -1934,9 +1945,9 @@ do -- AI_A2G_DISPATCHER --- Set the squadron Patrol parameters for CAS tasks. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The squadron name. - -- @param #number PatrolLimit (optional) The maximum amount of Patrol groups to be spawned. Note that a Patrol is a group, so can consist out of 1 to 4 airplanes. The default is 1 Patrol group. - -- @param #number LowInterval (optional) The minimum time boundary in seconds when a new Patrol will be spawned. The default is 180 seconds. - -- @param #number HighInterval (optional) The maximum time boundary in seconds when a new Patrol will be spawned. The default is 600 seconds. + -- @param #number PatrolLimit (optional) The maximum amount of Patrol groups to be spawned. Each Patrol group can consist of 1 to 4 aircraft. The default is 1 Patrol group. + -- @param #number LowInterval (optional) The minimum time in seconds between new Patrols being spawned. The default is 180 seconds. + -- @param #number HighInterval (optional) The maximum time in seconds between new Patrols being spawned. The default is 600 seconds. -- @param #number Probability Is not in use, you can skip this parameter. -- @return #AI_A2G_DISPATCHER -- @usage @@ -1956,9 +1967,9 @@ do -- AI_A2G_DISPATCHER --- Set the squadron Patrol parameters for BAI tasks. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The squadron name. - -- @param #number PatrolLimit (optional) The maximum amount of Patrol groups to be spawned. Note that a Patrol is a group, so can consist out of 1 to 4 airplanes. The default is 1 Patrol group. - -- @param #number LowInterval (optional) The minimum time boundary in seconds when a new Patrol will be spawned. The default is 180 seconds. - -- @param #number HighInterval (optional) The maximum time boundary in seconds when a new Patrol will be spawned. The default is 600 seconds. + -- @param #number PatrolLimit (optional) The maximum amount of Patrol groups to be spawned. Each Patrol group can consist of 1 to 4 aircraft. The default is 1 Patrol group. + -- @param #number LowInterval (optional) The minimum time in seconds between new Patrols being spawned. The default is 180 seconds. + -- @param #number HighInterval (optional) The maximum time in seconds between new Patrols being spawned. The default is 600 seconds. -- @param #number Probability Is not in use, you can skip this parameter. -- @return #AI_A2G_DISPATCHER -- @usage @@ -2529,7 +2540,7 @@ do -- AI_A2G_DISPATCHER end - --- Defines the default amount of extra planes that will take-off as part of the defense system. + --- Defines the default amount of extra planes that will takeoff as part of the defense system. -- @param #AI_A2G_DISPATCHER self -- @param #number Overhead The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units. -- The default overhead is 1, so equal balance. The @{#AI_A2G_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, @@ -2567,7 +2578,7 @@ do -- AI_A2G_DISPATCHER end - --- Defines the amount of extra planes that will take-off as part of the defense system. + --- Defines the amount of extra planes that will takeoff as part of the defense system. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @param #number Overhead The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units. @@ -2645,15 +2656,15 @@ do -- AI_A2G_DISPATCHER end - --- Sets the default grouping of new airplanes spawned. - -- Grouping will trigger how new airplanes will be grouped if more than one airplane is spawned for defense. + --- Sets the default grouping of new aircraft spawned. + -- Grouping will trigger how new aircraft will be grouped if more than one aircraft is spawned for defense. -- @param #AI_A2G_DISPATCHER self - -- @param #number Grouping The level of grouping that will be applied of the Patrol or GCI defenders. + -- @param #number Grouping The level of grouping that will be applied for the Patrol. -- @usage: -- -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) -- - -- -- Set a grouping by default per 2 airplanes. + -- -- Set a grouping by default per 2 aircraft. -- A2GDispatcher:SetDefaultGrouping( 2 ) -- -- @@ -2666,16 +2677,16 @@ do -- AI_A2G_DISPATCHER end - --- Sets the grouping of new airplanes spawned. - -- Grouping will trigger how new airplanes will be grouped if more than one airplane is spawned for defense. + --- Sets the Squadron grouping of new aircraft spawned. + -- Grouping will trigger how new aircraft will be grouped if more than one aircraft is spawned for defense. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The name of the squadron. - -- @param #number Grouping The level of grouping that will be applied of the Patrol or GCI defenders. + -- @param #number Grouping The level of grouping that will be applied for a Patrol from the Squadron. -- @usage: -- -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) -- - -- -- Set a grouping per 2 airplanes. + -- -- Set a Squadron specific grouping per 2 aircraft. -- A2GDispatcher:SetSquadronGrouping( "SquadronName", 2 ) -- -- @@ -2713,23 +2724,23 @@ do -- AI_A2G_DISPATCHER end - --- Defines the default method at which new flights will spawn and take-off as part of the defense system. + --- Defines the default method at which new flights will spawn and takeoff as part of the defense system. -- @param #AI_A2G_DISPATCHER self -- @param #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. -- @usage: -- -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) -- - -- -- Let new flights by default take-off in the air. + -- -- Let new flights by default takeoff in the air. -- A2GDispatcher:SetDefaultTakeoff( AI_A2G_Dispatcher.Takeoff.Air ) -- - -- -- Let new flights by default take-off from the runway. + -- -- Let new flights by default takeoff from the runway. -- A2GDispatcher:SetDefaultTakeoff( AI_A2G_Dispatcher.Takeoff.Runway ) -- - -- -- Let new flights by default take-off from the airbase hot. + -- -- Let new flights by default takeoff from the airbase hot. -- A2GDispatcher:SetDefaultTakeoff( AI_A2G_Dispatcher.Takeoff.Hot ) -- - -- -- Let new flights by default take-off from the airbase cold. + -- -- Let new flights by default takeoff from the airbase cold. -- A2GDispatcher:SetDefaultTakeoff( AI_A2G_Dispatcher.Takeoff.Cold ) -- -- @@ -2742,7 +2753,7 @@ do -- AI_A2G_DISPATCHER return self end - --- Defines the method at which new flights will spawn and take-off as part of the defense system. + --- Defines the method at which new flights will spawn and takeoff as part of the defense system. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @param #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. @@ -2750,16 +2761,16 @@ do -- AI_A2G_DISPATCHER -- -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) -- - -- -- Let new flights take-off in the air. + -- -- Let new flights takeoff in the air. -- A2GDispatcher:SetSquadronTakeoff( "SquadronName", AI_A2G_Dispatcher.Takeoff.Air ) -- - -- -- Let new flights take-off from the runway. + -- -- Let new flights takeoff from the runway. -- A2GDispatcher:SetSquadronTakeoff( "SquadronName", AI_A2G_Dispatcher.Takeoff.Runway ) -- - -- -- Let new flights take-off from the airbase hot. + -- -- Let new flights takeoff from the airbase hot. -- A2GDispatcher:SetSquadronTakeoff( "SquadronName", AI_A2G_Dispatcher.Takeoff.Hot ) -- - -- -- Let new flights take-off from the airbase cold. + -- -- Let new flights takeoff from the airbase cold. -- A2GDispatcher:SetSquadronTakeoff( "SquadronName", AI_A2G_Dispatcher.Takeoff.Cold ) -- -- @@ -2774,16 +2785,16 @@ do -- AI_A2G_DISPATCHER end - --- Gets the default method at which new flights will spawn and take-off as part of the defense system. + --- Gets the default method at which new flights will spawn and takeoff as part of the defense system. -- @param #AI_A2G_DISPATCHER self -- @return #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. -- @usage: -- -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) -- - -- -- Let new flights by default take-off in the air. + -- -- Let new flights by default takeoff in the air. -- local TakeoffMethod = A2GDispatcher:GetDefaultTakeoff() - -- if TakeOffMethod == , AI_A2G_Dispatcher.Takeoff.InAir then + -- if TakeoffMethod == , AI_A2G_Dispatcher.Takeoff.InAir then -- ... -- end -- @@ -2792,7 +2803,7 @@ do -- AI_A2G_DISPATCHER return self.DefenderDefault.Takeoff end - --- Gets the method at which new flights will spawn and take-off as part of the defense system. + --- Gets the method at which new flights will spawn and takeoff as part of the defense system. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @return #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. @@ -2800,9 +2811,9 @@ do -- AI_A2G_DISPATCHER -- -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) -- - -- -- Let new flights take-off in the air. + -- -- Let new flights takeoff in the air. -- local TakeoffMethod = A2GDispatcher:GetSquadronTakeoff( "SquadronName" ) - -- if TakeOffMethod == , AI_A2G_Dispatcher.Takeoff.InAir then + -- if TakeoffMethod == , AI_A2G_Dispatcher.Takeoff.InAir then -- ... -- end -- @@ -2813,13 +2824,13 @@ do -- AI_A2G_DISPATCHER end - --- Sets flights to default take-off in the air, as part of the defense system. + --- Sets flights to default takeoff in the air, as part of the defense system. -- @param #AI_A2G_DISPATCHER self -- @usage: -- -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) -- - -- -- Let new flights by default take-off in the air. + -- -- Let new flights by default takeoff in the air. -- A2GDispatcher:SetDefaultTakeoffInAir() -- -- @return #AI_A2G_DISPATCHER @@ -2832,7 +2843,7 @@ do -- AI_A2G_DISPATCHER end - --- Sets flights to take-off in the air, as part of the defense system. + --- Sets flights to takeoff in the air, as part of the defense system. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @param #number TakeoffAltitude (optional) The altitude in meters above the ground. If not given, the default takeoff altitude will be used. @@ -2840,7 +2851,7 @@ do -- AI_A2G_DISPATCHER -- -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) -- - -- -- Let new flights take-off in the air. + -- -- Let new flights takeoff in the air. -- A2GDispatcher:SetSquadronTakeoffInAir( "SquadronName" ) -- -- @return #AI_A2G_DISPATCHER @@ -2857,13 +2868,13 @@ do -- AI_A2G_DISPATCHER end - --- Sets flights by default to take-off from the runway, as part of the defense system. + --- Sets flights by default to takeoff from the runway, as part of the defense system. -- @param #AI_A2G_DISPATCHER self -- @usage: -- -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) -- - -- -- Let new flights by default take-off from the runway. + -- -- Let new flights by default takeoff from the runway. -- A2GDispatcher:SetDefaultTakeoffFromRunway() -- -- @return #AI_A2G_DISPATCHER @@ -2876,14 +2887,14 @@ do -- AI_A2G_DISPATCHER end - --- Sets flights to take-off from the runway, as part of the defense system. + --- Sets flights to takeoff from the runway, as part of the defense system. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @usage: -- -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) -- - -- -- Let new flights take-off from the runway. + -- -- Let new flights takeoff from the runway. -- A2GDispatcher:SetSquadronTakeoffFromRunway( "SquadronName" ) -- -- @return #AI_A2G_DISPATCHER @@ -2896,13 +2907,13 @@ do -- AI_A2G_DISPATCHER end - --- Sets flights by default to take-off from the airbase at a hot location, as part of the defense system. + --- Sets flights by default to takeoff from the airbase at a hot location, as part of the defense system. -- @param #AI_A2G_DISPATCHER self -- @usage: -- -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) -- - -- -- Let new flights by default take-off at a hot parking spot. + -- -- Let new flights by default takeoff at a hot parking spot. -- A2GDispatcher:SetDefaultTakeoffFromParkingHot() -- -- @return #AI_A2G_DISPATCHER @@ -2914,14 +2925,14 @@ do -- AI_A2G_DISPATCHER return self end - --- Sets flights to take-off from the airbase at a hot location, as part of the defense system. + --- Sets flights to takeoff from the airbase at a hot location, as part of the defense system. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @usage: -- -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) -- - -- -- Let new flights take-off in the air. + -- -- Let new flights takeoff in the air. -- A2GDispatcher:SetSquadronTakeoffFromParkingHot( "SquadronName" ) -- -- @return #AI_A2G_DISPATCHER @@ -2934,13 +2945,13 @@ do -- AI_A2G_DISPATCHER end - --- Sets flights to by default take-off from the airbase at a cold location, as part of the defense system. + --- Sets flights to by default takeoff from the airbase at a cold location, as part of the defense system. -- @param #AI_A2G_DISPATCHER self -- @usage: -- -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) -- - -- -- Let new flights take-off from a cold parking spot. + -- -- Let new flights takeoff from a cold parking spot. -- A2GDispatcher:SetDefaultTakeoffFromParkingCold() -- -- @return #AI_A2G_DISPATCHER @@ -2953,14 +2964,14 @@ do -- AI_A2G_DISPATCHER end - --- Sets flights to take-off from the airbase at a cold location, as part of the defense system. + --- Sets flights to takeoff from the airbase at a cold location, as part of the defense system. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @usage: -- -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) -- - -- -- Let new flights take-off from a cold parking spot. + -- -- Let new flights takeoff from a cold parking spot. -- A2GDispatcher:SetSquadronTakeoffFromParkingCold( "SquadronName" ) -- -- @return #AI_A2G_DISPATCHER @@ -2973,9 +2984,9 @@ do -- AI_A2G_DISPATCHER end - --- Defines the default altitude where airplanes will spawn in the air and take-off as part of the defense system, when the take-off in the air method has been selected. + --- Defines the default altitude where aircraft will spawn in the air and takeoff as part of the defense system, when the takeoff in the air method has been selected. -- @param #AI_A2G_DISPATCHER self - -- @param #number TakeoffAltitude The altitude in meters above the ground. + -- @param #number TakeoffAltitude The altitude in meters above ground level (AGL). -- @usage: -- -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) @@ -2992,16 +3003,16 @@ do -- AI_A2G_DISPATCHER return self end - --- Defines the default altitude where airplanes will spawn in the air and take-off as part of the defense system, when the take-off in the air method has been selected. + --- Defines the default altitude where aircraft will spawn in the air and takeoff as part of the defense system, when the takeoff in the air method has been selected. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The name of the squadron. - -- @param #number TakeoffAltitude The altitude in meters above the ground. + -- @param #number TakeoffAltitude The altitude in meters above ground level (AGL). -- @usage: -- -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) -- -- -- Set the default takeoff altitude when taking off in the air. - -- A2GDispatcher:SetSquadronTakeoffInAirAltitude( "SquadronName", 2000 ) -- This makes planes start at 2000 meters above the ground. + -- A2GDispatcher:SetSquadronTakeoffInAirAltitude( "SquadronName", 2000 ) -- This makes aircraft start at 2000 meters above ground level (AGL). -- -- @return #AI_A2G_DISPATCHER -- @@ -3217,9 +3228,9 @@ do -- AI_A2G_DISPATCHER end --- Set the default fuel treshold when defenders will RTB or Refuel in the air. - -- The fuel treshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed. + -- The fuel treshold is by default set to 15%, which means that an aircraft will stay in the air until 15% of its fuel is remaining. -- @param #AI_A2G_DISPATCHER self - -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the %-tage of the treshold of fuel remaining in the tank when the plane will go RTB or Refuel. + -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the treshold of fuel remaining in the tank when the plane will go RTB or Refuel. -- @return #AI_A2G_DISPATCHER -- @usage -- @@ -3238,10 +3249,10 @@ do -- AI_A2G_DISPATCHER --- Set the fuel treshold for the squadron when defenders will RTB or Refuel in the air. - -- The fuel treshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed. + -- The fuel treshold is by default set to 15%, which means that an aircraft will stay in the air until 15% of its fuel is remaining. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The name of the squadron. - -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the %-tage of the treshold of fuel remaining in the tank when the plane will go RTB or Refuel. + -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the treshold of fuel remaining in the tank when the plane will go RTB or Refuel. -- @return #AI_A2G_DISPATCHER -- @usage -- From ff4708b6245885c31dcca99b5ebc7da5ff7c2170 Mon Sep 17 00:00:00 2001 From: Tommy Carlsson Date: Wed, 1 Dec 2021 13:41:27 +0400 Subject: [PATCH 016/200] Update AI_A2G_Dispatcher.lua General code formatting fixes, and correction of typos/examples. --- .../Moose/AI/AI_A2G_Dispatcher.lua | 57 ++++++++++++------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua index 22e6f56e9..597f60ac7 100644 --- a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua @@ -262,7 +262,6 @@ -- @image AI_Air_To_Ground_Dispatching.JPG - do -- AI_A2G_DISPATCHER --- AI_A2G_DISPATCHER class. @@ -906,7 +905,6 @@ do -- AI_A2G_DISPATCHER -- @field #boolean Captured true if the squadron is captured. -- @field #number Overhead The overhead for the squadron. - --- List of defense coordinates. -- @type AI_A2G_DISPATCHER.DefenseCoordinates -- @map <#string,Core.Point#COORDINATE> A list of all defense coordinates mapped per defense coordinate name. @@ -952,7 +950,6 @@ do -- AI_A2G_DISPATCHER --- @field #AI_A2G_DISPATCHER.DefenseQueue DefenseQueue AI_A2G_DISPATCHER.DefenseQueue = {} - --- Defense approach types. -- @type #AI_A2G_DISPATCHER.DefenseApproach AI_A2G_DISPATCHER.DefenseApproach = { @@ -1158,6 +1155,7 @@ do -- AI_A2G_DISPATCHER end + --- Locks the DefenseItem from being defended. -- @param #AI_A2G_DISPATCHER self -- @param #string DetectedItemIndex The index of the detected item. @@ -1241,12 +1239,14 @@ do -- AI_A2G_DISPATCHER end end + --- @param #AI_A2G_DISPATCHER self -- @param Core.Event#EVENTDATA EventData function AI_A2G_DISPATCHER:OnEventCrashOrDead( EventData ) self.Detection:ForgetDetectedUnit( EventData.IniUnitName ) end + --- @param #AI_A2G_DISPATCHER self -- @param Core.Event#EVENTDATA EventData function AI_A2G_DISPATCHER:OnEventLand( EventData ) @@ -1275,6 +1275,7 @@ do -- AI_A2G_DISPATCHER end end + --- @param #AI_A2G_DISPATCHER self -- @param Core.Event#EVENTDATA EventData function AI_A2G_DISPATCHER:OnEventEngineShutdown( EventData ) @@ -1296,6 +1297,7 @@ do -- AI_A2G_DISPATCHER end end + do -- Manage the defensive behaviour --- @param #AI_A2G_DISPATCHER self @@ -1305,16 +1307,19 @@ do -- AI_A2G_DISPATCHER self.DefenseCoordinates[DefenseCoordinateName] = DefenseCoordinate end + --- @param #AI_A2G_DISPATCHER self function AI_A2G_DISPATCHER:SetDefenseReactivityLow() self.DefenseReactivity = 0.05 end + --- @param #AI_A2G_DISPATCHER self function AI_A2G_DISPATCHER:SetDefenseReactivityMedium() self.DefenseReactivity = 0.15 end + --- @param #AI_A2G_DISPATCHER self function AI_A2G_DISPATCHER:SetDefenseReactivityHigh() self.DefenseReactivity = 0.5 @@ -1379,7 +1384,6 @@ do -- AI_A2G_DISPATCHER end - --- Define a border area to simulate a **cold war** scenario. -- A **cold war** is one where Patrol aircraft patrol their territory but will not attack enemy aircraft or launch GCI aircraft unless enemy aircraft enter their territory. In other words the EWR may detect an enemy aircraft but will only send aircraft to attack it if it crosses the border. -- A **hot war** is one where Patrol aircraft will intercept any detected enemy aircraft and GCI aircraft will launch against detected enemy aircraft without regard for territory. In other words if the ground radar can detect the enemy aircraft then it will send Patrol and GCI aircraft to attack it. @@ -1404,7 +1408,6 @@ do -- AI_A2G_DISPATCHER -- local BorderZone2 = ZONE_POLYGON( "CCCP Border2", GROUP:FindByName( "CCCP Border2" ) ) -- The GROUP object is a late activate helicopter unit. -- A2GDispatcher:SetBorderZone( { BorderZone1, BorderZone2 } ) -- - -- function AI_A2G_DISPATCHER:SetBorderZone( BorderZone ) self.Detection:SetAcceptZones( BorderZone ) @@ -1412,6 +1415,7 @@ do -- AI_A2G_DISPATCHER return self end + --- Display a tactical report every 30 seconds about which aircraft are: -- * Patrolling -- * Engaging @@ -1563,36 +1567,42 @@ do -- AI_A2G_DISPATCHER return DefenderFriendliesNearBy end + --- -- @param #AI_A2G_DISPATCHER self function AI_A2G_DISPATCHER:GetDefenderTasks() return self.DefenderTasks or {} end + --- -- @param #AI_A2G_DISPATCHER self function AI_A2G_DISPATCHER:GetDefenderTask( Defender ) return self.DefenderTasks[Defender] end + --- -- @param #AI_A2G_DISPATCHER self function AI_A2G_DISPATCHER:GetDefenderTaskFsm( Defender ) return self:GetDefenderTask( Defender ).Fsm end + --- -- @param #AI_A2G_DISPATCHER self function AI_A2G_DISPATCHER:GetDefenderTaskTarget( Defender ) return self:GetDefenderTask( Defender ).Target end + --- -- @param #AI_A2G_DISPATCHER self function AI_A2G_DISPATCHER:GetDefenderTaskSquadronName( Defender ) return self:GetDefenderTask( Defender ).SquadronName end + --- -- @param #AI_A2G_DISPATCHER self function AI_A2G_DISPATCHER:ClearDefenderTask( Defender ) @@ -1609,6 +1619,7 @@ do -- AI_A2G_DISPATCHER return self end + --- -- @param #AI_A2G_DISPATCHER self function AI_A2G_DISPATCHER:ClearDefenderTaskTarget( Defender ) @@ -1778,6 +1789,7 @@ do -- AI_A2G_DISPATCHER return self end + --- Get an item from the Squadron table. -- @param #AI_A2G_DISPATCHER self -- @return #table @@ -1821,6 +1833,7 @@ do -- AI_A2G_DISPATCHER -- -- end + --- Check if the Squadron is visible before startup of the dispatcher. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The squadron name. @@ -1844,6 +1857,7 @@ do -- AI_A2G_DISPATCHER end + --- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The squadron name. -- @param #number TakeoffInterval Only Takeoff new units each specified interval in seconds in 10 seconds steps. @@ -1865,7 +1879,6 @@ do -- AI_A2G_DISPATCHER end - --- Set the squadron patrol parameters for a specific task type. -- Mission designers should not use this method, instead use the below methods. This method is used by the below methods. @@ -1919,7 +1932,6 @@ do -- AI_A2G_DISPATCHER end - --- Set the squadron Patrol parameters for SEAD tasks. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The squadron name. @@ -2005,6 +2017,7 @@ do -- AI_A2G_DISPATCHER end end + --- -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The squadron name. @@ -2057,6 +2070,7 @@ do -- AI_A2G_DISPATCHER return nil end + --- Set the squadron engage limit for a specific task type. -- Mission designers should not use this method, instead use the below methods. This method is used by the below methods. -- @@ -2089,8 +2103,6 @@ do -- AI_A2G_DISPATCHER end - - --- Set a squadron to engage for suppression of air defenses, when a defense point is under attack. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The squadron name. @@ -2127,6 +2139,7 @@ do -- AI_A2G_DISPATCHER return self end + --- Set a squadron to engage for suppression of air defenses, when a defense point is under attack. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The squadron name. @@ -2147,6 +2160,7 @@ do -- AI_A2G_DISPATCHER return self:SetSquadronSead2( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, "RADIO" ) end + --- Set the squadron SEAD engage limit. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The squadron name. @@ -2163,8 +2177,6 @@ do -- AI_A2G_DISPATCHER self:SetSquadronEngageLimit( SquadronName, EngageLimit, "SEAD" ) end - - --- Set a Sead patrol for a Squadron. @@ -2278,6 +2290,7 @@ do -- AI_A2G_DISPATCHER return self end + --- Set a squadron to engage for close air support, when a defense point is under attack. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The squadron name. @@ -2391,6 +2404,7 @@ do -- AI_A2G_DISPATCHER end + --- Set a squadron to engage for a battlefield area interdiction, when a defense point is under attack. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The squadron name. @@ -2427,6 +2441,7 @@ do -- AI_A2G_DISPATCHER return self end + --- Set a squadron to engage for a battlefield area interdiction, when a defense point is under attack. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The squadron name. @@ -2514,6 +2529,7 @@ do -- AI_A2G_DISPATCHER self:I( { BAI = { Zone:GetName(), PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) end + --- Set a Bai patrol for a Squadron. -- The Bai patrol will start a patrol of the aircraft at a specified zone, and will engage when commanded. -- @param #AI_A2G_DISPATCHER self @@ -2542,7 +2558,7 @@ do -- AI_A2G_DISPATCHER --- Defines the default amount of extra planes that will takeoff as part of the defense system. -- @param #AI_A2G_DISPATCHER self - -- @param #number Overhead The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units. + -- @param #number Overhead The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units. -- The default overhead is 1, so equal balance. The @{#AI_A2G_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance A2G missiles payload, may still be less effective than a F-15C with short missiles... -- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes. @@ -2581,7 +2597,7 @@ do -- AI_A2G_DISPATCHER --- Defines the amount of extra planes that will takeoff as part of the defense system. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The name of the squadron. - -- @param #number Overhead The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units. + -- @param #number Overhead The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units. -- The default overhead is 1, so equal balance. The @{#AI_A2G_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance A2G missiles payload, may still be less effective than a F-15C with short missiles... -- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes. @@ -2621,7 +2637,7 @@ do -- AI_A2G_DISPATCHER --- Gets the overhead of planes as part of the defense system, in comparison with the attackers. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The name of the squadron. - -- @return #number The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units. + -- @return #number The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units. -- The default overhead is 1, so equal balance. The @{#AI_A2G_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance A2G missiles payload, may still be less effective than a F-15C with short missiles... -- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes. @@ -3085,7 +3101,7 @@ do -- AI_A2G_DISPATCHER -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) -- -- -- Let new flights by default despawn near the airbase when returning. - -- local LandingMethod = A2GDispatcher:GetDefaultLanding( AI_A2G_Dispatcher.Landing.NearAirbase ) + -- local LandingMethod = A2GDispatcher:GetDefaultLanding() -- if LandingMethod == AI_A2G_Dispatcher.Landing.NearAirbase then -- ... -- end @@ -4265,6 +4281,7 @@ do -- AI_A2G_DISPATCHER return ShortestDistance end + --- Assigns A2G AI Tasks in relation to the detected items. -- @param #AI_A2G_DISPATCHER self function AI_A2G_DISPATCHER:Order( DetectedItem ) @@ -4285,6 +4302,7 @@ do -- AI_A2G_DISPATCHER return ShortestDistance end + --- Shows the tactical display. -- @param #AI_A2G_DISPATCHER self function AI_A2G_DISPATCHER:ShowTacticalDisplay( Detection ) @@ -4386,6 +4404,7 @@ do -- AI_A2G_DISPATCHER end + --- Assigns A2G AI Tasks in relation to the detected items. -- @param #AI_A2G_DISPATCHER self -- @param Functional.Detection#DETECTION_BASE Detection The detection created by the @{Functional.Detection#DETECTION_BASE} derived object. @@ -4624,7 +4643,7 @@ do local PlayersCount = 0 if PlayersNearBy then - local DetectedTreatLevel = DetectedSet:CalculateThreatLevelA2G() + local DetectedThreatLevel = DetectedSet:CalculateThreatLevelA2G() for PlayerUnitName, PlayerUnitData in pairs( PlayersNearBy ) do local PlayerUnit = PlayerUnitData -- Wrapper.Unit#UNIT local PlayerName = PlayerUnit:GetPlayerName() @@ -4634,7 +4653,7 @@ do PlayersCount = PlayersCount + 1 local PlayerType = PlayerUnit:GetTypeName() PlayerTypes[PlayerName] = PlayerType - if DetectedTreatLevel < FriendlyUnitThreatLevel + 2 then + if DetectedThreatLevel < FriendlyUnitThreatLevel + 2 then end end end @@ -4670,7 +4689,7 @@ do local FriendliesCount = 0 if FriendlyUnitsNearBy then - local DetectedTreatLevel = DetectedSet:CalculateThreatLevelA2G() + local DetectedThreatLevel = DetectedSet:CalculateThreatLevelA2G() for FriendlyUnitName, FriendlyUnitData in pairs( FriendlyUnitsNearBy ) do local FriendlyUnit = FriendlyUnitData -- Wrapper.Unit#UNIT if FriendlyUnit:IsAirPlane() then @@ -4678,7 +4697,7 @@ do FriendliesCount = FriendliesCount + 1 local FriendlyType = FriendlyUnit:GetTypeName() FriendlyTypes[FriendlyType] = FriendlyTypes[FriendlyType] and ( FriendlyTypes[FriendlyType] + 1 ) or 1 - if DetectedTreatLevel < FriendlyUnitThreatLevel + 2 then + if DetectedThreatLevel < FriendlyUnitThreatLevel + 2 then end end end From 73ea4c7b32332c3826de3ec5499f67dc2729bd6e Mon Sep 17 00:00:00 2001 From: Tommy Carlsson Date: Thu, 2 Dec 2021 20:49:48 +0400 Subject: [PATCH 017/200] Update Range.lua Fix documentation - including typos and updates to no longer correct text. Remove duplicated RANGE.Defaults.goodthitrange value. DCS seems to use headings 0-359 (i.e. <360), thus also deduct 360 from an exact 360 heading. --- Moose Development/Moose/Functional/Range.lua | 218 +++++++++---------- 1 file changed, 104 insertions(+), 114 deletions(-) diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index 9e5db273e..9f4675ef7 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -19,23 +19,23 @@ -- * Bomb, rocket and missile impact points can be marked by smoke. -- * Direct hits on targets can trigger flares. -- * Smoke and flare colors can be adjusted for each player via radio menu. --- * Range information and weather report at the range can be reported via radio menu. +-- * Range information and weather at the range can be obtained via radio menu. -- * Persistence: Bombing range results can be saved to disk and loaded the next time the mission is started. -- * Range control voice overs (>40) for hit assessment. -- -- === -- -- ## Youtube Videos: - -- +-- -- * [MOOSE YouTube Channel](https://www.youtube.com/channel/UCjrA9j5LQoWsG4SpS8i79Qg) -- * [MOOSE - On the Range - Demonstration Video](https://www.youtube.com/watch?v=kIXcxNB9_3M) --- +-- -- === -- -- ## Missions: -- -- * [MAR - On the Range - MOOSE - SC](https://www.digitalcombatsimulator.com/en/files/3317765/) by shagrat --- +-- -- === -- -- ## Sound files: [MOOSE Sound Files](https://github.com/FlightControl-Master/MOOSE_SOUND/releases) @@ -54,7 +54,7 @@ --- RANGE class -- @type RANGE -- @field #string ClassName Name of the Class. --- @field #boolean Debug If true, debug info is send as messages on the screen. +-- @field #boolean Debug If true, debug info is sent as messages on the screen. -- @field #boolean verbose Verbosity level. Higher means more output to DCS log file. -- @field #string id String id of range for output in DCS log. -- @field #string rangename Name of the range. @@ -77,13 +77,13 @@ -- @field #number Tmsg Time [sec] messages to players are displayed. Default 30 sec. -- @field #string examinergroupname Name of the examiner group which should get all messages. -- @field #boolean examinerexclusive If true, only the examiner gets messages. If false, clients and examiner get messages. --- @field #number strafemaxalt Maximum altitude above ground for registering for a strafe run. Default is 914 m = 3000 ft. +-- @field #number strafemaxalt Maximum altitude in meters AGL for registering for a strafe run. Default is 914 m = 3000 ft. -- @field #number ndisplayresult Number of (player) results that a displayed. Default is 10. -- @field Utilities.Utils#SMOKECOLOR BombSmokeColor Color id used for smoking bomb targets. -- @field Utilities.Utils#SMOKECOLOR StrafeSmokeColor Color id used to smoke strafe targets. -- @field Utilities.Utils#SMOKECOLOR StrafePitSmokeColor Color id used to smoke strafe pit approach boxes. --- @field #number illuminationminalt Minimum altitude AGL in meters at which illumination bombs are fired. Default is 500 m. --- @field #number illuminationmaxalt Maximum altitude AGL in meters at which illumination bombs are fired. Default is 1000 m. +-- @field #number illuminationminalt Minimum altitude in meters AGL at which illumination bombs are fired. Default is 500 m. +-- @field #number illuminationmaxalt Maximum altitude in meters AGL at which illumination bombs are fired. Default is 1000 m. -- @field #number scorebombdistance Distance from closest target up to which bomb hits are counted. Default 1000 m. -- @field #number TdelaySmoke Time delay in seconds between impact of bomb and starting the smoke. Default 3 seconds. -- @field #boolean eventmoose If true, events are handled by MOOSE. If false, events are handled directly by DCS eventhandler. Default true. @@ -102,7 +102,7 @@ -- @extends Core.Fsm#FSM --- *Don't only practice your art, but force your way into its secrets; art deserves that, for it and knowledge can raise man to the Divine.* - Ludwig van Beethoven --- +-- -- === -- -- ![Banner Image](..\Presentations\RANGE\RANGE_Main.png) @@ -126,17 +126,17 @@ -- there should be an "On the Range" menu items in the "F10. Other..." menu. -- -- # Strafe Pits --- +-- -- Each strafe pit can consist of multiple targets. Often one finds two or three strafe targets next to each other. -- -- A strafe pit can be added to the range by the @{#RANGE.AddStrafePit}(*targetnames, boxlength, boxwidth, heading, inverseheading, goodpass, foulline*) function. -- --- * The first parameter *targetnames* defines the target or targets. This has to be given as a lua table which contains the names of @{Wrapper.Unit} or @{Static} objects defined in the mission editor. +-- * The first parameter *targetnames* defines the target or targets. This can be a single item or a Table with the name(s) of @{Wrapper.Unit} or @{Static} objects defined in the mission editor. -- * In order to perform a valid pass on the strafe pit, the pilot has to begin his run from the correct direction. Therefore, an "approach box" is defined in front --- of the strafe targets. The parameters *boxlength* and *boxwidth* define the size of the box while the parameter *heading* defines its direction. --- If the parameter *heading* is passed as **nil**, the heading is automatically taken from the heading of the first target unit as defined in the ME. --- The parameter *inverseheading* turns the heading around by 180 degrees. This is sometimes useful, since the default heading of strafe target units point in the --- wrong/opposite direction. +-- of the strafe targets. The parameters *boxlength* and *boxwidth* define the size of the box in meters, while the *heading* parameter defines the heading of the box FROM the target. +-- For example, if heading 120 is set, the approach box will start FROM the target and extend outwards on heading 120. A strafe run approach must then be flown apx. heading 300 TOWARDS the target. +-- If the parameter *heading* is passed as **nil**, the heading is automatically taken from the heading set in the ME for the first target unit. +-- * The parameter *inverseheading* turns the heading around by 180 degrees. This is useful when the default heading of strafe target units point in the wrong/opposite direction. -- * The parameter *goodpass* defines the number of hits a pilot has to achieve during a run to be judged as a "good" pass. -- * The last parameter *foulline* sets the distance from the pit targets to the foul line. Hit from closer than this line are not counted! -- @@ -147,27 +147,26 @@ -- strafing pits of the range and can be adjusted by the @{#RANGE.SetMaxStrafeAlt}(maxalt) function. -- -- # Bombing targets --- +-- -- One ore multiple bombing targets can be added to the range by the @{#RANGE.AddBombingTargets}(targetnames, goodhitrange, randommove) function. -- --- * The first parameter *targetnames* has to be a lua table, which contains the names of @{Wrapper.Unit} and/or @{Static} objects defined in the mission editor. --- Note that the @{Range} logic **automatically** determines, if a name belongs to a @{Wrapper.Unit} or @{Static} object now. --- * The (optional) parameter *goodhitrange* specifies the radius around the target. If a bomb or rocket falls at a distance smaller than this number, the hit is considered to be "good". +-- * The first parameter *targetnames* defines the target or targets. This can be a single item or a Table with the name(s) of @{Wrapper.Unit} or @{Static} objects defined in the mission editor. +-- * The (optional) parameter *goodhitrange* specifies the radius in metres around the target within which a bomb/rocket hit is considered to be "good". -- * If final (optional) parameter "*randommove*" can be enabled to create moving targets. If this parameter is set to true, the units of this bombing target will randomly move within the range zone. -- Note that there might be quirks since DCS units can get stuck in buildings etc. So it might be safer to manually define a route for the units in the mission editor if moving targets are desired. -- -- ## Adding Groups --- +-- -- Another possibility to add bombing targets is the @{#RANGE.AddBombingTargetGroup}(*group, goodhitrange, randommove*) function. Here the parameter *group* is a MOOSE @{Wrapper.Group} object -- and **all** units in this group are defined as bombing targets. --- +-- -- ## Specifying Coordinates --- +-- -- It is also possible to specify coordinates rather than unit or static objects as bombing target locations. This has the advantage, that even when the unit/static object is dead, the specified -- coordinate will still be a valid impact point. This can be done via the @{#RANGE.AddBombingTargetCoordinate}(*coord*, *name*, *goodhitrange*) function. -- -- # Fine Tuning --- +-- -- Many range parameters have good default values. However, the mission designer can change these settings easily with the supplied user functions: -- -- * @{#RANGE.SetMaxStrafeAlt}() sets the max altitude for valid strafing runs. @@ -183,60 +182,60 @@ -- * @{#RANGE.TrackMissilesON}() or @{#RANGE.TrackMissilesOFF}() can be used to enable/disable tracking and evaluating of all missile types a player fires. -- -- # Radio Menu --- +-- -- Each range gets a radio menu with various submenus where each player can adjust his individual settings or request information about the range or his scores. -- -- The main range menu can be found at "F10. Other..." --> "F*X*. On the Range..." --> "F1. ...". -- --- The range menu contains the following submenues: --- +-- The range menu contains the following submenus: +-- -- ![Banner Image](..\Presentations\RANGE\Menu_Main.png) -- -- * "F1. Statistics...": Range results of all players and personal stats. -- * "F2. Mark Targets": Mark range targets by smoke or flares. -- * "F3. My Settings" Personal settings. -- * "F4. Range Info": Information about the range, such as bearing and range. --- +-- -- ## F1 Statistics --- +-- -- ![Banner Image](..\Presentations\RANGE\Menu_Stats.png) --- +-- -- ## F2 Mark Targets --- +-- -- ![Banner Image](..\Presentations\RANGE\Menu_Stats.png) --- +-- -- ## F3 My Settings --- +-- -- ![Banner Image](..\Presentations\RANGE\Menu_MySettings.png) --- +-- -- ## F4 Range Info --- +-- -- ![Banner Image](..\Presentations\RANGE\Menu_RangeInfo.png) --- +-- -- # Voice Overs --- +-- -- Voice over sound files can be downloaded from the Moose Discord. Check the pinned messages in the *#func-range* channel. --- +-- -- Instructor radio will inform players when they enter or exit the range zone and provide the radio frequency of the range control for hit assessment. -- This can be enabled via the @{#RANGE.SetInstructorRadio}(*frequency*) functions, where *frequency* is the AM frequency in MHz. --- +-- -- The range control can be enabled via the @{#RANGE.SetRangeControl}(*frequency*) functions, where *frequency* is the AM frequency in MHz. --- +-- -- By default, the sound files are placed in the "Range Soundfiles/" folder inside the mission (.miz) file. Another folder can be specified via the @{#RANGE.SetSoundfilesPath}(*path*) function. --- +-- -- # Persistence --- +-- -- To automatically save bombing results to disk, use the @{#RANGE.SetAutosave}() function. Bombing results will be saved as csv file in your "Saved Games\DCS.openbeta\Logs" directory. -- Each range has a separate file, which is named "RANGE-<*RangeName*>_BombingResults.csv". --- +-- -- The next time you start the mission, these results are also automatically loaded. --- +-- -- Strafing results are currently **not** saved. -- -- # Examples -- -- ## Goldwater Range --- +-- -- This example shows hot to set up the [Barry M. Goldwater range](https://en.wikipedia.org/wiki/Barry_M._Goldwater_Air_Force_Range). -- It consists of two strafe pits each has two targets plus three bombing targets. -- @@ -255,9 +254,9 @@ -- -- Note that this could also be done manually by simply measuring the distance between the target and the foul line in the ME. -- GoldwaterRange:GetFoullineDistance("GWR Strafe Pit Left 1", "GWR Foul Line Left") -- --- -- Add strafe pits. Each pit (left and right) consists of two targets. --- GoldwaterRange:AddStrafePit(strafepit_left, 3000, 300, nil, true, 20, fouldist) --- GoldwaterRange:AddStrafePit(strafepit_right, nil, nil, nil, true, nil, fouldist) +-- -- Add strafe pits. Each pit (left and right) consists of two targets. Where "nil" is used as input, the default value is used. +-- GoldwaterRange:AddStrafePit(strafepit_left, 3000, 300, nil, true, 30, 500) +-- GoldwaterRange:AddStrafePit(strafepit_right, nil, nil, nil, true, nil, 500) -- -- -- Add bombing targets. A good hit is if the bomb falls less then 50 m from the target. -- GoldwaterRange:AddBombingTargets(bombtargets, 50) @@ -349,7 +348,6 @@ RANGE.Defaults={ boxlength=3000, boxwidth=300, goodpass=20, - goodhitrange=25, foulline=610, } @@ -753,14 +751,14 @@ function RANGE:onafterStart() end end - + -- Init range control. if self.rangecontrolfreq then - + -- Radio queue. self.rangecontrol=RADIOQUEUE:New(self.rangecontrolfreq, nil, self.rangename) self.rangecontrol.schedonce=true - + -- Init numbers. 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) @@ -772,21 +770,21 @@ function RANGE:onafterStart() 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) - + -- Set location where the messages are transmitted from. self.rangecontrol:SetSenderCoordinate(self.location) self.rangecontrol:SetSenderUnitName(self.rangecontrolrelayname) - + -- Start range control radio queue. self.rangecontrol:Start(1, 0.1) -- Init range control. if self.instructorfreq then - + -- Radio queue. - self.instructor=RADIOQUEUE:New(self.instructorfreq, nil, self.rangename) + self.instructor=RADIOQUEUE:New(self.instructorfreq, nil, self.rangename) self.instructor.schedonce=true - + -- Init numbers. 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) @@ -798,18 +796,18 @@ function RANGE:onafterStart() 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) - + -- Set location where the messages are transmitted from. self.instructor:SetSenderCoordinate(self.location) self.instructor:SetSenderUnitName(self.instructorrelayname) - + -- Start instructor radio queue. - self.instructor:Start(1, 0.1) - + self.instructor:Start(1, 0.1) + end - + end - + -- Load prev results. if self.autosave then self:Load() @@ -833,7 +831,7 @@ end --- Set maximal strafing altitude. Player entering a strafe pit above that altitude are not registered for a valid pass. -- @param #RANGE self --- @param #number maxalt Maximum altitude AGL in meters. Default is 914 m= 3000 ft. +-- @param #number maxalt Maximum altitude in meters AGL. Default is 914 m = 3000 ft. -- @return #RANGE self function RANGE:SetMaxStrafeAlt(maxalt) self.strafemaxalt=maxalt or RANGE.Defaults.strafemaxalt @@ -1022,7 +1020,6 @@ function RANGE:SetMessagesON() return self end - --- Enables tracking of all bomb types. Note that this is the default setting. -- @param #RANGE self -- @return #RANGE self @@ -1071,7 +1068,6 @@ function RANGE:TrackMissilesOFF() return self end - --- Enable range control and set frequency. -- @param #RANGE self -- @param #number frequency Frequency in MHz. Default 256 MHz. @@ -1105,16 +1101,16 @@ function RANGE:SetSoundfilesPath(path) end --- Add new strafe pit. For a strafe pit, hits from guns are counted. One pit can consist of several units. --- Note, an approach is only valid, if the player enters via a zone in front of the pit, which defined by boxlength and boxheading. +-- A strafe run approach is only valid if the player enters via a zone in front of the pit, which is defined by boxlength, boxwidth, and heading. -- Furthermore, the player must not be too high and fly in the direction of the pit to make a valid target apporoach. -- @param #RANGE self --- @param #table targetnames Table of unit or static names defining the strafe targets. The first target in the list determines the approach zone (heading and box). +-- @param #table targetnames Single or multiple (Table) unit or static names defining the strafe targets. The first target in the list determines the approach box origin (heading and box). -- @param #number boxlength (Optional) Length of the approach box in meters. Default is 3000 m. -- @param #number boxwidth (Optional) Width of the approach box in meters. Default is 300 m. --- @param #number heading (Optional) Approach heading in Degrees. Default is heading of the unit as defined in the mission editor. --- @param #boolean inverseheading (Optional) Take inverse heading (heading --> heading - 180 Degrees). Default is false. +-- @param #number heading (Optional) Approach box heading in degrees (originating FROM the target). Default is the heading set in the ME for the first target unit +-- @param #boolean inverseheading (Optional) Use inverse heading (heading --> heading - 180 Degrees). Default is false. -- @param #number goodpass (Optional) Number of hits for a "good" strafing pass. Default is 20. --- @param #number foulline (Optional) Foul line distance. Hits from closer than this distance are not counted. Default 610 m = 2000 ft. Set to 0 for no foul line. +-- @param #number foulline (Optional) Foul line distance. Hits from closer than this distance are not counted. Default is 610 m = 2000 ft. Set to 0 for no foul line. -- @return #RANGE self 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}) @@ -1135,13 +1131,13 @@ function RANGE:AddStrafePit(targetnames, boxlength, boxwidth, heading, inversehe local _isstatic=self:_CheckStatic(_name) local unit=nil - if _isstatic==true then + if _isstatic == true then -- Add static object. 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 + elseif _isstatic == false then -- Add unit object. self:T(self.id..string.format("Adding UNIT object %s as strafe target #%d.", _name, _i)) @@ -1159,7 +1155,7 @@ function RANGE:AddStrafePit(targetnames, boxlength, boxwidth, heading, inversehe if unit then table.insert(_targets, unit) -- Define center as the first unit we find - if center==nil then + if center == nil then center=unit end ntargets=ntargets+1 @@ -1187,10 +1183,10 @@ function RANGE:AddStrafePit(targetnames, boxlength, boxwidth, heading, inversehe heading=heading-180 end end - if heading<0 then + if heading < 0 then heading=heading+360 end - if heading>360 then + if heading >= 360 then heading=heading-360 end @@ -1244,7 +1240,6 @@ function RANGE:AddStrafePit(targetnames, boxlength, boxwidth, heading, inversehe return self end - --- Add all units of a group as one new strafe target pit. -- For a strafe pit, hits from guns are counted. One pit can consist of several units. -- Note, an approach is only valid, if the player enters via a zone in front of the pit, which defined by boxlength and boxheading. @@ -1288,7 +1283,7 @@ end --- Add bombing target(s) to range. -- @param #RANGE self --- @param #table targetnames Table containing names of unit or static objects serving as bomb targets. +-- @param #table targetnames Single or multiple (Table) names of unit or static objects serving as bomb targets. -- @param #number goodhitrange (Optional) Max distance from target unit (in meters) which is considered as a good hit. Default is 25 m. -- @param #boolean randommove If true, unit will move randomly within the range. Default is false. -- @return #RANGE self @@ -1308,11 +1303,11 @@ function RANGE:AddBombingTargets(targetnames, goodhitrange, randommove) -- Check if we have a static or unit object. local _isstatic=self:_CheckStatic(name) - if _isstatic==true then + 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 + 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) @@ -1382,7 +1377,6 @@ function RANGE:AddBombingTargetUnit(unit, goodhitrange, randommove) return self end - --- Add a coordinate of a bombing target. This -- @param #RANGE self -- @param Core.Point#COORDINATE coord The coordinate. @@ -1544,7 +1538,6 @@ function RANGE:onEvent(Event) end - --- Range event handler for event birth. -- @param #RANGE self -- @param Core.Event#EVENTDATA EventData @@ -1678,10 +1671,10 @@ function RANGE:OnEventHit(EventData) -- Flare target. if self.PlayerSettings[_playername].flaredirecthits then - + -- Position of target. local targetPos = _target:GetCoordinate() - + targetPos:Flare(self.PlayerSettings[_playername].flarecolor) end @@ -1876,7 +1869,7 @@ function RANGE:OnEventShot(EventData) -- Send message. 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 @@ -1915,9 +1908,9 @@ 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 @@ -1928,7 +1921,7 @@ function RANGE:onafterStatus(From, Event, To) 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 @@ -1939,11 +1932,10 @@ function RANGE:onafterStatus(From, Event, To) end text=text..string.format(", Control %.3f MHz (Relay=%s alive=%s)", self.rangecontrolfreq, tostring(self.rangecontrolrelayname), alive) end - - + -- Check range status. self:I(self.id..text) - + end -- Check player status. @@ -1960,12 +1952,12 @@ end -- @param #string To To state. -- @param #RANGE.PlayerData player Player data. function RANGE:onafterEnterRange(From, Event, To, player) - + if self.instructor and self.rangecontrol then - + -- Range control radio frequency split. local RF=UTILS.Split(string.format("%.3f", self.rangecontrolfreq), ".") - + -- Radio message that player entered the range self.instructor:NewTransmission(RANGE.Sound.IREnterRange.filename, RANGE.Sound.IREnterRange.duration, self.soundpath) self.instructor:Number2Transmission(RF[1]) @@ -1975,7 +1967,7 @@ function RANGE:onafterEnterRange(From, Event, To, player) end self.instructor:NewTransmission(RANGE.Sound.IRMegaHertz.filename, RANGE.Sound.IRMegaHertz.duration, self.soundpath) end - + end --- Function called after player leaves the range zone. @@ -1992,7 +1984,6 @@ function RANGE:onafterExitRange(From, Event, To, player) end - --- Function called after bomb impact on range. -- @param #RANGE self -- @param #string From From state. @@ -2016,7 +2007,7 @@ function RANGE:onafterImpact(From, Event, To, result, player) 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) @@ -2033,8 +2024,8 @@ function RANGE:onafterImpact(From, Event, To, result, player) elseif result.quality=="EXCELLENT" then self.rangecontrol:NewTransmission(RANGE.Sound.RCExcellentHit.filename, RANGE.Sound.RCExcellentHit.duration, self.soundpath, nil, 0.5) end - - end + + end -- Unit. local unit=UNIT:FindByName(player.unitname) @@ -2042,7 +2033,7 @@ function RANGE:onafterImpact(From, Event, To, result, player) -- Send message. self:_DisplayMessageToGroup(unit, text, nil, true) self:T(self.id..text) - + -- Save results. if self.autosave then self:Save() @@ -2556,7 +2547,7 @@ function RANGE:_DisplayBombTargets(_unitname) local coord=self:_GetBombTargetCoordinate(bombtarget) if coord then - + -- Get elevation local elevation=coord:GetLandHeight() local eltxt=string.format("%d m", elevation) @@ -2614,7 +2605,6 @@ function RANGE:_DisplayStrafePits(_unitname) end end - --- Report weather conditions at range. Temperature, QFE pressure and wind data. -- @param #RANGE self -- @param #string _unitname Name of the player unit. @@ -2660,7 +2650,6 @@ function RANGE:_DisplayRangeWeather(_unitname) tP=string.format("%.2f inHg", P*hPa2inHg) end - -- Message text. text=text..string.format("Weather Report at %s:\n", self.rangename) text=text..string.format("--------------------------------------------------\n") @@ -2745,20 +2734,20 @@ function RANGE:_CheckInZone(_unitName) 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} --DCS#Vec2 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 @@ -2797,7 +2786,7 @@ function RANGE:_CheckInZone(_unitName) -- Send message. self:_DisplayMessageToGroup(_unit, _msg, nil, true) - + if self.rangecontrol then self.rangecontrol:NewTransmission(RANGE.Sound.RCLeftStrafePitTooQuickly.filename, RANGE.Sound.RCLeftStrafePitTooQuickly.duration, self.soundpath) end @@ -2843,7 +2832,7 @@ function RANGE:_CheckInZone(_unitName) -- Send message. self:_DisplayMessageToGroup(_unit, _text) - + -- Voice over. if self.rangecontrol then self.rangecontrol:NewTransmission(RANGE.Sound.RCHitsOnTarget.filename, RANGE.Sound.RCHitsOnTarget.duration, self.soundpath) @@ -2891,10 +2880,10 @@ function RANGE:_CheckInZone(_unitName) -- Rolling in! 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 + end -- Send message. self:_DisplayMessageToGroup(_unit, _msg, 10, true) @@ -3056,7 +3045,6 @@ function RANGE:_GetBombTargetCoordinate(target) return coord end - --- Get the number of shells a unit currently has. -- @param #RANGE self -- @param #string unitname Name of the player unit. @@ -3582,7 +3570,7 @@ function RANGE:_GetPlayerUnitAndName(_unitName) return nil,nil end ---- Returns a string which consits of this callsign and the player name. +--- Returns a string which consists of the player name. -- @param #RANGE self -- @param #string unitname Name of the player unit. function RANGE:_myname(unitname) @@ -3590,9 +3578,11 @@ function RANGE:_myname(unitname) local unit=UNIT:FindByName(unitname) local pname=unit:GetPlayerName() - local csign=unit:GetCallsign() - + + --TODO: Either remove these leftovers, or implement them. + --local csign=unit:GetCallsign() --return string.format("%s (%s)", csign, pname) + return string.format("%s", pname) end From 32deb160efecc330ce50dd57b3f0e32ff82899d7 Mon Sep 17 00:00:00 2001 From: TommyC81 Date: Sat, 4 Dec 2021 21:49:47 +0400 Subject: [PATCH 018/200] Formatting and typos (#1652) * Formatting and typo fixes. General formatting and typo fixes. * Update STTS.lua Keep class table on separate lines. --- Moose Development/Moose/Utilities/Enums.lua | 124 +- .../Moose/Utilities/Profiler.lua | 598 ++- .../Moose/Utilities/Routines.lua | 3397 ++++++++--------- Moose Development/Moose/Utilities/STTS.lua | 236 +- 4 files changed, 2095 insertions(+), 2260 deletions(-) diff --git a/Moose Development/Moose/Utilities/Enums.lua b/Moose Development/Moose/Utilities/Enums.lua index 757b67ea1..f4de6e4d7 100644 --- a/Moose Development/Moose/Utilities/Enums.lua +++ b/Moose Development/Moose/Utilities/Enums.lua @@ -1,18 +1,18 @@ --- **Utilities** Enumerators. --- +-- -- An enumerator is a variable that holds a constant value. Enumerators are very useful because they make the code easier to read and to change in general. --- +-- -- For example, instead of using the same value at multiple different places in your code, you should use a variable set to that value. -- If, for whatever reason, the value needs to be changed, you only have to change the variable once and do not have to search through you code and reset -- every value by hand. --- +-- -- Another big advantage is that the LDT intellisense "knows" the enumerators. So you can use the autocompletion feature and do not have to keep all the --- values in your head or look them up in the docs. --- +-- values in your head or look them up in the docs. +-- -- DCS itself provides a lot of enumerators for various things. See [Enumerators](https://wiki.hoggitworld.com/view/Category:Enumerators) on Hoggit. --- --- Other Moose classe also have enumerators. For example, the AIRBASE class has enumerators for airbase names. --- +-- +-- Other Moose classes also have enumerators. For example, the AIRBASE class has enumerators for airbase names. +-- -- @module ENUMS -- @image MOOSE.JPG @@ -20,7 +20,7 @@ -- @type ENUMS --- Because ENUMS are just better practice. --- +-- -- The ENUMS class adds some handy variables, which help you to make your code better and more general. -- -- @field #ENUMS @@ -30,16 +30,16 @@ ENUMS = {} -- @type ENUMS.ROE -- @field #number WeaponFree AI will engage any enemy group it detects. Target prioritization is based based on the threat of the target. -- @field #number OpenFireWeaponFree AI will engage any enemy group it detects, but will prioritize targets specified in the groups tasking. --- @field #number OpenFire AI will engage only targets specified in its taskings. +-- @field #number OpenFire AI will engage only targets specified in its tasking. -- @field #number ReturnFire AI will only engage threats that shoot first. -- @field #number WeaponHold AI will hold fire under all circumstances. ENUMS.ROE = { - WeaponFree=0, - OpenFireWeaponFree=1, - OpenFire=2, - ReturnFire=3, - WeaponHold=4, - } + WeaponFree = 0, + OpenFireWeaponFree = 1, + OpenFire = 2, + ReturnFire = 3, + WeaponHold = 4, +} --- Reaction On Threat. -- @type ENUMS.ROT @@ -49,11 +49,11 @@ ENUMS.ROE = { -- @field #number BypassAndEscape AI will attempt to avoid enemy threat zones all together. This includes attempting to fly above or around threats. -- @field #number AllowAbortMission If a threat is deemed severe enough the AI will abort its mission and return to base. ENUMS.ROT = { - NoReaction=0, - PassiveDefense=1, - EvadeFire=2, - BypassAndEscape=3, - AllowAbortMission=4, + NoReaction = 0, + PassiveDefense = 1, + EvadeFire = 2, + BypassAndEscape = 3, + AllowAbortMission = 4, } --- Alarm state. @@ -62,12 +62,12 @@ ENUMS.ROT = { -- @field #number Green Group is not combat ready. Sensors are stowed if possible. -- @field #number Red Group is combat ready and actively searching for targets. Some groups like infantry will not move in this state. ENUMS.AlarmState = { - Auto=0, - Green=1, - Red=2, + Auto = 0, + Green = 1, + Red = 2, } ---- Weapon types. See the [Weapon Flag](https://wiki.hoggitworld.com/view/DCS_enum_weapon_flag) enumerotor on hoggit wiki. +--- Weapon types. See the [Weapon Flag](https://wiki.hoggitworld.com/view/DCS_enum_weapon_flag) enumerator on Hoggit wiki. -- @type ENUMS.WeaponFlag ENUMS.WeaponFlag={ -- Bombs @@ -111,7 +111,7 @@ ENUMS.WeaponFlag={ -- -- Bombs GuidedBomb = 14, -- (LGB + TvGB + SNSGB) - AnyUnguidedBomb = 2147485680, -- (HeBomb + Penetrator + NapalmBomb + FAEBomb + ClusterBomb + Dispencer + CandleBomb + ParachuteBomb) + AnyUnguidedBomb = 2147485680, -- (HeBomb + Penetrator + NapalmBomb + FAEBomb + ClusterBomb + Dispenser + CandleBomb + ParachuteBomb) AnyBomb = 2147485694, -- (GuidedBomb + AnyUnguidedBomb) --- Rockets AnyRocket = 30720, -- LightRocket + MarkerRocket + CandleRocket + HeavyRocket @@ -123,7 +123,7 @@ ENUMS.WeaponFlag={ --- Air-To-Air Missiles AnyAAM = 264241152, -- IR_AAM + SAR_AAM + AR_AAM + SRAAM + MRAAM + LRAAM AnyAutonomousMissile = 36012032, -- IR_AAM + AntiRadarMissile + AntiShipMissile + FireAndForgetASM + CruiseMissile - AnyMissile = 268402688, -- AnyASM + AnyAAM + AnyMissile = 268402688, -- AnyASM + AnyAAM --- Guns Cannons = 805306368, -- GUN_POD + BuiltInCannon --- @@ -133,7 +133,7 @@ ENUMS.WeaponFlag={ AnyAG = 2956984318, -- Any Air-To-Ground Weapon AnyAA = 264241152, -- Any Air-To-Air Weapon AnyUnguided = 2952822768, -- Any Unguided Weapon - AnyGuided = 268402702, -- Any Guided Weapon + AnyGuided = 268402702, -- Any Guided Weapon } --- Mission tasks. @@ -173,7 +173,7 @@ ENUMS.MissionTask={ TRANSPORT="Transport", } ---- Formations (new). See the [Formations](https://wiki.hoggitworld.com/view/DCS_enum_formation) on hoggit wiki. +--- Formations (new). See the [Formations](https://wiki.hoggitworld.com/view/DCS_enum_formation) on Hoggit wiki. -- @type ENUMS.Formation ENUMS.Formation={} ENUMS.Formation.FixedWing={} @@ -216,23 +216,23 @@ 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.Column.D70 = 720896 ENUMS.Formation.RotaryWing.Wedge={} -ENUMS.Formation.RotaryWing.Wedge.D70=8 +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.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.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.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.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" @@ -244,34 +244,34 @@ ENUMS.Formation.Vehicle.Cone="Cone" ENUMS.Formation.Vehicle.Diamond="Diamond" --- Formations (old). The old format is a simplified version of the new formation enums, which allow more sophisticated settings. --- See the [Formations](https://wiki.hoggitworld.com/view/DCS_enum_formation) on hoggit wiki. +-- See the [Formations](https://wiki.hoggitworld.com/view/DCS_enum_formation) on Hoggit wiki. -- @type ENUMS.FormationOld 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.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.FormationOld.RotaryWing.Wedge = 8 +ENUMS.FormationOld.RotaryWing.Echelon = 9 +ENUMS.FormationOld.RotaryWing.Front = 10 +ENUMS.FormationOld.RotaryWing.Column = 11 --- Morse Code. See the [Wikipedia](https://en.wikipedia.org/wiki/Morse_code). --- +-- -- * Short pulse "*" -- * Long pulse "-" --- +-- -- Pulses are separated by a blank character " ". --- +-- -- @type ENUMS.Morse ENUMS.Morse={} ENUMS.Morse.A="* -" @@ -313,9 +313,9 @@ ENUMS.Morse.N0="- - - - -" ENUMS.Morse[" "]=" " --- ISO (639-1) 2-letter Language Codes. See the [Wikipedia](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes). --- +-- -- @type ENUMS.ISOLang -ENUMS.ISOLang = +ENUMS.ISOLang = { Arabic = 'AR', Chinese = 'ZH', @@ -329,7 +329,7 @@ ENUMS.ISOLang = } --- Phonetic Alphabet (NATO). See the [Wikipedia](https://en.wikipedia.org/wiki/NATO_phonetic_alphabet). --- +-- -- @type ENUMS.Phonetic ENUMS.Phonetic = { diff --git a/Moose Development/Moose/Utilities/Profiler.lua b/Moose Development/Moose/Utilities/Profiler.lua index a4bce95b5..7df211b0c 100644 --- a/Moose Development/Moose/Utilities/Profiler.lua +++ b/Moose Development/Moose/Utilities/Profiler.lua @@ -5,11 +5,10 @@ -- === -- -- ### Author: **TAW CougarNL**, *funkyfranky* --- +-- -- @module Utilities.PROFILER -- @image MOOSE.JPG - --- PROFILER class. -- @type PROFILER -- @field #string ClassName Name of the class. @@ -25,7 +24,6 @@ -- @field #number ThreshTtot Total time threshold. Only write output if total function CPU time is more than this value. -- @field #string fileNamePrefix Output file name prefix, e.g. "MooseProfiler". -- @field #string fileNameSuffix Output file name prefix, e.g. "txt" - --- *The emperor counsels simplicity. First principles. Of each particular thing, ask: What is it in itself, in its own constitution? What is its causal nature? * -- -- === @@ -33,60 +31,59 @@ -- ![Banner Image](..\Presentations\Utilities\PROFILER_Main.jpg) -- -- # The PROFILER Concept --- +-- -- Profile your lua code. This tells you, which functions are called very often and which consume most real time. --- With this information you can optimize the perfomance of your code. --- +-- With this information you can optimize the performance of your code. +-- -- # Prerequisites --- --- The modules **os** and **lfs** need to be desanizied. --- --- +-- +-- The modules **os** and **lfs** need to be de-sanitized. +-- -- # Start --- +-- -- The profiler can simply be started with the @{#PROFILER.Start}(*Delay, Duration*) function --- +-- -- PROFILER.Start() --- +-- -- The optional parameter *Delay* can be used to delay the start by a certain amount of seconds and the optional parameter *Duration* can be used to -- stop the profiler after a certain amount of seconds. --- +-- -- # Stop --- +-- -- The profiler automatically stops when the mission ends. But it can be stopped any time with the @{#PROFILER.Stop}(*Delay*) function --- +-- -- PROFILER.Stop() --- +-- -- The optional parameter *Delay* can be used to specify a delay after which the profiler is stopped. --- +-- -- When the profiler is stopped, the output is written to a file. --- +-- -- # Output --- +-- -- The profiler output is written to a file in your DCS home folder --- +-- -- X:\User\\Saved Games\DCS OpenBeta\Logs --- +-- -- The default file name is "MooseProfiler.txt". If that file exists, the file name is "MooseProfiler-001.txt" etc. --- +-- -- ## Data --- +-- -- The data in the output file provides information on the functions that were called in the mission. --- +-- -- It will tell you how many times a function was called in total, how many times per second, how much time in total and the percentage of time. --- +-- -- If you only want output for functions that are called more than *X* times per second, you can set --- +-- -- PROFILER.ThreshCPS=1.5 --- +-- -- With this setting, only functions which are called more than 1.5 times per second are displayed. The default setting is PROFILER.ThreshCPS=0.0 (no threshold). --- +-- -- Furthermore, you can limit the output for functions that consumed a certain amount of CPU time in total by --- +-- -- PROFILER.ThreshTtot=0.005 --- +-- -- With this setting, which is also the default, only functions which in total used more than 5 milliseconds CPU time. --- +-- -- @field #PROFILER PROFILER = { ClassName = "PROFILER", @@ -117,97 +114,95 @@ PROFILER = { --- Start profiler. -- @param #number Delay Delay in seconds before profiler is stated. Default is immediately. -- @param #number Duration Duration in (game) seconds before the profiler is stopped. Default is when mission ends. -function PROFILER.Start(Delay, Duration) +function PROFILER.Start( Delay, Duration ) -- Check if os, io and lfs are available. - local go=true + local go = true if not os then - env.error("ERROR: Profiler needs os to be desanitized!") - go=false + env.error( "ERROR: Profiler needs os to be de-sanitized!" ) + go = false end if not io then - env.error("ERROR: Profiler needs io to be desanitized!") - go=false - end + env.error( "ERROR: Profiler needs io to be de-sanitized!" ) + go = false + end if not lfs then - env.error("ERROR: Profiler needs lfs to be desanitized!") - go=false - end + env.error( "ERROR: Profiler needs lfs to be de-sanitized!" ) + go = false + end if not go then return end - if Delay and Delay>0 then - BASE:ScheduleOnce(Delay, PROFILER.Start, 0, Duration) + if Delay and Delay > 0 then + BASE:ScheduleOnce( Delay, PROFILER.Start, 0, Duration ) else - + -- Set start time. - PROFILER.TstartGame=timer.getTime() - PROFILER.TstartOS=os.clock() - + PROFILER.TstartGame = timer.getTime() + PROFILER.TstartOS = os.clock() + -- Add event handler. - world.addEventHandler(PROFILER.eventHandler) - + world.addEventHandler( PROFILER.eventHandler ) + -- Info in log. - env.info('############################ Profiler Started ############################') + env.info( '############################ Profiler Started ############################' ) if Duration then - env.info(string.format("- Will be running for %d seconds", Duration)) + env.info( string.format( "- Will be running for %d seconds", Duration ) ) else - env.info(string.format("- Will be stopped when mission ends")) + 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('###############################################################################') - - + 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( '###############################################################################' ) + -- Message on screen - local duration=Duration or 600 - trigger.action.outText("### Profiler running ###", duration) - + local duration = Duration or 600 + trigger.action.outText( "### Profiler running ###", duration ) + -- Set hook. - debug.sethook(PROFILER.hook, "cr") - + debug.sethook( PROFILER.hook, "cr" ) + -- Auto stop profiler. if Duration then - PROFILER.Stop(Duration) + PROFILER.Stop( Duration ) end - + end - + end --- Stop profiler. -- @param #number Delay Delay before stop in seconds. -function PROFILER.Stop(Delay) +function PROFILER.Stop( Delay ) + + if Delay and Delay > 0 then + + BASE:ScheduleOnce( Delay, PROFILER.Stop ) - if Delay and Delay>0 then - - BASE:ScheduleOnce(Delay, PROFILER.Stop) - else -- Remove hook. debug.sethook() - - + -- Run time game. - local runTimeGame=timer.getTime()-PROFILER.TstartGame - + local runTimeGame = timer.getTime() - PROFILER.TstartGame + -- Run time real OS. - local runTimeOS=os.clock()-PROFILER.TstartOS - + local runTimeOS = os.clock() - PROFILER.TstartOS + -- Show info. - PROFILER.showInfo(runTimeGame, runTimeOS) - + PROFILER.showInfo( runTimeGame, runTimeOS ) + end end --- Event handler. -function PROFILER.eventHandler:onEvent(event) - if event.id==world.event.S_EVENT_MISSION_END then +function PROFILER.eventHandler:onEvent( event ) + if event.id == world.event.S_EVENT_MISSION_END then PROFILER.Stop() end end @@ -218,38 +213,38 @@ end --- Debug hook. -- @param #table event Event. -function PROFILER.hook(event) +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 + 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 + PROFILER.Counters[f] = PROFILER.Counters[f] + 1 end - - if PROFILER.fTime[f]==nil then - PROFILER.fTime[f]=os.clock() + + 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 + + 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -262,105 +257,104 @@ end -- @return #string Source file name. -- @return #string Line number. -- @return #number Function time in seconds. -function PROFILER.getData(func) +function PROFILER.getData( func ) - local n=PROFILER.dInfo[func] - - if n.what=="C" then + 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] + + return n.name, n.short_src, n.linedefined, PROFILER.fTimeTotal[func] end --- Write text to log file. -- @param #function f The file. -- @param #string txt The text. -function PROFILER._flog(f, txt) - f:write(txt.."\r\n") +function PROFILER._flog( f, txt ) + f:write( txt .. "\r\n" ) end --- Show table. -- @param #table data Data table. -- @param #function f The file. -- @param #number runTimeGame Game run time in seconds. -function PROFILER.showTable(data, f, runTimeGame) +function PROFILER.showTable( data, f, runTimeGame ) -- Loop over data. - for i=1, #data do - local t=data[i] --#PROFILER.Data - + for i = 1, #data do + local t = data[i] -- #PROFILER.Data + -- Calls per second. - local cps=t.count/runTimeGame - - local threshCPS=cps>=PROFILER.ThreshCPS - local threshTot=t.tm>=PROFILER.ThreshTtot - + local cps = t.count / runTimeGame + + local threshCPS = cps >= PROFILER.ThreshCPS + local threshTot = t.tm >= PROFILER.ThreshTtot + if threshCPS and threshTot then - + -- Output - 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) - + 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 --- Print csv file. -- @param #table data Data table. -- @param #number runTimeGame Game run time in seconds. -function PROFILER.printCSV(data, runTimeGame) +function PROFILER.printCSV( data, runTimeGame ) -- Output file. - local file=PROFILER.getfilename("csv") - local g=io.open(file, 'w') + local file = PROFILER.getfilename( "csv" ) + local g = io.open( file, 'w' ) -- Header. - local text="Function,Total Calls,Calls per Sec,Total Time,Total in %,Sec per Call,Source File;Line Number," - g:write(text.."\r\n") - + local text = "Function,Total Calls,Calls per Sec,Total Time,Total in %,Sec per Call,Source File;Line Number," + g:write( text .. "\r\n" ) + -- Loop over data. - for i=1, #data do - local t=data[i] --#PROFILER.Data - + for i = 1, #data do + local t = data[i] -- #PROFILER.Data + -- Calls per second. - local cps=t.count/runTimeGame + local cps = t.count / runTimeGame -- Output - 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") - + 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 - + -- Close file. g:close() end - --- Write info to output file. -- @param #string ext Extension. -- @return #string File name. -function PROFILER.getfilename(ext) +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 + 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 + 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 @@ -368,172 +362,172 @@ end --- Write info to output file. -- @param #number runTimeGame Game time in seconds. -- @param #number runTimeOS OS time in seconds. -function PROFILER.showInfo(runTimeGame, runTimeOS) +function PROFILER.showInfo( runTimeGame, runTimeOS ) -- Output file. - local file=PROFILER.getfilename(PROFILER.fileNameSuffix) - local f=io.open(file, 'w') - + local file = PROFILER.getfilename( PROFILER.fileNameSuffix ) + local f = io.open( file, 'w' ) + -- Gather data. - local Ttot=0 - local Calls=0 - - local t={} - - local tcopy=nil --#PROFILER.Data - local tserialize=nil --#PROFILER.Data - local tforgen=nil --#PROFILER.Data - local tpairs=nil --#PROFILER.Data - - - 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 - - -- Profile data. - local T= - { func=s, - src=src, - line=line, - count=count, - tm=tm, - } --#PROFILER.Data - - -- Collect special cases. Somehow, e.g. "_copy" appears multiple times so we try to gather all data. - 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) + local Ttot = 0 + local Calls = 0 + + local t = {} + + local tcopy = nil -- #PROFILER.Data + local tserialize = nil -- #PROFILER.Data + local tforgen = nil -- #PROFILER.Data + local tpairs = nil -- #PROFILER.Data + + 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 - - -- Total function time. - Ttot=Ttot+tm - - -- Total number of calls. - Calls=Calls+count - end - + + if s ~= nil then + + -- Profile data. + local T = { func = s, src = src, line = line, count = count, tm = tm } -- #PROFILER.Data + + -- Collect special cases. Somehow, e.g. "_copy" appears multiple times so we try to gather all data. + 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 + + -- Total function time. + Ttot = Ttot + tm + + -- Total number of calls. + Calls = Calls + count + + end + end -- Add special cases. if tcopy then - table.insert(t, tcopy) + table.insert( t, tcopy ) end if tserialize then - table.insert(t, tserialize) + table.insert( t, tserialize ) end if tforgen then - table.insert(t, tforgen) + 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.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( "##############################################################################" ) + -- Sort by total time. - table.sort(t, function(a,b) return a.tm>b.tm end) - + table.sort( t, function( a, b ) + return a.tm > b.tm + end ) + -- Write data. - 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) + 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 ) -- Sort by number of calls. - table.sort(t, function(a,b) return a.tm/a.count>b.tm/b.count end) - + table.sort( t, function( a, b ) + return a.tm / a.count > b.tm / b.count + end ) + -- Detailed data. - 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) - + 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 ) + -- Sort by number of calls. - table.sort(t, function(a,b) return a.count>b.count end) - + table.sort( t, function( a, b ) + return a.count > b.count + end ) + -- Detailed data. - 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, "------------------------------------" ) + PROFILER._flog( f, "---- Data Sorted by Total Calls ----" ) + PROFILER._flog( f, "------------------------------------" ) + PROFILER._flog( f, "" ) + PROFILER.showTable( t, f, runTimeGame ) + -- Closing. - PROFILER._flog(f,"") - PROFILER._flog(f,"************************************************************************************************************************") - PROFILER._flog(f,"************************************************************************************************************************") - PROFILER._flog(f,"************************************************************************************************************************") + PROFILER._flog( f, "" ) + PROFILER._flog( f, "************************************************************************************************************************" ) + PROFILER._flog( f, "************************************************************************************************************************" ) + PROFILER._flog( f, "************************************************************************************************************************" ) -- Close file. f:close() - - -- Print csv file. - PROFILER.printCSV(t, runTimeGame) -end + -- Print csv file. + PROFILER.printCSV( t, runTimeGame ) +end diff --git a/Moose Development/Moose/Utilities/Routines.lua b/Moose Development/Moose/Utilities/Routines.lua index 15e4cc1c8..83d52180d 100644 --- a/Moose Development/Moose/Utilities/Routines.lua +++ b/Moose Development/Moose/Utilities/Routines.lua @@ -1,15 +1,13 @@ --- Various routines -- @module routines -- @image MOOSE.JPG - -env.setErrorMessageBoxEnabled(false) +env.setErrorMessageBoxEnabled( false ) --- Extract of MIST functions. -- @author Grimes routines = {} - -- don't change these routines.majorVersion = 3 routines.minorVersion = 3 @@ -21,291 +19,281 @@ routines.build = 22 -- Utils- conversion, Lua utils, etc. routines.utils = {} -routines.utils.round = function(number, decimals) - local power = 10^decimals - return math.floor(number * power) / power +routines.utils.round = function( number, decimals ) + local power = 10 ^ decimals + return math.floor( number * power ) / power end ---from http://lua-users.org/wiki/CopyTable -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 +-- from http://lua-users.org/wiki/CopyTable +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 - -- porting in Slmod's serialize_slmod2 -routines.utils.oneLineSerialize = function(tbl) -- serialization of a table all on a single line, no comments, made to replace old get_table_string function +routines.utils.oneLineSerialize = function( tbl ) -- serialization of a table all on a single line, no comments, made to replace old get_table_string function - lookup_table = {} - - local function _Serialize( tbl ) + lookup_table = {} - if type(tbl) == 'table' then --function only works for tables! - - if lookup_table[tbl] then - return lookup_table[object] - end + local function _Serialize( tbl ) - local tbl_str = {} - - lookup_table[tbl] = tbl_str - - tbl_str[#tbl_str + 1] = '{' + if type( tbl ) == 'table' then -- function only works for tables! - for ind,val in pairs(tbl) do -- serialize its fields - 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 --must be a string - ind_str[#ind_str + 1] = '[' - ind_str[#ind_str + 1] = routines.utils.basicSerialize(ind) - ind_str[#ind_str + 1] = ']=' - end + if lookup_table[tbl] then + return lookup_table[object] + 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 -- won't ever happen, right? - 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 - -- tbl_str[#tbl_str + 1] = "__index" - -- tbl_str[#tbl_str + 1] = ',' --I think this is right, I just added it - else + local tbl_str = {} - val_str[#val_str + 1] = _Serialize(val) - val_str[#val_str + 1] = ',' --I think this is right, I just added it - 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] = "function " .. tostring(ind) - -- tbl_str[#tbl_str + 1] = ',' --I think this is right, I just added it - 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 - if type(tbl) == 'string' then - return tbl - else - return tostring(tbl) - end - end - end - - local objectreturn = _Serialize(tbl) - return objectreturn + lookup_table[tbl] = tbl_str + + tbl_str[#tbl_str + 1] = '{' + + for ind, val in pairs( tbl ) do -- serialize its fields + 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 -- must be a string + 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 -- won't ever happen, right? + 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 + -- tbl_str[#tbl_str + 1] = "__index" + -- tbl_str[#tbl_str + 1] = ',' --I think this is right, I just added it + else + + val_str[#val_str + 1] = _Serialize( val ) + val_str[#val_str + 1] = ',' -- I think this is right, I just added it + 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] = "function " .. tostring(ind) + -- tbl_str[#tbl_str + 1] = ',' --I think this is right, I just added it + 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 + if type( tbl ) == 'string' then + return tbl + else + return tostring( tbl ) + end + end + end + + local objectreturn = _Serialize( tbl ) + return objectreturn end ---porting in Slmod's "safestring" basic serialize -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 +-- porting in Slmod's "safestring" basic serialize +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 +routines.utils.toDegree = function( angle ) + return angle * 180 / math.pi end -routines.utils.toRadian = function(angle) - return angle*math.pi/180 +routines.utils.toRadian = function( angle ) + return angle * math.pi / 180 end -routines.utils.metersToNM = function(meters) - return meters/1852 +routines.utils.metersToNM = function( meters ) + return meters / 1852 end -routines.utils.metersToFeet = function(meters) - return meters/0.3048 +routines.utils.metersToFeet = function( meters ) + return meters / 0.3048 end -routines.utils.NMToMeters = function(NM) - return NM*1852 +routines.utils.NMToMeters = function( NM ) + return NM * 1852 end -routines.utils.feetToMeters = function(feet) - return feet*0.3048 +routines.utils.feetToMeters = function( feet ) + return feet * 0.3048 end -routines.utils.mpsToKnots = function(mps) - return mps*3600/1852 +routines.utils.mpsToKnots = function( mps ) + return mps * 3600 / 1852 end -routines.utils.mpsToKmph = function(mps) - return mps*3.6 +routines.utils.mpsToKmph = function( mps ) + return mps * 3.6 end -routines.utils.knotsToMps = function(knots) - return knots*1852/3600 +routines.utils.knotsToMps = function( knots ) + return knots * 1852 / 3600 end -routines.utils.kmphToMps = function(kmph) - return kmph/3.6 +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} -- it was actually already vec2. - 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 } -- it was actually already vec2. + 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} -- it was already Vec3, actually. - 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 } -- it was already Vec3, actually. + end end -function routines.utils.makeVec3GL(Vec2, offset) - local adj = offset or 0 +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 + 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 +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 -- gets heading-error corrected direction from point along vector vec. -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 -- put dir in range of 0 to 2*pi - end - return dir +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 -- put dir in range of 0 to 2*pi + end + return dir end -- gets distance in meters between two points (2 dimensional) -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}) +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 -- gets distance in meters between two points (3 dimensional) -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}) +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 - - - - ---3D Vector manipulation +-- 3D Vector manipulation routines.vec = {} -routines.vec.add = function(vec1, vec2) - return {x = vec1.x + vec2.x, y = vec1.y + vec2.y, z = vec1.z + vec2.z} +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} +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} +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 +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} +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 +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 } +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)} +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 --------------------------------------------------------------------------------------------------------------------------- - - - -- acc- the accuracy of each easting/northing. 0, 1, 2, 3, 4, or 5. -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 +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 --[[acc: @@ -315,86 +303,84 @@ position after the decimal of the least significant digit: So: 42.32 - acc of 2. ]] -routines.tostringLL = function(lat, lon, acc, DMS) +routines.tostringLL = function( lat, lon, acc, DMS ) - local latHemi, lonHemi - if lat > 0 then - latHemi = 'N' - else - latHemi = 'S' - end + local latHemi, lonHemi + if lat > 0 then + latHemi = 'N' + else + latHemi = 'S' + end - if lon > 0 then - lonHemi = 'E' - else - lonHemi = 'W' - end + if lon > 0 then + lonHemi = 'E' + else + lonHemi = 'W' + end - lat = math.abs(lat) - lon = math.abs(lon) + lat = math.abs( lat ) + lon = math.abs( lon ) - local latDeg = math.floor(lat) - local latMin = (lat - latDeg)*60 + local latDeg = math.floor( lat ) + local latMin = (lat - latDeg) * 60 - local lonDeg = math.floor(lon) - local lonMin = (lon - lonDeg)*60 + local lonDeg = math.floor( lon ) + local lonMin = (lon - lonDeg) * 60 - if DMS then -- degrees, minutes, and seconds. - local oldLatMin = latMin - latMin = math.floor(latMin) - local latSec = routines.utils.round((oldLatMin - latMin)*60, acc) + if DMS then -- degrees, minutes, and seconds. + 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) + 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 latSec == 60 then + latSec = 0 + latMin = latMin + 1 + end - if lonSec == 60 then - lonSec = 0 - lonMin = lonMin + 1 - end + if lonSec == 60 then + lonSec = 0 + lonMin = lonMin + 1 + end - local secFrmtStr -- create the formatting string for the seconds place - if acc <= 0 then -- no decimal place. - secFrmtStr = '%02d' - else - local width = 3 + acc -- 01.310 - that's a width of 6, for example. - secFrmtStr = '%0' .. width .. '.' .. acc .. 'f' - end + local secFrmtStr -- create the formatting string for the seconds place + if acc <= 0 then -- no decimal place. + secFrmtStr = '%02d' + else + local width = 3 + acc -- 01.310 - that's a width of 6, for example. + 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 + 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 -- degrees, decimal minutes. - latMin = routines.utils.round(latMin, acc) - lonMin = routines.utils.round(lonMin, acc) + else -- degrees, decimal minutes. + latMin = routines.utils.round( latMin, acc ) + lonMin = routines.utils.round( lonMin, acc ) - if latMin == 60 then - latMin = 0 - latDeg = latDeg + 1 - end + if latMin == 60 then + latMin = 0 + latDeg = latDeg + 1 + end - if lonMin == 60 then - lonMin = 0 - lonDeg = lonDeg + 1 - end + if lonMin == 60 then + lonMin = 0 + lonDeg = lonDeg + 1 + end - local minFrmtStr -- create the formatting string for the minutes place - if acc <= 0 then -- no decimal place. - minFrmtStr = '%02d' - else - local width = 3 + acc -- 01.310 - that's a width of 6, for example. - minFrmtStr = '%0' .. width .. '.' .. acc .. 'f' - end + local minFrmtStr -- create the formatting string for the minutes place + if acc <= 0 then -- no decimal place. + minFrmtStr = '%02d' + else + local width = 3 + acc -- 01.310 - that's a width of 6, for example. + 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 + return string.format( '%02d', latDeg ) .. ' ' .. string.format( minFrmtStr, latMin ) .. '\'' .. latHemi .. ' ' .. string.format( '%02d', lonDeg ) .. ' ' .. string.format( minFrmtStr, lonMin ) .. '\'' .. lonHemi - end + end end --[[ required: az - radian @@ -402,471 +388,449 @@ end optional: alt - meters (set to false or nil if you don't want to use it). optional: metric - set true to get dist and alt in km and m. precision will always be nearest degree and NM or km.]] -routines.tostringBR = function(az, dist, alt, metric) - az = routines.utils.round(routines.utils.toDegree(az), 0) +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 + 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 + 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 + 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) --gets the correction needed for true north - if not point.z then --Vec2; convert to Vec3 - 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) +routines.getNorthCorrection = function( point ) -- gets the correction needed for true north + if not point.z then -- Vec2; convert to Vec3 + 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 + local idNum = 0 - --Simplified event handler - routines.addEventHandler = function(f) --id is optional! - local handler = {} - idNum = idNum + 1 - handler.id = idNum - handler.f = f - handler.onEvent = function(self, event) - self.f(event) - end - world.addEventHandler(handler) - end + -- Simplified event handler + routines.addEventHandler = function( f ) -- id is optional! + 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 + 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 -- need to return a Vec3 or Vec2? -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 +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 + local radMult + if innerRadius and innerRadius <= radius then + radMult = (radius - innerRadius) * rad + innerRadius + else + radMult = radius * rad + end - if not point.z then --might as well work with vec2/3 - point.z = point.y - end + if not point.z then -- might as well work with vec2/3 + 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 + 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 +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 + Controller.setTask( groupCon, misTask ) + return false end - -- Useful atomic functions from mist, ported. routines.ground = {} routines.fixedWing = {} routines.heli = {} -routines.ground.buildWP = function(point, overRideForm, overRideSpeed) +routines.ground.buildWP = function( point, overRideForm, overRideSpeed ) - local wp = {} - wp.x = point.x + 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.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.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 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' -- if nothing matched - end - end - - wp.type = 'Turning Point' - - return wp + 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' -- if nothing matched + end + end + wp.type = 'Turning Point' + return wp end -routines.fixedWing.buildWP = function(point, WPtype, speed, alt, altType) +routines.fixedWing.buildWP = function( point, WPtype, speed, alt, altType ) - local wp = {} - wp.x = point.x + local wp = {} + wp.x = point.x - if point.z then - wp.y = point.z - else - wp.y = point.y - end + 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 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 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.speed then + speed = point.speed + end - if point.type then - WPtype = point.type - 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 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 + 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 + wp.type = 'Turning Point' + return wp end -routines.heli.buildWP = function(point, WPtype, speed, alt, altType) +routines.heli.buildWP = function( point, WPtype, speed, alt, altType ) - local wp = {} - wp.x = point.x + local wp = {} + wp.x = point.x - if point.z then - wp.y = point.z - else - wp.y = point.y - end + 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 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 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.speed then + speed = point.speed + end - if point.type then - WPtype = point.type - 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 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 + 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 + wp.type = 'Turning Point' + return wp end -routines.groupToRandomPoint = function(vars) - local group = vars.group --Required - local point = vars.point --required - 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) +routines.groupToRandomPoint = function( vars ) + local group = vars.group -- Required + local point = vars.point -- required + 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 useRoads - if not vars.disableRoads then - useRoads = true - else - useRoads = false - end + local path = {} - local path = {} + if headingDegrees then + heading = headingDegrees * math.pi / 180 + end - if headingDegrees then - heading = headingDegrees*math.pi/180 - end + if heading >= 2 * math.pi then + heading = heading - 2 * math.pi + end - if heading >= 2*math.pi then - heading = heading - 2*math.pi - end + local rndCoord = routines.getRandPointInCircle( point, radius, innerRadius ) - local rndCoord = routines.getRandPointInCircle(point, radius, innerRadius) + local offset = {} + local posStart = routines.getLeadPos( group ) - 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 ) - 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 ) - 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 + routines.goRoute( group, path ) 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 +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 ) end -routines.groupToRandomZone = function(gpData, zone, form, heading, speed) - if type(gpData) == 'string' then - gpData = Group.getByName(gpData) - 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 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 + 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) + 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 + routines.groupToRandomPoint( vars ) end -routines.isTerrainValid = function(coord, terrainTypes) -- vec2/3 and enum or table of acceptable terrain types - if coord.z then - coord.y = coord.z - end - local typeConverted = {} +routines.isTerrainValid = function( coord, terrainTypes ) -- vec2/3 and enum or table of acceptable terrain types + if coord.z then + coord.y = coord.z + end + local typeConverted = {} - if type(terrainTypes) == 'string' then -- if its a string it does this check - 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 -- if its a table it does this check - 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 + if type( terrainTypes ) == 'string' then -- if its a string it does this check + 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 -- if its a table it does this check + 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 +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 + 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 ) end +routines.getLeadPos = function( group ) + if type( group ) == 'string' then -- group name + group = Group.getByName( group ) + end -routines.getLeadPos = function(group) - if type(group) == 'string' then -- group name - group = Group.getByName(group) - end + local units = group:getUnits() - local units = group:getUnits() - - local leader = units[1] - if not leader then -- SHOULD be good, but if there is a bug, this code future-proofs it then. - local lowestInd = math.huge - for ind, unit in pairs(units) do - if ind < lowestInd then - lowestInd = ind - leader = unit - end - end - end - if leader and Unit.isExist(leader) then -- maybe a little too paranoid now... - return leader:getPosition().p - end + local leader = units[1] + if not leader then -- SHOULD be good, but if there is a bug, this code future-proofs it then. + local lowestInd = math.huge + for ind, unit in pairs( units ) do + if ind < lowestInd then + lowestInd = ind + leader = unit + end + end + end + if leader and Unit.isExist( leader ) then -- maybe a little too paranoid now... + return leader:getPosition().p + end end --[[ vars for routines.getMGRSString: vars.units - table of unit names (NOT unitNameTable- maybe this should change). vars.acc - integer between 0 and 5, inclusive ]] -routines.getMGRSString = function(vars) - local units = vars.units - local acc = vars.acc or 5 - local avgPos = routines.getAvgPos(units) - if avgPos then - return routines.tostringMGRS(coord.LLtoMGRS(coord.LOtoLL(avgPos)), acc) - end +routines.getMGRSString = function( vars ) + local units = vars.units + local acc = vars.acc or 5 + local avgPos = routines.getAvgPos( units ) + if avgPos then + return routines.tostringMGRS( coord.LLtoMGRS( coord.LOtoLL( avgPos ) ), acc ) + end end --[[ vars for routines.getLLString @@ -876,15 +840,15 @@ vars.DMS - if true, output in degrees, minutes, seconds. Otherwise, output in d ]] -routines.getLLString = function(vars) - local units = vars.units - local acc = vars.acc or 3 - local DMS = vars.DMS - local avgPos = routines.getAvgPos(units) - if avgPos then - local lat, lon = coord.LOtoLL(avgPos) - return routines.tostringLL(lat, lon, acc, DMS) - end +routines.getLLString = function( vars ) + local units = vars.units + local acc = vars.acc or 3 + local DMS = vars.DMS + local avgPos = routines.getAvgPos( units ) + if avgPos then + local lat, lon = coord.LOtoLL( avgPos ) + return routines.tostringLL( lat, lon, acc, DMS ) + end end --[[ @@ -893,22 +857,22 @@ vars.ref - vec3 ref point, maybe overload for vec2 as well? vars.alt - boolean, if used, includes altitude in string vars.metric - boolean, gives distance in km instead of NM. ]] -routines.getBRStringZone = function(vars) - local zone = trigger.misc.getZone( vars.zone ) - local ref = routines.utils.makeVec3(vars.ref, 0) -- turn it into Vec3 if it is not already. - local alt = vars.alt - local metric = vars.metric - if zone then - local vec = {x = zone.point.x - ref.x, y = zone.point.y - ref.y, z = zone.point.z - ref.z} - local dir = routines.utils.getDir(vec, ref) - local dist = routines.utils.get2DDist(zone.point, ref) - if alt then - alt = zone.y - end - return routines.tostringBR(dir, dist, alt, metric) - else - env.info( 'routines.getBRStringZone: error: zone is nil' ) - end +routines.getBRStringZone = function( vars ) + local zone = trigger.misc.getZone( vars.zone ) + local ref = routines.utils.makeVec3( vars.ref, 0 ) -- turn it into Vec3 if it is not already. + local alt = vars.alt + local metric = vars.metric + if zone then + local vec = { x = zone.point.x - ref.x, y = zone.point.y - ref.y, z = zone.point.z - ref.z } + local dir = routines.utils.getDir( vec, ref ) + local dist = routines.utils.get2DDist( zone.point, ref ) + if alt then + alt = zone.y + end + return routines.tostringBR( dir, dist, alt, metric ) + else + env.info( 'routines.getBRStringZone: error: zone is nil' ) + end end --[[ @@ -917,24 +881,23 @@ vars.ref - vec3 ref point, maybe overload for vec2 as well? vars.alt - boolean, if used, includes altitude in string vars.metric - boolean, gives distance in km instead of NM. ]] -routines.getBRString = function(vars) - local units = vars.units - local ref = routines.utils.makeVec3(vars.ref, 0) -- turn it into Vec3 if it is not already. - local alt = vars.alt - local metric = vars.metric - local avgPos = routines.getAvgPos(units) - if avgPos then - local vec = {x = avgPos.x - ref.x, y = avgPos.y - ref.y, z = avgPos.z - ref.z} - local dir = routines.utils.getDir(vec, ref) - local dist = routines.utils.get2DDist(avgPos, ref) - if alt then - alt = avgPos.y - end - return routines.tostringBR(dir, dist, alt, metric) - end +routines.getBRString = function( vars ) + local units = vars.units + local ref = routines.utils.makeVec3( vars.ref, 0 ) -- turn it into Vec3 if it is not already. + local alt = vars.alt + local metric = vars.metric + local avgPos = routines.getAvgPos( units ) + if avgPos then + local vec = { x = avgPos.x - ref.x, y = avgPos.y - ref.y, z = avgPos.z - ref.z } + local dir = routines.utils.getDir( vec, ref ) + local dist = routines.utils.get2DDist( avgPos, ref ) + if alt then + alt = avgPos.y + end + return routines.tostringBR( dir, dist, alt, metric ) + end end - -- Returns the Vec3 coordinates of the average position of the concentration of units most in the heading direction. --[[ vars for routines.getLeadingPos: vars.units - table of unit names @@ -942,57 +905,56 @@ vars.heading - direction vars.radius - number vars.headingDegrees - boolean, switches heading to degrees ]] -routines.getLeadingPos = function(vars) - local units = vars.units - local heading = vars.heading - local radius = vars.radius - if vars.headingDegrees then - heading = routines.utils.toRadian(vars.headingDegrees) - end +routines.getLeadingPos = function( vars ) + local units = vars.units + local heading = vars.heading + local radius = vars.radius + if vars.headingDegrees then + heading = routines.utils.toRadian( vars.headingDegrees ) + end - local unitPosTbl = {} - for i = 1, #units do - local unit = Unit.getByName(units[i]) - if unit and unit:isExist() then - unitPosTbl[#unitPosTbl + 1] = unit:getPosition().p - end - end - if #unitPosTbl > 0 then -- one more more units found. - -- first, find the unit most in the heading direction - local maxPos = -math.huge + local unitPosTbl = {} + for i = 1, #units do + local unit = Unit.getByName( units[i] ) + if unit and unit:isExist() then + unitPosTbl[#unitPosTbl + 1] = unit:getPosition().p + end + end + if #unitPosTbl > 0 then -- one more more units found. + -- first, find the unit most in the heading direction + local maxPos = -math.huge - local maxPosInd -- maxPos - the furthest in direction defined by heading; maxPosInd = - for i = 1, #unitPosTbl do - local rotatedVec2 = routines.vec.rotateVec2(routines.utils.makeVec2(unitPosTbl[i]), heading) - if (not maxPos) or maxPos < rotatedVec2.x then - maxPos = rotatedVec2.x - maxPosInd = i - end - end + local maxPosInd -- maxPos - the furthest in direction defined by heading; maxPosInd = + for i = 1, #unitPosTbl do + local rotatedVec2 = routines.vec.rotateVec2( routines.utils.makeVec2( unitPosTbl[i] ), heading ) + if (not maxPos) or maxPos < rotatedVec2.x then + maxPos = rotatedVec2.x + maxPosInd = i + end + end - --now, get all the units around this unit... - local avgPos - if radius then - local maxUnitPos = unitPosTbl[maxPosInd] - local avgx, avgy, avgz, totNum = 0, 0, 0, 0 - for i = 1, #unitPosTbl do - if routines.utils.get2DDist(maxUnitPos, unitPosTbl[i]) <= radius then - avgx = avgx + unitPosTbl[i].x - avgy = avgy + unitPosTbl[i].y - avgz = avgz + unitPosTbl[i].z - totNum = totNum + 1 - end - end - avgPos = { x = avgx/totNum, y = avgy/totNum, z = avgz/totNum} - else - avgPos = unitPosTbl[maxPosInd] - end + -- now, get all the units around this unit... + local avgPos + if radius then + local maxUnitPos = unitPosTbl[maxPosInd] + local avgx, avgy, avgz, totNum = 0, 0, 0, 0 + for i = 1, #unitPosTbl do + if routines.utils.get2DDist( maxUnitPos, unitPosTbl[i] ) <= radius then + avgx = avgx + unitPosTbl[i].x + avgy = avgy + unitPosTbl[i].y + avgz = avgz + unitPosTbl[i].z + totNum = totNum + 1 + end + end + avgPos = { x = avgx / totNum, y = avgy / totNum, z = avgz / totNum } + else + avgPos = unitPosTbl[maxPosInd] + end - return avgPos - end + return avgPos + end end - --[[ vars for routines.getLeadingMGRSString: vars.units - table of unit names vars.heading - direction @@ -1000,12 +962,12 @@ vars.radius - number vars.headingDegrees - boolean, switches heading to degrees vars.acc - number, 0 to 5. ]] -routines.getLeadingMGRSString = function(vars) - local pos = routines.getLeadingPos(vars) - if pos then - local acc = vars.acc or 5 - return routines.tostringMGRS(coord.LLtoMGRS(coord.LOtoLL(pos)), acc) - end +routines.getLeadingMGRSString = function( vars ) + local pos = routines.getLeadingPos( vars ) + if pos then + local acc = vars.acc or 5 + return routines.tostringMGRS( coord.LLtoMGRS( coord.LOtoLL( pos ) ), acc ) + end end --[[ vars for routines.getLeadingLLString: @@ -1016,18 +978,16 @@ vars.headingDegrees - boolean, switches heading to degrees vars.acc - number of digits after decimal point (can be negative) vars.DMS - boolean, true if you want DMS. ]] -routines.getLeadingLLString = function(vars) - local pos = routines.getLeadingPos(vars) - if pos then - local acc = vars.acc or 3 - local DMS = vars.DMS - local lat, lon = coord.LOtoLL(pos) - return routines.tostringLL(lat, lon, acc, DMS) - end +routines.getLeadingLLString = function( vars ) + local pos = routines.getLeadingPos( vars ) + if pos then + local acc = vars.acc or 3 + local DMS = vars.DMS + local lat, lon = coord.LOtoLL( pos ) + return routines.tostringLL( lat, lon, acc, DMS ) + end end - - --[[ vars for routines.getLeadingBRString: vars.units - table of unit names vars.heading - direction, number @@ -1037,21 +997,21 @@ vars.metric - boolean, if true, use km instead of NM. vars.alt - boolean, if true, include altitude. vars.ref - vec3/vec2 reference point. ]] -routines.getLeadingBRString = function(vars) - local pos = routines.getLeadingPos(vars) - if pos then - local ref = vars.ref - local alt = vars.alt - local metric = vars.metric +routines.getLeadingBRString = function( vars ) + local pos = routines.getLeadingPos( vars ) + if pos then + local ref = vars.ref + local alt = vars.alt + local metric = vars.metric - local vec = {x = pos.x - ref.x, y = pos.y - ref.y, z = pos.z - ref.z} - local dir = routines.utils.getDir(vec, ref) - local dist = routines.utils.get2DDist(pos, ref) - if alt then - alt = pos.y - end - return routines.tostringBR(dir, dist, alt, metric) - end + local vec = { x = pos.x - ref.x, y = pos.y - ref.y, z = pos.z - ref.z } + local dir = routines.utils.getDir( vec, ref ) + local dist = routines.utils.get2DDist( pos, ref ) + if alt then + alt = pos.y + end + return routines.tostringBR( dir, dist, alt, metric ) + end end --[[ vars for routines.message.add @@ -1068,26 +1028,22 @@ vars.text - text in the message vars.displayTime - self explanatory vars.msgFor - scope ]] -routines.msgMGRS = function(vars) - local units = vars.units - local acc = vars.acc - local text = vars.text - local displayTime = vars.displayTime - local msgFor = vars.msgFor +routines.msgMGRS = function( vars ) + local units = vars.units + local acc = vars.acc + local text = vars.text + local displayTime = vars.displayTime + local msgFor = vars.msgFor - local s = routines.getMGRSString{units = units, acc = acc} - local newText - if string.find(text, '%%s') then -- look for %s - newText = string.format(text, s) -- insert the coordinates into the message - else -- else, just append to the end. - newText = text .. s - end + local s = routines.getMGRSString { units = units, acc = acc } + local newText + if string.find( text, '%%s' ) then -- look for %s + newText = string.format( text, s ) -- insert the coordinates into the message + else -- else, just append to the end. + newText = text .. s + end - routines.message.add{ - text = newText, - displayTime = displayTime, - msgFor = msgFor - } + routines.message.add { text = newText, displayTime = displayTime, msgFor = msgFor } end --[[ vars for routines.msgLL @@ -1098,31 +1054,26 @@ vars.text - text in the message vars.displayTime - self explanatory vars.msgFor - scope ]] -routines.msgLL = function(vars) - local units = vars.units -- technically, I don't really need to do this, but it helps readability. - local acc = vars.acc - local DMS = vars.DMS - local text = vars.text - local displayTime = vars.displayTime - local msgFor = vars.msgFor +routines.msgLL = function( vars ) + local units = vars.units -- technically, I don't really need to do this, but it helps readability. + local acc = vars.acc + local DMS = vars.DMS + local text = vars.text + local displayTime = vars.displayTime + local msgFor = vars.msgFor - local s = routines.getLLString{units = units, acc = acc, DMS = DMS} - local newText - if string.find(text, '%%s') then -- look for %s - newText = string.format(text, s) -- insert the coordinates into the message - else -- else, just append to the end. - newText = text .. s - end + local s = routines.getLLString { units = units, acc = acc, DMS = DMS } + local newText + if string.find( text, '%%s' ) then -- look for %s + newText = string.format( text, s ) -- insert the coordinates into the message + else -- else, just append to the end. + newText = text .. s + end - routines.message.add{ - text = newText, - displayTime = displayTime, - msgFor = msgFor - } + routines.message.add { text = newText, displayTime = displayTime, msgFor = msgFor } end - --[[ vars.units- table of unit names (NOT unitNameTable- maybe this should change). vars.ref - vec3 ref point, maybe overload for vec2 as well? @@ -1132,32 +1083,27 @@ vars.text - text of the message vars.displayTime vars.msgFor - scope ]] -routines.msgBR = function(vars) - local units = vars.units -- technically, I don't really need to do this, but it helps readability. - local ref = vars.ref -- vec2/vec3 will be handled in routines.getBRString - local alt = vars.alt - local metric = vars.metric - local text = vars.text - local displayTime = vars.displayTime - local msgFor = vars.msgFor +routines.msgBR = function( vars ) + local units = vars.units -- technically, I don't really need to do this, but it helps readability. + local ref = vars.ref -- vec2/vec3 will be handled in routines.getBRString + local alt = vars.alt + local metric = vars.metric + local text = vars.text + local displayTime = vars.displayTime + local msgFor = vars.msgFor - local s = routines.getBRString{units = units, ref = ref, alt = alt, metric = metric} - local newText - if string.find(text, '%%s') then -- look for %s - newText = string.format(text, s) -- insert the coordinates into the message - else -- else, just append to the end. - newText = text .. s - end + local s = routines.getBRString { units = units, ref = ref, alt = alt, metric = metric } + local newText + if string.find( text, '%%s' ) then -- look for %s + newText = string.format( text, s ) -- insert the coordinates into the message + else -- else, just append to the end. + newText = text .. s + end - routines.message.add{ - text = newText, - displayTime = displayTime, - msgFor = msgFor - } + routines.message.add { text = newText, displayTime = displayTime, msgFor = msgFor } end - -------------------------------------------------------------------------------------------- -- basically, just sub-types of routines.msgBR... saves folks the work of getting the ref point. --[[ @@ -1169,14 +1115,14 @@ vars.text - text of the message vars.displayTime vars.msgFor - scope ]] -routines.msgBullseye = function(vars) - if string.lower(vars.ref) == 'red' then - vars.ref = routines.DBs.missionData.bullseye.red - routines.msgBR(vars) - elseif string.lower(vars.ref) == 'blue' then - vars.ref = routines.DBs.missionData.bullseye.blue - routines.msgBR(vars) - end +routines.msgBullseye = function( vars ) + if string.lower( vars.ref ) == 'red' then + vars.ref = routines.DBs.missionData.bullseye.red + routines.msgBR( vars ) + elseif string.lower( vars.ref ) == 'blue' then + vars.ref = routines.DBs.missionData.bullseye.blue + routines.msgBR( vars ) + end end --[[ @@ -1189,14 +1135,14 @@ vars.displayTime vars.msgFor - scope ]] -routines.msgBRA = function(vars) - if Unit.getByName(vars.ref) then - vars.ref = Unit.getByName(vars.ref):getPosition().p - if not vars.alt then - vars.alt = true - end - routines.msgBR(vars) - end +routines.msgBRA = function( vars ) + if Unit.getByName( vars.ref ) then + vars.ref = Unit.getByName( vars.ref ):getPosition().p + if not vars.alt then + vars.alt = true + end + routines.msgBR( vars ) + end end -------------------------------------------------------------------------------------------- @@ -1210,32 +1156,27 @@ vars.text - text of the message vars.displayTime vars.msgFor - scope ]] -routines.msgLeadingMGRS = function(vars) - local units = vars.units -- technically, I don't really need to do this, but it helps readability. - local heading = vars.heading - local radius = vars.radius - local headingDegrees = vars.headingDegrees - local acc = vars.acc - local text = vars.text - local displayTime = vars.displayTime - local msgFor = vars.msgFor - - local s = routines.getLeadingMGRSString{units = units, heading = heading, radius = radius, headingDegrees = headingDegrees, acc = acc} - local newText - if string.find(text, '%%s') then -- look for %s - newText = string.format(text, s) -- insert the coordinates into the message - else -- else, just append to the end. - newText = text .. s - end - - routines.message.add{ - text = newText, - displayTime = displayTime, - msgFor = msgFor - } +routines.msgLeadingMGRS = function( vars ) + local units = vars.units -- technically, I don't really need to do this, but it helps readability. + local heading = vars.heading + local radius = vars.radius + local headingDegrees = vars.headingDegrees + local acc = vars.acc + local text = vars.text + local displayTime = vars.displayTime + local msgFor = vars.msgFor + local s = routines.getLeadingMGRSString { units = units, heading = heading, radius = radius, headingDegrees = headingDegrees, acc = acc } + local newText + if string.find( text, '%%s' ) then -- look for %s + newText = string.format( text, s ) -- insert the coordinates into the message + else -- else, just append to the end. + newText = text .. s + end + routines.message.add { text = newText, displayTime = displayTime, msgFor = msgFor } end + --[[ vars for routines.msgLeadingLL: vars.units - table of unit names vars.heading - direction, number @@ -1247,31 +1188,26 @@ vars.text - text of the message vars.displayTime vars.msgFor - scope ]] -routines.msgLeadingLL = function(vars) - local units = vars.units -- technically, I don't really need to do this, but it helps readability. - local heading = vars.heading - local radius = vars.radius - local headingDegrees = vars.headingDegrees - local acc = vars.acc - local DMS = vars.DMS - local text = vars.text - local displayTime = vars.displayTime - local msgFor = vars.msgFor +routines.msgLeadingLL = function( vars ) + local units = vars.units -- technically, I don't really need to do this, but it helps readability. + local heading = vars.heading + local radius = vars.radius + local headingDegrees = vars.headingDegrees + local acc = vars.acc + local DMS = vars.DMS + local text = vars.text + local displayTime = vars.displayTime + local msgFor = vars.msgFor - local s = routines.getLeadingLLString{units = units, heading = heading, radius = radius, headingDegrees = headingDegrees, acc = acc, DMS = DMS} - local newText - if string.find(text, '%%s') then -- look for %s - newText = string.format(text, s) -- insert the coordinates into the message - else -- else, just append to the end. - newText = text .. s - end - - routines.message.add{ - text = newText, - displayTime = displayTime, - msgFor = msgFor - } + local s = routines.getLeadingLLString { units = units, heading = heading, radius = radius, headingDegrees = headingDegrees, acc = acc, DMS = DMS } + local newText + if string.find( text, '%%s' ) then -- look for %s + newText = string.format( text, s ) -- insert the coordinates into the message + else -- else, just append to the end. + newText = text .. s + end + routines.message.add { text = newText, displayTime = displayTime, msgFor = msgFor } end --[[ @@ -1286,137 +1222,93 @@ vars.text - text of the message vars.displayTime vars.msgFor - scope ]] -routines.msgLeadingBR = function(vars) - local units = vars.units -- technically, I don't really need to do this, but it helps readability. - local heading = vars.heading - local radius = vars.radius - local headingDegrees = vars.headingDegrees - local metric = vars.metric - local alt = vars.alt - local ref = vars.ref -- vec2/vec3 will be handled in routines.getBRString - local text = vars.text - local displayTime = vars.displayTime - local msgFor = vars.msgFor +routines.msgLeadingBR = function( vars ) + local units = vars.units -- technically, I don't really need to do this, but it helps readability. + local heading = vars.heading + local radius = vars.radius + local headingDegrees = vars.headingDegrees + local metric = vars.metric + local alt = vars.alt + local ref = vars.ref -- vec2/vec3 will be handled in routines.getBRString + local text = vars.text + local displayTime = vars.displayTime + local msgFor = vars.msgFor - local s = routines.getLeadingBRString{units = units, heading = heading, radius = radius, headingDegrees = headingDegrees, metric = metric, alt = alt, ref = ref} - local newText - if string.find(text, '%%s') then -- look for %s - newText = string.format(text, s) -- insert the coordinates into the message - else -- else, just append to the end. - newText = text .. s - end + local s = routines.getLeadingBRString { units = units, heading = heading, radius = radius, headingDegrees = headingDegrees, metric = metric, alt = alt, ref = ref } + local newText + if string.find( text, '%%s' ) then -- look for %s + newText = string.format( text, s ) -- insert the coordinates into the message + else -- else, just append to the end. + newText = text .. s + end - routines.message.add{ - text = newText, - displayTime = displayTime, - msgFor = msgFor - } + routines.message.add { text = newText, displayTime = displayTime, msgFor = msgFor } end +function spairs( t, order ) + -- collect the keys + local keys = {} + for k in pairs( t ) do + keys[#keys + 1] = k + end -function spairs(t, order) - -- collect the keys - local keys = {} - for k in pairs(t) do keys[#keys+1] = k end + -- if order function given, sort by it by passing the table and keys a, b, + -- otherwise just sort the keys + if order then + table.sort( keys, function( a, b ) + return order( t, a, b ) + end ) + else + table.sort( keys ) + end - -- if order function given, sort by it by passing the table and keys a, b, - -- otherwise just sort the keys - if order then - table.sort(keys, function(a,b) return order(t, a, b) end) - else - table.sort(keys) - end - - -- return the iterator function - local i = 0 - return function() - i = i + 1 - if keys[i] then - return keys[i], t[keys[i]] - end + -- return the iterator function + local i = 0 + return function() + i = i + 1 + if keys[i] then + return keys[i], t[keys[i]] end + end end - function routines.IsPartOfGroupInZones( CargoGroup, LandingZones ) ---trace.f() + -- trace.f() - local CurrentZoneID = nil + local CurrentZoneID = nil - if CargoGroup then - local CargoUnits = CargoGroup:getUnits() - for CargoUnitID, CargoUnit in pairs( CargoUnits ) do - if CargoUnit and CargoUnit:getLife() >= 1.0 then - CurrentZoneID = routines.IsUnitInZones( CargoUnit, LandingZones ) - if CurrentZoneID then - break - end - end - end - end + if CargoGroup then + local CargoUnits = CargoGroup:getUnits() + for CargoUnitID, CargoUnit in pairs( CargoUnits ) do + if CargoUnit and CargoUnit:getLife() >= 1.0 then + CurrentZoneID = routines.IsUnitInZones( CargoUnit, LandingZones ) + if CurrentZoneID then + break + end + end + end + end ---trace.r( "", "", { CurrentZoneID } ) - return CurrentZoneID + -- trace.r( "", "", { CurrentZoneID } ) + return CurrentZoneID end - - function routines.IsUnitInZones( TransportUnit, LandingZones ) ---trace.f("", "routines.IsUnitInZones" ) - - local TransportZoneResult = nil - local TransportZonePos = nil - local TransportZone = nil - - -- fill-up some local variables to support further calculations to determine location of units within the zone. - 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 - --trace.i( "routines", "TransportZone:" .. TransportZoneResult ) - else - --trace.i( "routines", "TransportZone:nil logic" ) - end - return TransportZoneResult - else - --trace.i( "routines", "TransportZone:nil hard" ) - return nil - end -end - -function routines.IsUnitNearZonesRadius( TransportUnit, LandingZones, ZoneRadius ) ---trace.f("", "routines.IsUnitInZones" ) + -- trace.f("", "routines.IsUnitInZones" ) local TransportZoneResult = nil local TransportZonePos = nil local TransportZone = nil - -- fill-up some local variables to support further calculations to determine location of units within the zone. + -- fill-up some local variables to support further calculations to determine location of units within the zone. 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 + 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 @@ -1424,59 +1316,97 @@ function routines.IsUnitNearZonesRadius( TransportUnit, LandingZones, ZoneRadius 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 + 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 - --trace.i( "routines", "TransportZone:" .. TransportZoneResult ) + -- trace.i( "routines", "TransportZone:" .. TransportZoneResult ) else - --trace.i( "routines", "TransportZone:nil logic" ) + -- trace.i( "routines", "TransportZone:nil logic" ) end return TransportZoneResult else - --trace.i( "routines", "TransportZone:nil hard" ) + -- trace.i( "routines", "TransportZone:nil hard" ) return nil end end +function routines.IsUnitNearZonesRadius( TransportUnit, LandingZones, ZoneRadius ) + -- trace.f("", "routines.IsUnitInZones" ) -function routines.IsStaticInZones( TransportStatic, LandingZones ) ---trace.f() + local TransportZoneResult = nil + local TransportZonePos = nil + local TransportZone = nil - local TransportZoneResult = nil - local TransportZonePos = nil - local TransportZone = nil - - -- fill-up some local variables to support further calculations to determine location of units within the zone. - 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 - ---trace.r( "", "", { TransportZoneResult } ) + -- fill-up some local variables to support further calculations to determine location of units within the zone. + 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 + -- trace.i( "routines", "TransportZone:" .. TransportZoneResult ) + else + -- trace.i( "routines", "TransportZone:nil logic" ) + end return TransportZoneResult + else + -- trace.i( "routines", "TransportZone:nil hard" ) + return nil + end end +function routines.IsStaticInZones( TransportStatic, LandingZones ) + -- trace.f() + + local TransportZoneResult = nil + local TransportZonePos = nil + local TransportZone = nil + + -- fill-up some local variables to support further calculations to determine location of units within the zone. + 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 + + -- trace.r( "", "", { TransportZoneResult } ) + return TransportZoneResult +end function routines.IsUnitInRadius( CargoUnit, ReferencePosition, Radius ) ---trace.f() + -- trace.f() local Valid = true @@ -1484,7 +1414,7 @@ function routines.IsUnitInRadius( CargoUnit, ReferencePosition, Radius ) local CargoPos = CargoUnit:getPosition().p local ReferenceP = ReferencePosition.p - if (((CargoPos.x - ReferenceP.x)^2 + (CargoPos.z - ReferenceP.z)^2)^0.5 <= Radius) then + if (((CargoPos.x - ReferenceP.x) ^ 2 + (CargoPos.z - ReferenceP.z) ^ 2) ^ 0.5 <= Radius) then else Valid = false end @@ -1493,7 +1423,7 @@ function routines.IsUnitInRadius( CargoUnit, ReferencePosition, Radius ) end function routines.IsPartOfGroupInRadius( CargoGroup, ReferencePosition, Radius ) ---trace.f() + -- trace.f() local Valid = true @@ -1503,11 +1433,11 @@ function routines.IsPartOfGroupInRadius( CargoGroup, ReferencePosition, Radius ) local CargoUnits = CargoGroup:getUnits() for CargoUnitId, CargoUnit in pairs( CargoUnits ) do local CargoUnitPos = CargoUnit:getPosition().p --- env.info( 'routines.IsPartOfGroupInRadius: CargoUnitPos.x = ' .. CargoUnitPos.x .. ' CargoUnitPos.z = ' .. CargoUnitPos.z ) + -- env.info( 'routines.IsPartOfGroupInRadius: CargoUnitPos.x = ' .. CargoUnitPos.x .. ' CargoUnitPos.z = ' .. CargoUnitPos.z ) local ReferenceP = ReferencePosition.p --- env.info( 'routines.IsPartOfGroupInRadius: ReferenceGroupPos.x = ' .. ReferenceGroupPos.x .. ' ReferenceGroupPos.z = ' .. ReferenceGroupPos.z ) + -- env.info( 'routines.IsPartOfGroupInRadius: ReferenceGroupPos.x = ' .. ReferenceGroupPos.x .. ' ReferenceGroupPos.z = ' .. ReferenceGroupPos.z ) - if ((( CargoUnitPos.x - ReferenceP.x)^2 + (CargoUnitPos.z - ReferenceP.z)^2)^0.5 <= Radius) then + if (((CargoUnitPos.x - ReferenceP.x) ^ 2 + (CargoUnitPos.z - ReferenceP.z) ^ 2) ^ 0.5 <= Radius) then else Valid = false break @@ -1517,11 +1447,10 @@ function routines.IsPartOfGroupInRadius( CargoGroup, ReferencePosition, Radius ) return Valid end - function routines.ValidateString( Variable, VariableName, Valid ) ---trace.f() + -- trace.f() - if type( Variable ) == "string" then + if type( Variable ) == "string" then if Variable == "" then error( "routines.ValidateString: error: " .. VariableName .. " must be filled out!" ) Valid = false @@ -1531,65 +1460,64 @@ function routines.ValidateString( Variable, VariableName, Valid ) Valid = false end ---trace.r( "", "", { Valid } ) + -- trace.r( "", "", { Valid } ) return Valid end function routines.ValidateNumber( Variable, VariableName, Valid ) ---trace.f() + -- trace.f() - if type( Variable ) == "number" then + if type( Variable ) == "number" then else error( "routines.ValidateNumber: error: " .. VariableName .. " is not a number." ) Valid = false end ---trace.r( "", "", { Valid } ) + -- trace.r( "", "", { Valid } ) return Valid - end function routines.ValidateGroup( Variable, VariableName, Valid ) ---trace.f() + -- trace.f() - if Variable == nil then - error( "routines.ValidateGroup: error: " .. VariableName .. " is a nil value!" ) - Valid = false - end + if Variable == nil then + error( "routines.ValidateGroup: error: " .. VariableName .. " is a nil value!" ) + Valid = false + end ---trace.r( "", "", { Valid } ) - return Valid + -- trace.r( "", "", { Valid } ) + return Valid end function routines.ValidateZone( LandingZones, VariableName, Valid ) ---trace.f() + -- trace.f() - if LandingZones == nil then - error( "routines.ValidateGroup: error: " .. VariableName .. " is a nil value!" ) - Valid = false - end + 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 + 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 ---trace.r( "", "", { Valid } ) - return Valid + -- trace.r( "", "", { Valid } ) + return Valid end function routines.ValidateEnumeration( Variable, VariableName, Enum, Valid ) ---trace.f() + -- trace.f() local ValidVariable = false @@ -1600,214 +1528,191 @@ function routines.ValidateEnumeration( Variable, VariableName, Enum, Valid ) end end - if ValidVariable then + if ValidVariable then else error( 'TransportValidateEnum: " .. VariableName .. " is not a valid type.' .. Variable ) Valid = false end ---trace.r( "", "", { Valid } ) + -- trace.r( "", "", { Valid } ) return Valid end -function routines.getGroupRoute(groupIdent, task) -- same as getGroupPoints but returns speed and formation type along with vec2 of point} - -- refactor to search by groupId and allow groupId and groupName as inputs - 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 --there is a country table - 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 -- only these types have points - 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 --there's a group! - for group_num, group_data in pairs(obj_type_data.group) do - if group_data and group_data.groupId == gpId then -- this is the group we are looking for - 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 --it's possible that the ME could move to the point = Vec2 notation. - 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 --if group_data and group_data.name and group_data.name == 'groupname' - end --for group_num, group_data in pairs(obj_type_data.group) do - end --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 - end --if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" or obj_type_name == "static" then - end --for obj_type_name, obj_type_data in pairs(cntry_data) do - end --for cntry_id, cntry_data in pairs(coa_data.country) do - end --if coa_data.country then --there is a country table - end --if coa_name == 'red' or coa_name == 'blue' and type(coa_data) == 'table' then - end --for coa_name, coa_data in pairs(mission.coalition) do +function routines.getGroupRoute( groupIdent, task ) -- same as getGroupPoints but returns speed and formation type along with vec2 of point} + -- refactor to search by groupId and allow groupId and groupName as inputs + 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 -- there is a country table + 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 -- only these types have points + 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 -- there's a group! + for group_num, group_data in pairs( obj_type_data.group ) do + if group_data and group_data.groupId == gpId then -- this is the group we are looking for + 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 -- it's possible that the ME could move to the point = Vec2 notation. + 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 -- if group_data and group_data.name and group_data.name == 'groupname' + end -- for group_num, group_data in pairs(obj_type_data.group) do + end -- 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 + end -- if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" or obj_type_name == "static" then + end -- for obj_type_name, obj_type_data in pairs(cntry_data) do + end -- for cntry_id, cntry_data in pairs(coa_data.country) do + end -- if coa_data.country then --there is a country table + end -- if coa_name == 'red' or coa_name == 'blue' and type(coa_data) == 'table' then + end -- for coa_name, coa_data in pairs(mission.coalition) do 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 -- make it so the first WP matches the last WP - 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, - - }, - }, - }, - } +routines.ground.patrolRoute = function( vars ) - - useRoute[#useRoute].task = tempTask - routines.goRoute(gpData, useRoute) - - return + 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 -- make it so the first WP matches the last WP + 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 ) 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) +routines.ground.patrol = function( gpData, pType, form, speed ) + local vars = {} - return + 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 ) end function routines.GetUnitHeight( CheckUnit ) ---trace.f( "routines" ) + -- trace.f( "routines" ) - local UnitPoint = CheckUnit:getPoint() - local UnitPosition = { x = UnitPoint.x, y = UnitPoint.z } - local UnitHeight = UnitPoint.y + local UnitPoint = CheckUnit:getPoint() + local UnitPosition = { x = UnitPoint.x, y = UnitPoint.z } + local UnitHeight = UnitPoint.y - local LandHeight = land.getHeight( UnitPosition ) + local LandHeight = land.getHeight( UnitPosition ) - --env.info(( 'CarrierHeight: LandHeight = ' .. LandHeight .. ' CarrierHeight = ' .. CarrierHeight )) + -- env.info(( 'CarrierHeight: LandHeight = ' .. LandHeight .. ' CarrierHeight = ' .. CarrierHeight )) - --trace.f( "routines", "Unit Height = " .. UnitHeight - LandHeight ) - - return UnitHeight - LandHeight + -- trace.f( "routines", "Unit Height = " .. UnitHeight - LandHeight ) + return UnitHeight - LandHeight end - - Su34Status = { status = {} } boardMsgRed = { statusMsg = "" } boardMsgAll = { timeMsg = "" } @@ -1815,688 +1720,626 @@ SpawnSettings = {} Su34MenuPath = {} Su34Menus = 0 - -function Su34AttackCarlVinson(groupName) ---trace.menu("", "Su34AttackCarlVinson") - 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 ) +function Su34AttackCarlVinson( groupName ) + -- trace.menu("", "Su34AttackCarlVinson") + 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) ---trace.f("","Su34AttackWest") - 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 ) +function Su34AttackWest( groupName ) + -- trace.f("","Su34AttackWest") + 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) ---trace.menu("","Su34AttackNorth") - 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 ) +function Su34AttackNorth( groupName ) + -- trace.menu("","Su34AttackNorth") + 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) ---trace.menu("","Su34Orbit") - 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 ) +function Su34Orbit( groupName ) + -- trace.menu("","Su34Orbit") + 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) ---trace.menu("","Su34TakeOff") - 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 ) +function Su34TakeOff( groupName ) + -- trace.menu("","Su34TakeOff") + 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) ---trace.menu("","Su34Hold") - 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 ) +function Su34Hold( groupName ) + -- trace.menu("","Su34Hold") + 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) ---trace.menu("","Su34RTB") - Su34Status.status[groupName] = 6 - MessageToRed( string.format('%s: ',groupName) .. 'Return to Krasnodar. ', 10, 'RedStatus' .. groupName ) +function Su34RTB( groupName ) + -- trace.menu("","Su34RTB") + Su34Status.status[groupName] = 6 + MessageToRed( string.format( '%s: ', groupName ) .. 'Return to Krasnodar. ', 10, 'RedStatus' .. groupName ) end -function Su34Destroyed(groupName) ---trace.menu("","Su34Destroyed") - Su34Status.status[groupName] = 7 - MessageToRed( string.format('%s: ',groupName) .. 'Destroyed. ', 30, 'RedStatus' .. groupName ) +function Su34Destroyed( groupName ) + -- trace.menu("","Su34Destroyed") + Su34Status.status[groupName] = 7 + MessageToRed( string.format( '%s: ', groupName ) .. 'Destroyed. ', 30, 'RedStatus' .. groupName ) end function GroupAlive( groupName ) ---trace.menu("","GroupAlive") - local groupTest = Group.getByName( groupName ) + -- trace.menu("","GroupAlive") + local groupTest = Group.getByName( groupName ) - local groupExists = false + local groupExists = false - if groupTest then - groupExists = groupTest:isExist() - end + if groupTest then + groupExists = groupTest:isExist() + end - --trace.r( "", "", { groupExists } ) - return groupExists + -- trace.r( "", "", { groupExists } ) + return groupExists end function Su34IsDead() ---trace.f() + -- trace.f() end function Su34OverviewStatus() ---trace.menu("","Su34OverviewStatus") - local msg = "" - local currentStatus = 0 - local Exists = false + -- trace.menu("","Su34OverviewStatus") + local msg = "" + local currentStatus = 0 + local Exists = false - for groupName, currentStatus in pairs(Su34Status.status) do + for groupName, currentStatus in pairs( Su34Status.status ) do - env.info(('Su34 Overview Status: GroupName = ' .. groupName )) - Alive = GroupAlive( groupName ) + 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 + 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 + boardMsgRed.statusMsg = msg end - function UpdateBoardMsg() ---trace.f() - Su34OverviewStatus() - MessageToRed( boardMsgRed.statusMsg, 15, 'RedStatus' ) + -- trace.f() + Su34OverviewStatus() + MessageToRed( boardMsgRed.statusMsg, 15, 'RedStatus' ) end function MusicReset( flg ) ---trace.f() - trigger.action.setUserFlag(95,flg) + -- trace.f() + trigger.action.setUserFlag( 95, flg ) end -function PlaneActivate(groupNameFormat, flg) ---trace.f() - local groupName = groupNameFormat .. string.format("#%03d", trigger.misc.getUserFlag(flg)) - --trigger.action.outText(groupName,10) - trigger.action.activateGroup(Group.getByName(groupName)) +function PlaneActivate( groupNameFormat, flg ) + -- trace.f() + local groupName = groupNameFormat .. string.format( "#%03d", trigger.misc.getUserFlag( flg ) ) + -- trigger.action.outText(groupName,10) + trigger.action.activateGroup( Group.getByName( groupName ) ) end -function Su34Menu(groupName) ---trace.f() +function Su34Menu( groupName ) + -- trace.f() - --env.info(( 'Su34Menu(' .. groupName .. ')' )) - local groupSu34 = Group.getByName( groupName ) + -- env.info(( '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 - ) + 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 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 west", Su34MenuPath[groupName], Su34AttackWest, groupName ) - missionCommands.addCommandForCoalition( - coalition.side.RED, - "Attack ships in the north", - Su34MenuPath[groupName], - Su34AttackNorth, - 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, "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 + 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 --- Obsolete function, but kept to rework in framework. -function ChooseInfantry ( TeleportPrefixTable, TeleportMax ) ---trace.f("Spawn") - --env.info(( 'ChooseInfantry: ' )) +function ChooseInfantry( TeleportPrefixTable, TeleportMax ) + -- trace.f("Spawn") + -- env.info(( 'ChooseInfantry: ' )) - TeleportPrefixTableCount = #TeleportPrefixTable - TeleportPrefixTableIndex = math.random( 1, TeleportPrefixTableCount ) + TeleportPrefixTableCount = #TeleportPrefixTable + TeleportPrefixTableIndex = math.random( 1, TeleportPrefixTableCount ) - --env.info(( 'ChooseInfantry: TeleportPrefixTableIndex = ' .. TeleportPrefixTableIndex .. ' TeleportPrefixTableCount = ' .. TeleportPrefixTableCount .. ' TeleportMax = ' .. TeleportMax )) + -- env.info(( 'ChooseInfantry: TeleportPrefixTableIndex = ' .. TeleportPrefixTableIndex .. ' TeleportPrefixTableCount = ' .. TeleportPrefixTableCount .. ' TeleportMax = ' .. TeleportMax )) - local TeleportFound = false - local TeleportLoop = true - local Index = TeleportPrefixTableIndex - local TeleportPrefix = '' + 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'] - 1 < TeleportMax then - SpawnSettings[TeleportPrefix]['SpawnCount'] = SpawnSettings[TeleportPrefix]['SpawnCount'] + 1 - TeleportFound = true - else - TeleportFound = false - end - else - SpawnSettings[TeleportPrefix] = {} - SpawnSettings[TeleportPrefix]['SpawnCount'] = 0 - TeleportFound = true - end - if TeleportFound then - TeleportLoop = false - else - if Index < TeleportPrefixTableCount then - Index = Index + 1 - else - TeleportLoop = false - end - end - --env.info(( 'ChooseInfantry: Loop 1 - TeleportPrefix = ' .. TeleportPrefix .. ' Index = ' .. Index )) - end + while TeleportLoop do + TeleportPrefix = TeleportPrefixTable[Index] + if SpawnSettings[TeleportPrefix] then + if SpawnSettings[TeleportPrefix]['SpawnCount'] - 1 < TeleportMax then + SpawnSettings[TeleportPrefix]['SpawnCount'] = SpawnSettings[TeleportPrefix]['SpawnCount'] + 1 + TeleportFound = true + else + TeleportFound = false + end + else + SpawnSettings[TeleportPrefix] = {} + SpawnSettings[TeleportPrefix]['SpawnCount'] = 0 + TeleportFound = true + end + if TeleportFound then + TeleportLoop = false + else + if Index < TeleportPrefixTableCount then + Index = Index + 1 + else + TeleportLoop = false + end + end + -- env.info(( 'ChooseInfantry: Loop 1 - TeleportPrefix = ' .. TeleportPrefix .. ' Index = ' .. Index )) + end - if TeleportFound == false then - TeleportLoop = true - Index = 1 - while TeleportLoop do - TeleportPrefix = TeleportPrefixTable[Index] - if SpawnSettings[TeleportPrefix] then - if SpawnSettings[TeleportPrefix]['SpawnCount'] - 1 < TeleportMax then - SpawnSettings[TeleportPrefix]['SpawnCount'] = SpawnSettings[TeleportPrefix]['SpawnCount'] + 1 - TeleportFound = true - else - TeleportFound = false - end - else - SpawnSettings[TeleportPrefix] = {} - SpawnSettings[TeleportPrefix]['SpawnCount'] = 0 - TeleportFound = true - end - if TeleportFound then - TeleportLoop = false - else - if Index < TeleportPrefixTableIndex then - Index = Index + 1 - else - TeleportLoop = false - end - end - --env.info(( 'ChooseInfantry: Loop 2 - TeleportPrefix = ' .. TeleportPrefix .. ' Index = ' .. Index )) - end - end + if TeleportFound == false then + TeleportLoop = true + Index = 1 + while TeleportLoop do + TeleportPrefix = TeleportPrefixTable[Index] + if SpawnSettings[TeleportPrefix] then + if SpawnSettings[TeleportPrefix]['SpawnCount'] - 1 < TeleportMax then + SpawnSettings[TeleportPrefix]['SpawnCount'] = SpawnSettings[TeleportPrefix]['SpawnCount'] + 1 + TeleportFound = true + else + TeleportFound = false + end + else + SpawnSettings[TeleportPrefix] = {} + SpawnSettings[TeleportPrefix]['SpawnCount'] = 0 + TeleportFound = true + end + if TeleportFound then + TeleportLoop = false + else + if Index < TeleportPrefixTableIndex then + Index = Index + 1 + else + TeleportLoop = false + end + end + -- env.info(( 'ChooseInfantry: Loop 2 - TeleportPrefix = ' .. TeleportPrefix .. ' Index = ' .. Index )) + end + end - local TeleportGroupName = '' - if TeleportFound == true then - TeleportGroupName = TeleportPrefix .. string.format("#%03d", SpawnSettings[TeleportPrefix]['SpawnCount'] ) - else - TeleportGroupName = '' - end + local TeleportGroupName = '' + if TeleportFound == true then + TeleportGroupName = TeleportPrefix .. string.format( "#%03d", SpawnSettings[TeleportPrefix]['SpawnCount'] ) + else + TeleportGroupName = '' + end - --env.info(('ChooseInfantry: TeleportGroupName = ' .. TeleportGroupName )) - --env.info(('ChooseInfantry: return')) + -- env.info(('ChooseInfantry: TeleportGroupName = ' .. TeleportGroupName )) + -- env.info(('ChooseInfantry: return')) - return TeleportGroupName + return TeleportGroupName end SpawnedInfantry = 0 -function LandCarrier ( CarrierGroup, LandingZonePrefix ) ---trace.f() - --env.info(( 'LandCarrier: ' )) - --env.info(( 'LandCarrier: CarrierGroup = ' .. CarrierGroup:getName() )) - --env.info(( 'LandCarrier: LandingZone = ' .. LandingZonePrefix )) +function LandCarrier( CarrierGroup, LandingZonePrefix ) + -- trace.f() + -- env.info(( 'LandCarrier: ' )) + -- env.info(( 'LandCarrier: CarrierGroup = ' .. CarrierGroup:getName() )) + -- env.info(( 'LandCarrier: LandingZone = ' .. LandingZonePrefix )) - local controllerGroup = CarrierGroup:getController() + local controllerGroup = CarrierGroup:getController() - local LandingZone = trigger.misc.getZone(LandingZonePrefix) - local LandingZonePos = {} - LandingZonePos.x = LandingZone.point.x + math.random(LandingZone.radius * -1, LandingZone.radius) - LandingZonePos.y = LandingZone.point.z + math.random(LandingZone.radius * -1, LandingZone.radius) + local LandingZone = trigger.misc.getZone( LandingZonePrefix ) + local LandingZonePos = {} + LandingZonePos.x = LandingZone.point.x + math.random( LandingZone.radius * -1, LandingZone.radius ) + LandingZonePos.y = LandingZone.point.z + math.random( LandingZone.radius * -1, LandingZone.radius ) - controllerGroup:pushTask( { id = 'Land', params = { point = LandingZonePos, durationFlag = true, duration = 10 } } ) + controllerGroup:pushTask( { id = 'Land', params = { point = LandingZonePos, durationFlag = true, duration = 10 } } ) - --env.info(( 'LandCarrier: end' )) + -- env.info(( 'LandCarrier: end' )) end EscortCount = 0 -function EscortCarrier ( CarrierGroup, EscortPrefix, EscortLastWayPoint, EscortEngagementDistanceMax, EscortTargetTypes ) ---trace.f() - --env.info(( 'EscortCarrier: ' )) - --env.info(( 'EscortCarrier: CarrierGroup = ' .. CarrierGroup:getName() )) - --env.info(( 'EscortCarrier: EscortPrefix = ' .. EscortPrefix )) +function EscortCarrier( CarrierGroup, EscortPrefix, EscortLastWayPoint, EscortEngagementDistanceMax, EscortTargetTypes ) + -- trace.f() + -- env.info(( 'EscortCarrier: ' )) + -- env.info(( 'EscortCarrier: CarrierGroup = ' .. CarrierGroup:getName() )) + -- env.info(( 'EscortCarrier: EscortPrefix = ' .. EscortPrefix )) - local CarrierName = CarrierGroup:getName() + local CarrierName = CarrierGroup:getName() - local EscortMission = {} - local CarrierMission = {} + local EscortMission = {} + local CarrierMission = {} - local EscortMission = SpawnMissionGroup( EscortPrefix ) - local CarrierMission = SpawnMissionGroup( CarrierGroup:getName() ) + local EscortMission = SpawnMissionGroup( EscortPrefix ) + local CarrierMission = SpawnMissionGroup( CarrierGroup:getName() ) - if EscortMission ~= nil and CarrierMission ~= nil then + if EscortMission ~= nil and CarrierMission ~= nil then - EscortCount = EscortCount + 1 - EscortMissionName = string.format( EscortPrefix .. '#Escort %s', CarrierName ) - EscortMission.name = EscortMissionName - EscortMission.groupId = nil - EscortMission.lateActivation = false - EscortMission.taskSelected = false + EscortCount = EscortCount + 1 + EscortMissionName = string.format( EscortPrefix .. '#Escort %s', CarrierName ) + EscortMission.name = EscortMissionName + EscortMission.groupId = nil + EscortMission.lateActivation = false + EscortMission.taskSelected = false - local EscortUnits = #EscortMission.units - for u = 1, EscortUnits do - EscortMission.units[u].name = string.format( EscortPrefix .. '#Escort %s %02d', CarrierName, u ) - EscortMission.units[u].unitId = nil - end + local EscortUnits = #EscortMission.units + for u = 1, EscortUnits do + EscortMission.units[u].name = string.format( EscortPrefix .. '#Escort %s %02d', CarrierName, u ) + EscortMission.units[u].unitId = nil + end + EscortMission.route.points[1].task = { + id = "ComboTask", + params = { + tasks = { + [1] = { + enabled = true, + auto = false, + id = "Escort", + number = 1, + params = { + lastWptIndexFlagChangedManually = false, + groupId = CarrierGroup:getID(), + lastWptIndex = nil, + lastWptIndexFlag = false, + engagementDistMax = EscortEngagementDistanceMax, + targetTypes = EscortTargetTypes, + pos = { y = 20, x = 20, z = 0 } -- end of ["pos"] + } -- end of ["params"] + } -- end of [1] + } -- end of ["tasks"] + } -- end of ["params"] + } -- end of ["task"] - EscortMission.route.points[1].task = { id = "ComboTask", - params = - { - tasks = - { - [1] = - { - enabled = true, - auto = false, - id = "Escort", - number = 1, - params = - { - lastWptIndexFlagChangedManually = false, - groupId = CarrierGroup:getID(), - lastWptIndex = nil, - lastWptIndexFlag = false, - engagementDistMax = EscortEngagementDistanceMax, - targetTypes = EscortTargetTypes, - pos = - { - y = 20, - x = 20, - z = 0, - } -- end of ["pos"] - } -- end of ["params"] - } -- end of [1] - } -- end of ["tasks"] - } -- end of ["params"] - } -- end of ["task"] + SpawnGroupAdd( EscortPrefix, EscortMission ) - SpawnGroupAdd( EscortPrefix, EscortMission ) - - end + end end function SendMessageToCarrier( CarrierGroup, CarrierMessage ) ---trace.f() + -- trace.f() - if CarrierGroup ~= nil then - MessageToGroup( CarrierGroup, CarrierMessage, 30, 'Carrier/' .. CarrierGroup:getName() ) - end + if CarrierGroup ~= nil then + MessageToGroup( CarrierGroup, CarrierMessage, 30, 'Carrier/' .. CarrierGroup:getName() ) + end end function MessageToGroup( MsgGroup, MsgText, MsgTime, MsgName ) ---trace.f() + -- trace.f() - if type(MsgGroup) == 'string' then - --env.info( 'MessageToGroup: Converted MsgGroup string "' .. MsgGroup .. '" into a Group structure.' ) - MsgGroup = Group.getByName( MsgGroup ) - end + if type( MsgGroup ) == 'string' then + -- env.info( 'MessageToGroup: Converted MsgGroup string "' .. MsgGroup .. '" into a Group structure.' ) + MsgGroup = Group.getByName( MsgGroup ) + end - if MsgGroup ~= nil then - local MsgTable = {} - MsgTable.text = MsgText - MsgTable.displayTime = MsgTime - MsgTable.msgFor = { units = { MsgGroup:getUnits()[1]:getName() } } - MsgTable.name = MsgName - --routines.message.add( MsgTable ) - --env.info(('MessageToGroup: Message sent to ' .. MsgGroup:getUnits()[1]:getName() .. ' -> ' .. MsgText )) - end + if MsgGroup ~= nil then + local MsgTable = {} + MsgTable.text = MsgText + MsgTable.displayTime = MsgTime + MsgTable.msgFor = { units = { MsgGroup:getUnits()[1]:getName() } } + MsgTable.name = MsgName + -- routines.message.add( MsgTable ) + -- env.info(('MessageToGroup: Message sent to ' .. MsgGroup:getUnits()[1]:getName() .. ' -> ' .. MsgText )) + end end function MessageToUnit( UnitName, MsgText, MsgTime, MsgName ) ---trace.f() + -- trace.f() - if UnitName ~= nil then - local MsgTable = {} - MsgTable.text = MsgText - MsgTable.displayTime = MsgTime - MsgTable.msgFor = { units = { UnitName } } - MsgTable.name = MsgName - --routines.message.add( MsgTable ) - end + if UnitName ~= nil then + local MsgTable = {} + MsgTable.text = MsgText + MsgTable.displayTime = MsgTime + MsgTable.msgFor = { units = { UnitName } } + MsgTable.name = MsgName + -- routines.message.add( MsgTable ) + end end function MessageToAll( MsgText, MsgTime, MsgName ) ---trace.f() + -- trace.f() - MESSAGE:New( MsgText, MsgTime, "Message" ):ToCoalition( coalition.side.RED ):ToCoalition( coalition.side.BLUE ) + MESSAGE:New( MsgText, MsgTime, "Message" ):ToCoalition( coalition.side.RED ):ToCoalition( coalition.side.BLUE ) end function MessageToRed( MsgText, MsgTime, MsgName ) ---trace.f() + -- trace.f() - MESSAGE:New( MsgText, MsgTime, "To Red Coalition" ):ToCoalition( coalition.side.RED ) + MESSAGE:New( MsgText, MsgTime, "To Red Coalition" ):ToCoalition( coalition.side.RED ) end function MessageToBlue( MsgText, MsgTime, MsgName ) ---trace.f() + -- trace.f() - MESSAGE:New( MsgText, MsgTime, "To Blue Coalition" ):ToCoalition( coalition.side.BLUE ) + MESSAGE:New( MsgText, MsgTime, "To Blue Coalition" ):ToCoalition( coalition.side.BLUE ) end function getCarrierHeight( CarrierGroup ) ---trace.f() + -- trace.f() - if CarrierGroup ~= nil then - if table.getn(CarrierGroup:getUnits()) == 1 then - local CarrierUnit = CarrierGroup:getUnits()[1] - local CurrentPoint = CarrierUnit:getPoint() + if CarrierGroup ~= nil then + if table.getn( CarrierGroup:getUnits() ) == 1 then + local CarrierUnit = CarrierGroup:getUnits()[1] + local CurrentPoint = CarrierUnit:getPoint() - local CurrentPosition = { x = CurrentPoint.x, y = CurrentPoint.z } - local CarrierHeight = CurrentPoint.y + local CurrentPosition = { x = CurrentPoint.x, y = CurrentPoint.z } + local CarrierHeight = CurrentPoint.y - local LandHeight = land.getHeight( CurrentPosition ) + local LandHeight = land.getHeight( CurrentPosition ) - --env.info(( 'CarrierHeight: LandHeight = ' .. LandHeight .. ' CarrierHeight = ' .. CarrierHeight )) - - return CarrierHeight - LandHeight - else - return 999999 - end - else - return 999999 - end + -- env.info(( 'CarrierHeight: LandHeight = ' .. LandHeight .. ' CarrierHeight = ' .. CarrierHeight )) + return CarrierHeight - LandHeight + else + return 999999 + end + else + return 999999 + end end function GetUnitHeight( CheckUnit ) ---trace.f() + -- trace.f() - local UnitPoint = CheckUnit:getPoint() - local UnitPosition = { x = CurrentPoint.x, y = CurrentPoint.z } - local UnitHeight = CurrentPoint.y + local UnitPoint = CheckUnit:getPoint() + local UnitPosition = { x = CurrentPoint.x, y = CurrentPoint.z } + local UnitHeight = CurrentPoint.y - local LandHeight = land.getHeight( CurrentPosition ) + local LandHeight = land.getHeight( CurrentPosition ) - --env.info(( 'CarrierHeight: LandHeight = ' .. LandHeight .. ' CarrierHeight = ' .. CarrierHeight )) - - return UnitHeight - LandHeight + -- env.info(( 'CarrierHeight: LandHeight = ' .. LandHeight .. ' CarrierHeight = ' .. CarrierHeight )) + return UnitHeight - LandHeight end - _MusicTable = {} _MusicTable.Files = {} _MusicTable.Queue = {} _MusicTable.FileCnt = 0 - function MusicRegister( SndRef, SndFile, SndTime ) ---trace.f() + -- trace.f() - env.info(( 'MusicRegister: SndRef = ' .. SndRef )) - env.info(( 'MusicRegister: SndFile = ' .. SndFile )) - env.info(( 'MusicRegister: SndTime = ' .. SndTime )) + env.info( ('MusicRegister: SndRef = ' .. SndRef) ) + env.info( ('MusicRegister: SndFile = ' .. SndFile) ) + env.info( ('MusicRegister: SndTime = ' .. SndTime) ) + _MusicTable.FileCnt = _MusicTable.FileCnt + 1 - _MusicTable.FileCnt = _MusicTable.FileCnt + 1 - - _MusicTable.Files[_MusicTable.FileCnt] = {} - _MusicTable.Files[_MusicTable.FileCnt].Ref = SndRef - _MusicTable.Files[_MusicTable.FileCnt].File = SndFile - _MusicTable.Files[_MusicTable.FileCnt].Time = SndTime - - if not _MusicTable.Function then - _MusicTable.Function = routines.scheduleFunction( MusicScheduler, { }, timer.getTime() + 10, 10) - end + _MusicTable.Files[_MusicTable.FileCnt] = {} + _MusicTable.Files[_MusicTable.FileCnt].Ref = SndRef + _MusicTable.Files[_MusicTable.FileCnt].File = SndFile + _MusicTable.Files[_MusicTable.FileCnt].Time = SndTime + if not _MusicTable.Function then + _MusicTable.Function = routines.scheduleFunction( MusicScheduler, {}, timer.getTime() + 10, 10 ) + end end function MusicToPlayer( SndRef, PlayerName, SndContinue ) ---trace.f() + -- trace.f() - --env.info(( 'MusicToPlayer: SndRef = ' .. SndRef )) + -- env.info(( 'MusicToPlayer: SndRef = ' .. SndRef )) - local PlayerUnits = AlivePlayerUnits() - for PlayerUnitIdx, PlayerUnit in pairs(PlayerUnits) do - local PlayerUnitName = PlayerUnit:getPlayerName() - --env.info(( 'MusicToPlayer: PlayerUnitName = ' .. PlayerUnitName )) - if PlayerName == PlayerUnitName then - PlayerGroup = PlayerUnit:getGroup() - if PlayerGroup then - --env.info(( 'MusicToPlayer: PlayerGroup = ' .. PlayerGroup:getName() )) - MusicToGroup( SndRef, PlayerGroup, SndContinue ) - end - break - end - end - - --env.info(( 'MusicToPlayer: end' )) + local PlayerUnits = AlivePlayerUnits() + for PlayerUnitIdx, PlayerUnit in pairs( PlayerUnits ) do + local PlayerUnitName = PlayerUnit:getPlayerName() + -- env.info(( 'MusicToPlayer: PlayerUnitName = ' .. PlayerUnitName )) + if PlayerName == PlayerUnitName then + PlayerGroup = PlayerUnit:getGroup() + if PlayerGroup then + -- env.info(( 'MusicToPlayer: PlayerGroup = ' .. PlayerGroup:getName() )) + MusicToGroup( SndRef, PlayerGroup, SndContinue ) + end + break + end + end + -- env.info(( 'MusicToPlayer: end' )) end function MusicToGroup( SndRef, SndGroup, SndContinue ) ---trace.f() + -- trace.f() - --env.info(( 'MusicToGroup: SndRef = ' .. SndRef )) + -- env.info(( 'MusicToGroup: SndRef = ' .. SndRef )) - if SndGroup ~= nil then - if _MusicTable and _MusicTable.FileCnt > 0 then - if SndGroup:isExist() then - if MusicCanStart(SndGroup:getUnit(1):getPlayerName()) then - --env.info(( 'MusicToGroup: OK for Sound.' )) - local SndIdx = 0 - if SndRef == '' then - --env.info(( 'MusicToGroup: SndRef as empty. Queueing at random.' )) - SndIdx = math.random( 1, _MusicTable.FileCnt ) - else - for SndIdx = 1, _MusicTable.FileCnt do - if _MusicTable.Files[SndIdx].Ref == SndRef then - break - end - end - end - --env.info(( 'MusicToGroup: SndIdx = ' .. SndIdx )) - --env.info(( 'MusicToGroup: Queueing Music ' .. _MusicTable.Files[SndIdx].File .. ' for Group ' .. SndGroup:getID() )) - trigger.action.outSoundForGroup( SndGroup:getID(), _MusicTable.Files[SndIdx].File ) - MessageToGroup( SndGroup, 'Playing ' .. _MusicTable.Files[SndIdx].File, 15, 'Music-' .. SndGroup:getUnit(1):getPlayerName() ) + if SndGroup ~= nil then + if _MusicTable and _MusicTable.FileCnt > 0 then + if SndGroup:isExist() then + if MusicCanStart( SndGroup:getUnit( 1 ):getPlayerName() ) then + -- env.info(( 'MusicToGroup: OK for Sound.' )) + local SndIdx = 0 + if SndRef == '' then + -- env.info(( 'MusicToGroup: SndRef as empty. Queueing at random.' )) + SndIdx = math.random( 1, _MusicTable.FileCnt ) + else + for SndIdx = 1, _MusicTable.FileCnt do + if _MusicTable.Files[SndIdx].Ref == SndRef then + break + end + end + end + -- env.info(( 'MusicToGroup: SndIdx = ' .. SndIdx )) + -- env.info(( 'MusicToGroup: Queueing Music ' .. _MusicTable.Files[SndIdx].File .. ' for Group ' .. SndGroup:getID() )) + trigger.action.outSoundForGroup( SndGroup:getID(), _MusicTable.Files[SndIdx].File ) + MessageToGroup( SndGroup, 'Playing ' .. _MusicTable.Files[SndIdx].File, 15, 'Music-' .. SndGroup:getUnit( 1 ):getPlayerName() ) - local SndQueueRef = SndGroup:getUnit(1):getPlayerName() - if _MusicTable.Queue[SndQueueRef] == nil then - _MusicTable.Queue[SndQueueRef] = {} - end - _MusicTable.Queue[SndQueueRef].Start = timer.getTime() - _MusicTable.Queue[SndQueueRef].PlayerName = SndGroup:getUnit(1):getPlayerName() - _MusicTable.Queue[SndQueueRef].Group = SndGroup - _MusicTable.Queue[SndQueueRef].ID = SndGroup:getID() - _MusicTable.Queue[SndQueueRef].Ref = SndIdx - _MusicTable.Queue[SndQueueRef].Continue = SndContinue - _MusicTable.Queue[SndQueueRef].Type = Group - end - end - end - end + local SndQueueRef = SndGroup:getUnit( 1 ):getPlayerName() + if _MusicTable.Queue[SndQueueRef] == nil then + _MusicTable.Queue[SndQueueRef] = {} + end + _MusicTable.Queue[SndQueueRef].Start = timer.getTime() + _MusicTable.Queue[SndQueueRef].PlayerName = SndGroup:getUnit( 1 ):getPlayerName() + _MusicTable.Queue[SndQueueRef].Group = SndGroup + _MusicTable.Queue[SndQueueRef].ID = SndGroup:getID() + _MusicTable.Queue[SndQueueRef].Ref = SndIdx + _MusicTable.Queue[SndQueueRef].Continue = SndContinue + _MusicTable.Queue[SndQueueRef].Type = Group + end + end + end + end end -function MusicCanStart(PlayerName) ---trace.f() +function MusicCanStart( PlayerName ) + -- trace.f() - --env.info(( 'MusicCanStart:' )) + -- env.info(( 'MusicCanStart:' )) - local MusicOut = false + local MusicOut = false - if _MusicTable['Queue'] ~= nil and _MusicTable.FileCnt > 0 then - --env.info(( 'MusicCanStart: PlayerName = ' .. PlayerName )) - 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 - --env.info(( 'MusicCanStart: MusicStart = ' .. MusicStart )) - --env.info(( 'MusicCanStart: MusicTime = ' .. MusicTime )) - --env.info(( 'MusicCanStart: timer.getTime() = ' .. timer.getTime() )) + if _MusicTable['Queue'] ~= nil and _MusicTable.FileCnt > 0 then + -- env.info(( 'MusicCanStart: PlayerName = ' .. PlayerName )) + 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 + -- env.info(( 'MusicCanStart: MusicStart = ' .. MusicStart )) + -- env.info(( 'MusicCanStart: MusicTime = ' .. MusicTime )) + -- env.info(( 'MusicCanStart: timer.getTime() = ' .. timer.getTime() )) - if MusicStart + MusicTime <= timer.getTime() then - MusicOut = true - end - else - MusicOut = true - end - end + if MusicStart + MusicTime <= timer.getTime() then + MusicOut = true + end + else + MusicOut = true + end + end - if MusicOut then - --env.info(( 'MusicCanStart: true' )) - else - --env.info(( 'MusicCanStart: false' )) - end + if MusicOut then + -- env.info(( 'MusicCanStart: true' )) + else + -- env.info(( 'MusicCanStart: false' )) + end - return MusicOut + return MusicOut end function MusicScheduler() ---trace.scheduled("", "MusicScheduler") - - --env.info(( 'MusicScheduler:' )) - if _MusicTable['Queue'] ~= nil and _MusicTable.FileCnt > 0 then - --env.info(( 'MusicScheduler: Walking Sound Queue.')) - for SndQueueIdx, SndQueue in pairs( _MusicTable.Queue ) do - if SndQueue.Continue then - if MusicCanStart(SndQueue.PlayerName) then - --env.info(('MusicScheduler: MusicToGroup')) - MusicToPlayer( '', SndQueue.PlayerName, true ) - end - end - end - end + -- trace.scheduled("", "MusicScheduler") + -- env.info(( 'MusicScheduler:' )) + if _MusicTable['Queue'] ~= nil and _MusicTable.FileCnt > 0 then + -- env.info(( 'MusicScheduler: Walking Sound Queue.')) + for SndQueueIdx, SndQueue in pairs( _MusicTable.Queue ) do + if SndQueue.Continue then + if MusicCanStart( SndQueue.PlayerName ) then + -- env.info(('MusicScheduler: MusicToGroup')) + MusicToPlayer( '', SndQueue.PlayerName, true ) + end + end + end + end end - -env.info(( 'Init: Scripts Loaded v1.1' )) - +env.info( ('Init: Scripts Loaded v1.1') ) diff --git a/Moose Development/Moose/Utilities/STTS.lua b/Moose Development/Moose/Utilities/STTS.lua index 97836a1ee..37a6b3035 100644 --- a/Moose Development/Moose/Utilities/STTS.lua +++ b/Moose Development/Moose/Utilities/STTS.lua @@ -1,38 +1,35 @@ --- **Utilities** DCS Simple Text-To-Speech (STTS). --- --- --- +-- +-- -- @module Utils.STTS -- @image MOOSE.JPG - --- [DCS Enum world](https://wiki.hoggitworld.com/view/DCS_enum_world) -- @type STTS -- @field #string DIRECTORY Path of the SRS directory. - --- Simple Text-To-Speech --- +-- -- Version 0.4 - Compatible with SRS version 1.9.6.0+ --- +-- -- # DCS Modification Required --- --- You will need to edit MissionScripting.lua in DCS World/Scripts/MissionScripting.lua and remove the sanitisation. +-- +-- You will need to edit MissionScripting.lua in DCS World/Scripts/MissionScripting.lua and remove the sanitization. -- To do this remove all the code below the comment - the line starts "local function sanitizeModule(name)" -- Do this without DCS running to allow mission scripts to use os functions. --- +-- -- *You WILL HAVE TO REAPPLY AFTER EVERY DCS UPDATE* --- +-- -- # USAGE: --- --- Add this script into the mission as a DO SCRIPT or DO SCRIPT FROM FILE to initialise it +-- +-- Add this script into the mission as a DO SCRIPT or DO SCRIPT FROM FILE to initialize it -- Make sure to edit the STTS.SRS_PORT and STTS.DIRECTORY to the correct values before adding to the mission. -- Then its as simple as calling the correct function in LUA as a DO SCRIPT or in your own scripts. --- +-- -- Example calls: -- -- STTS.TextToSpeech("Hello DCS WORLD","251","AM","1.0","SRS",2) --- +-- -- Arguments in order are: --- +-- -- * Message to say, make sure not to use a newline (\n) ! -- * Frequency in MHz -- * Modulation - AM/FM @@ -50,37 +47,37 @@ -- ## Example -- -- This example will say the words "Hello DCS WORLD" on 251 MHz AM at maximum volume with a client called SRS and to the Blue coalition only --- +-- -- STTS.TextToSpeech("Hello DCS WORLD","251","AM","1.0","SRS",2,null,-5,"male","en-GB") --- +-- -- ## Example --- ---This example will say the words "Hello DCS WORLD" on 251 MHz AM at maximum volume with a client called SRS and to the Blue coalition only centered on the position of the Unit called "A UNIT" +-- +-- This example will say the words "Hello DCS WORLD" on 251 MHz AM at maximum volume with a client called SRS and to the Blue coalition only centered on the position of the Unit called "A UNIT" -- -- STTS.TextToSpeech("Hello DCS WORLD","251","AM","1.0","SRS",2,Unit.getByName("A UNIT"):getPoint(),-5,"male","en-GB") --- +-- -- Arguments in order are: --- +-- -- * FULL path to the MP3 OR OGG to play -- * Frequency in MHz - to use multiple separate with a comma - Number of frequencies MUST match number of Modulations -- * Modulation - AM/FM - to use multiple -- * Volume - 1.0 max, 0.5 half -- * Name of the transmitter - ATC, RockFM etc -- * Coalition - 0 spectator, 1 red 2 blue --- +-- -- ## Example --- +-- -- This will play that MP3 on 255MHz AM & 31 FM at half volume with a client called "Multiple" and to Spectators only --- +-- -- STTS.PlayMP3("C:\\Users\\Ciaran\\Downloads\\PR-Music.mp3","255,31","AM,FM","0.5","Multiple",0) --- +-- -- @field #STTS -STTS={ - ClassName="STTS", - DIRECTORY="", - SRS_PORT=5002, - GOOGLE_CREDENTIALS="C:\\Users\\Ciaran\\Downloads\\googletts.json", - EXECUTABLE="DCS-SR-ExternalAudio.exe", +STTS = { + ClassName = "STTS", + DIRECTORY = "", + SRS_PORT = 5002, + GOOGLE_CREDENTIALS = "C:\\Users\\Ciaran\\Downloads\\googletts.json", + EXECUTABLE = "DCS-SR-ExternalAudio.exe" } --- FULL Path to the FOLDER containing DCS-SR-ExternalAudio.exe - EDIT TO CORRECT FOLDER @@ -95,139 +92,141 @@ STTS.GOOGLE_CREDENTIALS = "C:\\Users\\Ciaran\\Downloads\\googletts.json" --- DONT CHANGE THIS UNLESS YOU KNOW WHAT YOU'RE DOING STTS.EXECUTABLE = "DCS-SR-ExternalAudio.exe" - --- Function for UUID. function STTS.uuid() local random = math.random - local template ='yxxx-xxxxxxxxxxxx' - return string.gsub(template, '[xy]', function (c) - local v = (c == 'x') and random(0, 0xf) or random(8, 0xb) - return string.format('%x', v) - end) + local template = 'yxxx-xxxxxxxxxxxx' + return string.gsub( template, '[xy]', function( c ) + local v = (c == 'x') and random( 0, 0xf ) or random( 8, 0xb ) + return string.format( '%x', v ) + end ) end --- Round a number. -- @param #number x Number. -- @param #number n Precision. -function STTS.round(x, n) - n = math.pow(10, n or 0) +function STTS.round( x, n ) + n = math.pow( 10, n or 0 ) x = x * n - if x >= 0 then x = math.floor(x + 0.5) else x = math.ceil(x - 0.5) end + if x >= 0 then + x = math.floor( x + 0.5 ) + else + x = math.ceil( x - 0.5 ) + end return x / n end --- Function returns estimated speech time in seconds. -- Assumptions for time calc: 100 Words per min, avarage of 5 letters for english word so --- +-- -- * 5 chars * 100wpm = 500 characters per min = 8.3 chars per second --- +-- -- So lengh of msg / 8.3 = number of seconds needed to read it. rounded down to 8 chars per sec map function: --- +-- -- * (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min --- -function STTS.getSpeechTime(length,speed,isGoogle) +-- +function STTS.getSpeechTime( length, speed, isGoogle ) - local maxRateRatio = 3 + local maxRateRatio = 3 speed = speed or 1.0 isGoogle = isGoogle or false local speedFactor = 1.0 if isGoogle then - speedFactor = speed + speedFactor = speed else - if speed ~= 0 then - speedFactor = math.abs(speed) * (maxRateRatio - 1) / 10 + 1 - end - if speed < 0 then - speedFactor = 1/speedFactor - end + if speed ~= 0 then + speedFactor = math.abs( speed ) * (maxRateRatio - 1) / 10 + 1 + end + if speed < 0 then + speedFactor = 1 / speedFactor + end end - local wpm = math.ceil(100 * speedFactor) - local cps = math.floor((wpm * 5)/60) + local wpm = math.ceil( 100 * speedFactor ) + local cps = math.floor( (wpm * 5) / 60 ) - if type(length) == "string" then - length = string.len(length) + if type( length ) == "string" then + length = string.len( length ) end - return math.ceil(length/cps) + return math.ceil( length / cps ) end --- Text to speech function. -function STTS.TextToSpeech(message, freqs, modulations, volume, name, coalition, point, speed, gender, culture, voice, googleTTS) - if os == nil or io == nil then - env.info("[DCS-STTS] LUA modules os or io are sanitized. skipping. ") - return - end +function STTS.TextToSpeech( message, freqs, modulations, volume, name, coalition, point, speed, gender, culture, voice, googleTTS ) + if os == nil or io == nil then + env.info( "[DCS-STTS] LUA modules os or io are sanitized. skipping. " ) + return + end - speed = speed or 1 - gender = gender or "female" - culture = culture or "" - voice = voice or "" - coalition=coalition or "0" - name=name or "ROBOT" - volume=1 - speed=1 + speed = speed or 1 + gender = gender or "female" + culture = culture or "" + voice = voice or "" + coalition = coalition or "0" + name = name or "ROBOT" + volume = 1 + speed = 1 + message = message:gsub( "\"", "\\\"" ) + + local cmd = string.format( "start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -h", STTS.DIRECTORY, STTS.EXECUTABLE, freqs or "305", modulations or "AM", coalition, STTS.SRS_PORT, name ) - message = message:gsub("\"","\\\"") - - local cmd = string.format("start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -h", STTS.DIRECTORY, STTS.EXECUTABLE, freqs or "305", modulations or "AM", coalition, STTS.SRS_PORT, name) - if voice ~= "" then - cmd = cmd .. string.format(" -V \"%s\"",voice) + cmd = cmd .. string.format( " -V \"%s\"", voice ) else - if culture ~= "" then - cmd = cmd .. string.format(" -l %s",culture) - end + if culture ~= "" then + cmd = cmd .. string.format( " -l %s", culture ) + end - if gender ~= "" then - cmd = cmd .. string.format(" -g %s",gender) - end + if gender ~= "" then + cmd = cmd .. string.format( " -g %s", gender ) + end end if googleTTS == true then - cmd = cmd .. string.format(" -G \"%s\"",STTS.GOOGLE_CREDENTIALS) + cmd = cmd .. string.format( " -G \"%s\"", STTS.GOOGLE_CREDENTIALS ) end if speed ~= 1 then - cmd = cmd .. string.format(" -s %s",speed) + cmd = cmd .. string.format( " -s %s", speed ) end if volume ~= 1.0 then - cmd = cmd .. string.format(" -v %s",volume) + cmd = cmd .. string.format( " -v %s", volume ) end - if point and type(point) == "table" and point.x then - local lat, lon, alt = coord.LOtoLL(point) + if point and type( point ) == "table" and point.x then + local lat, lon, alt = coord.LOtoLL( point ) - lat = STTS.round(lat,4) - lon = STTS.round(lon,4) - alt = math.floor(alt) + lat = STTS.round( lat, 4 ) + lon = STTS.round( lon, 4 ) + alt = math.floor( alt ) - cmd = cmd .. string.format(" -L %s -O %s -A %s",lat,lon,alt) + cmd = cmd .. string.format( " -L %s -O %s -A %s", lat, lon, alt ) end - cmd = cmd ..string.format(" -t \"%s\"",message) + cmd = cmd .. string.format( " -t \"%s\"", message ) - if string.len(cmd) > 255 then - local filename = os.getenv('TMP') .. "\\DCS_STTS-" .. STTS.uuid() .. ".bat" - local script = io.open(filename,"w+") - script:write(cmd .. " && exit" ) - script:close() - cmd = string.format("\"%s\"",filename) - timer.scheduleFunction(os.remove, filename, timer.getTime() + 1) + if string.len( cmd ) > 255 then + local filename = os.getenv( 'TMP' ) .. "\\DCS_STTS-" .. STTS.uuid() .. ".bat" + local script = io.open( filename, "w+" ) + script:write( cmd .. " && exit" ) + script:close() + cmd = string.format( "\"%s\"", filename ) + timer.scheduleFunction( os.remove, filename, timer.getTime() + 1 ) end - if string.len(cmd) > 255 then - env.info("[DCS-STTS] - cmd string too long") - env.info("[DCS-STTS] TextToSpeech Command :\n" .. cmd.."\n") + if string.len( cmd ) > 255 then + env.info( "[DCS-STTS] - cmd string too long" ) + env.info( "[DCS-STTS] TextToSpeech Command :\n" .. cmd .. "\n" ) end - os.execute(cmd) + os.execute( cmd ) - return STTS.getSpeechTime(message,speed,googleTTS) + return STTS.getSpeechTime( message, speed, googleTTS ) end --- Play mp3 function. @@ -235,22 +234,21 @@ end -- @param #string freqs Frequencies, e.g. "305, 256". -- @param #string modulations Modulations, e.g. "AM, FM". -- @param #string volume Volume, e.g. "0.5". -function STTS.PlayMP3(pathToMP3, freqs, modulations, volume, name, coalition, point) +function STTS.PlayMP3( pathToMP3, freqs, modulations, volume, name, coalition, point ) - local cmd = string.format("start \"\" /d \"%s\" /b /min \"%s\" -i \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -v %s -h", - STTS.DIRECTORY, STTS.EXECUTABLE, pathToMP3, freqs or "305", modulations or "AM", coalition or "0", STTS.SRS_PORT, name or "ROBOT", volume or "1") - - if point and type(point) == "table" and point.x then - local lat, lon, alt = coord.LOtoLL(point) + local cmd = string.format( "start \"\" /d \"%s\" /b /min \"%s\" -i \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -v %s -h", STTS.DIRECTORY, STTS.EXECUTABLE, pathToMP3, freqs or "305", modulations or "AM", coalition or "0", STTS.SRS_PORT, name or "ROBOT", volume or "1" ) - lat = STTS.round(lat,4) - lon = STTS.round(lon,4) - alt = math.floor(alt) + if point and type( point ) == "table" and point.x then + local lat, lon, alt = coord.LOtoLL( point ) - cmd = cmd .. string.format(" -L %s -O %s -A %s",lat,lon,alt) - end + lat = STTS.round( lat, 4 ) + lon = STTS.round( lon, 4 ) + alt = math.floor( alt ) - env.info("[DCS-STTS] MP3/OGG Command :\n" .. cmd.."\n") - os.execute(cmd) + cmd = cmd .. string.format( " -L %s -O %s -A %s", lat, lon, alt ) + end -end \ No newline at end of file + env.info( "[DCS-STTS] MP3/OGG Command :\n" .. cmd .. "\n" ) + os.execute( cmd ) + +end From b0818977cfa45b4d08fb8d7db5ffee5e3dc927c5 Mon Sep 17 00:00:00 2001 From: TommyC81 Date: Sat, 4 Dec 2021 21:49:57 +0400 Subject: [PATCH 019/200] Code formatting. (#1651) General code formatting and fixes of minor typos. --- Moose Development/Moose/Tasking/Task_A2A.lua | 244 +++++------ .../Moose/Tasking/Task_A2A_Dispatcher.lua | 293 ++++++------- Moose Development/Moose/Tasking/Task_A2G.lua | 244 +++++------ .../Moose/Tasking/Task_A2G_Dispatcher.lua | 399 +++++++++--------- 4 files changed, 549 insertions(+), 631 deletions(-) diff --git a/Moose Development/Moose/Tasking/Task_A2A.lua b/Moose Development/Moose/Tasking/Task_A2A.lua index 2da9be005..389be7dd7 100644 --- a/Moose Development/Moose/Tasking/Task_A2A.lua +++ b/Moose Development/Moose/Tasking/Task_A2A.lua @@ -1,13 +1,13 @@ --- **Tasking** - The TASK_A2A models tasks for players in Air to Air engagements. --- +-- -- === --- +-- -- ### Author: **FlightControl** --- --- ### Contributions: --- +-- +-- ### Contributions: +-- -- === --- +-- -- @module Tasking.Task_A2A -- @image MOOSE.JPG @@ -35,12 +35,12 @@ do -- TASK_A2A -- * @{#TASK_A2A.SetScoreOnDestroy}(): Set a score when a target in scope of the A2A attack, has been destroyed. -- * @{#TASK_A2A.SetScoreOnSuccess}(): Set a score when all the targets in scope of the A2A attack, have been destroyed. -- * @{#TASK_A2A.SetPenaltyOnFailed}(): Set a penalty when the A2A attack has failed. - -- + -- -- @field #TASK_A2A TASK_A2A = { - ClassName = "TASK_A2A", + ClassName = "TASK_A2A" } - + --- Instantiates a new TASK_A2A. -- @param #TASK_A2A self -- @param Tasking.Mission#MISSION Mission @@ -54,51 +54,49 @@ do -- TASK_A2A function TASK_A2A:New( Mission, SetAttack, TaskName, TargetSetUnit, TaskType, TaskBriefing ) local self = BASE:Inherit( self, TASK:New( Mission, SetAttack, TaskName, TaskType, TaskBriefing ) ) -- Tasking.Task#TASK_A2A 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: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: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( "Accounted", "DestroyedAll", "Accounted" ) --- Fsm:AddTransition( "Accounted", "Success", "Success" ) + + -- Fsm:AddTransition( "Accounted", "DestroyedAll", "Accounted" ) + -- Fsm:AddTransition( "Accounted", "Success", "Success" ) Fsm:AddTransition( "Rejected", "Reject", "Aborted" ) Fsm:AddTransition( "Failed", "Fail", "Failed" ) - - ---- @param #FSM_PROCESS self + -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param #TASK_CARGO Task function Fsm:OnLeaveAssigned( TaskUnit, Task ) self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - + self:SelectAction() end - - --- Test + + --- Test -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task_A2A#TASK_A2A Task function Fsm:onafterRouteToRendezVous( TaskUnit, Task ) self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) -- Determine the first Unit from the self.RendezVousSetUnit - + if Task:GetRendezVousZone( TaskUnit ) then self:__RouteToRendezVousZone( 0.1 ) else @@ -110,36 +108,36 @@ do -- TASK_A2A end end - --- Test + --- Test -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task#TASK_A2A Task function Fsm:OnAfterArriveAtRendezVous( TaskUnit, Task ) self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) -- Determine the first Unit from the self.TargetSetUnit - - self:__Engage( 0.1 ) + + self:__Engage( 0.1 ) end - - --- Test + + --- Test -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task#TASK_A2A Task function Fsm:onafterEngage( TaskUnit, Task ) self:F( { self } ) self:__Account( 0.1 ) - self:__RouteToTarget(0.1 ) + self:__RouteToTarget( 0.1 ) self:__RouteToTargets( -10 ) end - - --- Test + + --- Test -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task_A2A#TASK_A2A Task function Fsm:onafterRouteToTarget( TaskUnit, Task ) self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) -- Determine the first Unit from the self.TargetSetUnit - + if Task:GetTargetZone( TaskUnit ) then self:__RouteToTargetZone( 0.1 ) else @@ -152,8 +150,8 @@ do -- TASK_A2A self:__RouteToTargetPoint( 0.1 ) end end - - --- Test + + --- Test -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task_A2A#TASK_A2A Task @@ -165,20 +163,18 @@ do -- TASK_A2A end self:__RouteToTargets( -10 ) end - + return self - + end - + --- @param #TASK_A2A self -- @param Core.Set#SET_UNIT TargetSetUnit The set of targets. function TASK_A2A:SetTargetSetUnit( TargetSetUnit ) - + self.TargetSetUnit = TargetSetUnit end - - --- @param #TASK_A2A self function TASK_A2A:GetPlannedMenuText() return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" @@ -188,34 +184,32 @@ do -- TASK_A2A -- @param Core.Point#COORDINATE RendezVousCoordinate The Coordinate object referencing to the 2D point where the RendezVous point is located on the map. -- @param #number RendezVousRange The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point. -- @param Wrapper.Unit#UNIT TaskUnit - function TASK_A2A:SetRendezVousCoordinate( RendezVousCoordinate, RendezVousRange, TaskUnit ) - + function TASK_A2A:SetRendezVousCoordinate( RendezVousCoordinate, RendezVousRange, TaskUnit ) + local ProcessUnit = self:GetUnitProcess( TaskUnit ) - + local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT ActRouteRendezVous:SetCoordinate( RendezVousCoordinate ) ActRouteRendezVous:SetRange( RendezVousRange ) end - + --- @param #TASK_A2A self -- @param Wrapper.Unit#UNIT TaskUnit -- @return Core.Point#COORDINATE The Coordinate object referencing to the 2D point where the RendezVous point is located on the map. -- @return #number The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point. function TASK_A2A:GetRendezVousCoordinate( TaskUnit ) - + local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT return ActRouteRendezVous:GetCoordinate(), ActRouteRendezVous:GetRange() end - - - + --- @param #TASK_A2A self -- @param Core.Zone#ZONE_BASE RendezVousZone The Zone object where the RendezVous is located on the map. -- @param Wrapper.Unit#UNIT TaskUnit function TASK_A2A:SetRendezVousZone( RendezVousZone, TaskUnit ) - + local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE @@ -232,18 +226,17 @@ do -- TASK_A2A local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE return ActRouteRendezVous:GetZone() end - + --- @param #TASK_A2A self -- @param Core.Point#COORDINATE TargetCoordinate The Coordinate object where the Target is located on the map. -- @param Wrapper.Unit#UNIT TaskUnit function TASK_A2A:SetTargetCoordinate( TargetCoordinate, TaskUnit ) - + local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT ActRouteTarget:SetCoordinate( TargetCoordinate ) end - --- @param #TASK_A2A self -- @param Wrapper.Unit#UNIT TaskUnit @@ -256,18 +249,16 @@ do -- TASK_A2A return ActRouteTarget:GetCoordinate() end - --- @param #TASK_A2A self -- @param Core.Zone#ZONE_BASE TargetZone The Zone object where the Target is located on the map. -- @param Wrapper.Unit#UNIT TaskUnit function TASK_A2A:SetTargetZone( TargetZone, Altitude, Heading, TaskUnit ) - + local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE ActRouteTarget:SetZone( TargetZone, Altitude, Heading ) end - --- @param #TASK_A2A self -- @param Wrapper.Unit#UNIT TaskUnit @@ -281,45 +272,43 @@ do -- TASK_A2A end function TASK_A2A:SetGoalTotal() - + self.GoalTotal = self.TargetSetUnit:Count() end function TASK_A2A:GetGoalTotal() - + return self.GoalTotal end --- Return the relative distance to the target vicinity from the player, in order to sort the targets in the reports per distance from the threats. -- @param #TASK_A2A self function TASK_A2A:ReportOrder( ReportGroup ) - self:UpdateTaskInfo( self.DetectedItem ) - + self:UpdateTaskInfo( self.DetectedItem ) + local Coordinate = self.TaskInfo:GetData( "Coordinate" ) local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate ) - + return Distance end - - + --- This method checks every 10 seconds if the goal has been reached of the task. -- @param #TASK_A2A self function TASK_A2A:onafterGoal( TaskUnit, From, Event, To ) local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT - + if TargetSetUnit:Count() == 0 then self:Success() end - + self:__Goal( -10 ) end - --- @param #TASK_A2A self 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() + local TargetCoordinate = DetectedItem and self.Detection:GetDetectedItemCoordinate( DetectedItem ) or self.TargetSetUnit:GetFirst():GetCoordinate() self.TaskInfo:AddTaskName( 0, "MSOD" ) self.TaskInfo:AddCoordinate( TargetCoordinate, 1, "SOD" ) @@ -343,12 +332,12 @@ do -- TASK_A2A end end self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true ) - self.TaskInfo:AddTargets( DetectedItemsCount, ReportTypes:Text( ", " ), 20, "D", 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 ) + self.TaskInfo:AddTargets( DetectedItemsCount, DetectedItemsTypes, 20, "D", true ) end end end @@ -359,8 +348,8 @@ do -- TASK_A2A -- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter The command center. -- @param Wrapper.Group#GROUP TaskGroup The player group. function TASK_A2A:GetAutoAssignPriority( AutoAssignMethod, CommandCenter, TaskGroup ) - - if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then + + if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then return math.random( 1, 9 ) elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Distance then local Coordinate = self.TaskInfo:GetData( "Coordinate" ) @@ -373,8 +362,7 @@ do -- TASK_A2A return 0 end -end - +end do -- TASK_A2A_INTERCEPT @@ -384,44 +372,39 @@ do -- TASK_A2A_INTERCEPT -- @extends Tasking.Task#TASK --- Defines an intercept task for a human player to be executed. - -- When enemy planes need to be intercepted by human players, use this task type to urgen the players to get out there! - -- - -- The TASK_A2A_INTERCEPT is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create intercept tasks + -- When enemy planes need to be intercepted by human players, use this task type to urge the players to get out there! + -- + -- The TASK_A2A_INTERCEPT is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create intercept tasks -- based on detected airborne enemy targets intruding friendly airspace. - -- + -- -- The task is defined for a @{Tasking.Mission#MISSION}, where a friendly @{Core.Set#SET_GROUP} consisting of GROUPs with one human players each, is intercepting the targets. -- The task is given a name and a briefing, that is used in the menu structure and in the reporting. - -- + -- -- @field #TASK_A2A_INTERCEPT TASK_A2A_INTERCEPT = { - ClassName = "TASK_A2A_INTERCEPT", + ClassName = "TASK_A2A_INTERCEPT" } - - --- Instantiates a new TASK_A2A_INTERCEPT. -- @param #TASK_A2A_INTERCEPT self -- @param Tasking.Mission#MISSION Mission -- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. -- @param #string TaskName The name of the Task. - -- @param Core.Set#SET_UNIT TargetSetUnit + -- @param Core.Set#SET_UNIT TargetSetUnit -- @param #string TaskBriefing The briefing of the task. -- @return #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 ) ) -- #TASK_A2A_INTERCEPT self:F() - + Mission:AddTask( self ) - - self:SetBriefing( - TaskBriefing or - "Intercept incoming intruders.\n" - ) + + self:SetBriefing( TaskBriefing or "Intercept incoming intruders.\n" ) return self end - - --- Set a score when a target in scope of the A2A attack, has been destroyed . + + --- Set a score when a target in scope of the A2A attack, has been destroyed. -- @param #TASK_A2A_INTERCEPT self -- @param #string PlayerName The name of the player. -- @param #number Score The score in points to be granted when task process has been achieved. @@ -433,7 +416,7 @@ do -- TASK_A2A_INTERCEPT local ProcessUnit = self:GetUnitProcess( TaskUnit ) ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has intercepted a target.", Score ) - + return self end @@ -449,7 +432,7 @@ do -- TASK_A2A_INTERCEPT local ProcessUnit = self:GetUnitProcess( TaskUnit ) ProcessUnit:AddScore( "Success", "All targets have been successfully intercepted!", Score ) - + return self end @@ -465,14 +448,12 @@ do -- TASK_A2A_INTERCEPT local ProcessUnit = self:GetUnitProcess( TaskUnit ) ProcessUnit:AddScore( "Failed", "The intercept has failed!", Penalty ) - + return self end - end - do -- TASK_A2A_SWEEP --- The TASK_A2A_SWEEP class @@ -484,20 +465,18 @@ do -- TASK_A2A_SWEEP -- A sweep task needs to be given when targets were detected but somehow the detection was lost. -- Most likely, these enemy planes are hidden in the mountains or are flying under radar. -- These enemy planes need to be sweeped by human players, and use this task type to urge the players to get out there and find those enemy fighters. - -- - -- The TASK_A2A_SWEEP is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create sweep tasks + -- + -- The TASK_A2A_SWEEP is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create sweep tasks -- based on detected airborne enemy targets intruding friendly airspace, for which the detection has been lost for more than 60 seconds. - -- + -- -- The task is defined for a @{Tasking.Mission#MISSION}, where a friendly @{Core.Set#SET_GROUP} consisting of GROUPs with one human players each, is sweeping the targets. -- The task is given a name and a briefing, that is used in the menu structure and in the reporting. - -- + -- -- @field #TASK_A2A_SWEEP TASK_A2A_SWEEP = { - ClassName = "TASK_A2A_SWEEP", + ClassName = "TASK_A2A_SWEEP" } - - --- Instantiates a new TASK_A2A_SWEEP. -- @param #TASK_A2A_SWEEP self -- @param Tasking.Mission#MISSION Mission @@ -509,29 +488,26 @@ do -- 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 ) ) -- #TASK_A2A_SWEEP 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" - ) + + self:SetBriefing( TaskBriefing or "Perform a fighter sweep. Incoming intruders were detected and could be hiding at the location.\n" ) return self - end + end --- @param #TASK_A2A_SWEEP self function TASK_A2A_SWEEP:onafterGoal( TaskUnit, From, Event, To ) local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT - + if TargetSetUnit:Count() == 0 then self:Success() end - + self:__Goal( -10 ) end - --- Set a score when a target in scope of the A2A attack, has been destroyed . + --- Set a score when a target in scope of the A2A attack, has been destroyed. -- @param #TASK_A2A_SWEEP self -- @param #string PlayerName The name of the player. -- @param #number Score The score in points to be granted when task process has been achieved. @@ -543,7 +519,7 @@ do -- TASK_A2A_SWEEP local ProcessUnit = self:GetUnitProcess( TaskUnit ) ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has sweeped a target.", Score ) - + return self end @@ -559,7 +535,7 @@ do -- TASK_A2A_SWEEP local ProcessUnit = self:GetUnitProcess( TaskUnit ) ProcessUnit:AddScore( "Success", "All targets have been successfully sweeped!", Score ) - + return self end @@ -575,13 +551,12 @@ do -- TASK_A2A_SWEEP local ProcessUnit = self:GetUnitProcess( TaskUnit ) ProcessUnit:AddScore( "Failed", "The sweep has failed!", Penalty ) - + return self end end - do -- TASK_A2A_ENGAGE --- The TASK_A2A_ENGAGE class @@ -591,42 +566,37 @@ do -- TASK_A2A_ENGAGE --- Defines an engage task for a human player to be executed. -- When enemy planes are close to human players, use this task type is used urge the players to get out there! - -- - -- The TASK_A2A_ENGAGE is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create engage tasks + -- + -- The TASK_A2A_ENGAGE is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create engage tasks -- based on detected airborne enemy targets intruding friendly airspace. - -- + -- -- The task is defined for a @{Tasking.Mission#MISSION}, where a friendly @{Core.Set#SET_GROUP} consisting of GROUPs with one human players each, is engaging the targets. -- The task is given a name and a briefing, that is used in the menu structure and in the reporting. - -- + -- -- @field #TASK_A2A_ENGAGE TASK_A2A_ENGAGE = { - ClassName = "TASK_A2A_ENGAGE", + ClassName = "TASK_A2A_ENGAGE" } - - --- Instantiates a new TASK_A2A_ENGAGE. -- @param #TASK_A2A_ENGAGE self -- @param Tasking.Mission#MISSION Mission -- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. -- @param #string TaskName The name of the Task. - -- @param Core.Set#SET_UNIT TargetSetUnit + -- @param Core.Set#SET_UNIT TargetSetUnit -- @param #string TaskBriefing The briefing of the task. -- @return #TASK_A2A_ENGAGE self function TASK_A2A_ENGAGE:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing ) local self = BASE:Inherit( self, TASK_A2A:New( Mission, SetGroup, TaskName, TargetSetUnit, "ENGAGE", TaskBriefing ) ) -- #TASK_A2A_ENGAGE self:F() - + Mission:AddTask( self ) - - self:SetBriefing( - TaskBriefing or - "Bogeys are nearby! Players close by are ordered to ENGAGE the intruders!\n" - ) + + self:SetBriefing( TaskBriefing or "Bogeys are nearby! Players close by are ordered to ENGAGE the intruders!\n" ) return self - end - + end + --- Set a score when a target in scope of the A2A attack, has been destroyed . -- @param #TASK_A2A_ENGAGE self -- @param #string PlayerName The name of the player. @@ -639,7 +609,7 @@ do -- TASK_A2A_ENGAGE local ProcessUnit = self:GetUnitProcess( TaskUnit ) ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has engaged and destroyed a target.", Score ) - + return self end @@ -655,7 +625,7 @@ do -- TASK_A2A_ENGAGE local ProcessUnit = self:GetUnitProcess( TaskUnit ) ProcessUnit:AddScore( "Success", "All targets have been successfully engaged!", Score ) - + return self end @@ -671,7 +641,7 @@ do -- TASK_A2A_ENGAGE local ProcessUnit = self:GetUnitProcess( TaskUnit ) ProcessUnit:AddScore( "Failed", "The target engagement has failed!", Penalty ) - + return self end diff --git a/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua b/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua index 76c7225a7..d3b67cbff 100644 --- a/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua +++ b/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua @@ -1,7 +1,7 @@ --- **Tasking** - Dynamically allocates A2A tasks to human players, based on detected airborne targets through an EWR network. --- +-- -- **Features:** --- +-- -- * Dynamically assign tasks to human players based on detected targets. -- * Dynamically change the tasks as the tactical situation evolves during the mission. -- * Dynamically assign (CAP) Control Air Patrols tasks for human players to perform CAP. @@ -11,15 +11,15 @@ -- * Define different ranges to engage upon intruders. -- * Keep task achievements. -- * Score task achievements. --- +-- -- === --- +-- -- ### Author: **FlightControl** --- --- ### Contributions: --- +-- +-- ### Contributions: +-- -- === --- +-- -- @module Tasking.Task_A2A_Dispatcher -- @image Task_A2A_Dispatcher.JPG @@ -30,72 +30,72 @@ do -- TASK_A2A_DISPATCHER -- @extends Tasking.DetectionManager#DETECTION_MANAGER --- Orchestrates the dynamic dispatching of tasks upon groups of detected units determined a @{Set} of EWR installation groups. - -- + -- -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia3.JPG) - -- + -- -- The EWR will detect units, will group them, and will dispatch @{Task}s to groups. Depending on the type of target detected, different tasks will be dispatched. -- Find a summary below describing for which situation a task type is created: - -- + -- -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia9.JPG) - -- + -- -- * **INTERCEPT Task**: Is created when the target is known, is detected and within a danger zone, and there is no friendly airborne in range. -- * **SWEEP Task**: Is created when the target is unknown, was detected and the last position is only known, and within a danger zone, and there is no friendly airborne in range. -- * **ENGAGE Task**: Is created when the target is known, is detected and within a danger zone, and there is a friendly airborne in range, that will receive this task. - -- + -- -- ## 1. TASK\_A2A\_DISPATCHER constructor: - -- + -- -- The @{#TASK_A2A_DISPATCHER.New}() method creates a new TASK\_A2A\_DISPATCHER instance. - -- + -- -- ### 1.1. Define or set the **Mission**: - -- + -- -- Tasking is executed to accomplish missions. Therefore, a MISSION object needs to be given as the first parameter. - -- + -- -- local HQ = GROUP:FindByName( "HQ", "Bravo" ) -- local CommandCenter = COMMANDCENTER:New( HQ, "Lima" ) -- local Mission = MISSION:New( CommandCenter, "A2A Mission", "High", "Watch the air enemy units being detected.", coalition.side.RED ) - -- + -- -- Missions are governed by COMMANDCENTERS, so, ensure you have a COMMANDCENTER object installed and setup within your mission. -- Create the MISSION object, and hook it under the command center. - -- + -- -- ### 1.2. Build a set of the groups seated by human players: - -- + -- -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia6.JPG) - -- + -- -- A set or collection of the groups wherein human players can be seated, these can be clients or units that can be joined as a slot or jumping into. - -- + -- -- local AttackGroups = SET_GROUP:New():FilterCoalitions( "red" ):FilterPrefixes( "Defender" ):FilterStart() - -- + -- -- The set is built using the SET_GROUP class. Apply any filter criteria to identify the correct groups for your mission. -- Only these slots or units will be able to execute the mission and will receive tasks for this mission, once available. - -- + -- -- ### 1.3. Define the **EWR network**: - -- + -- -- As part of the TASK\_A2A\_DISPATCHER constructor, an EWR network must be given as the third parameter. -- An EWR network, or, Early Warning Radar network, is used to early detect potential airborne targets and to understand the position of patrolling targets of the enemy. - -- + -- -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia5.JPG) - -- + -- -- Typically EWR networks are setup using 55G6 EWR, 1L13 EWR, Hawk sr and Patriot str ground based radar units. -- These radars have different ranges and 55G6 EWR and 1L13 EWR radars are Eastern Bloc units (eg Russia, Ukraine, Georgia) while the Hawk and Patriot radars are Western (eg US). -- Additionally, ANY other radar capable unit can be part of the EWR network! Also AWACS airborne units, planes, helicopters can help to detect targets, as long as they have radar. -- The position of these units is very important as they need to provide enough coverage -- to pick up enemy aircraft as they approach so that CAP and GCI flights can be tasked to intercept them. - -- + -- -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia7.JPG) - -- - -- Additionally in a hot war situation where the border is no longer respected the placement of radars has a big effect on how fast the war escalates. - -- For example if they are a long way forward and can detect enemy planes on the ground and taking off - -- they will start to vector CAP and GCI flights to attack them straight away which will immediately draw a response from the other coalition. - -- Having the radars further back will mean a slower escalation because fewer targets will be detected and - -- therefore less CAP and GCI flights will spawn and this will tend to make just the border area active rather than a melee over the whole map. - -- It all depends on what the desired effect is. -- + -- Additionally in a hot war situation where the border is no longer respected the placement of radars has a big effect on how fast the war escalates. + -- For example if they are a long way forward and can detect enemy planes on the ground and taking off + -- they will start to vector CAP and GCI flights to attack them straight away which will immediately draw a response from the other coalition. + -- Having the radars further back will mean a slower escalation because fewer targets will be detected and + -- therefore less CAP and GCI flights will spawn and this will tend to make just the border area active rather than a melee over the whole map. + -- It all depends on what the desired effect is. + -- -- EWR networks are **dynamically constructed**, that is, they form part of the @{Functional.Detection#DETECTION_BASE} object that is given as the input parameter of the TASK\_A2A\_DISPATCHER class. - -- By defining in a **smart way the names or name prefixes of the groups** with EWR capable units, these groups will be **automatically added or deleted** from the EWR network, + -- By defining in a **smart way the names or name prefixes of the groups** with EWR capable units, these groups will be **automatically added or deleted** from the EWR network, -- increasing or decreasing the radar coverage of the Early Warning System. - -- + -- -- See the following example to setup an EWR network containing EWR stations and AWACS. - -- + -- -- local EWRSet = SET_GROUP:New():FilterPrefixes( "EWR" ):FilterCoalitions("red"):FilterStart() -- -- local EWRDetection = DETECTION_AREAS:New( EWRSet, 6000 ) @@ -104,50 +104,50 @@ do -- TASK_A2A_DISPATCHER -- -- -- Setup the A2A dispatcher, and initialize it. -- A2ADispatcher = TASK_A2A_DISPATCHER:New( Mission, AttackGroups, EWRDetection ) - -- + -- -- The above example creates a SET_GROUP instance, and stores this in the variable (object) **EWRSet**. -- **EWRSet** is then being configured to filter all active groups with a group name starting with **EWR** to be included in the Set. -- **EWRSet** is then being ordered to start the dynamic filtering. Note that any destroy or new spawn of a group with the above names will be removed or added to the Set. - -- Then a new **EWRDetection** object is created from the class DETECTION_AREAS. A grouping radius of 6000 is choosen, which is 6km. + -- Then a new **EWRDetection** object is created from the class DETECTION_AREAS. A grouping radius of 6000 is chosen, which is 6 km. -- The **EWRDetection** object is then passed to the @{#TASK_A2A_DISPATCHER.New}() method to indicate the EWR network configuration and setup the A2A tasking and detection mechanism. - -- + -- -- ### 2. Define the detected **target grouping radius**: - -- + -- -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia8.JPG) - -- + -- -- The target grouping radius is a property of the Detection object, that was passed to the AI\_A2A\_DISPATCHER object, but can be changed. -- The grouping radius should not be too small, but also depends on the types of planes and the era of the simulation. - -- Fast planes like in the 80s, need a larger radius than WWII planes. + -- Fast planes like in the 80s, need a larger radius than WWII planes. -- Typically I suggest to use 30000 for new generation planes and 10000 for older era aircraft. - -- + -- -- Note that detected targets are constantly re-grouped, that is, when certain detected aircraft are moving further than the group radius, then these aircraft will become a separate -- group being detected. This may result in additional GCI being started by the dispatcher! So don't make this value too small! - -- + -- -- ## 3. Set the **Engage radius**: - -- + -- -- Define the radius to engage any target by airborne friendlies, which are executing cap or returning from an intercept mission. - -- + -- -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia11.JPG) - -- - -- So, if there is a target area detected and reported, - -- then any friendlies that are airborne near this target area, + -- + -- So, if there is a target area detected and reported, + -- then any friendlies that are airborne near this target area, -- will be commanded to (re-)engage that target when available (if no other tasks were commanded). - -- For example, if 100000 is given as a value, then any friendly that is airborne within 100km from the detected target, + -- For example, if 100000 is given as a value, then any friendly that is airborne within 100km from the detected target, -- will be considered to receive the command to engage that target area. -- You need to evaluate the value of this parameter carefully. -- If too small, more intercept missions may be triggered upon detected target areas. -- If too large, any airborne cap may not be able to reach the detected target area in time, because it is too far. - -- + -- -- ## 4. Set **Scoring** and **Messages**: - -- + -- -- The TASK\_A2A\_DISPATCHER is a state machine. It triggers the event Assign when a new player joins a @{Task} dispatched by the TASK\_A2A\_DISPATCHER. -- An _event handler_ can be defined to catch the **Assign** event, and add **additional processing** to set _scoring_ and to _define messages_, -- when the player reaches certain achievements in the task. - -- + -- -- The prototype to handle the **Assign** event needs to be developed as follows: - -- + -- -- TaskDispatcher = TASK_A2A_DISPATCHER:New( ... ) - -- + -- -- --- @param #TaskDispatcher self -- -- @param #string From Contains the name of the state from where the Event was triggered. -- -- @param #string Event Contains the name of the event that was triggered. In this case Assign. @@ -160,32 +160,31 @@ do -- TASK_A2A_DISPATCHER -- Task:SetScoreOnSuccess( PlayerName, 200, TaskUnit ) -- Task:SetScoreOnFail( PlayerName, -100, TaskUnit ) -- end - -- + -- -- The **OnAfterAssign** method (function) is added to the TaskDispatcher object. -- This method will be called when a new player joins a unit in the set of groups in scope of the dispatcher. -- So, this method will be called only **ONCE** when a player joins a unit in scope of the task. - -- + -- -- The TASK class implements various methods to additional **set scoring** for player achievements: - -- + -- -- * @{Tasking.Task#TASK.SetScoreOnProgress}() will add additional scores when a player achieves **Progress** while executing the task. -- Examples of **task progress** can be destroying units, arriving at zones etc. - -- - -- * @{Tasking.Task#TASK.SetScoreOnSuccess}() will add additional scores when the task goes into **Success** state. + -- + -- * @{Tasking.Task#TASK.SetScoreOnSuccess}() will add additional scores when the task goes into **Success** state. -- This means the **task has been successfully completed**. - -- - -- * @{Tasking.Task#TASK.SetScoreOnSuccess}() will add additional (negative) scores when the task goes into **Failed** state. + -- + -- * @{Tasking.Task#TASK.SetScoreOnSuccess}() will add additional (negative) scores when the task goes into **Failed** state. -- This means the **task has not been successfully completed**, and the scores must be given with a negative value! - -- + -- -- @field #TASK_A2A_DISPATCHER TASK_A2A_DISPATCHER = { ClassName = "TASK_A2A_DISPATCHER", Mission = nil, Detection = nil, Tasks = {}, - SweepZones = {}, + SweepZones = {} } - - + --- TASK_A2A_DISPATCHER constructor. -- @param #TASK_A2A_DISPATCHER self -- @param Tasking.Mission#MISSION Mission The mission for which the task dispatching is done. @@ -193,22 +192,21 @@ do -- TASK_A2A_DISPATCHER -- @param Functional.Detection#DETECTION_BASE Detection The detection results that are used to dynamically assign new tasks to human players. -- @return #TASK_A2A_DISPATCHER self function TASK_A2A_DISPATCHER:New( Mission, SetGroup, Detection ) - + -- Inherits from DETECTION_MANAGER local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #TASK_A2A_DISPATCHER - + self.Detection = Detection self.Mission = Mission self.FlashNewTask = false - + -- TODO: Check detection through radar. self.Detection:FilterCategories( Unit.Category.AIRPLANE, Unit.Category.HELICOPTER ) self.Detection:InitDetectRadar( true ) self.Detection:SetRefreshTimeInterval( 30 ) - + self:AddTransition( "Started", "Assign", "Started" ) - --- OnAfter Transition Handler for Event Assign. -- @function [parent=#TASK_A2A_DISPATCHER] OnAfterAssign -- @param #TASK_A2A_DISPATCHER self @@ -220,14 +218,13 @@ do -- TASK_A2A_DISPATCHER -- @param #string PlayerName self:__Start( 5 ) - + return self end - --- Define the radius to when an ENGAGE task will be generated for any nearby by airborne friendlies, which are executing cap or returning from an intercept mission. - -- So, if there is a target area detected and reported, - -- then any friendlies that are airborne near this target area, + -- So, if there is a target area detected and reported, + -- then any friendlies that are airborne near this target area, -- will be commanded to (re-)engage that target when available (if no other tasks were commanded). -- An ENGAGE task will be created for those pilots. -- For example, if 100000 is given as a value, then any friendly that is airborne within 100km from the detected target, @@ -239,27 +236,27 @@ do -- TASK_A2A_DISPATCHER -- @param #number EngageRadius (Optional, Default = 100000) The radius to report friendlies near the target. -- @return #TASK_A2A_DISPATCHER -- @usage - -- + -- -- -- Set 50km as the radius to engage any target by airborne friendlies. -- TaskA2ADispatcher:SetEngageRadius( 50000 ) - -- + -- -- -- Set 100km as the radius to engage any target by airborne friendlies. -- TaskA2ADispatcher:SetEngageRadius() -- 100000 is the default value. - -- + -- function TASK_A2A_DISPATCHER:SetEngageRadius( EngageRadius ) self.Detection:SetFriendliesRange( EngageRadius or 100000 ) - + return self end - + --- Set flashing player messages on or off -- @param #TASK_A2A_DISPATCHER self -- @param #boolean onoff Set messages on (true) or off (false) function TASK_A2A_DISPATCHER:SetSendMessages( onoff ) - self.FlashNewTask = onoff + self.FlashNewTask = onoff end - + --- Creates an INTERCEPT task when there are targets for it. -- @param #TASK_A2A_DISPATCHER self -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem @@ -267,26 +264,25 @@ do -- TASK_A2A_DISPATCHER -- @return #nil If there are no targets to be set. function TASK_A2A_DISPATCHER:EvaluateINTERCEPT( DetectedItem ) self:F( { DetectedItem.ItemID } ) - + local DetectedSet = DetectedItem.Set local DetectedZone = DetectedItem.Zone -- Check if there is at least one UNIT in the DetectedSet is visible. - + if DetectedItem.IsDetected == true then -- Here we're doing something advanced... We're copying the DetectedSet. local TargetSetUnit = SET_UNIT:New() TargetSetUnit:SetDatabase( DetectedSet ) TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. - + return TargetSetUnit end - + return nil end - --- Creates an SWEEP task when there are targets for it. -- @param #TASK_A2A_DISPATCHER self -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem @@ -294,25 +290,23 @@ do -- TASK_A2A_DISPATCHER -- @return #nil If there are no targets to be set. function TASK_A2A_DISPATCHER:EvaluateSWEEP( DetectedItem ) self:F( { DetectedItem.ItemID } ) - + local DetectedSet = DetectedItem.Set local DetectedZone = DetectedItem.Zone - if DetectedItem.IsDetected == false then -- Here we're doing something advanced... We're copying the DetectedSet. local TargetSetUnit = SET_UNIT:New() TargetSetUnit:SetDatabase( DetectedSet ) TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. - + return TargetSetUnit end - + return nil end - --- Creates an ENGAGE task when there are human friendlies airborne near the targets. -- @param #TASK_A2A_DISPATCHER self -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem @@ -320,13 +314,12 @@ do -- TASK_A2A_DISPATCHER -- @return #nil If there are no targets to be set. function TASK_A2A_DISPATCHER:EvaluateENGAGE( DetectedItem ) self:F( { DetectedItem.ItemID } ) - + local DetectedSet = DetectedItem.Set local DetectedZone = DetectedItem.Zone local PlayersCount, PlayersReport = self:GetPlayerFriendliesNearBy( DetectedItem ) - -- Only allow ENGAGE when there are Players near the zone, and when the Area has detected items since the last run in a 60 seconds time zone. if PlayersCount > 0 and DetectedItem.IsDetected == true then @@ -334,16 +327,13 @@ do -- TASK_A2A_DISPATCHER local TargetSetUnit = SET_UNIT:New() TargetSetUnit:SetDatabase( DetectedSet ) TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. - + return TargetSetUnit end - + return nil end - - - --- Evaluates the removal of the Task from the Mission. -- Can only occur when the DetectedItem is Changed AND the state of the Task is "Planned". -- @param #TASK_A2A_DISPATCHER self @@ -354,24 +344,24 @@ do -- TASK_A2A_DISPATCHER -- @param #boolean DetectedItemChange -- @return Tasking.Task#TASK 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 @@ -380,7 +370,7 @@ do -- TASK_A2A_DISPATCHER Remove = true end end - + if TaskType == "SWEEP" then if DetectedItem.IsDetected == true then Remove = true @@ -388,18 +378,18 @@ do -- TASK_A2A_DISPATCHER end local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT - --DetectedSet:Flush( self ) - --self:F( { DetectedSetCount = DetectedSet:Count() } ) + -- DetectedSet:Flush( self ) + -- self:F( { DetectedSetCount = DetectedSet:Count() } ) if DetectedSet:Count() == 0 then Remove = true end - + if DetectedItemChanged == true or Remove then Task = self:RemoveTask( DetectedItemIndex ) end end end - + return Task end @@ -408,10 +398,10 @@ do -- TASK_A2A_DISPATCHER -- @param DetectedItem -- @return #number, Core.CommandCenter#REPORT function TASK_A2A_DISPATCHER:GetFriendliesNearBy( DetectedItem ) - + local DetectedSet = DetectedItem.Set local FriendlyUnitsNearBy = self.Detection:GetFriendliesNearBy( DetectedItem, Unit.Category.AIRPLANE ) - + local FriendlyTypes = {} local FriendliesCount = 0 @@ -423,27 +413,26 @@ do -- TASK_A2A_DISPATCHER local FriendlyUnitThreatLevel = FriendlyUnit:GetThreatLevel() FriendliesCount = FriendliesCount + 1 local FriendlyType = FriendlyUnit:GetTypeName() - FriendlyTypes[FriendlyType] = FriendlyTypes[FriendlyType] and ( FriendlyTypes[FriendlyType] + 1 ) or 1 + FriendlyTypes[FriendlyType] = FriendlyTypes[FriendlyType] and (FriendlyTypes[FriendlyType] + 1) or 1 if DetectedTreatLevel < FriendlyUnitThreatLevel + 2 then end end end - + end - --self:F( { FriendliesCount = FriendliesCount } ) - + -- self:F( { FriendliesCount = FriendliesCount } ) + local FriendlyTypesReport = REPORT:New() - + if FriendliesCount > 0 then for FriendlyType, FriendlyTypeCount in pairs( FriendlyTypes ) do - FriendlyTypesReport:Add( string.format("%d of %s", FriendlyTypeCount, FriendlyType ) ) + FriendlyTypesReport:Add( string.format( "%d of %s", FriendlyTypeCount, FriendlyType ) ) end else FriendlyTypesReport:Add( "-" ) end - - + return FriendliesCount, FriendlyTypesReport end @@ -452,10 +441,10 @@ do -- TASK_A2A_DISPATCHER -- @param DetectedItem -- @return #number, Core.CommandCenter#REPORT function TASK_A2A_DISPATCHER:GetPlayerFriendliesNearBy( DetectedItem ) - + local DetectedSet = DetectedItem.Set local PlayersNearBy = self.Detection:GetPlayersNearBy( DetectedItem ) - + local PlayerTypes = {} local PlayersCount = 0 @@ -464,7 +453,7 @@ do -- TASK_A2A_DISPATCHER for PlayerUnitName, PlayerUnitData in pairs( PlayersNearBy ) do local PlayerUnit = PlayerUnitData -- Wrapper.Unit#UNIT local PlayerName = PlayerUnit:GetPlayerName() - --self:F( { PlayerName = PlayerName, PlayerUnit = PlayerUnit } ) + -- self:F( { PlayerName = PlayerName, PlayerUnit = PlayerUnit } ) if PlayerUnit:IsAirPlane() and PlayerName ~= nil then local FriendlyUnitThreatLevel = PlayerUnit:GetThreatLevel() PlayersCount = PlayersCount + 1 @@ -474,20 +463,19 @@ do -- TASK_A2A_DISPATCHER end end end - + end local PlayerTypesReport = REPORT:New() - + if PlayersCount > 0 then for PlayerName, PlayerType in pairs( PlayerTypes ) do - PlayerTypesReport:Add( string.format('"%s" in %s', PlayerName, PlayerType ) ) + PlayerTypesReport:Add( string.format( '"%s" in %s', PlayerName, PlayerType ) ) end else PlayerTypesReport:Add( "-" ) end - - + return PlayersCount, PlayerTypesReport end @@ -496,24 +484,23 @@ do -- TASK_A2A_DISPATCHER self.Tasks[TaskIndex] = nil end - --- Assigns tasks in relation to the detected items to the @{Core.Set#SET_GROUP}. -- @param #TASK_A2A_DISPATCHER self -- @param Functional.Detection#DETECTION_BASE Detection The detection created by the @{Functional.Detection#DETECTION_BASE} derived object. -- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop. 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() - + -- Checking the task queue for the dispatcher, and removing any obsolete task! for TaskIndex, TaskData in pairs( self.Tasks ) do local Task = TaskData -- Tasking.Task#TASK @@ -531,18 +518,18 @@ do -- TASK_A2A_DISPATCHER -- Now that all obsolete tasks are removed, loop through the detected targets. for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do - + local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT local DetectedCount = DetectedSet:Count() local DetectedZone = DetectedItem.Zone - --self:F( { "Targets in DetectedItem", DetectedItem.ItemID, DetectedSet:Count(), tostring( DetectedItem ) } ) - --DetectedSet:Flush( self ) - + -- self:F( { "Targets in DetectedItem", DetectedItem.ItemID, DetectedSet:Count(), tostring( DetectedItem ) } ) + -- DetectedSet:Flush( self ) + 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 ) -- Task will be removed if it is planned and changed. @@ -565,7 +552,7 @@ do -- TASK_A2A_DISPATCHER Task = TASK_A2A_SWEEP:New( Mission, self.SetGroup, string.format( "SWEEP.%03d", DetectedID ), TargetSetUnit ) Task:SetDetection( Detection, DetectedItem ) Task:UpdateTaskInfo( DetectedItem ) - end + end end end @@ -582,7 +569,7 @@ do -- TASK_A2A_DISPATCHER function Task.OnEnterCancelled( Task, From, Event, To ) self:Cancelled( Task ) end - + function Task.OnEnterFailed( Task, From, Event, To ) self:Failed( Task ) end @@ -590,38 +577,38 @@ do -- TASK_A2A_DISPATCHER function Task.OnEnterAborted( Task, From, Event, To ) self:Aborted( Task ) end - + TaskReport:Add( Task:GetName() ) else - self:F("This should not happen") + 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" ) + 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" ) + Task.TaskInfo:AddText( "Players", string.format( "%d ( %s )", PlayersCount, PlayersReport:Text( "," ) ), 40, "MOD" ) end - + -- OK, so the tasking has been done, now delete the changes reported for the area. Detection:AcceptChanges( DetectedItem ) end - + -- TODO set menus using the HQ coordinator Mission:GetCommandCenter():SetMenu() - local TaskText = TaskReport:Text(", ") - + local TaskText = TaskReport:Text( ", " ) + for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" and (self.FlashNewTask) then + 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 diff --git a/Moose Development/Moose/Tasking/Task_A2G.lua b/Moose Development/Moose/Tasking/Task_A2G.lua index f4ccf40d6..20e5df555 100644 --- a/Moose Development/Moose/Tasking/Task_A2G.lua +++ b/Moose Development/Moose/Tasking/Task_A2G.lua @@ -1,13 +1,13 @@ --- **Tasking** - The TASK_A2G models tasks for players in Air to Ground engagements. --- +-- -- === --- +-- -- ### Author: **FlightControl** --- --- ### Contributions: --- +-- +-- ### Contributions: +-- -- === --- +-- -- @module Tasking.Task_A2G -- @image MOOSE.JPG @@ -18,29 +18,29 @@ do -- TASK_A2G -- @field Core.Set#SET_UNIT TargetSetUnit -- @extends Tasking.Task#TASK - --- The TASK_A2G class defines Air To Ground tasks for a @{Set} of Target Units, + --- The TASK_A2G class defines Air To Ground tasks for a @{Set} of Target Units, -- based on the tasking capabilities defined in @{Tasking.Task#TASK}. -- The TASK_A2G is implemented using a @{Core.Fsm#FSM_TASK}, and has the following statuses: - -- + -- -- * **None**: Start of the process -- * **Planned**: The A2G task is planned. -- * **Assigned**: The A2G task is assigned to a @{Wrapper.Group#GROUP}. -- * **Success**: The A2G task is successfully completed. -- * **Failed**: The A2G task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. - -- + -- -- ## 1) Set the scoring of achievements in an A2G attack. - -- + -- -- Scoring or penalties can be given in the following circumstances: - -- + -- -- * @{#TASK_A2G.SetScoreOnDestroy}(): Set a score when a target in scope of the A2G attack, has been destroyed. -- * @{#TASK_A2G.SetScoreOnSuccess}(): Set a score when all the targets in scope of the A2G attack, have been destroyed. -- * @{#TASK_A2G.SetPenaltyOnFailed}(): Set a penalty when the A2G attack has failed. - -- + -- -- @field #TASK_A2G TASK_A2G = { - ClassName = "TASK_A2G", + ClassName = "TASK_A2G" } - + --- Instantiates a new TASK_A2G. -- @param #TASK_A2G self -- @param Tasking.Mission#MISSION Mission @@ -54,53 +54,51 @@ do -- TASK_A2G function TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskType, TaskBriefing ) local self = BASE:Inherit( self, TASK:New( Mission, SetGroup, TaskName, TaskType, TaskBriefing ) ) -- Tasking.Task#TASK_A2G 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: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: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( "Accounted", "DestroyedAll", "Accounted" ) - --Fsm:AddTransition( "Accounted", "Success", "Success" ) + + -- Fsm:AddTransition( "Accounted", "DestroyedAll", "Accounted" ) + -- Fsm:AddTransition( "Accounted", "Success", "Success" ) Fsm:AddTransition( "Rejected", "Reject", "Aborted" ) Fsm:AddTransition( "Failed", "Fail", "Failed" ) - - - --- Test + --- Test -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task_A2G#TASK_A2G Task function Fsm:onafterAssigned( TaskUnit, Task ) self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) -- Determine the first Unit from the self.RendezVousSetUnit - + self:RouteToRendezVous() end - - --- Test + + --- Test -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task_A2G#TASK_A2G Task function Fsm:onafterRouteToRendezVous( TaskUnit, Task ) self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) -- Determine the first Unit from the self.RendezVousSetUnit - + if Task:GetRendezVousZone( TaskUnit ) then self:__RouteToRendezVousZone( 0.1 ) else @@ -112,36 +110,36 @@ do -- TASK_A2G end end - --- Test + --- Test -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task#TASK_A2G Task function Fsm:OnAfterArriveAtRendezVous( TaskUnit, Task ) self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) -- Determine the first Unit from the self.TargetSetUnit - - self:__Engage( 0.1 ) + + self:__Engage( 0.1 ) end - - --- Test + + --- Test -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task#TASK_A2G Task function Fsm:onafterEngage( TaskUnit, Task ) self:F( { self } ) self:__Account( 0.1 ) - self:__RouteToTarget(0.1 ) + self:__RouteToTarget( 0.1 ) self:__RouteToTargets( -10 ) end - - --- Test + + --- Test -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task_A2G#TASK_A2G Task function Fsm:onafterRouteToTarget( TaskUnit, Task ) self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) -- Determine the first Unit from the self.TargetSetUnit - + if Task:GetTargetZone( TaskUnit ) then self:__RouteToTargetZone( 0.1 ) else @@ -154,8 +152,8 @@ do -- TASK_A2G self:__RouteToTargetPoint( 0.1 ) end end - - --- Test + + --- Test -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task_A2G#TASK_A2G Task @@ -167,20 +165,18 @@ do -- TASK_A2G end self:__RouteToTargets( -10 ) end - + return self - + end --- @param #TASK_A2G self -- @param Core.Set#SET_UNIT TargetSetUnit The set of targets. function TASK_A2G:SetTargetSetUnit( TargetSetUnit ) - + self.TargetSetUnit = TargetSetUnit end - - --- @param #TASK_A2G self function TASK_A2G:GetPlannedMenuText() return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" @@ -190,34 +186,32 @@ do -- TASK_A2G -- @param Core.Point#COORDINATE RendezVousCoordinate The Coordinate object referencing to the 2D point where the RendezVous point is located on the map. -- @param #number RendezVousRange The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point. -- @param Wrapper.Unit#UNIT TaskUnit - function TASK_A2G:SetRendezVousCoordinate( RendezVousCoordinate, RendezVousRange, TaskUnit ) - + function TASK_A2G:SetRendezVousCoordinate( RendezVousCoordinate, RendezVousRange, TaskUnit ) + local ProcessUnit = self:GetUnitProcess( TaskUnit ) - + local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT ActRouteRendezVous:SetCoordinate( RendezVousCoordinate ) ActRouteRendezVous:SetRange( RendezVousRange ) end - + --- @param #TASK_A2G self -- @param Wrapper.Unit#UNIT TaskUnit -- @return Core.Point#COORDINATE The Coordinate object referencing to the 2D point where the RendezVous point is located on the map. -- @return #number The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point. function TASK_A2G:GetRendezVousCoordinate( TaskUnit ) - + local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT return ActRouteRendezVous:GetCoordinate(), ActRouteRendezVous:GetRange() end - - - + --- @param #TASK_A2G self -- @param Core.Zone#ZONE_BASE RendezVousZone The Zone object where the RendezVous is located on the map. -- @param Wrapper.Unit#UNIT TaskUnit function TASK_A2G:SetRendezVousZone( RendezVousZone, TaskUnit ) - + local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE @@ -234,18 +228,17 @@ do -- TASK_A2G local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE return ActRouteRendezVous:GetZone() end - + --- @param #TASK_A2G self -- @param Core.Point#COORDINATE TargetCoordinate The Coordinate object where the Target is located on the map. -- @param Wrapper.Unit#UNIT TaskUnit function TASK_A2G:SetTargetCoordinate( TargetCoordinate, TaskUnit ) - + local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT ActRouteTarget:SetCoordinate( TargetCoordinate ) end - --- @param #TASK_A2G self -- @param Wrapper.Unit#UNIT TaskUnit @@ -258,18 +251,16 @@ do -- TASK_A2G return ActRouteTarget:GetCoordinate() end - --- @param #TASK_A2G self -- @param Core.Zone#ZONE_BASE TargetZone The Zone object where the Target is located on the map. -- @param Wrapper.Unit#UNIT TaskUnit function TASK_A2G:SetTargetZone( TargetZone, TaskUnit ) - + local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE ActRouteTarget:SetZone( TargetZone ) end - --- @param #TASK_A2G self -- @param Wrapper.Unit#UNIT TaskUnit @@ -283,47 +274,46 @@ do -- TASK_A2G end function TASK_A2G:SetGoalTotal() - + self.GoalTotal = self.TargetSetUnit:Count() end function TASK_A2G:GetGoalTotal() - + return self.GoalTotal end - + --- Return the relative distance to the target vicinity from the player, in order to sort the targets in the reports per distance from the threats. -- @param #TASK_A2G self - function TASK_A2G:ReportOrder( ReportGroup ) - self:UpdateTaskInfo( self.DetectedItem ) - + function TASK_A2G:ReportOrder( ReportGroup ) + self:UpdateTaskInfo( self.DetectedItem ) + local Coordinate = self.TaskInfo:GetData( "Coordinate" ) local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate ) - + return Distance end - - + --- This method checks every 10 seconds if the goal has been reached of the task. -- @param #TASK_A2G self function TASK_A2G:onafterGoal( TaskUnit, From, Event, To ) local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT - + if TargetSetUnit:Count() == 0 then self:Success() end - + self:__Goal( -10 ) end --- @param #TASK_A2G self 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() + 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 ) @@ -331,7 +321,7 @@ do -- TASK_A2G 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() @@ -344,33 +334,33 @@ do -- TASK_A2G end end self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true ) - self.TaskInfo:AddTargets( DetectedItemsCount, ReportTypes:Text( ", " ), 20, "D", 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 ) + 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 - + --- This function is called from the @{Tasking.CommandCenter#COMMANDCENTER} to determine the method of automatic task selection. -- @param #TASK_A2G self -- @param #number AutoAssignMethod The method to be applied to the task. -- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter The command center. -- @param Wrapper.Group#GROUP TaskGroup The player group. function TASK_A2G:GetAutoAssignPriority( AutoAssignMethod, CommandCenter, TaskGroup ) - - if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then + + 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}) + self:F( { Distance = Distance } ) return math.floor( Distance ) elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Priority then return 1 @@ -379,8 +369,7 @@ do -- TASK_A2G return 0 end -end - +end do -- TASK_A2G_SEAD @@ -397,9 +386,9 @@ do -- TASK_A2G_SEAD -- -- @field #TASK_A2G_SEAD TASK_A2G_SEAD = { - ClassName = "TASK_A2G_SEAD", + ClassName = "TASK_A2G_SEAD" } - + --- Instantiates a new TASK_A2G_SEAD. -- @param #TASK_A2G_SEAD self -- @param Tasking.Mission#MISSION Mission @@ -408,19 +397,16 @@ do -- TASK_A2G_SEAD -- @param Core.Set#SET_UNIT TargetSetUnit -- @param #string TaskBriefing The briefing of the task. -- @return #TASK_A2G_SEAD self - function TASK_A2G_SEAD:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing) + function TASK_A2G_SEAD:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing ) local self = BASE:Inherit( self, TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, "SEAD", TaskBriefing ) ) -- #TASK_A2G_SEAD self:F() - + Mission:AddTask( self ) - - self:SetBriefing( - TaskBriefing or - "Execute a Suppression of Enemy Air Defenses." - ) + + self:SetBriefing( TaskBriefing or "Execute a Suppression of Enemy Air Defenses." ) return self - end + end --- Set a score when a target in scope of the A2G attack, has been destroyed . -- @param #TASK_A2G_SEAD self @@ -434,7 +420,7 @@ do -- TASK_A2G_SEAD local ProcessUnit = self:GetUnitProcess( TaskUnit ) ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has SEADed a target.", Score ) - + return self end @@ -450,7 +436,7 @@ do -- TASK_A2G_SEAD local ProcessUnit = self:GetUnitProcess( TaskUnit ) ProcessUnit:AddScore( "Success", "All radar emitting targets have been successfully SEADed!", Score ) - + return self end @@ -466,11 +452,10 @@ do -- TASK_A2G_SEAD local ProcessUnit = self:GetUnitProcess( TaskUnit ) ProcessUnit:AddScore( "Failed", "The SEADing has failed!", Penalty ) - + return self end - end do -- TASK_A2G_BAI @@ -488,10 +473,8 @@ do -- TASK_A2G_BAI -- based on detected enemy ground targets. -- -- @field #TASK_A2G_BAI - TASK_A2G_BAI = { - ClassName = "TASK_A2G_BAI", - } - + TASK_A2G_BAI = { ClassName = "TASK_A2G_BAI" } + --- Instantiates a new TASK_A2G_BAI. -- @param #TASK_A2G_BAI self -- @param Tasking.Mission#MISSION Mission @@ -503,14 +486,11 @@ do -- 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 ) ) -- #TASK_A2G_BAI self:F() - + Mission:AddTask( self ) - - self:SetBriefing( - TaskBriefing or - "Execute a Battlefield Air Interdiction of a group of enemy targets." - ) - + + self:SetBriefing( TaskBriefing or "Execute a Battlefield Air Interdiction of a group of enemy targets." ) + return self end @@ -526,7 +506,7 @@ do -- TASK_A2G_BAI 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 @@ -542,7 +522,7 @@ do -- TASK_A2G_BAI 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 @@ -558,15 +538,12 @@ do -- TASK_A2G_BAI local ProcessUnit = self:GetUnitProcess( TaskUnit ) ProcessUnit:AddScore( "Failed", "The Battlefield Air Interdiction (BAI) has failed!", Penalty ) - + return self end end - - - do -- TASK_A2G_CAS --- The TASK_A2G_CAS class @@ -581,10 +558,8 @@ do -- TASK_A2G_CAS -- based on detected enemy ground targets. -- -- @field #TASK_A2G_CAS - TASK_A2G_CAS = { - ClassName = "TASK_A2G_CAS", - } - + TASK_A2G_CAS = { ClassName = "TASK_A2G_CAS" } + --- Instantiates a new TASK_A2G_CAS. -- @param #TASK_A2G_CAS self -- @param Tasking.Mission#MISSION Mission @@ -596,19 +571,13 @@ do -- 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 ) ) -- #TASK_A2G_CAS 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! " - ) - + 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 - + end --- Set a score when a target in scope of the A2G attack, has been destroyed . -- @param #TASK_A2G_CAS self @@ -622,7 +591,7 @@ do -- TASK_A2G_CAS 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 @@ -638,7 +607,7 @@ do -- TASK_A2G_CAS 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 @@ -654,9 +623,8 @@ do -- TASK_A2G_CAS local ProcessUnit = self:GetUnitProcess( TaskUnit ) ProcessUnit:AddScore( "Failed", "The Close Air Support (CAS) has failed!", Penalty ) - + return self end - end diff --git a/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua b/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua index 05bf2d11f..b9aedb1b6 100644 --- a/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua +++ b/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua @@ -1,24 +1,25 @@ ---- **Tasking** -- Dynamically allocates A2G tasks to human players, based on detected ground targets through reconnaissance. --- +--- **Tasking** -- Dynamically allocates A2G tasks to human players, based on detected ground targets through reconnaissance. +-- -- **Features:** --- +-- -- * Dynamically assign tasks to human players based on detected targets. -- * Dynamically change the tasks as the tactical situation evolves during the mission. -- * Dynamically assign (CAS) Close Air Support tasks for human players. -- * Dynamically assign (BAI) Battlefield Air Interdiction tasks for human players. --- * Dynamically assign (SEAD) Supression of Enemy Air Defense tasks for human players to eliminate G2A missile threats. +-- * Dynamically assign (SEAD) Suppression of Enemy Air Defense tasks for human players to eliminate G2A missile threats. -- * Define and use an EWR (Early Warning Radar) network. -- * Define different ranges to engage upon intruders. -- * Keep task achievements. --- * Score task achievements.-- +-- * Score task achievements. +-- -- === --- +-- -- ### Author: **FlightControl** --- --- ### Contributions: --- +-- +-- ### Contributions: +-- -- === --- +-- -- @module Tasking.Task_A2G_Dispatcher -- @image Task_A2G_Dispatcher.JPG @@ -32,151 +33,151 @@ do -- TASK_A2G_DISPATCHER -- @extends Tasking.DetectionManager#DETECTION_MANAGER --- Orchestrates dynamic **A2G Task Dispatching** based on the detection results of a linked @{Detection} object. - -- + -- -- It uses the Tasking System within the MOOSE framework, which is a multi-player Tasking Orchestration system. -- It provides a truly dynamic battle environment for pilots and ground commanders to engage upon, -- in a true co-operation environment wherein **Multiple Teams** will collaborate in Missions to **achieve a common Mission Goal**. - -- + -- -- The A2G dispatcher will dispatch the A2G Tasks to a defined @{Set} of @{Wrapper.Group}s that will be manned by **Players**. -- We call this the **AttackSet** of the A2G dispatcher. So, the Players are seated in the @{Client}s of the @{Wrapper.Group} @{Set}. - -- + -- -- Depending on the actions of the enemy, preventive tasks are dispatched to the players to orchestrate the engagement in a true co-operation. -- The detection object will group the detected targets by its grouping method, and integrates a @{Set} of @{Wrapper.Group}s that are Recce vehicles or air units. -- We call this the **RecceSet** of the A2G dispatcher. - -- + -- -- Depending on the current detected tactical situation, different task types will be dispatched to the Players seated in the AttackSet.. -- There are currently 3 **Task Types** implemented in the TASK\_A2G\_DISPATCHER: - -- + -- -- - **SEAD Task**: Dispatched when there are ground based Radar Emitters detected within an area. -- - **CAS Task**: Dispatched when there are no ground based Radar Emitters within the area, but there are friendly ground Units within 6 km from the enemy. -- - **BAI Task**: Dispatched when there are no ground based Radar Emitters within the area, and there aren't friendly ground Units within 6 km from the enemy. -- -- # 0. Tactical Situations - -- + -- -- This chapters provides some insights in the tactical situations when certain Task Types are created. -- The Task Types are depending on the enemy positions that were detected, and the current location of friendly units. - -- + -- -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia3.JPG) - -- + -- -- In the demonstration mission [TAD-A2G-000 - AREAS - Detection test], -- the tactical situation is a demonstration how the A2G detection works. -- This example will be taken further in the explanation in the following chapters. - -- + -- -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia4.JPG) - -- + -- -- The red coalition are the players, the blue coalition is the enemy. - -- + -- -- Red reconnaissance vehicles and airborne units are detecting the targets. -- We call this the RecceSet as explained above, which is a Set of Groups that -- have a group name starting with `Recce` (configured in the mission script). - -- + -- -- Red attack units are responsible for executing the mission for the command center. -- We call this the AttackSet, which is a Set of Groups with a group name starting with `Attack` (configured in the mission script). -- These units are setup in this demonstration mission to be ground vehicles and airplanes. -- For demonstration purposes, the attack airplane is stationed on the ground to explain -- the messages and the menus properly. -- Further test missions demonstrate the A2G task dispatcher from within air. - -- + -- -- Depending upon the detection results, the A2G dispatcher will create different tasks. - -- + -- -- # 0.1. SEAD Task - -- + -- -- A SEAD Task is dispatched when there are ground based Radar Emitters detected within an area. - -- + -- -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia9.JPG) - -- + -- -- - Once all Radar Emitting Units have been destroyed, the Task will convert into a BAI or CAS task! -- - A CAS and BAI task may be converted into a SEAD task, once a radar has been detected within the area! - -- + -- -- # 0.2. CAS Task - -- + -- -- A CAS Task is dispatched when there are no ground based Radar Emitters within the area, but there are friendly ground Units within 6 km from the enemy. - -- + -- -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia10.JPG) - -- + -- -- - After the detection of the CAS task, if the friendly Units are destroyed, the CAS task will convert into a BAI task! -- - Only ground Units are taken into account. Airborne units are ships are not considered friendlies that require Close Air Support. - -- + -- -- # 0.3. BAI Task - -- + -- -- A BAI Task is dispatched when there are no ground based Radar Emitters within the area, and there aren't friendly ground Units within 6 km from the enemy. - -- + -- -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia11.JPG) -- -- - A BAI task may be converted into a CAS task if friendly Ground Units approach within 6 km range! -- -- # 1. Player Experience - -- + -- -- The A2G dispatcher is residing under a @{CommandCenter}, which is orchestrating a @{Mission}. -- As a result, you'll find for DCS World missions that implement the A2G dispatcher a **Command Center Menu** and under this one or more **Mission Menus**. - -- + -- -- For example, if there are 2 Command Centers (CC). -- Each CC is controlling a couple of Missions, the Radio Menu Structure could look like this: - -- + -- -- Radio MENU Structure (F10. Other) - -- + -- -- F1. Command Center [Gori] -- F1. Mission "Alpha (Primary)" -- F2. Mission "Beta (Secondary)" -- F3. Mission "Gamma (Tactical)" -- F1. Command Center [Lima] -- F1. Mission "Overlord (High)" - -- - -- Command Center [Gori] is controlling Mission "Alpha", "Beta", "Gamma". Alpha is the Primary mission, Beta the Secondary and there is a Tacical mission Gamma. + -- + -- Command Center [Gori] is controlling Mission "Alpha", "Beta", "Gamma". Alpha is the Primary mission, Beta the Secondary and there is a Tactical mission Gamma. -- Command Center [Lima] is controlling Missions "Overlord", which needs to be executed with High priority. -- -- ## 1.1. Mission Menu (Under the Command Center Menu) - -- + -- -- The Mission Menu controls the information of the mission, including the: - -- + -- -- - **Mission Briefing**: A briefing of the Mission in text, which will be shown as a message. -- - **Mark Task Locations**: A summary of each Task will be shown on the map as a marker. -- - **Create Task Reports**: A menu to create various reports of the current tasks dispatched by the A2G dispatcher. -- - **Create Mission Reports**: A menu to create various reports on the current mission. - -- + -- -- For CC [Lima], Mission "Overlord", the menu structure could look like this: - -- + -- -- Radio MENU Structure (F10. Other) - -- + -- -- F1. Command Center [Lima] -- F1. Mission "Overlord" -- F1. Mission Briefing -- F2. Mark Task Locations on Map -- F3. Task Reports -- F4. Mission Reports - -- + -- -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia5.JPG) - -- + -- -- ### 1.1.1. Mission Briefing Menu - -- + -- -- The Mission Briefing Menu will show in text a summary description of the overall mission objectives and expectations. -- Note that the Mission Briefing is not the briefing of a specific task, but rather provides an overall strategy and tactical situation, - -- and explains the mission goals. - -- - -- + -- and explains the mission goals. + -- + -- -- ### 1.1.2. Mark Task Locations Menu - -- + -- -- The Mark Task Locations Menu will mark the location indications of the Tasks on the map, if this intelligence is known by the Command Center. -- For A2G tasks this information will always be know, but it can be that for other tasks a location intelligence will be less relevant. -- Note that each Planned task and each Engaged task will be marked. Completed, Failed and Cancelled tasks are not marked. -- Depending on the task type, a summary information is shown to bring to the player the relevant information for situational awareness. - -- + -- -- ### 1.1.3. Task Reports Menu - -- + -- -- The Task Reports Menu is a sub menu, that allows to create various reports: - -- + -- -- - **Tasks Summary**: This report will list all the Tasks that are or were active within the mission, indicating its status. -- - **Planned Tasks**: This report will list all the Tasks that are in status Planned, which are Tasks not assigned to any player, and are ready to be executed. -- - **Assigned Tasks**: This report will list all the Tasks that are in status Assigned, which are Tasks assigned to (a) player(s) and are currently executed. -- - **Successful Tasks**: This report will list all the Tasks that are in status Success, which are Tasks executed by (a) player(s) and are completed successfully. -- - **Failed Tasks**: This report will list all the Tasks that are in status Success, which are Tasks executed by (a) player(s) and that have failed. - -- + -- -- The information shown of the tasks will vary according the underlying task type, but are self explanatory. -- -- For CC [Gori], Mission "Alpha", the Task Reports menu structure could look like this: - -- + -- -- Radio MENU Structure (F10. Other) - -- + -- -- F1. Command Center [Gori] -- F1. Mission "Alpha" -- F1. Mission Briefing @@ -188,21 +189,21 @@ do -- TASK_A2G_DISPATCHER -- F4. Successful Tasks -- F5. Failed Tasks -- F4. Mission Reports - -- + -- -- Note that these reports provide an "overview" of the tasks. Detailed information of the task can be retrieved using the Detailed Report on the Task Menu. -- (See later). - -- + -- -- ### 1.1.4. Mission Reports Menu - -- + -- -- The Mission Reports Menu is a sub menu, that provides options to retrieve further information on the current Mission: - -- - -- - **Report Mission Progress**: Shows the progress of the current Mission. Each Task has a %-tage of completion. + -- + -- - **Report Mission Progress**: Shows the progress of the current Mission. Each Task has a % of completion. -- - **Report Players per Task**: Show which players are engaged on which Task within the Mission. - -- + -- -- For CC |Gori|, Mission "Alpha", the Mission Reports menu structure could look like this: - -- + -- -- Radio MENU Structure (F10. Other) - -- + -- -- F1. Command Center [Gori] -- F1. Mission "Alpha" -- F1. Mission Briefing @@ -211,25 +212,25 @@ do -- TASK_A2G_DISPATCHER -- F4. Mission Reports -- F1. Report Mission Progress -- F2. Report Players per Task - -- - -- + -- + -- -- ## 1.2. Task Management Menus - -- + -- -- Very important to remember is: **Multiple Players can be assigned to the same Task, but from the player perspective, the Player can only be assigned to one Task per Mission at the same time!** -- Consider this like the two major modes in which a player can be in. He can be free of tasks or he can be assigned to a Task. -- Depending on whether a Task has been Planned or Assigned to a Player (Group), -- **the Mission Menu will contain extra Menus to control specific Tasks.** - -- + -- -- #### 1.2.1. Join a Planned Task - -- + -- -- If the Player has not yet been assigned to a Task within the Mission, the Mission Menu will contain additionally a: - -- + -- -- - Join Planned Task Menu: This menu structure allows the player to join a planned task (a Task with status Planned). - -- + -- -- For CC |Gori|, Mission "Alpha", the menu structure could look like this: - -- + -- -- Radio MENU Structure (F10. Other) - -- + -- -- F1. Command Center [Gori] -- F1. Mission "Alpha" -- F1. Mission Briefing @@ -237,23 +238,23 @@ do -- TASK_A2G_DISPATCHER -- F3. Task Reports -- F4. Mission Reports -- F5. Join Planned Task - -- + -- -- **The F5. Join Planned Task allows the player to join a Planned Task and take an engagement in the running Mission.** - -- - -- #### 1.2.2. Manage an Assigned Task - -- + -- + -- #### 1.2.2. Manage an Assigned Task + -- -- If the Player has been assigned to one Task within the Mission, the Mission Menu will contain an extra: - -- + -- -- - Assigned Task __TaskName__ Menu: This menu structure allows the player to take actions on the currently engaged task. - -- + -- -- In this example, the Group currently seated by the player is not assigned yet to a Task. -- The Player has the option to assign itself to a Planned Task using menu option F5 under the Mission Menu "Alpha". - -- + -- -- This would be an example menu structure, -- for CC |Gori|, Mission "Alpha", when a player would have joined Task CAS.001: - -- + -- -- Radio MENU Structure (F10. Other) - -- + -- -- F1. Command Center [Gori] -- F1. Mission "Alpha" -- F1. Mission Briefing @@ -261,26 +262,25 @@ do -- TASK_A2G_DISPATCHER -- F3. Task Reports -- F4. Mission Reports -- F5. Assigned Task CAS.001 - -- + -- -- **The F5. Assigned Task __TaskName__ allows the player to control the current Assigned Task and take further actions.** - -- - -- + -- -- ## 1.3. Join Planned Task Menu - -- + -- -- The Join Planned Task Menu contains the different Planned A2G Tasks **in a structured Menu Hierarchy**. - -- The Menu Hierarchy is structuring the Tasks per **Task Type**, and then by **Task Name (ID)**. - -- - -- For example, for CC [Gori], Mission "Alpha", + -- The Menu Hierarchy is structuring the Tasks per **Task Type**, and then by **Task Name (ID)**. + -- + -- For example, for CC [Gori], Mission "Alpha", -- if a Mission "ALpha" contains 5 Planned Tasks, which would be: - -- - -- - 2 CAS Tasks + -- + -- - 2 CAS Tasks -- - 1 BAI Task -- - 2 SEAD Tasks - -- + -- -- the Join Planned Task Menu Hierarchy could look like this: - -- + -- -- Radio MENU Structure (F10. Other) - -- + -- -- F1. Command Center [Gori] -- F1. Mission "Alpha" -- F1. Mission Briefing @@ -296,26 +296,26 @@ do -- TASK_A2G_DISPATCHER -- F1. SEAD.003 -- F2. SEAD.004 -- F3. SEAD.005 - -- + -- -- An example from within a running simulation: - -- + -- -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia6.JPG) - -- + -- -- Each Task Type Menu would have a list of the Task Menus underneath. -- Each Task Menu (eg. `CAS.001`) has a **detailed Task Menu structure to control the specific task**! -- -- ### 1.3.1. Planned Task Menu -- -- Each Planned Task Menu will allow for the following actions: - -- + -- -- - Report Task Details: Provides a detailed report on the Planned Task. -- - Mark Task Location on Map: Mark the approximate location of the Task on the Map, if relevant. -- - Join Task: Join the Task. This is THE menu option to let a Player join the Task, and to engage within the Mission. - -- - -- The Join Planned Task Menu could look like this for for CC |Gori|, Mission "Alpha": - -- + -- + -- The Join Planned Task Menu could look like this for for CC |Gori|, Mission "Alpha": + -- -- Radio MENU Structure (F10. Other) - -- + -- -- F1. Command Center |Gori| -- F1. Mission "Alpha" -- F1. Mission Briefing @@ -328,22 +328,22 @@ do -- TASK_A2G_DISPATCHER -- F1. Report Task Details -- F2. Mark Task Location on Map -- F3. Join Task - -- + -- -- **The Join Task is THE menu option to let a Player join the Task, and to engage within the Mission.** - -- - -- + -- + -- -- ## 1.4. Assigned Task Menu - -- + -- -- The Assigned Task Menu allows to control the **current assigned task** within the Mission. - -- + -- -- Depending on the Type of Task, the following menu options will be available: - -- + -- -- - **Report Task Details**: Provides a detailed report on the Planned Task. -- - **Mark Task Location on Map**: Mark the approximate location of the Task on the Map, if relevant. -- - **Abort Task: Abort the current assigned Task:** This menu option lets the player abort the Task. - -- + -- -- For example, for CC |Gori|, Mission "Alpha", the Assigned Menu could be: - -- + -- -- F1. Command Center |Gori| -- F1. Mission "Alpha" -- F1. Mission Briefing @@ -354,90 +354,89 @@ do -- TASK_A2G_DISPATCHER -- F1. Report Task Details -- F2. Mark Task Location on Map -- F3. Abort Task - -- + -- -- Task abortion will result in the Task to be Cancelled, and the Task **may** be **Replanned**. - -- However, this will depend on the setup of each Mission. - -- + -- However, this will depend on the setup of each Mission. + -- -- ## 1.5. Messages - -- + -- -- During game play, different messages are displayed. -- These messages provide an update of the achievements made, and the state wherein the task is. - -- + -- -- The various reports can be used also to retrieve the current status of the mission and its tasks. - -- + -- -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia7.JPG) - -- + -- -- The @{Settings} menu provides additional options to control the timing of the messages. -- There are: - -- + -- -- - Status messages, which are quick status updates. The settings menu allows to switch off these messages. -- - Information messages, which are shown a bit longer, as they contain important information. -- - Summary reports, which are quick reports showing a high level summary. -- - Overview reports, which are providing the essential information. It provides an overview of a greater thing, and may take a bit of time to read. -- - Detailed reports, which provide with very detailed information. It takes a bit longer to read those reports, so the display of those could be a bit longer. - -- + -- -- # 2. TASK\_A2G\_DISPATCHER constructor - -- + -- -- The @{#TASK_A2G_DISPATCHER.New}() method creates a new TASK\_A2G\_DISPATCHER instance. -- -- # 3. Usage -- -- To use the TASK\_A2G\_DISPATCHER class, you need: - -- + -- -- - A @{CommandCenter} object. The master communication channel. -- - A @{Mission} object. Each task belongs to a Mission. -- - A @{Detection} object. There are several detection grouping methods to choose from. -- - A @{Task_A2G_Dispatcher} object. The master A2G task dispatcher. - -- - A @{Set} of @{Wrapper.Group} objects that will detect the emeny, the RecceSet. This is attached to the @{Detection} object. + -- - A @{Set} of @{Wrapper.Group} objects that will detect the enemy, the RecceSet. This is attached to the @{Detection} object. -- - A @{Set} ob @{Wrapper.Group} objects that will attack the enemy, the AttackSet. This is attached to the @{Task_A2G_Dispatcher} object. - -- - -- Below an example mission declaration that is defines a Task A2G Dispatcher object. -- - -- -- Declare the Command Center + -- Below an example mission declaration that is defines a Task A2G Dispatcher object. + -- + -- -- Declare the Command Center -- local HQ = GROUP -- :FindByName( "HQ", "Bravo HQ" ) -- -- local CommandCenter = COMMANDCENTER -- :New( HQ, "Lima" ) - -- + -- -- -- Declare the Mission for the Command Center. -- local Mission = MISSION -- :New( CommandCenter, "Overlord", "High", "Attack Detect Mission Briefing", coalition.side.RED ) - -- + -- -- -- Define the RecceSet that will detect the enemy. -- local RecceSet = SET_GROUP -- :New() -- :FilterPrefixes( "FAC" ) -- :FilterCoalitions("red") -- :FilterStart() - -- + -- -- -- Setup the detection. We use DETECTION_AREAS to detect and group the enemies within areas of 3 km radius. -- local DetectionAreas = DETECTION_AREAS -- :New( RecceSet, 3000 ) -- The RecceSet will detect the enemies. - -- + -- -- -- Setup the AttackSet, which is a SET_GROUP. - -- -- The SET_GROUP is a dynamic collection of GROUP objects. + -- -- The SET_GROUP is a dynamic collection of GROUP objects. -- local AttackSet = SET_GROUP -- :New() -- Create the SET_GROUP object. -- :FilterCoalitions( "red" ) -- Only incorporate the RED coalitions. -- :FilterPrefixes( "Attack" ) -- Only incorporate groups that start with the name Attack. -- :FilterStart() -- Enable the dynamic filtering. From this moment the AttackSet will contain all groups that are red and start with the name Attack. - -- + -- -- -- Now we have everything to setup the main A2G TaskDispatcher. -- TaskDispatcher = TASK_A2G_DISPATCHER - -- :New( Mission, AttackSet, DetectionAreas ) -- We assign the TaskDispatcher under Mission. The AttackSet will engage the enemy and will recieve the dispatched Tasks. The DetectionAreas will report any detected enemies to the TaskDispatcher. - -- - -- + -- :New( Mission, AttackSet, DetectionAreas ) -- We assign the TaskDispatcher under Mission. The AttackSet will engage the enemy and will receive the dispatched Tasks. The DetectionAreas will report any detected enemies to the TaskDispatcher. + -- + -- -- -- @field #TASK_A2G_DISPATCHER TASK_A2G_DISPATCHER = { ClassName = "TASK_A2G_DISPATCHER", Mission = nil, Detection = nil, - Tasks = {}, + Tasks = {} } - - + --- TASK_A2G_DISPATCHER constructor. -- @param #TASK_A2G_DISPATCHER self -- @param Tasking.Mission#MISSION Mission The mission for which the task dispatching is done. @@ -445,18 +444,18 @@ do -- TASK_A2G_DISPATCHER -- @param Functional.Detection#DETECTION_BASE Detection The detection results that are used to dynamically assign new tasks to human players. -- @return #TASK_A2G_DISPATCHER self function TASK_A2G_DISPATCHER:New( Mission, SetGroup, Detection ) - + -- Inherits from DETECTION_MANAGER local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #TASK_A2G_DISPATCHER - + self.Detection = Detection self.Mission = Mission - self.FlashNewTask = true --set to false to suppress flash messages - + self.FlashNewTask = true -- set to false to suppress flash messages + self.Detection:FilterCategories( { Unit.Category.GROUND_UNIT } ) - + self:AddTransition( "Started", "Assign", "Started" ) - + --- OnAfter Transition Handler for Event Assign. -- @function [parent=#TASK_A2G_DISPATCHER] OnAfterAssign -- @param #TASK_A2G_DISPATCHER self @@ -466,19 +465,19 @@ do -- TASK_A2G_DISPATCHER -- @param Tasking.Task_A2G#TASK_A2G Task -- @param Wrapper.Unit#UNIT TaskUnit -- @param #string PlayerName - + self:__Start( 5 ) - + return self end - - --- Set flashing player messages on or off + + --- Set flashing player messages on or off -- @param #TASK_A2G_DISPATCHER self -- @param #boolean onoff Set messages on (true) or off (false) function TASK_A2G_DISPATCHER:SetSendMessages( onoff ) - self.FlashNewTask = onoff + self.FlashNewTask = onoff end - + --- Creates a SEAD task when there are targets for it. -- @param #TASK_A2G_DISPATCHER self -- @param Functional.Detection#DETECTION_AREAS.DetectedItem DetectedItem @@ -486,7 +485,7 @@ do -- TASK_A2G_DISPATCHER -- @return #nil If there are no targets to be set. function TASK_A2G_DISPATCHER:EvaluateSEAD( DetectedItem ) self:F( { DetectedItem.ItemID } ) - + local DetectedSet = DetectedItem.Set local DetectedZone = DetectedItem.Zone @@ -500,10 +499,10 @@ do -- TASK_A2G_DISPATCHER TargetSetUnit:SetDatabase( DetectedSet ) TargetSetUnit:FilterHasSEAD() TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. - + return TargetSetUnit end - + return nil end @@ -514,11 +513,10 @@ do -- TASK_A2G_DISPATCHER -- @return #nil If there are no targets to be set. function TASK_A2G_DISPATCHER:EvaluateCAS( DetectedItem ) self:F( { DetectedItem.ItemID } ) - + local DetectedSet = DetectedItem.Set local DetectedZone = DetectedItem.Zone - -- Determine if the set has ground units. -- There should be ground unit friendlies nearby. Airborne units are valid friendlies types. -- And there shouldn't be any radar. @@ -532,13 +530,13 @@ do -- TASK_A2G_DISPATCHER local TargetSetUnit = SET_UNIT:New() TargetSetUnit:SetDatabase( DetectedSet ) TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. - + return TargetSetUnit end - + return nil end - + --- Creates a BAI task when there are targets for it. -- @param #TASK_A2G_DISPATCHER self -- @param Functional.Detection#DETECTION_AREAS.DetectedItem DetectedItem @@ -546,11 +544,10 @@ do -- TASK_A2G_DISPATCHER -- @return #nil If there are no targets to be set. function TASK_A2G_DISPATCHER:EvaluateBAI( DetectedItem, FriendlyCoalition ) self:F( { DetectedItem.ItemID } ) - + local DetectedSet = DetectedItem.Set local DetectedZone = DetectedItem.Zone - -- Determine if the set has ground units. -- There shouldn't be any ground unit friendlies nearby. -- And there shouldn't be any radar. @@ -564,19 +561,18 @@ do -- TASK_A2G_DISPATCHER local TargetSetUnit = SET_UNIT:New() TargetSetUnit:SetDatabase( DetectedSet ) TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. - + return TargetSetUnit end - + return nil end - - + function TASK_A2G_DISPATCHER:RemoveTask( TaskIndex ) self.Mission:RemoveTask( self.Tasks[TaskIndex] ) self.Tasks[TaskIndex] = nil end - + --- Evaluates the removal of the Task from the Mission. -- Can only occur when the DetectedItem is Changed AND the state of the Task is "Planned". -- @param #TASK_A2G_DISPATCHER self @@ -586,17 +582,16 @@ do -- TASK_A2G_DISPATCHER -- @param #boolean DetectedItemChange -- @return Tasking.Task#TASK function TASK_A2G_DISPATCHER:EvaluateRemoveTask( Mission, Task, TaskIndex, DetectedItemChanged ) - + if Task then - if ( Task:IsStatePlanned() and DetectedItemChanged == true ) or Task:IsStateCancelled() then - --self:F( "Removing Tasking: " .. Task:GetTaskName() ) + if (Task:IsStatePlanned() and DetectedItemChanged == true) or Task:IsStateCancelled() then + -- self:F( "Removing Tasking: " .. Task:GetTaskName() ) self:RemoveTask( TaskIndex ) end end - + return Task end - --- Assigns tasks in relation to the detected items to the @{Core.Set#SET_GROUP}. -- @param #TASK_A2G_DISPATCHER self @@ -604,15 +599,15 @@ do -- TASK_A2G_DISPATCHER -- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop. 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() -- Checking the task queue for the dispatcher, and removing any obsolete task! @@ -634,21 +629,21 @@ do -- TASK_A2G_DISPATCHER --- First we need to the detected targets. for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do - + local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT local DetectedZone = DetectedItem.Zone - --self:F( { "Targets in DetectedItem", DetectedItem.ItemID, DetectedSet:Count(), tostring( DetectedItem ) } ) - --DetectedSet:Flush( self ) - + -- self:F( { "Targets in DetectedItem", DetectedItem.ItemID, DetectedSet:Count(), tostring( DetectedItem ) } ) + -- DetectedSet:Flush( self ) + 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] -- Tasking.Task_A2G#TASK_A2G - + if Task then -- If there is a Task and the task was assigned, then we check if the task was changed ... If it was, we need to reevaluate the targets. if Task:IsStateAssigned() then @@ -660,7 +655,7 @@ do -- TASK_A2G_DISPATCHER Task:SetTargetSetUnit( TargetSetUnit ) Task:SetDetection( Detection, DetectedItem ) Task:UpdateTaskInfo( DetectedItem ) - TargetsReport:Add( Detection:GetChangeText( DetectedItem ) ) + TargetsReport:Add( Detection:GetChangeText( DetectedItem ) ) else Task:Cancel() end @@ -691,18 +686,18 @@ do -- TASK_A2G_DISPATCHER end end end - + -- Now we send to each group the changes, if any. for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - local TargetsText = TargetsReport:Text(", ") - if ( Mission:IsGroupAssigned(TaskGroup) ) and TargetsText ~= "" and self.FlashNewTask then + 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 -- The detection has changed, thus a new TargetSet is to be evaluated and set @@ -753,7 +748,7 @@ do -- TASK_A2G_DISPATCHER local TargetSetUnit = self:EvaluateSEAD( DetectedItem ) -- Returns a SetUnit if there are targets to be SEADed... if TargetSetUnit then Task = TASK_A2G_SEAD:New( Mission, self.SetGroup, string.format( "SEAD.%03d", DetectedItemID ), TargetSetUnit ) - DetectedItem.DesignateMenuName = string.format( "SEAD.%03d", DetectedItemID ) --inject a name for DESIGNATE, if using same DETECTION object + DetectedItem.DesignateMenuName = string.format( "SEAD.%03d", DetectedItemID ) -- inject a name for DESIGNATE, if using same DETECTION object Task:SetDetection( Detection, DetectedItem ) end @@ -762,7 +757,7 @@ do -- TASK_A2G_DISPATCHER local TargetSetUnit = self:EvaluateCAS( DetectedItem ) -- Returns a SetUnit if there are targets to be CASed... if TargetSetUnit then Task = TASK_A2G_CAS:New( Mission, self.SetGroup, string.format( "CAS.%03d", DetectedItemID ), TargetSetUnit ) - DetectedItem.DesignateMenuName = string.format( "CAS.%03d", DetectedItemID ) --inject a name for DESIGNATE, if using same DETECTION object + DetectedItem.DesignateMenuName = string.format( "CAS.%03d", DetectedItemID ) -- inject a name for DESIGNATE, if using same DETECTION object Task:SetDetection( Detection, DetectedItem ) end @@ -771,19 +766,19 @@ do -- TASK_A2G_DISPATCHER local TargetSetUnit = self:EvaluateBAI( DetectedItem, self.Mission:GetCommandCenter():GetPositionable():GetCoalition() ) -- Returns a SetUnit if there are targets to be BAIed... if TargetSetUnit then Task = TASK_A2G_BAI:New( Mission, self.SetGroup, string.format( "BAI.%03d", DetectedItemID ), TargetSetUnit ) - DetectedItem.DesignateMenuName = string.format( "BAI.%03d", DetectedItemID ) --inject a name for DESIGNATE, if using same DETECTION object + DetectedItem.DesignateMenuName = string.format( "BAI.%03d", DetectedItemID ) -- inject a name for DESIGNATE, if using same DETECTION object 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 @@ -791,7 +786,7 @@ do -- TASK_A2G_DISPATCHER function Task.OnEnterCancelled( Task, From, Event, To ) self:Cancelled( Task ) end - + function Task.OnEnterFailed( Task, From, Event, To ) self:Failed( Task ) end @@ -799,31 +794,29 @@ do -- TASK_A2G_DISPATCHER function Task.OnEnterAborted( Task, From, Event, To ) self:Aborted( Task ) end - - + TaskReport:Add( Task:GetName() ) else - self:F("This should not happen") + self:F( "This should not happen" ) end end - -- OK, so the tasking has been done, now delete the changes reported for the area. Detection:AcceptChanges( DetectedItem ) end - + -- TODO set menus using the HQ coordinator Mission:GetCommandCenter():SetMenu() - - local TaskText = TaskReport:Text(", ") + + local TaskText = TaskReport:Text( ", " ) for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" and self.FlashNewTask then + 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 From 6360b8c58f2dcb9de9d556c91f134eefd6fb7429 Mon Sep 17 00:00:00 2001 From: TommyC81 Date: Sat, 4 Dec 2021 21:50:05 +0400 Subject: [PATCH 020/200] General documentation and code fixes (#1650) Documentation updates for correctness and clarity. General code formatting updates. Ajustment to POSITIONABLE:GetCoord to make use of existing POINT:UpdateFromVec3. Added comments about clarifying the difference between POSITIONABLE:GetCoordinate() and POSITIONABLE:GetCoord() and to perhaps consider a renaming or merging the functions with an optional flag. --- Moose Development/Moose/Core/Point.lua | 73 ++- Moose Development/Moose/Functional/Range.lua | 88 +-- .../Moose/Wrapper/Positionable.lua | 508 +++++++++--------- Moose Development/Moose/Wrapper/Static.lua | 140 +++-- Moose Development/Moose/Wrapper/Unit.lua | 16 - 5 files changed, 408 insertions(+), 417 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index f79d54593..1851db807 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -314,39 +314,38 @@ do -- COORDINATE -- @param DCS#Vec3 Vec3 The 3D vector with x,y,z components. -- @return #COORDINATE The modified COORDINATE itself. function COORDINATE:UpdateFromVec3(Vec3) - + self.x=Vec3.x self.y=Vec3.y self.z=Vec3.z - + return self end - + --- Update x,y,z coordinates from another given COORDINATE. -- @param #COORDINATE self -- @param #COORDINATE Coordinate The coordinate with the new x,y,z positions. -- @return #COORDINATE The modified COORDINATE itself. function COORDINATE:UpdateFromCoordinate(Coordinate) - + self.x=Coordinate.x self.y=Coordinate.y self.z=Coordinate.z - + return self - end + end --- Update x and z coordinates from a given 2D vector. -- @param #COORDINATE self -- @param DCS#Vec2 Vec2 The 2D vector with x,y components. x is overwriting COORDINATE.x while y is overwriting COORDINATE.z. -- @return #COORDINATE The modified COORDINATE itself. function COORDINATE:UpdateFromVec2(Vec2) - + self.x=Vec2.x self.z=Vec2.y - + return self end - --- Returns the coordinate from the latitude and longitude given in decimal degrees. -- @param #COORDINATE self @@ -355,13 +354,13 @@ do -- COORDINATE -- @param #number altitude (Optional) Altitude in meters. Default is the land height at the coordinate. -- @return #COORDINATE function COORDINATE:NewFromLLDD( latitude, longitude, altitude) - + -- Returns a point from latitude and longitude in the vec3 format. local vec3=coord.LLtoLO(latitude, longitude) - + -- Convert vec3 to coordinate object. local _coord=self:NewFromVec3(vec3) - + -- Adjust height if altitude==nil then _coord.y=self:GetLandHeight() @@ -372,23 +371,22 @@ do -- COORDINATE return _coord end - --- Returns if the 2 coordinates are at the same 2D position. -- @param #COORDINATE self -- @param #COORDINATE Coordinate -- @param #number Precision -- @return #boolean true if at the same position. function COORDINATE:IsAtCoordinate2D( Coordinate, Precision ) - + self:F( { Coordinate = Coordinate:GetVec2() } ) self:F( { self = self:GetVec2() } ) - + local x = Coordinate.x local z = Coordinate.z - + return x - Precision <= self.x and x + Precision >= self.x and z - Precision <= self.z and z + Precision >= self.z end - + --- Scan/find objects (units, statics, scenery) within a certain radius around the coordinate using the world.searchObjects() DCS API function. -- @param #COORDINATE self -- @param #number radius (Optional) Scan radius in meters. Default 100 m. @@ -423,7 +421,7 @@ do -- COORDINATE if scanscenery==nil then scanscenery=false end - + --{Object.Category.UNIT, Object.Category.STATIC, Object.Category.SCENERY} local scanobjects={} if scanunits then @@ -435,7 +433,7 @@ do -- COORDINATE if scanscenery then table.insert(scanobjects, Object.Category.SCENERY) end - + -- Found stuff. local Units = {} local Statics = {} @@ -443,40 +441,40 @@ do -- COORDINATE local gotstatics=false local gotunits=false local gotscenery=false - + local function EvaluateZone(ZoneObject) - + if ZoneObject then - + -- Get category of scanned object. local ObjectCategory = ZoneObject:getCategory() - + -- Check for unit or static objects 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 - + -- Search the world. world.searchObjects(scanobjects, SphereSearch, EvaluateZone) - + for _,unit in pairs(Units) do self:T(string.format("Scan found unit %s", unit:GetName())) end @@ -551,7 +549,7 @@ do -- COORDINATE -- @param DCS#Distance Distance The Distance to be added in meters. -- @param DCS#Angle Angle The Angle in degrees. Defaults to 0 if not specified (nil). -- @param #boolean Keepalt If true, keep altitude of original coordinate. Default is that the new coordinate is created at the translated land height. - -- @param #boolean Overwrite If true, overwrite the original COORDINATE with the translated one. Otherwise, create a new COODINATE. + -- @param #boolean Overwrite If true, overwrite the original COORDINATE with the translated one. Otherwise, create a new COORDINATE. -- @return #COORDINATE The new calculated COORDINATE. function COORDINATE:Translate( Distance, Angle, Keepalt, Overwrite ) @@ -2987,7 +2985,7 @@ do -- POINT_VEC3 local self = BASE:Inherit( self, COORDINATE:New( x, y, z ) ) -- Core.Point#POINT_VEC3 self:F2( self ) - + return self end @@ -3004,7 +3002,6 @@ do -- POINT_VEC3 return self end - --- Create a new POINT_VEC3 object from Vec3 coordinates. -- @param #POINT_VEC3 self -- @param DCS#Vec3 Vec3 The Vec3 point. @@ -3013,12 +3010,10 @@ do -- POINT_VEC3 local self = BASE:Inherit( self, COORDINATE:NewFromVec3( Vec3 ) ) -- Core.Point#POINT_VEC3 self:F2( self ) - + return self end - - --- Return the x coordinate of the POINT_VEC3. -- @param #POINT_VEC3 self -- @return #number The x coodinate. diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index 9f4675ef7..64c4d49a3 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -152,7 +152,7 @@ -- -- * The first parameter *targetnames* defines the target or targets. This can be a single item or a Table with the name(s) of @{Wrapper.Unit} or @{Static} objects defined in the mission editor. -- * The (optional) parameter *goodhitrange* specifies the radius in metres around the target within which a bomb/rocket hit is considered to be "good". --- * If final (optional) parameter "*randommove*" can be enabled to create moving targets. If this parameter is set to true, the units of this bombing target will randomly move within the range zone. +-- * If final (optional) parameter *randommove* can be enabled to create moving targets. If this parameter is set to true, the units of this bombing target will randomly move within the range zone. -- Note that there might be quirks since DCS units can get stuck in buildings etc. So it might be safer to manually define a route for the units in the mission editor if moving targets are desired. -- -- ## Adding Groups @@ -903,10 +903,10 @@ end --- Set player setting whether bomb impact points are smoked or not. -- @param #RANGE self --- @param #boolean switch If true nor nil default is to smoke impact points of bombs. +-- @param #boolean switch (Optional) If true, impact points of bombs will be smoked. Default is true. -- @return #RANGE self function RANGE:SetDefaultPlayerSmokeBomb(switch) - if switch==true or switch==nil then + if switch == nil or switch == true then self.defaultsmokebomb=true else self.defaultsmokebomb=false @@ -1249,7 +1249,7 @@ end -- @param #number boxlength (Optional) Length of the approach box in meters. Default is 3000 m. -- @param #number boxwidth (Optional) Width of the approach box in meters. Default is 300 m. -- @param #number heading (Optional) Approach heading in Degrees. Default is heading of the unit as defined in the mission editor. --- @param #boolean inverseheading (Optional) Take inverse heading (heading --> heading - 180 Degrees). Default is false. +-- @param #boolean inverseheading (Optional) Use inverse heading (heading --> heading - 180 Degrees). Default is false. -- @param #number goodpass (Optional) Number of hits for a "good" strafing pass. Default is 20. -- @param #number foulline (Optional) Foul line distance. Hits from closer than this distance are not counted. Default 610 m = 2000 ft. Set to 0 for no foul line. -- @return #RANGE self @@ -1284,8 +1284,8 @@ end --- Add bombing target(s) to range. -- @param #RANGE self -- @param #table targetnames Single or multiple (Table) names of unit or static objects serving as bomb targets. --- @param #number goodhitrange (Optional) Max distance from target unit (in meters) which is considered as a good hit. Default is 25 m. --- @param #boolean randommove If true, unit will move randomly within the range. Default is false. +-- @param #number goodhitrange (Optional) Max hit distance from target unit in meters which is considered as a good hit. Default is 25 m. +-- @param #boolean randommove (Optional) If true, unit will move randomly within the range. Default is false. -- @return #RANGE self function RANGE:AddBombingTargets(targetnames, goodhitrange, randommove) self:F({targetnames=targetnames, goodhitrange=goodhitrange, randommove=randommove}) @@ -1323,8 +1323,8 @@ end --- Add a unit or static object as bombing target. -- @param #RANGE self -- @param Wrapper.Positionable#POSITIONABLE unit Positionable (unit or static) of the strafe target. --- @param #number goodhitrange Max distance from unit which is considered as a good hit. --- @param #boolean randommove If true, unit will move randomly within the range. Default is false. +-- @param #number goodhitrange (Optional) Max hit distance from target unit in meters which is considered as a good hit. Default is 25 m. +-- @param #boolean randommove (Optional) If true, unit will move randomly within the range. Default is false. -- @return #RANGE self function RANGE:AddBombingTargetUnit(unit, goodhitrange, randommove) self:F({unit=unit, goodhitrange=goodhitrange, randommove=randommove}) @@ -1339,7 +1339,7 @@ function RANGE:AddBombingTargetUnit(unit, goodhitrange, randommove) goodhitrange=goodhitrange or RANGE.Defaults.goodhitrange -- Set randommove to false if it was not specified. - if randommove==nil or _isstatic==true then + if randommove == nil or _isstatic == true then randommove=false end @@ -1354,7 +1354,7 @@ function RANGE:AddBombingTargetUnit(unit, goodhitrange, randommove) -- Get max speed of unit in km/h. local speed=0 - if _isstatic==false then + if _isstatic == false then speed=self:_GetSpeed(unit) end @@ -1377,12 +1377,23 @@ function RANGE:AddBombingTargetUnit(unit, goodhitrange, randommove) return self end ---- Add a coordinate of a bombing target. This +--- Add a coordinate of a bombing target. -- @param #RANGE self -- @param Core.Point#COORDINATE coord The coordinate. --- @param #string name Name of target. --- @param #number goodhitrange Max distance from unit which is considered as a good hit. +-- @param #string name (Optional) Name of target. Default is "Bomb Target". +-- @param #number goodhitrange (Optional) Max hit distance from target unit in meters which is considered as a good hit. Default is 25 m. -- @return #RANGE self +-- @usage +-- +-- -- Setup a Range +-- RangeOne = RANGE:New( "Range One" ) +-- -- Find the STATIC target object as setup in the ME +-- RangeOneBombTarget = STATIC:FindByName( "RangeOneBombTarget" ): +-- -- Add the coordinate of the STATIC target object as a bomb target (thus keeping the bomb function active, even if the STATIC target is destroyed) +-- RangeOne:AddBombingTargetCoordinate( RangeOneBombTarget:GetCoordinate(), "RangeOneBombTarget", 50) +-- -- Or, add the coordinate of the STATIC target object as a bomb target using default values (name will be "Bomb Target", goodhitrange will be 25 m) +-- RangeOne:AddBombingTargetCoordinate( RangeOneBombTarget:GetCoordinate() ) +-- function RANGE:AddBombingTargetCoordinate(coord, name, goodhitrange) local target={} --#RANGE.BombTarget @@ -1403,8 +1414,8 @@ end --- Add all units of a group as bombing targets. -- @param #RANGE self -- @param Wrapper.Group#GROUP group Group of bombing targets. --- @param #number goodhitrange Max distance from unit which is considered as a good hit. --- @param #boolean randommove If true, unit will move randomly within the range. Default is false. +-- @param #number goodhitrange (Optional) Max hit distance from target unit in meters which is considered as a good hit. Default is 25 m. +-- @param #boolean randommove (Optional) If true, unit will move randomly within the range. Default is false. -- @return #RANGE self function RANGE:AddBombingTargetGroup(group, goodhitrange, randommove) self:F({group=group, goodhitrange=goodhitrange, randommove=randommove}) @@ -1423,10 +1434,10 @@ function RANGE:AddBombingTargetGroup(group, goodhitrange, randommove) return self end ---- Measures the foule line distance between two unit or static objects. +--- Returns the foul line distance between strafe pit target and a foul line distance marker object. -- @param #RANGE self -- @param #string namepit Name of the strafe pit target object. --- @param #string namefoulline Name of the fould line distance marker object. +-- @param #string namefoulline Name of the foul line distance marker object. -- @return #number Foul line distance in meters. function RANGE:GetFoullineDistance(namepit, namefoulline) self:F({namepit=namepit, namefoulline=namefoulline}) @@ -1437,7 +1448,7 @@ function RANGE:GetFoullineDistance(namepit, namefoulline) -- Get the unit or static pit object. local pit=nil - if _staticpit==true then + if _staticpit == true then pit=STATIC:FindByName(namepit, false) elseif _staticpit==false then pit=UNIT:FindByName(namepit) @@ -1447,9 +1458,9 @@ function RANGE:GetFoullineDistance(namepit, namefoulline) -- Get the unit or static foul line object. local foul=nil - if _staticfoul==true then + if _staticfoul == true then foul=STATIC:FindByName(namefoulline, false) - elseif _staticfoul==false then + 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)) @@ -1457,7 +1468,7 @@ function RANGE:GetFoullineDistance(namepit, namefoulline) -- Get the distance between the two objects. local fouldist=0 - if pit~=nil and foul~=nil then + 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)) @@ -1552,7 +1563,6 @@ function RANGE:OnEventBirth(EventData) 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() @@ -1589,8 +1599,8 @@ function RANGE:OnEventBirth(EventData) self.timerCheckZone=TIMER:New(self._CheckInZone, self, EventData.IniUnitName):Start(1, 1) self.planes[_uid] = true end - end + end --- Range event handler for event hit. @@ -1607,7 +1617,7 @@ function RANGE:OnEventHit(EventData) -- Player info local _unitName = EventData.IniUnitName local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) - if _unit==nil or _playername==nil then + if _unit == nil or _playername == nil then return end @@ -1681,6 +1691,7 @@ function RANGE:OnEventHit(EventData) end end end + end --- Range event handler for event shot (when a unit releases a rocket or bomb (but not a fast firing gun). @@ -1690,10 +1701,10 @@ function RANGE:OnEventShot(EventData) self:F({eventshot = EventData}) -- Nil checks. - if EventData.Weapon==nil then + if EventData.Weapon == nil then return end - if EventData.IniDCSUnit==nil then + if EventData.IniDCSUnit == nil then return end @@ -1741,7 +1752,6 @@ function RANGE:OnEventShot(EventData) -- Only track if distance player to range is < 25 km. Also check that a player shot. No need to track AI weapons. if _track and dPR<=self.BombtrackThreshold and _unit and _playername then - -- Player data. local playerData=self.PlayerSettings[_playername] --#RANGE.PlayerData @@ -1911,7 +1921,7 @@ function RANGE:onafterStatus(From, Event, To) local text=string.format("Range status: %s", fsmstate) - if self.instructor then + if self.instructor then local alive="N/A" if self.instructorrelayname then local relay=UNIT:FindByName(self.instructorrelayname) @@ -2363,7 +2373,7 @@ function RANGE:_DisplayMyBombingResults(_unitName) end -- Best 10 runs only. - if i==self.ndisplayresult then + if i == self.ndisplayresult then break end @@ -2694,7 +2704,7 @@ function RANGE:_CheckPlayers() if not playersettings.inzone then playersettings.inzone=true - self:EnterRange(playersettings) + self:EnterRange(playersettings) end else @@ -2705,7 +2715,7 @@ function RANGE:_CheckPlayers() if playersettings.inzone==true then playersettings.inzone=false - self:ExitRange(playersettings) + self:ExitRange(playersettings) end end @@ -2738,12 +2748,12 @@ function RANGE:_CheckInZone(_unitName) if towardspit then local vec3=_unit:GetVec3() - local vec2={x=vec3.x, y=vec3.z} --DCS#Vec2 - local landheight=land.getHeight(vec2) + local vec2={x=vec3.x, y=vec3.z} --DCS#Vec2 + local landheight=land.getHeight(vec2) local unitalt=vec3.y-landheight - if unitalt<=self.strafemaxalt then - local unitinzone=zone:IsVec2InZone(vec2) + if unitalt<=self.strafemaxalt then + local unitinzone=zone:IsVec2InZone(vec2) return unitinzone end end @@ -3008,7 +3018,7 @@ end -- Helper Functions ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Get the number of shells a unit currently has. +--- Get the coordinate of a Bomb target. -- @param #RANGE self -- @param #RANGE.BombTarget target Bomb target data. -- @return Core.Point#COORDINATE Target coordinate. @@ -3016,7 +3026,7 @@ function RANGE:_GetBombTargetCoordinate(target) local coord=nil --Core.Point#COORDINATE - if target.type==RANGE.TargetType.UNIT then + if target.type == RANGE.TargetType.UNIT then if not target.move then -- Target should not move. @@ -3028,12 +3038,12 @@ function RANGE:_GetBombTargetCoordinate(target) end end - elseif target.type==RANGE.TargetType.STATIC then + elseif target.type == RANGE.TargetType.STATIC then -- Static targets dont move. coord=target.coordinate - elseif target.type==RANGE.TargetType.COORD then + elseif target.type == RANGE.TargetType.COORD then -- Coordinates dont move. coord=target.coordinate diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index e4a7b4e4c..b2706fa02 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -57,7 +57,6 @@ POSITIONABLE.__ = {} --- @field #POSITIONABLE.__.Cargo POSITIONABLE.__.Cargo = {} - --- A DCSPositionable -- @type DCSPositionable -- @field id_ The ID of the controllable in DCS @@ -75,16 +74,19 @@ end --- Destroys the POSITIONABLE. -- @param #POSITIONABLE self --- @param #boolean GenerateEvent (Optional) true if you want to generate a crash or dead event for the unit. +-- @param #boolean GenerateEvent (Optional) If true, generates a crash or dead event for the unit. If false, no event generated. If nil, a remove event is generated. -- @return #nil The DCS Unit is not existing or alive. -- @usage --- -- Air unit example: destroy the Helicopter and generate a S_EVENT_CRASH for each unit in the Helicopter group. +-- +-- Air unit example: destroy the Helicopter and generate a S_EVENT_CRASH for each unit in the Helicopter group. -- Helicopter = UNIT:FindByName( "Helicopter" ) -- Helicopter:Destroy( true ) +-- -- @usage -- -- Ground unit example: destroy the Tanks and generate a S_EVENT_DEAD for each unit in the Tanks group. -- Tanks = UNIT:FindByName( "Tanks" ) -- Tanks:Destroy( true ) +-- -- @usage -- -- Ship unit example: destroy the Ship silently. -- Ship = STATIC:FindByName( "Ship" ) @@ -147,7 +149,7 @@ function POSITIONABLE:GetPosition() return PositionablePosition end - BASE:E( { "Cannot GetPositionVec3", Positionable = self, Alive = self:IsAlive() } ) + BASE:E( { "Cannot GetPosition", Positionable = self, Alive = self:IsAlive() } ) return nil end @@ -157,8 +159,9 @@ end -- @return DCS#Vec3 X orientation, i.e. parallel to the direction of movement. -- @return DCS#Vec3 Y orientation, i.e. vertical. -- @return DCS#Vec3 Z orientation, i.e. perpendicular to the direction of movement. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetOrientation() - local position=self:GetPosition() + local position = self:GetPosition() if position then return position.x, position.y, position.z else @@ -170,8 +173,9 @@ end --- Returns a {@DCS#Vec3} table of the objects current X orientation in 3D space, i.e. along the direction of movement. -- @param Wrapper.Positionable#POSITIONABLE self -- @return DCS#Vec3 X orientation, i.e. parallel to the direction of movement. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetOrientationX() - local position=self:GetPosition() + local position = self:GetPosition() if position then return position.x else @@ -183,8 +187,9 @@ end --- Returns a {@DCS#Vec3} table of the objects current Y orientation in 3D space, i.e. vertical orientation. -- @param Wrapper.Positionable#POSITIONABLE self -- @return DCS#Vec3 Y orientation, i.e. vertical. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetOrientationY() - local position=self:GetPosition() + local position = self:GetPosition() if position then return position.y else @@ -196,8 +201,9 @@ end --- Returns a {@DCS#Vec3} table of the objects current Z orientation in 3D space, i.e. perpendicular to direction of movement. -- @param Wrapper.Positionable#POSITIONABLE self -- @return DCS#Vec3 Z orientation, i.e. perpendicular to movement. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetOrientationZ() - local position=self:GetPosition() + local position = self:GetPosition() if position then return position.z else @@ -228,19 +234,20 @@ end --- Returns the @{DCS#Vec3} vector indicating the 3D vector of the POSITIONABLE within the mission. -- @param Wrapper.Positionable#POSITIONABLE self --- @return DCS#Vec3 The 3D point vector of the POSITIONABLE or `nil` if it is not existing or alive. +-- @return DCS#Vec3 The 3D point vector of the POSITIONABLE. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetVec3() local DCSPositionable = self:GetDCSObject() if DCSPositionable then - local vec3=DCSPositionable:getPoint() + local vec3 = DCSPositionable:getPoint() if vec3 then return vec3 else - self:E("ERROR: Cannot get vec3!") + self:E( "ERROR: Cannot get vec3!" ) end end @@ -251,16 +258,17 @@ end --- Returns the @{DCS#Vec2} vector indicating the point in 2D of the POSITIONABLE within the mission. -- @param Wrapper.Positionable#POSITIONABLE self --- @return DCS#Vec2 The 2D point vector of the POSITIONABLE or #nil if it is not existing or alive. +-- @return DCS#Vec2 The 2D point vector of the POSITIONABLE. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetVec2() local DCSPositionable = self:GetDCSObject() if DCSPositionable then - local Vec3=DCSPositionable:getPoint() --DCS#Vec3 + local Vec3 = DCSPositionable:getPoint() -- DCS#Vec3 - return {x=Vec3.x, y=Vec3.z} + return { x = Vec3.x, y = Vec3.z } end self:E( { "Cannot GetVec2", Positionable = self, Alive = self:IsAlive() } ) @@ -282,7 +290,7 @@ function POSITIONABLE:GetPointVec2() local PositionablePointVec2 = POINT_VEC2:NewFromVec3( PositionableVec3 ) - --self:F( PositionablePointVec2 ) + -- self:F( PositionablePointVec2 ) return PositionablePointVec2 end @@ -307,14 +315,14 @@ function POSITIONABLE:GetPointVec3() if false and self.pointvec3 then -- Update vector. - self.pointvec3.x=PositionableVec3.x - self.pointvec3.y=PositionableVec3.y - self.pointvec3.z=PositionableVec3.z + self.pointvec3.x = PositionableVec3.x + self.pointvec3.y = PositionableVec3.y + self.pointvec3.z = PositionableVec3.z else -- Create a new POINT_VEC3 object. - self.pointvec3=POINT_VEC3:NewFromVec3(PositionableVec3) + self.pointvec3 = POINT_VEC3:NewFromVec3( PositionableVec3 ) end @@ -327,8 +335,10 @@ function POSITIONABLE:GetPointVec3() end --- Returns a COORDINATE object indicating the point in 3D of the POSITIONABLE within the mission. +-- If the POSITIONABLE has a COORDINATE OBJECT set, it updates it. If not, it creates a new COORDINATE object. -- @param Wrapper.Positionable#POSITIONABLE self -- @return Core.Point#COORDINATE The COORDINATE of the POSITIONABLE. +-- TODO: Seems to have been introduced with Airboss. Should it be renamed to better reflect the difference to "GetCoordinate" (it is currently ambiguous)? Or perhaps just be a switch in the the GetCoordinate function; forceCoordinateUpate? function POSITIONABLE:GetCoord() -- Get DCS object. @@ -337,20 +347,14 @@ function POSITIONABLE:GetCoord() if DCSPositionable then -- Get the current position. - local Vec3 = self:GetVec3() + local PositionableVec3 = self:GetVec3() if self.coordinate then - - -- Update vector. - self.coordinate.x=Vec3.x - self.coordinate.y=Vec3.y - self.coordinate.z=Vec3.z - + -- Update COORDINATE from 3D vector. + self.coordinate:UpdateFromVec3( PositionableVec3 ) else - -- New COORDINATE. - self.coordinate=COORDINATE:NewFromVec3(Vec3) - + self.coordinate = COORDINATE:NewFromVec3( PositionableVec3 ) end return self.coordinate @@ -375,9 +379,9 @@ function POSITIONABLE:GetCoordinate() -- Get the current position. local PositionableVec3 = self:GetVec3() - local coord=COORDINATE:NewFromVec3(PositionableVec3) + local coord = COORDINATE:NewFromVec3( PositionableVec3 ) - -- Return a new coordiante object. + -- Return a new coordinate object. return coord end @@ -393,36 +397,36 @@ end -- @param #number y Offset "above" the unit in meters. Default 0 m. -- @param #number z Offset in the direction "the wing" of the unit is pointing in meters. z>0 starboard, z<0 port. Default 0 m. -- @return Core.Point#COORDINATE The COORDINATE of the offset with respect to the orientation of the POSITIONABLE. -function POSITIONABLE:GetOffsetCoordinate(x,y,z) +function POSITIONABLE:GetOffsetCoordinate( x, y, z ) -- Default if nil. - x=x or 0 - y=y or 0 - z=z or 0 + x = x or 0 + y = y or 0 + z = z or 0 -- Vectors making up the coordinate system. - local X=self:GetOrientationX() - local Y=self:GetOrientationY() - local Z=self:GetOrientationZ() + local X = self:GetOrientationX() + local Y = self:GetOrientationY() + local Z = self:GetOrientationZ() -- Offset vector: x meters ahead, z meters starboard, y meters above. - local A={x=x, y=y, z=z} + local A = { x = x, y = y, z = z } -- Scale components of orthonormal coordinate vectors. - local x={x=X.x*A.x, y=X.y*A.x, z=X.z*A.x} - local y={x=Y.x*A.y, y=Y.y*A.y, z=Y.z*A.y} - local z={x=Z.x*A.z, y=Z.y*A.z, z=Z.z*A.z} + local x = { x = X.x * A.x, y = X.y * A.x, z = X.z * A.x } + local y = { x = Y.x * A.y, y = Y.y * A.y, z = Y.z * A.y } + local z = { x = Z.x * A.z, y = Z.y * A.z, z = Z.z * A.z } -- Add up vectors in the unit coordinate system ==> this gives the offset vector relative the the origin of the map. - local a={x=x.x+y.x+z.x, y=x.y+y.y+z.y, z=x.z+y.z+z.z} + local a = { x = x.x + y.x + z.x, y = x.y + y.y + z.y, z = x.z + y.z + z.z } -- Vector from the origin of the map to the unit. - local u=self:GetVec3() + local u = self:GetVec3() -- Translate offset vector from map origin to the unit: v=u+a. - local v={x=a.x+u.x, y=a.y+u.y, z=a.z+u.z} + local v = { x = a.x + u.x, y = a.y + u.y, z = a.z + u.z } - local coord=COORDINATE:NewFromVec3(v) + local coord = COORDINATE:NewFromVec3( v ) -- Return the offset coordinate. return coord @@ -445,7 +449,7 @@ function POSITIONABLE:GetRandomVec3( Radius ) if Radius then local PositionableRandomVec3 = {} - local angle = math.random() * math.pi*2; + local angle = math.random() * math.pi * 2; PositionableRandomVec3.x = PositionablePointVec3.x + math.cos( angle ) * math.random() * Radius; PositionableRandomVec3.y = PositionablePointVec3.y PositionableRandomVec3.z = PositionablePointVec3.z + math.sin( angle ) * math.random() * Radius; @@ -453,7 +457,7 @@ function POSITIONABLE:GetRandomVec3( Radius ) self:T3( PositionableRandomVec3 ) return PositionableRandomVec3 else - self:F("Radius is nil, returning the PointVec3 of the POSITIONABLE", PositionablePointVec3) + self:F( "Radius is nil, returning the PointVec3 of the POSITIONABLE", PositionablePointVec3 ) return PositionablePointVec3 end end @@ -463,18 +467,17 @@ function POSITIONABLE:GetRandomVec3( Radius ) return nil end - --- Get the bounding box of the underlying POSITIONABLE DCS Object. -- @param #POSITIONABLE self -- @return DCS#Box3 The bounding box of the POSITIONABLE. -- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetBoundingBox() --R2.1 +function POSITIONABLE:GetBoundingBox() self:F2() local DCSPositionable = self:GetDCSObject() if DCSPositionable then - local PositionableDesc = DCSPositionable:getDesc() --DCS#Desc + local PositionableDesc = DCSPositionable:getDesc() -- DCS#Desc if PositionableDesc then local PositionableBox = PositionableDesc.box return PositionableBox @@ -486,7 +489,6 @@ function POSITIONABLE:GetBoundingBox() --R2.1 return nil end - --- Get the object size. -- @param #POSITIONABLE self -- @return DCS#Distance Max size of object in x, z or 0 if bounding box could not be obtained. @@ -496,28 +498,29 @@ end function POSITIONABLE:GetObjectSize() -- Get bounding box. - local box=self:GetBoundingBox() + local box = self:GetBoundingBox() if box then - local x=box.max.x+math.abs(box.min.x) --length - local y=box.max.y+math.abs(box.min.y) --height - local z=box.max.z+math.abs(box.min.z) --width - return math.max(x,z), x , y, z + local x = box.max.x + math.abs( box.min.x ) -- length + local y = box.max.y + math.abs( box.min.y ) -- height + local z = box.max.z + math.abs( box.min.z ) -- width + return math.max( x, z ), x, y, z end - return 0,0,0,0 + return 0, 0, 0, 0 end --- Get the bounding radius of the underlying POSITIONABLE DCS Object. -- @param #POSITIONABLE self -- @param #number mindist (Optional) If bounding box is smaller than this value, mindist is returned. --- @return DCS#Distance The bounding radius of the POSITIONABLE or #nil if the POSITIONABLE is not existing or alive. -function POSITIONABLE:GetBoundingRadius(mindist) +-- @return DCS#Distance The bounding radius of the POSITIONABLE +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:GetBoundingRadius( mindist ) self:F2() local Box = self:GetBoundingBox() - local boxmin=mindist or 0 + local boxmin = mindist or 0 if Box then local X = Box.max.x - Box.min.x local Z = Box.max.z - Box.min.z @@ -541,7 +544,7 @@ function POSITIONABLE:GetAltitude() local DCSPositionable = self:GetDCSObject() if DCSPositionable then - local PositionablePointVec3 = DCSPositionable:getPoint() --DCS#Vec3 + local PositionablePointVec3 = DCSPositionable:getPoint() -- DCS#Vec3 return PositionablePointVec3.y end @@ -574,7 +577,6 @@ function POSITIONABLE:IsAboveRunway() return nil end - function POSITIONABLE:GetSize() local DCSObject = self:GetDCSObject() @@ -586,11 +588,10 @@ function POSITIONABLE:GetSize() end end - - --- Returns the POSITIONABLE heading in degrees. -- @param Wrapper.Positionable#POSITIONABLE self --- @return #number The POSITIONABLE heading in degrees or `nil` if not existing or alive. +-- @return #number The POSITIONABLE heading in degrees. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetHeading() local DCSPositionable = self:GetDCSObject() @@ -598,22 +599,21 @@ function POSITIONABLE:GetHeading() if DCSPositionable then local PositionablePosition = DCSPositionable:getPosition() - + if PositionablePosition then local PositionableHeading = math.atan2( PositionablePosition.x.z, PositionablePosition.x.x ) - + if PositionableHeading < 0 then PositionableHeading = PositionableHeading + 2 * math.pi end - + PositionableHeading = PositionableHeading * 180 / math.pi - + return PositionableHeading end end - self:E({"Cannot GetHeading", Positionable = self, Alive = self:IsAlive()}) - + self:E( { "Cannot GetHeading", Positionable = self, Alive = self:IsAlive() } ) return nil end @@ -623,6 +623,7 @@ end -- If the unit is a helicopter or a plane, then this method will return true, otherwise false. -- @param #POSITIONABLE self -- @return #boolean Air category evaluation result. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:IsAir() self:F2() @@ -638,6 +639,7 @@ function POSITIONABLE:IsAir() return IsAirResult end + self:E( { "Cannot check IsAir", Positionable = self, Alive = self:IsAlive() } ) return nil end @@ -645,6 +647,7 @@ end -- If the unit is a ground vehicle or infantry, this method will return true, otherwise false. -- @param #POSITIONABLE self -- @return #boolean Ground category evaluation result. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:IsGround() self:F2() @@ -654,19 +657,20 @@ function POSITIONABLE:IsGround() local UnitDescriptor = DCSUnit:getDesc() self:T3( { UnitDescriptor.category, Unit.Category.GROUND_UNIT } ) - local IsGroundResult = ( UnitDescriptor.category == Unit.Category.GROUND_UNIT ) + local IsGroundResult = (UnitDescriptor.category == Unit.Category.GROUND_UNIT) self:T3( IsGroundResult ) return IsGroundResult end + self:E( { "Cannot check IsGround", Positionable = self, Alive = self:IsAlive() } ) return nil end - --- Returns if the unit is of ship category. -- @param #POSITIONABLE self -- @return #boolean Ship category evaluation result. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:IsShip() self:F2() @@ -674,16 +678,18 @@ function POSITIONABLE:IsShip() if DCSUnit then local UnitDescriptor = DCSUnit:getDesc() + self:T3( { UnitDescriptor.category, Unit.Category.SHIP } ) - local IsShip = ( UnitDescriptor.category == Unit.Category.SHIP ) + local IsShipResult = (UnitDescriptor.category == Unit.Category.SHIP) - return IsShip + self:T3( IsShipResult ) + return IsShipResult end + self:E( { "Cannot check IsShip", Positionable = self, Alive = self:IsAlive() } ) return nil end - --- Returns if the unit is a submarine. -- @param #POSITIONABLE self -- @return #boolean Submarines attributes result. @@ -701,10 +707,10 @@ function POSITIONABLE:IsSubmarine() end end + self:E( { "Cannot check IsSubmarine", Positionable = self, Alive = self:IsAlive() } ) return nil end - --- Returns true if the POSITIONABLE is in the air. -- Polymorphic, is overridden in GROUP and UNIT. -- @param Wrapper.Positionable#POSITIONABLE self @@ -716,8 +722,7 @@ function POSITIONABLE:InAir() return nil end - ---- Returns the a @{Velocity} object from the positionable. +--- Returns the a @{Velocity} object from the POSITIONABLE. -- @param Wrapper.Positionable#POSITIONABLE self -- @return Core.Velocity#VELOCITY Velocity The Velocity object. -- @return #nil The POSITIONABLE is not existing or alive. @@ -736,8 +741,6 @@ function POSITIONABLE:GetVelocity() return nil end - - --- Returns the POSITIONABLE velocity Vec3 vector. -- @param Wrapper.Positionable#POSITIONABLE self -- @return DCS#Vec3 The velocity Vec3 vector @@ -760,30 +763,29 @@ end --- Get relative velocity with respect to another POSITIONABLE. -- @param #POSITIONABLE self --- @param #POSITIONABLE positionable Other positionable. +-- @param #POSITIONABLE positionable Other POSITIONABLE. -- @return #number Relative velocity in m/s. -function POSITIONABLE:GetRelativeVelocity(positionable) +function POSITIONABLE:GetRelativeVelocity( positionable ) self:F2( self.PositionableName ) - local v1=self:GetVelocityVec3() - local v2=positionable:GetVelocityVec3() + local v1 = self:GetVelocityVec3() + local v2 = positionable:GetVelocityVec3() - local vtot=UTILS.VecAdd(v1,v2) + local vtot = UTILS.VecAdd( v1, v2 ) - return UTILS.VecNorm(vtot) + return UTILS.VecNorm( vtot ) end - --- Returns the POSITIONABLE height in meters. -- @param Wrapper.Positionable#POSITIONABLE self --- @return DCS#Vec3 The height of the positionable. +-- @return DCS#Vec3 The height of the POSITIONABLE in meters. -- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetHeight() --R2.1 +function POSITIONABLE:GetHeight() self:F2( self.PositionableName ) local DCSPositionable = self:GetDCSObject() - if DCSPositionable then + if DCSPositionable and DCSPositionable:isExist() then local PositionablePosition = DCSPositionable:getPosition() if PositionablePosition then local PositionableHeight = PositionablePosition.p.y @@ -795,10 +797,9 @@ function POSITIONABLE:GetHeight() --R2.1 return nil end - --- Returns the POSITIONABLE velocity in km/h. -- @param Wrapper.Positionable#POSITIONABLE self --- @return #number The velocity in km/h +-- @return #number The velocity in km/h. function POSITIONABLE:GetVelocityKMH() self:F2( self.PositionableName ) @@ -838,12 +839,13 @@ end -- @return #number The velocity in knots. function POSITIONABLE:GetVelocityKNOTS() self:F2( self.PositionableName ) - return UTILS.MpsToKnots(self:GetVelocityMPS()) + return UTILS.MpsToKnots( self:GetVelocityMPS() ) end ---- Returns the Angle of Attack of a positionable. +--- Returns the Angle of Attack of a POSITIONABLE. -- @param Wrapper.Positionable#POSITIONABLE self -- @return #number Angle of attack in degrees. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetAoA() -- Get position of the unit. @@ -854,34 +856,34 @@ function POSITIONABLE:GetAoA() -- Get velocity vector of the unit. local unitvel = self:GetVelocityVec3() - if unitvel and UTILS.VecNorm(unitvel)~=0 then + if unitvel and UTILS.VecNorm( unitvel ) ~= 0 then -- Get wind vector including turbulences. - local wind=self:GetCoordinate():GetWindWithTurbulenceVec3() + local wind = self:GetCoordinate():GetWindWithTurbulenceVec3() -- Include wind vector. - unitvel.x=unitvel.x-wind.x - unitvel.y=unitvel.y-wind.y - unitvel.z=unitvel.z-wind.z + unitvel.x = unitvel.x - wind.x + unitvel.y = unitvel.y - wind.y + unitvel.z = unitvel.z - wind.z -- Unit velocity transformed into aircraft axes directions. local AxialVel = {} -- Transform velocity components in direction of aircraft axes. - AxialVel.x = UTILS.VecDot(unitpos.x, unitvel) - AxialVel.y = UTILS.VecDot(unitpos.y, unitvel) - AxialVel.z = UTILS.VecDot(unitpos.z, unitvel) + AxialVel.x = UTILS.VecDot( unitpos.x, unitvel ) + AxialVel.y = UTILS.VecDot( unitpos.y, unitvel ) + AxialVel.z = UTILS.VecDot( unitpos.z, unitvel ) -- AoA is angle between unitpos.x and the x and y velocities. - local AoA = math.acos(UTILS.VecDot({x = 1, y = 0, z = 0}, {x = AxialVel.x, y = AxialVel.y, z = 0})/UTILS.VecNorm({x = AxialVel.x, y = AxialVel.y, z = 0})) + local AoA = math.acos( UTILS.VecDot( { x = 1, y = 0, z = 0 }, { x = AxialVel.x, y = AxialVel.y, z = 0 } ) / UTILS.VecNorm( { x = AxialVel.x, y = AxialVel.y, z = 0 } ) ) - --Set correct direction: + -- Set correct direction: if AxialVel.y > 0 then AoA = -AoA end -- Return AoA value in degrees. - return math.deg(AoA) + return math.deg( AoA ) end end @@ -889,9 +891,10 @@ function POSITIONABLE:GetAoA() return nil end ---- Returns the unit's climb or descent angle. +--- Returns the climb or descent angle of the POSITIONABLE. -- @param Wrapper.Positionable#POSITIONABLE self --- @return #number Climb or descent angle in degrees. Or 0 if velocity vector norm is zero (or nil). Or nil, if the position of the POSITIONABLE returns nil. +-- @return #number Climb or descent angle in degrees. Or 0 if velocity vector norm is zero. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetClimbAngle() -- Get position of the unit. @@ -902,31 +905,33 @@ function POSITIONABLE:GetClimbAngle() -- Get velocity vector of the unit. local unitvel = self:GetVelocityVec3() - if unitvel and UTILS.VecNorm(unitvel)~=0 then + if unitvel and UTILS.VecNorm( unitvel ) ~= 0 then -- Calculate climb angle. - local angle=math.asin(unitvel.y/UTILS.VecNorm(unitvel)) + local angle = math.asin( unitvel.y / UTILS.VecNorm( unitvel ) ) -- Return angle in degrees. - return math.deg(angle) + return math.deg( angle ) else return 0 end + end return nil end ---- Returns the pitch angle of a unit. +--- Returns the pitch angle of a POSITIONABLE. -- @param Wrapper.Positionable#POSITIONABLE self --- @return #number Pitch ange in degrees. +-- @return #number Pitch angle in degrees. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetPitch() -- Get position of the unit. local unitpos = self:GetPosition() if unitpos then - return math.deg(math.asin(unitpos.x.y)) + return math.deg( math.asin( unitpos.x.y ) ) end return nil @@ -934,7 +939,8 @@ end --- Returns the roll angle of a unit. -- @param Wrapper.Positionable#POSITIONABLE self --- @return #number Pitch ange in degrees. +-- @return #number Pitch angle in degrees. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetRoll() -- Get position of the unit. @@ -942,87 +948,98 @@ function POSITIONABLE:GetRoll() if unitpos then - --first, make a vector that is perpendicular to y and unitpos.x with cross product - local cp = UTILS.VecCross(unitpos.x, {x = 0, y = 1, z = 0}) + -- First, make a vector that is perpendicular to y and unitpos.x with cross product + local cp = UTILS.VecCross( unitpos.x, { x = 0, y = 1, z = 0 } ) - --now, get dot product of of this cross product with unitpos.z - local dp = UTILS.VecDot(cp, unitpos.z) + -- Now, get dot product of of this cross product with unitpos.z + local dp = UTILS.VecDot( cp, unitpos.z ) - --now get the magnitude of the roll (magnitude of the angle between two vectors is acos(vec1.vec2/|vec1||vec2|) - local Roll = math.acos(dp/(UTILS.VecNorm(cp)*UTILS.VecNorm(unitpos.z))) + -- Now get the magnitude of the roll (magnitude of the angle between two vectors is acos(vec1.vec2/|vec1||vec2|) + local Roll = math.acos( dp / (UTILS.VecNorm( cp ) * UTILS.VecNorm( unitpos.z )) ) - --now, have to get sign of roll. - -- by convention, making right roll positive - -- to get sign of roll, use the y component of unitpos.z. For right roll, y component is negative. + -- Now, have to get sign of roll. By convention, making right roll positive + -- To get sign of roll, use the y component of unitpos.z. For right roll, y component is negative. if unitpos.z.y > 0 then -- left roll, flip the sign of the roll Roll = -Roll end - return math.deg(Roll) - end -end + return math.deg( Roll ) ---- Returns the yaw angle of a unit. --- @param Wrapper.Positionable#POSITIONABLE self --- @return #number Yaw ange in degrees. -function POSITIONABLE:GetYaw() - - local unitpos = self:GetPosition() - if unitpos then - -- get unit velocity - local unitvel = self:GetVelocityVec3() - - if unitvel and UTILS.VecNorm(unitvel) ~= 0 then --must have non-zero velocity! - local AxialVel = {} --unit velocity transformed into aircraft axes directions - - --transform velocity components in direction of aircraft axes. - AxialVel.x = UTILS.VecDot(unitpos.x, unitvel) - AxialVel.y = UTILS.VecDot(unitpos.y, unitvel) - AxialVel.z = UTILS.VecDot(unitpos.z, unitvel) - - --Yaw is the angle between unitpos.x and the x and z velocities - --define right yaw as positive - 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})) - - --now set correct direction: - if AxialVel.z > 0 then - Yaw = -Yaw - end - return Yaw - end - end - -end - - ---- Returns the message text with the callsign embedded (if there is one). --- @param #POSITIONABLE self --- @param #string Message The message text --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. --- @return #string The message text -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 +--- Returns the yaw angle of a POSITIONABLE. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return #number Yaw angle in degrees. +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:GetYaw() + + -- Get position of the unit. + local unitpos = self:GetPosition() + + if unitpos then + + -- get unit velocity + local unitvel = self:GetVelocityVec3() + + if unitvel and UTILS.VecNorm( unitvel ) ~= 0 then -- must have non-zero velocity! + local AxialVel = {} -- unit velocity transformed into aircraft axes directions + + -- transform velocity components in direction of aircraft axes. + AxialVel.x = UTILS.VecDot( unitpos.x, unitvel ) + AxialVel.y = UTILS.VecDot( unitpos.y, unitvel ) + AxialVel.z = UTILS.VecDot( unitpos.z, unitvel ) + + -- Yaw is the angle between unitpos.x and the x and z velocities + -- define right yaw as positive + 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 } ) ) + + -- now set correct direction: + if AxialVel.z > 0 then + Yaw = -Yaw + end + return Yaw + end + + end + + return nil +end + +--- Returns the message text with the callsign embedded (if there is one). +-- @param #POSITIONABLE self +-- @param #string Message The message text. +-- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. +-- @return #string The message text. +-- @return #nil The POSITIONABLE is not existing or alive. +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 --- Returns a message with the callsign embedded (if there is one). -- @param #POSITIONABLE self -- @param #string Message The message text -- @param DCS#Duration Duration The duration of the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. +-- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. -- @return Core.Message#MESSAGE -function POSITIONABLE:GetMessage( Message, Duration, Name ) --R2.1 changed callsign and name and using GetMessageText +function POSITIONABLE:GetMessage( Message, Duration, Name ) local DCSObject = self:GetDCSObject() + if DCSObject then local MessageText = self:GetMessageText( Message, Name ) return MESSAGE:New( MessageText, Duration ) @@ -1035,7 +1052,7 @@ end -- @param #POSITIONABLE self -- @param #string Message The message text -- @param Core.Message#MESSAGE MessageType MessageType The message type. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. +-- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. -- @return Core.Message#MESSAGE function POSITIONABLE:GetMessageType( Message, MessageType, Name ) -- R2.2 changed callsign and name and using GetMessageText @@ -1053,7 +1070,7 @@ end -- @param #POSITIONABLE self -- @param #string Message The message text -- @param DCS#Duration Duration The duration of the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. +-- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. function POSITIONABLE:MessageToAll( Message, Duration, Name ) self:F2( { Message, Duration } ) @@ -1071,7 +1088,7 @@ end -- @param #string Message The message text -- @param DCS#Duration Duration The duration of the message. -- @param DCS#coalition MessageCoalition The Coalition receiving the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. +-- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. function POSITIONABLE:MessageToCoalition( Message, Duration, MessageCoalition, Name ) self:F2( { Message, Duration } ) @@ -1085,14 +1102,13 @@ function POSITIONABLE:MessageToCoalition( Message, Duration, MessageCoalition, N return nil end - --- Send a message to a coalition. -- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. -- @param #POSITIONABLE self -- @param #string Message The message text -- @param Core.Message#MESSAGE.Type MessageType The message type that determines the duration. -- @param DCS#coalition MessageCoalition The Coalition receiving the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. +-- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. function POSITIONABLE:MessageTypeToCoalition( Message, MessageType, MessageCoalition, Name ) self:F2( { Message, MessageType } ) @@ -1106,13 +1122,12 @@ function POSITIONABLE:MessageTypeToCoalition( Message, MessageType, MessageCoali return nil end - --- Send a message to the red coalition. -- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. -- @param #POSITIONABLE self -- @param #string Message The message text -- @param DCS#Duration Duration The duration of the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. +-- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. function POSITIONABLE:MessageToRed( Message, Duration, Name ) self:F2( { Message, Duration } ) @@ -1129,7 +1144,7 @@ end -- @param #POSITIONABLE self -- @param #string Message The message text -- @param DCS#Duration Duration The duration of the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. +-- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. function POSITIONABLE:MessageToBlue( Message, Duration, Name ) self:F2( { Message, Duration } ) @@ -1147,7 +1162,7 @@ end -- @param #string Message The message text -- @param DCS#Duration Duration The duration of the message. -- @param Wrapper.Client#CLIENT Client The client object receiving the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. +-- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. function POSITIONABLE:MessageToClient( Message, Duration, Client, Name ) self:F2( { Message, Duration } ) @@ -1165,7 +1180,7 @@ end -- @param #string Message The message text -- @param DCS#Duration Duration The duration of the message. -- @param Wrapper.Group#GROUP MessageGroup The GROUP object receiving the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. +-- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. function POSITIONABLE:MessageToGroup( Message, Duration, MessageGroup, Name ) self:F2( { Message, Duration } ) @@ -1178,11 +1193,15 @@ function POSITIONABLE:MessageToGroup( Message, Duration, MessageGroup, Name ) 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 } ) + BASE:E( { + "Message not sent to Group; Positionable is not alive ...", + Message = Message, + Positionable = self, + MessageGroup = MessageGroup + } ) end end - return nil end @@ -1192,7 +1211,7 @@ end -- @param #string Message The message text -- @param Core.Message#MESSAGE.Type MessageType The message type that determines the duration. -- @param Wrapper.Group#GROUP MessageGroup The GROUP object receiving the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. +-- @param #string Name (Optional) The Name of the sender. If not provided, the Name is the type of the POSITIONABLE. function POSITIONABLE:MessageTypeToGroup( Message, MessageType, MessageGroup, Name ) self:F2( { Message, MessageType } ) @@ -1212,18 +1231,16 @@ end -- @param #string Message The message text -- @param DCS#Duration Duration The duration of the message. -- @param Core.Set#SET_GROUP MessageSetGroup The SET_GROUP collection receiving the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. -function POSITIONABLE:MessageToSetGroup( Message, Duration, MessageSetGroup, Name ) --R2.1 +-- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. +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 - ) + MessageSetGroup:ForEachGroupAlive( function( MessageGroup ) + self:GetMessage( Message, Duration, Name ):ToGroup( MessageGroup ) + end ) end end @@ -1235,7 +1252,7 @@ end -- @param #POSITIONABLE self -- @param #string Message The message text -- @param DCS#Duration Duration The duration of the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. +-- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. function POSITIONABLE:Message( Message, Duration, Name ) self:F2( { Message, Duration } ) @@ -1251,17 +1268,17 @@ end -- Set parameters with the methods provided, then use RADIO:Broadcast() to actually broadcast the message -- @param #POSITIONABLE self -- @return Core.Radio#RADIO Radio -function POSITIONABLE:GetRadio() --R2.1 - self:F2(self) - return RADIO:New(self) +function POSITIONABLE:GetRadio() + self:F2( self ) + return RADIO:New( self ) end --- Create a @{Core.Radio#BEACON}, to allow this POSITIONABLE to broadcast beacon signals -- @param #POSITIONABLE self -- @return Core.Radio#RADIO Radio -function POSITIONABLE:GetBeacon() --R2.1 - self:F2(self) - return BEACON:New(self) +function POSITIONABLE:GetBeacon() + self:F2( self ) + return BEACON:New( self ) end --- Start Lasing a POSITIONABLE @@ -1270,7 +1287,7 @@ end -- @param #number LaserCode Laser code or random number in [1000, 9999]. -- @param #number Duration Duration of lasing in seconds. -- @return Core.Spot#SPOT -function POSITIONABLE:LaseUnit( Target, LaserCode, Duration ) --R2.1 +function POSITIONABLE:LaseUnit( Target, LaserCode, Duration ) self:F2() LaserCode = LaserCode or math.random( 1000, 9999 ) @@ -1278,9 +1295,9 @@ function POSITIONABLE:LaseUnit( Target, LaserCode, Duration ) --R2.1 local RecceDcsUnit = self:GetDCSObject() local TargetVec3 = Target:GetVec3() - self:F("bulding spot") + self:F( "building spot" ) self.Spot = SPOT:New( self ) -- Core.Spot#SPOT - self.Spot:LaseOn( Target, LaserCode, Duration) + self.Spot:LaseOn( Target, LaserCode, Duration ) self.LaserCode = LaserCode return self.Spot @@ -1289,17 +1306,17 @@ end --- Start Lasing a COORDINATE. -- @param #POSITIONABLE self --- @param Core.Point#COORDIUNATE Coordinate The coordinate where the lase is pointing at. +-- @param Core.Point#COORDINATE Coordinate The coordinate where the lase is pointing at. -- @param #number LaserCode Laser code or random number in [1000, 9999]. -- @param #number Duration Duration of lasing in seconds. -- @return Core.Spot#SPOT -function POSITIONABLE:LaseCoordinate(Coordinate, LaserCode, Duration) +function POSITIONABLE:LaseCoordinate( Coordinate, LaserCode, Duration ) self:F2() - LaserCode = LaserCode or math.random(1000, 9999) + LaserCode = LaserCode or math.random( 1000, 9999 ) - self.Spot = SPOT:New(self) -- Core.Spot#SPOT - self.Spot:LaseOnCoordinate(Coordinate, LaserCode, Duration) + self.Spot = SPOT:New( self ) -- Core.Spot#SPOT + self.Spot:LaseOnCoordinate( Coordinate, LaserCode, Duration ) self.LaserCode = LaserCode return self.Spot @@ -1308,7 +1325,7 @@ end --- Stop Lasing a POSITIONABLE -- @param #POSITIONABLE self -- @return #POSITIONABLE -function POSITIONABLE:LaseOff() --R2.1 +function POSITIONABLE:LaseOff() self:F2() if self.Spot then @@ -1322,7 +1339,7 @@ end --- Check if the POSITIONABLE is lasing a target -- @param #POSITIONABLE self -- @return #boolean true if it is lasing a target -function POSITIONABLE:IsLasing() --R2.1 +function POSITIONABLE:IsLasing() self:F2() local Lasing = false @@ -1337,7 +1354,7 @@ end --- Get the Spot -- @param #POSITIONABLE self -- @return Core.Spot#SPOT The Spot -function POSITIONABLE:GetSpot() --R2.1 +function POSITIONABLE:GetSpot() return self.Spot end @@ -1345,7 +1362,7 @@ end --- Get the last assigned laser code -- @param #POSITIONABLE self -- @return #number The laser code -function POSITIONABLE:GetLaserCode() --R2.1 +function POSITIONABLE:GetLaserCode() return self.LaserCode end @@ -1368,8 +1385,6 @@ do -- Cargo return self.__.Cargo end - - --- Remove cargo. -- @param #POSITIONABLE self -- @param Core.Cargo#CARGO Cargo @@ -1457,7 +1472,7 @@ do -- Cargo if WeightLimit then self.__.CargoBayWeightLimit = WeightLimit - elseif self.__.CargoBayWeightLimit~=nil then + elseif self.__.CargoBayWeightLimit ~= nil then -- Value already set ==> Do nothing! else -- If weightlimit is not provided, we will calculate it depending on the type of unit. @@ -1465,29 +1480,29 @@ do -- Cargo -- When an airplane or helicopter, we calculate the weightlimit based on the descriptor. if self:IsAir() then local Desc = self:GetDesc() - self:F({Desc=Desc}) + self:F( { Desc = Desc } ) local Weights = { - ["C-17A"] = 35000, --77519 cannot be used, because it loads way too much apcs and infantry., - ["C-130"] = 22000 --The real value cannot be used, because it loads way too much apcs and infantry., + ["C-17A"] = 35000, -- 77519 cannot be used, because it loads way too many APCs and infantry. + ["C-130"] = 22000 -- The real value cannot be used, because it loads way too many APCs and infantry. } - self.__.CargoBayWeightLimit = Weights[Desc.typeName] or ( Desc.massMax - ( Desc.massEmpty + Desc.fuelMassMax ) ) + self.__.CargoBayWeightLimit = Weights[Desc.typeName] or (Desc.massMax - (Desc.massEmpty + Desc.fuelMassMax)) elseif self:IsShip() then local Desc = self:GetDesc() - self:F({Desc=Desc}) + 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, -- Higgins Boat can load 3700 kg of general cargo or 36 men (source wikipedia). - ["USS_Samuel_Chase"] = 25000, -- Let's say 25 tons for now. Wiki says 33 Higgins boats, which would be 264 tons (can't be right!) and/or 578 troops. - ["LST_Mk2"] =2100000, -- Can carry 2100 tons according to wiki source! + ["Type_071"] = 245000, + ["LHA_Tarawa"] = 500000, + ["Ropucha-class"] = 150000, + ["Dry-cargo ship-1"] = 70000, + ["Dry-cargo ship-2"] = 70000, + ["Higgins_boat"] = 3700, -- Higgins Boat can load 3700 kg of general cargo or 36 men (source wikipedia). + ["USS_Samuel_Chase"] = 25000, -- Let's say 25 tons for now. Wiki says 33 Higgins boats, which would be 264 tons (can't be right!) and/or 578 troops. + ["LST_Mk2"] = 2100000 -- Can carry 2100 tons according to wiki source! } - self.__.CargoBayWeightLimit = ( Weights[Desc.typeName] or 50000 ) + self.__.CargoBayWeightLimit = (Weights[Desc.typeName] or 50000) else local Desc = self:GetDesc() @@ -1496,14 +1511,14 @@ do -- Cargo ["AAV7"] = 25, ["Bedford_MWD"] = 8, -- new by kappa ["Blitz_36-6700A"] = 10, -- new by kappa - ["BMD-1"] = 9, -- IRL should be 4 passengers + ["BMD-1"] = 9, -- IRL should be 4 passengers ["BMP-1"] = 8, ["BMP-2"] = 7, - ["BMP-3"] = 8, -- IRL should be 7+2 passengers + ["BMP-3"] = 8, -- IRL should be 7+2 passengers ["Boman"] = 25, ["BTR-80"] = 9, -- IRL should be 7 passengers ["BTR-82A"] = 9, -- new by kappa -- IRL should be 7 passengers - ["BTR_D"] = 12, -- IRL should be 10 passengers + ["BTR_D"] = 12, -- IRL should be 10 passengers ["Cobra"] = 8, ["Land_Rover_101_FC"] = 11, -- new by kappa ["Land_Rover_109_S3"] = 7, -- new by kappa @@ -1514,11 +1529,11 @@ do -- Cargo ["M1126 Stryker ICV"] = 9, ["M1134 Stryker ATGM"] = 9, ["M2A1_halftrack"] = 9, - ["M-113"] = 9, -- IRL should be 11 passengers + ["M-113"] = 9, -- IRL should be 11 passengers ["Marder"] = 6, ["MCV-80"] = 9, -- IRL should be 7 passengers ["MLRS FDDM"] = 4, - ["MTLB"] = 25, -- IRL should be 11 passengers + ["MTLB"] = 25, -- IRL should be 11 passengers ["GAZ-66"] = 8, ["GAZ-3307"] = 12, ["GAZ-3308"] = 14, @@ -1534,14 +1549,14 @@ do -- Cargo ["Ural-4320 APA-5D"] = 10, ["Ural-4320T"] = 14, ["ZBD04A"] = 7, -- new by kappa - ["VAB_Mephisto"] = 8, -- new by Apple + ["VAB_Mephisto"] = 8 -- new by Apple } - local CargoBayWeightLimit = ( Weights[Desc.typeName] or 0 ) * 95 + local CargoBayWeightLimit = (Weights[Desc.typeName] or 0) * 95 self.__.CargoBayWeightLimit = CargoBayWeightLimit end end - self:F({CargoBayWeightLimit = self.__.CargoBayWeightLimit}) + self:F( { CargoBayWeightLimit = self.__.CargoBayWeightLimit } ) end end --- Cargo @@ -1550,28 +1565,28 @@ end --- Cargo -- @param Utilities.Utils#FLARECOLOR FlareColor function POSITIONABLE:Flare( FlareColor ) self:F2() - trigger.action.signalFlare( self:GetVec3(), FlareColor , 0 ) + trigger.action.signalFlare( self:GetVec3(), FlareColor, 0 ) end --- Signal a white flare at the position of the POSITIONABLE. -- @param #POSITIONABLE self function POSITIONABLE:FlareWhite() self:F2() - trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.White , 0 ) + trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.White, 0 ) end --- Signal a yellow flare at the position of the POSITIONABLE. -- @param #POSITIONABLE self function POSITIONABLE:FlareYellow() self:F2() - trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.Yellow , 0 ) + trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.Yellow, 0 ) end --- Signal a green flare at the position of the POSITIONABLE. -- @param #POSITIONABLE self function POSITIONABLE:FlareGreen() self:F2() - trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.Green , 0 ) + trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.Green, 0 ) end --- Signal a red flare at the position of the POSITIONABLE. @@ -1586,9 +1601,9 @@ end --- Smoke the POSITIONABLE. -- @param #POSITIONABLE self --- @param Utilities.Utils#SMOKECOLOR SmokeColor The color to smoke to positionable. --- @param #number Range The range in meters to randomize the smoking around the positionable. --- @param #number AddHeight The height in meters to add to the altitude of the positionable. +-- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. +-- @param #number Range The range in meters to randomize the smoking around the POSITIONABLE. +-- @param #number AddHeight The height in meters to add to the altitude of the POSITIONABLE. function POSITIONABLE:Smoke( SmokeColor, Range, AddHeight ) self:F2() if Range then @@ -1638,7 +1653,6 @@ function POSITIONABLE:SmokeBlue() trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Blue ) end - --- Returns true if the unit is within a @{Zone}. -- @param #POSITIONABLE self -- @param Core.Zone#ZONE_BASE Zone The zone to test. diff --git a/Moose Development/Moose/Wrapper/Static.lua b/Moose Development/Moose/Wrapper/Static.lua index 8377d9803..7394d0861 100644 --- a/Moose Development/Moose/Wrapper/Static.lua +++ b/Moose Development/Moose/Wrapper/Static.lua @@ -1,52 +1,46 @@ --- **Wrapper** -- STATIC wraps the DCS StaticObject class. --- +-- -- === --- +-- -- ### Author: **FlightControl** --- +-- -- ### Contributions: **funkyfranky** --- +-- -- === --- +-- -- @module Wrapper.Static -- @image Wrapper_Static.JPG - - --- @type STATIC -- @extends Wrapper.Positionable#POSITIONABLE - --- Wrapper class to handle Static objects. --- +-- -- Note that Statics are almost the same as Units, but they don't have a controller. -- The @{Wrapper.Static#STATIC} class is a wrapper class to handle the DCS Static objects: --- +-- -- * Wraps the DCS Static objects. -- * Support all DCS Static APIs. -- * Enhance with Static specific APIs not in the DCS API set. --- +-- -- ## STATIC reference methods --- +-- -- For each DCS Static will have a STATIC wrapper object (instance) within the _@{DATABASE} object. -- This is done at the beginning of the mission (when the mission starts). --- +-- -- The STATIC class does not contain a :New() method, rather it provides :Find() methods to retrieve the object reference -- using the Static Name. --- +-- -- Another thing to know is that STATIC objects do not "contain" the DCS Static object. -- The STATIc methods will reference the DCS Static object by name when it is needed during API execution. -- If the DCS Static object does not exist or is nil, the STATIC methods will return nil and log an exception in the DCS.log file. --- +-- -- The STATIc class provides the following functions to retrieve quickly the relevant STATIC instance: --- +-- -- * @{#STATIC.FindByName}(): Find a STATIC instance from the _DATABASE object using a DCS Static name. --- --- IMPORTANT: ONE SHOULD NEVER SANATIZE these STATIC OBJECT REFERENCES! (make the STATIC object references nil). --- +-- +-- IMPORTANT: ONE SHOULD NEVER SANITIZE these STATIC OBJECT REFERENCES! (make the STATIC object references nil). +-- -- @field #STATIC -STATIC = { - ClassName = "STATIC", -} - +STATIC = { ClassName = "STATIC" } --- Register a static object. -- @param #STATIC self @@ -58,7 +52,6 @@ function STATIC:Register( StaticName ) return self end - --- Finds a STATIC from the _DATABASE using a DCSStatic object. -- @param #STATIC self -- @param DCS#StaticObject DCSStatic An existing DCS Static object reference. @@ -83,13 +76,13 @@ function STATIC:FindByName( StaticName, RaiseError ) -- Set static name. self.StaticName = StaticName - + if StaticFound then - return StaticFound + return StaticFound end - - if RaiseError == nil or RaiseError == true then - error( "STATIC not found for: " .. StaticName ) + + if RaiseError == nil or RaiseError == true then + error( "STATIC not found for: " .. StaticName ) end return nil @@ -97,38 +90,39 @@ end --- Destroys the STATIC. -- @param #STATIC self --- @param #boolean GenerateEvent (Optional) true if you want to generate a crash or dead event for the static. --- @return #nil The DCS StaticObject is not existing or alive. +-- @param #boolean GenerateEvent (Optional) true to generate a crash or dead event, false to not generate any event. `nil` (default) creates a remove event. +-- @return #nil The DCS StaticObject is not existing or alive. +-- -- @usage -- -- Air static example: destroy the static Helicopter and generate a S_EVENT_CRASH. -- Helicopter = STATIC:FindByName( "Helicopter" ) -- Helicopter:Destroy( true ) --- +-- -- @usage -- -- Ground static example: destroy the static Tank and generate a S_EVENT_DEAD. -- Tanks = UNIT:FindByName( "Tank" ) -- Tanks:Destroy( true ) --- +-- -- @usage -- -- Ship static example: destroy the Ship silently. -- Ship = STATIC:FindByName( "Ship" ) -- Ship:Destroy() --- +-- -- @usage -- -- Destroy without event generation example. -- Ship = STATIC:FindByName( "Boat" ) --- Ship:Destroy( false ) -- Don't generate an event upon destruction. --- +-- Ship:Destroy( false ) -- Don't generate any event upon destruction. +-- 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 ) @@ -140,7 +134,7 @@ function STATIC:Destroy( GenerateEvent ) else self:CreateEventRemoveUnit( timer.getTime(), DCSObject ) end - + DCSObject:destroy() return true end @@ -148,17 +142,16 @@ function STATIC:Destroy( GenerateEvent ) return nil end - --- Get DCS object of static of static. -- @param #STATIC self -- @return DCS static object function STATIC:GetDCSObject() local DCSStatic = StaticObject.getByName( self.StaticName ) - + if DCSStatic then return DCSStatic end - + return nil end @@ -170,7 +163,7 @@ function STATIC:GetUnits() local DCSStatic = self:GetDCSObject() local Statics = {} - + if DCSStatic then Statics[1] = STATIC:Find( DCSStatic ) self:T3( Statics ) @@ -180,7 +173,6 @@ function STATIC:GetUnits() return nil end - --- Get threat level of static. -- @param #STATIC self -- @return #number Threat level 1. @@ -194,66 +186,62 @@ end -- @param Core.Point#COORDINATE Coordinate The coordinate where to spawn the new Static. -- @param #number Heading The heading of the static respawn in degrees. Default is 0 deg. -- @param #number Delay Delay in seconds before the static is spawned. -function STATIC:SpawnAt(Coordinate, Heading, Delay) +function STATIC:SpawnAt( Coordinate, Heading, Delay ) - Heading=Heading or 0 + Heading = Heading or 0 - if Delay and Delay>0 then - SCHEDULER:New(nil, self.SpawnAt, {self, Coordinate, Heading}, Delay) + if Delay and Delay > 0 then + SCHEDULER:New( nil, self.SpawnAt, { self, Coordinate, Heading }, Delay ) else - local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName) - + local SpawnStatic = SPAWNSTATIC:NewFromStatic( self.StaticName ) + SpawnStatic:SpawnFromPointVec2( Coordinate, Heading, self.StaticName ) - + end - + return self end - --- Respawn the @{Wrapper.Unit} at the same location with the same properties. -- This is useful to respawn a cargo after it has been destroyed. -- @param #STATIC self -- @param DCS#country.id CountryID (Optional) The country ID used for spawning the new static. Default is same as currently. -- @param #number Delay (Optional) Delay in seconds before static is respawned. Default now. -function STATIC:ReSpawn(CountryID, Delay) +function STATIC:ReSpawn( CountryID, Delay ) - if Delay and Delay>0 then - SCHEDULER:New(nil, self.ReSpawn, {self, CountryID}, Delay) + if Delay and Delay > 0 then + SCHEDULER:New( nil, self.ReSpawn, { self, CountryID }, Delay ) else - CountryID=CountryID or self:GetCountry() + CountryID = CountryID or self:GetCountry() + + local SpawnStatic = SPAWNSTATIC:NewFromStatic( self.StaticName, CountryID ) + + SpawnStatic:Spawn( nil, self.StaticName ) - local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName, CountryID) - - SpawnStatic:Spawn(nil, self.StaticName) - end - + return self end - --- Respawn the @{Wrapper.Unit} at a defined Coordinate with an optional heading. -- @param #STATIC self -- @param Core.Point#COORDINATE Coordinate The coordinate where to spawn the new Static. --- @param #number Heading (Optional) The heading of the static respawn in degrees. Default the current heading. --- @param #number Delay (Optional) Delay in seconds before static is respawned. Default now. -function STATIC:ReSpawnAt(Coordinate, Heading, Delay) +-- @param #number Heading (Optional) The heading of the static respawn in degrees. Default is the current heading. +-- @param #number Delay (Optional) Delay in seconds before static is respawned. Default is now. +function STATIC:ReSpawnAt( Coordinate, Heading, Delay ) - --Heading=Heading or 0 + -- Heading=Heading or 0 - if Delay and Delay>0 then - SCHEDULER:New(nil, self.ReSpawnAt, {self, 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) - + local SpawnStatic = SPAWNSTATIC:NewFromStatic( self.StaticName, self:GetCountry() ) + + SpawnStatic:SpawnFromCoordinate( Coordinate, Heading, self.StaticName ) end - + return self end diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index d878c3b72..ad2ddc433 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -168,9 +168,6 @@ function UNIT:GetDCSObject() return nil end - - - --- Respawn the @{Wrapper.Unit} using a (tweaked) template of the parent Group. -- -- This function will: @@ -263,8 +260,6 @@ function UNIT:ReSpawnAt( Coordinate, Heading ) _DATABASE:Spawn( SpawnGroupTemplate ) end - - --- Returns if the unit is activated. -- @param #UNIT self -- @return #boolean `true` if Unit is activated. `nil` The DCS Unit is not existing or alive. @@ -301,8 +296,6 @@ function UNIT:IsAlive() return nil end - - --- Returns the Unit's callsign - the localized string. -- @param #UNIT self -- @return #string The Callsign of the Unit. @@ -961,7 +954,6 @@ end -- @return #string Some text. function UNIT:GetThreatLevel() - local ThreatLevel = 0 local ThreatText = "" @@ -987,7 +979,6 @@ function UNIT:GetThreatLevel() "LR SAMs" } - if Attributes["LR SAM"] then ThreatLevel = 10 elseif Attributes["MR SAM"] then ThreatLevel = 9 elseif Attributes["SR SAM"] and @@ -1023,7 +1014,6 @@ function UNIT:GetThreatLevel() "Fighter" } - if Attributes["Fighters"] then ThreatLevel = 10 elseif Attributes["Multirole fighters"] then ThreatLevel = 9 elseif Attributes["Battleplanes"] then ThreatLevel = 8 @@ -1141,12 +1131,6 @@ function UNIT:OtherUnitInRadius( AwaitUnit, Radius ) return nil end - - - - - - --- Returns if the unit is a friendly unit. -- @param #UNIT self -- @return #boolean IsFriendly evaluation result. From 389adab9b864edf4ccc56bf16c6d9ac86dde19b5 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 6 Dec 2021 10:26:32 +0100 Subject: [PATCH 021/200] SET - slight change to remove function --- Moose Development/Moose/Core/Set.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 5230b20fe..c6de50afb 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -208,7 +208,9 @@ do -- SET_BASE -- @param NoTriggerEvent (optional) When `true`, the :Remove() method will not trigger a **Removed** event. function SET_BASE:Remove( ObjectName, NoTriggerEvent ) self:F2( { ObjectName = ObjectName } ) - + + local TriggerEvent = NoTriggerEvent==nil and true or (not NoTriggerEvent) + local Object = self.Set[ObjectName] if Object then @@ -220,7 +222,7 @@ do -- SET_BASE end end -- When NoTriggerEvent is true, then no Removed event will be triggered. - if not NoTriggerEvent then + if TriggerEvent then self:Removed( ObjectName, Object ) end end From 493b090534c4b67b185c0701f0ca4e2505dfacb4 Mon Sep 17 00:00:00 2001 From: TommyC81 Date: Mon, 6 Dec 2021 17:57:36 +0400 Subject: [PATCH 022/200] LuaFormatter, RANGE formatting, and minor code fixes. (#1653) * Create .lua-format * Update Range.lua Format code. * Display distance in meters from bombtarget. All other numbers, including menu lists etc. uses meters. Feet kept in parens. * Fixed displaying of targetname when bombing. --- Moose Development/Moose/.lua-format | 33 + Moose Development/Moose/Functional/Range.lua | 2195 +++++++++--------- 2 files changed, 1132 insertions(+), 1096 deletions(-) create mode 100644 Moose Development/Moose/.lua-format diff --git a/Moose Development/Moose/.lua-format b/Moose Development/Moose/.lua-format new file mode 100644 index 000000000..26b4f5897 --- /dev/null +++ b/Moose Development/Moose/.lua-format @@ -0,0 +1,33 @@ +# See https://github.com/Koihik/LuaFormatter +# Use '-- LuaFormatter off' and '-- LuaFormatter on' around code blocks to inhibit formatting + +column_limit: 500 +indent_width: 2 +use_tab: false +continuation_indent_width: 2 +keep_simple_control_block_one_line: false +keep_simple_function_one_line: false +align_args: true +break_after_functioncall_lp: false +break_before_functioncall_rp: false +align_parameter: true +chop_down_parameter: true +break_after_functiondef_lp: false +break_before_functiondef_rp: false +align_table_field: true +break_after_table_lb: true +break_before_table_rb: true +chop_down_table: true +chop_down_kv_table: true +column_table_limit: 500 +table_sep: ',' +extra_sep_at_table_end: false +break_after_operator: true +single_quote_to_double_quote: true +double_quote_to_single_quote: false +spaces_before_call: 1 +spaces_inside_functiondef_parens: true +spaces_inside_functioncall_parens: true +spaces_inside_table_braces: true +spaces_around_equals_in_field: true +line_breaks_after_function_body: 1 \ No newline at end of file diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index 64c4d49a3..0f180e9aa 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -49,7 +49,6 @@ -- === -- @module Functional.Range -- @image Range.JPG - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- RANGE class -- @type RANGE @@ -100,7 +99,7 @@ -- @field #string instructorrelayname Name of relay unit. -- @field #string soundpath Path inside miz file where the sound files are located. Default is "Range Soundfiles/". -- @extends Core.Fsm#FSM - +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- *Don't only practice your art, but force your way into its secrets; art deserves that, for it and knowledge can raise man to the Divine.* - Ludwig van Beethoven -- -- === @@ -285,10 +284,9 @@ -- Note that it can happen that the RANGE radio menu is not shown. Check that the range object is defined as a **global** variable rather than a local one. -- The could avoid the lua garbage collection to accidentally/falsely deallocate the RANGE objects. -- --- --- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- @field #RANGE -RANGE={ +RANGE = { -- LuaFormatter off ClassName = "RANGE", Debug = false, verbose = 0, @@ -333,34 +331,34 @@ RANGE={ rangecontrolfreq = nil, rangecontrol = nil, soundpath = "Range Soundfiles/" -} +} -- LuaFormatter on --- Default range parameters. -- @list Defaults -RANGE.Defaults={ - goodhitrange=25, - strafemaxalt=914, - dtBombtrack=0.005, - Tmsg=30, - ndisplayresult=10, - rangeradius=5000, - TdelaySmoke=3.0, - boxlength=3000, - boxwidth=300, - goodpass=20, - foulline=610, -} +RANGE.Defaults = { -- LuaFormatter off + goodhitrange = 25, -- meters + strafemaxalt = 914, -- meters AGL + dtBombtrack = 0.005, -- seconds + Tmsg = 30, -- seconds + ndisplayresult = 10, + rangeradius = 5000, -- meters + TdelaySmoke = 3.0, -- seconds + boxlength = 3000, -- meters + boxwidth = 300, -- meters + goodpass = 20, -- targethits per pass + foulline = 610 -- meters +} -- LuaFormatter on --- Target type, i.e. unit, static, or coordinate. -- @type RANGE.TargetType -- @field #string UNIT Target is a unit. -- @field #string STATIC Target is a static. -- @field #string COORD Target is a coordinate. -RANGE.TargetType={ - UNIT="Unit", - STATIC="Static", - COORD="Coordinate", -} +RANGE.TargetType = { -- LuaFormatter off + UNIT = "Unit", + STATIC = "Static", + COORD = "Coordinate", +} -- LuaFormatter on --- Player settings. -- @type RANGE.PlayerData @@ -460,81 +458,81 @@ RANGE.TargetType={ -- @field #RANGE.Soundfile IREnterRange -- @field #RANGE.Soundfile IRExitRange 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}, + 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 } } --- Global list of all defined range names. -- @field #table Names -RANGE.Names={} +RANGE.Names = {} --- Main radio menu on group level. -- @field #table MenuF10 Root menu table on group level. -RANGE.MenuF10={} +RANGE.MenuF10 = {} --- Main radio menu on mission level. -- @field #table MenuF10Root Root menu on mission level. -RANGE.MenuF10Root=nil +RANGE.MenuF10Root = nil --- Range script version. -- @field #string version -RANGE.version="2.3.0" +RANGE.version = "2.3.0" ---TODO list: ---TODO: Verbosity level for messages. ---TODO: Add option for default settings such as smoke off. ---TODO: Add custom weapons, which can be specified by the user. ---TODO: Check if units are still alive. ---DONE: Add statics for strafe pits. ---DONE: Add missiles. ---DONE: Convert env.info() to self:T() ---DONE: Add user functions. ---DONE: Rename private functions, i.e. start with _functionname. ---DONE: number of displayed results variable. ---DONE: Add tire option for strafe pits. ==> No really feasible since tires are very small and cannot be seen. ---DONE: Check that menu texts are short enough to be correctly displayed in VR. +-- TODO list: +-- TODO: Verbosity level for messages. +-- TODO: Add option for default settings such as smoke off. +-- TODO: Add custom weapons, which can be specified by the user. +-- TODO: Check if units are still alive. +-- DONE: Add statics for strafe pits. +-- DONE: Add missiles. +-- DONE: Convert env.info() to self:T() +-- DONE: Add user functions. +-- DONE: Rename private functions, i.e. start with _functionname. +-- DONE: number of displayed results variable. +-- DONE: Add tire option for strafe pits. ==> No really feasible since tires are very small and cannot be seen. +-- DONE: Check that menu texts are short enough to be correctly displayed in VR. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -542,29 +540,30 @@ RANGE.version="2.3.0" -- @param #RANGE self -- @param #string rangename Name of the range. Has to be unique. Will we used to create F10 menu items etc. -- @return #RANGE RANGE object. -function RANGE:New(rangename) - BASE:F({rangename=rangename}) +function RANGE:New( rangename ) + BASE:F( { rangename = rangename } ) -- Inherit BASE. - local self=BASE:Inherit(self, FSM:New()) -- #RANGE + local self = BASE:Inherit( self, FSM:New() ) -- #RANGE -- Get range name. - --TODO: make sure that the range name is not given twice. This would lead to problems in the F10 radio menu. - self.rangename=rangename or "Practice Range" + -- TODO: make sure that the range name is not given twice. This would lead to problems in the F10 radio menu. + self.rangename = rangename or "Practice Range" -- Log id. - self.id=string.format("RANGE %s | ", self.rangename) + self.id = string.format( "RANGE %s | ", self.rangename ) -- Debug info. - local text=string.format("Script version %s - creating new RANGE object %s.", RANGE.version, self.rangename) - self:I(self.id..text) + local text = string.format( "Script version %s - creating new RANGE object %s.", RANGE.version, self.rangename ) + self:I( self.id .. text ) -- Defaults self:SetDefaultPlayerSmokeBomb() -- Start State. - self:SetStartState("Stopped") + self:SetStartState( "Stopped" ) + -- LuaFormatter off --- -- Add FSM transitions. -- From State --> Event --> To State @@ -575,6 +574,7 @@ function RANGE:New(rangename) self:AddTransition("*", "ExitRange", "*") -- Player leaves the range. self:AddTransition("*", "Save", "*") -- Save player results. self:AddTransition("*", "Load", "*") -- Load player results. +-- LuaFormatter on ------------------------ --- Pseudo Functions --- @@ -678,76 +678,76 @@ end function RANGE:onafterStart() -- Location/coordinate of range. - local _location=nil + local _location = nil -- Count bomb targets. - local _count=0 - for _,_target in pairs(self.bombingTargets) do - _count=_count+1 + local _count = 0 + for _, _target in pairs( self.bombingTargets ) do + _count = _count + 1 -- Get range location. - if _location==nil then - _location=self:_GetBombTargetCoordinate(_target) + if _location == nil then + _location = self:_GetBombTargetCoordinate( _target ) end end - self.nbombtargets=_count + self.nbombtargets = _count -- Count strafing targets. - _count=0 - for _,_target in pairs(self.strafeTargets) do - _count=_count+1 + _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() + for _, _unit in pairs( _target.targets ) do + if _location == nil then + _location = _unit:GetCoordinate() end end end - self.nstrafetargets=_count + self.nstrafetargets = _count -- Location of the range. We simply take the first unit/target we find if it was not explicitly specified by the user. - if self.location==nil then - self.location=_location + 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) + 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 -- Define a MOOSE zone of the range. - if self.rangezone==nil then - self.rangezone=ZONE_RADIUS:New(self.rangename, {x=self.location.x, y=self.location.z}, self.rangeradius) + if self.rangezone == nil then + self.rangezone = ZONE_RADIUS:New( self.rangename, { x = self.location.x, y = self.location.z }, self.rangeradius ) end -- Starting range. - 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) + 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 ) -- Event handling. if self.eventmoose then -- Events are handled my MOOSE. - self:T(self.id.."Events are handled by MOOSE.") - self:HandleEvent(EVENTS.Birth) - self:HandleEvent(EVENTS.Hit) - self:HandleEvent(EVENTS.Shot) + self:T( self.id .. "Events are handled by MOOSE." ) + self:HandleEvent( EVENTS.Birth ) + self:HandleEvent( EVENTS.Hit ) + self:HandleEvent( EVENTS.Shot ) else -- Events are handled directly by DCS. - self:T(self.id.."Events are handled directly by DCS.") - world.addEventHandler(self) + self:T( self.id .. "Events are handled directly by DCS." ) + world.addEventHandler( self ) end -- Make bomb target move randomly within the range zone. - for _,_target in pairs(self.bombingTargets) do + for _, _target in pairs( self.bombingTargets ) do -- Check if it is a static object. - --local _static=self:_CheckStatic(_target.target:GetName()) - local _static=_target.type==RANGE.TargetType.STATIC + -- local _static=self:_CheckStatic(_target.target:GetName()) + local _static = _target.type == RANGE.TargetType.STATIC - if _target.move and _static==false and _target.speed>1 then - local unit=_target.target --Wrapper.Unit#UNIT - _target.target:PatrolZones({self.rangezone}, _target.speed*0.75, "Off road") + if _target.move and _static == false and _target.speed > 1 then + local unit = _target.target -- Wrapper.Unit#UNIT + _target.target:PatrolZones( { self.rangezone }, _target.speed * 0.75, "Off road" ) end end @@ -756,53 +756,53 @@ function RANGE:onafterStart() if self.rangecontrolfreq then -- Radio queue. - self.rangecontrol=RADIOQUEUE:New(self.rangecontrolfreq, nil, self.rangename) - self.rangecontrol.schedonce=true + self.rangecontrol = RADIOQUEUE:New( self.rangecontrolfreq, nil, self.rangename ) + self.rangecontrol.schedonce = true -- Init numbers. - self.rangecontrol:SetDigit(0, RANGE.Sound.RC0.filename, RANGE.Sound.RC0.duration, self.soundpath) - self.rangecontrol:SetDigit(1, RANGE.Sound.RC1.filename, RANGE.Sound.RC1.duration, self.soundpath) - self.rangecontrol:SetDigit(2, RANGE.Sound.RC2.filename, RANGE.Sound.RC2.duration, self.soundpath) - self.rangecontrol:SetDigit(3, RANGE.Sound.RC3.filename, RANGE.Sound.RC3.duration, self.soundpath) - self.rangecontrol:SetDigit(4, RANGE.Sound.RC4.filename, RANGE.Sound.RC4.duration, self.soundpath) - self.rangecontrol:SetDigit(5, RANGE.Sound.RC5.filename, RANGE.Sound.RC5.duration, self.soundpath) - self.rangecontrol:SetDigit(6, RANGE.Sound.RC6.filename, RANGE.Sound.RC6.duration, self.soundpath) - self.rangecontrol:SetDigit(7, RANGE.Sound.RC7.filename, RANGE.Sound.RC7.duration, self.soundpath) - self.rangecontrol:SetDigit(8, RANGE.Sound.RC8.filename, RANGE.Sound.RC8.duration, self.soundpath) - self.rangecontrol:SetDigit(9, RANGE.Sound.RC9.filename, RANGE.Sound.RC9.duration, self.soundpath) + self.rangecontrol:SetDigit( 0, 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 ) -- Set location where the messages are transmitted from. - self.rangecontrol:SetSenderCoordinate(self.location) - self.rangecontrol:SetSenderUnitName(self.rangecontrolrelayname) + self.rangecontrol:SetSenderCoordinate( self.location ) + self.rangecontrol:SetSenderUnitName( self.rangecontrolrelayname ) -- Start range control radio queue. - self.rangecontrol:Start(1, 0.1) + self.rangecontrol:Start( 1, 0.1 ) -- Init range control. if self.instructorfreq then -- Radio queue. - self.instructor=RADIOQUEUE:New(self.instructorfreq, nil, self.rangename) - self.instructor.schedonce=true + self.instructor = RADIOQUEUE:New( self.instructorfreq, nil, self.rangename ) + self.instructor.schedonce = true -- Init numbers. - self.instructor:SetDigit(0, RANGE.Sound.IR0.filename, RANGE.Sound.IR0.duration, self.soundpath) - self.instructor:SetDigit(1, RANGE.Sound.IR1.filename, RANGE.Sound.IR1.duration, self.soundpath) - self.instructor:SetDigit(2, RANGE.Sound.IR2.filename, RANGE.Sound.IR2.duration, self.soundpath) - self.instructor:SetDigit(3, RANGE.Sound.IR3.filename, RANGE.Sound.IR3.duration, self.soundpath) - self.instructor:SetDigit(4, RANGE.Sound.IR4.filename, RANGE.Sound.IR4.duration, self.soundpath) - self.instructor:SetDigit(5, RANGE.Sound.IR5.filename, RANGE.Sound.IR5.duration, self.soundpath) - self.instructor:SetDigit(6, RANGE.Sound.IR6.filename, RANGE.Sound.IR6.duration, self.soundpath) - self.instructor:SetDigit(7, RANGE.Sound.IR7.filename, RANGE.Sound.IR7.duration, self.soundpath) - self.instructor:SetDigit(8, RANGE.Sound.IR8.filename, RANGE.Sound.IR8.duration, self.soundpath) - self.instructor:SetDigit(9, RANGE.Sound.IR9.filename, RANGE.Sound.IR9.duration, self.soundpath) + self.instructor:SetDigit( 0, 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 ) -- Set location where the messages are transmitted from. - self.instructor:SetSenderCoordinate(self.location) - self.instructor:SetSenderUnitName(self.instructorrelayname) + self.instructor:SetSenderCoordinate( self.location ) + self.instructor:SetSenderUnitName( self.instructorrelayname ) -- Start instructor radio queue. - self.instructor:Start(1, 0.1) + self.instructor:Start( 1, 0.1 ) end @@ -819,10 +819,10 @@ function RANGE:onafterStart() self:_SmokeBombTargets() self:_SmokeStrafeTargets() self:_SmokeStrafeTargetBoxes() - self.rangezone:SmokeZone(SMOKECOLOR.White) + self.rangezone:SmokeZone( SMOKECOLOR.White ) end - self:__Status(-60) + self:__Status( -60 ) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -833,8 +833,8 @@ end -- @param #RANGE self -- @param #number maxalt Maximum altitude in meters AGL. Default is 914 m = 3000 ft. -- @return #RANGE self -function RANGE:SetMaxStrafeAlt(maxalt) - self.strafemaxalt=maxalt or RANGE.Defaults.strafemaxalt +function RANGE:SetMaxStrafeAlt( maxalt ) + self.strafemaxalt = maxalt or RANGE.Defaults.strafemaxalt return self end @@ -842,8 +842,8 @@ end -- @param #RANGE self -- @param #number dt Time interval in seconds. Default is 0.005 s. -- @return #RANGE self -function RANGE:SetBombtrackTimestep(dt) - self.dtBombtrack=dt or RANGE.Defaults.dtBombtrack +function RANGE:SetBombtrackTimestep( dt ) + self.dtBombtrack = dt or RANGE.Defaults.dtBombtrack return self end @@ -851,8 +851,8 @@ end -- @param #RANGE self -- @param #number time Time in seconds. Default is 30 s. -- @return #RANGE self -function RANGE:SetMessageTimeDuration(time) - self.Tmsg=time or RANGE.Defaults.Tmsg +function RANGE:SetMessageTimeDuration( time ) + self.Tmsg = time or RANGE.Defaults.Tmsg return self end @@ -860,7 +860,7 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:SetAutosaveOn() - self.autosave=true + self.autosave = true return self end @@ -868,7 +868,7 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:SetAutosaveOff() - self.autosave=false + self.autosave = false return self end @@ -877,9 +877,9 @@ end -- @param #string examinergroupname Name of the group of the examiner. -- @param #boolean exclusively If true, messages are send exclusively to the examiner, i.e. not to the clients. -- @return #RANGE self -function RANGE:SetMessageToExaminer(examinergroupname, exclusively) - self.examinergroupname=examinergroupname - self.examinerexclusive=exclusively +function RANGE:SetMessageToExaminer( examinergroupname, exclusively ) + self.examinergroupname = examinergroupname + self.examinerexclusive = exclusively return self end @@ -887,8 +887,8 @@ end -- @param #RANGE self -- @param #number nmax Number of results. Default is 10. -- @return #RANGE self -function RANGE:SetDisplayedMaxPlayerResults(nmax) - self.ndisplayresult=nmax or RANGE.Defaults.ndisplayresult +function RANGE:SetDisplayedMaxPlayerResults( nmax ) + self.ndisplayresult = nmax or RANGE.Defaults.ndisplayresult return self end @@ -896,8 +896,8 @@ end -- @param #RANGE self -- @param #number radius Radius in km. Default 5 km. -- @return #RANGE self -function RANGE:SetRangeRadius(radius) - self.rangeradius=radius*1000 or RANGE.Defaults.rangeradius +function RANGE:SetRangeRadius( radius ) + self.rangeradius = radius * 1000 or RANGE.Defaults.rangeradius return self end @@ -905,11 +905,11 @@ end -- @param #RANGE self -- @param #boolean switch (Optional) If true, impact points of bombs will be smoked. Default is true. -- @return #RANGE self -function RANGE:SetDefaultPlayerSmokeBomb(switch) +function RANGE:SetDefaultPlayerSmokeBomb( switch ) if switch == nil or switch == true then - self.defaultsmokebomb=true + self.defaultsmokebomb = true else - self.defaultsmokebomb=false + self.defaultsmokebomb = false end return self end @@ -918,8 +918,8 @@ end -- @param #RANGE self -- @param #number distance Threshold distance in km. Default 25 km. -- @return #RANGE self -function RANGE:SetBombtrackThreshold(distance) - self.BombtrackThreshold=(distance or 25)*1000 +function RANGE:SetBombtrackThreshold( distance ) + self.BombtrackThreshold = (distance or 25) * 1000 return self end @@ -928,8 +928,8 @@ end -- @param #RANGE self -- @param Core.Point#COORDINATE coordinate Coordinate of the range. -- @return #RANGE self -function RANGE:SetRangeLocation(coordinate) - self.location=coordinate +function RANGE:SetRangeLocation( coordinate ) + self.location = coordinate return self end @@ -938,8 +938,8 @@ end -- @param #RANGE self -- @param Core.Zone#ZONE zone MOOSE zone defining the range perimeters. -- @return #RANGE self -function RANGE:SetRangeZone(zone) - self.rangezone=zone +function RANGE:SetRangeZone( zone ) + self.rangezone = zone return self end @@ -947,8 +947,8 @@ end -- @param #RANGE self -- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default SMOKECOLOR.Red. -- @return #RANGE self -function RANGE:SetBombTargetSmokeColor(colorid) - self.BombSmokeColor=colorid or SMOKECOLOR.Red +function RANGE:SetBombTargetSmokeColor( colorid ) + self.BombSmokeColor = colorid or SMOKECOLOR.Red return self end @@ -956,8 +956,8 @@ end -- @param #RANGE self -- @param #number distance Distance in meters. Default 1000 m. -- @return #RANGE self -function RANGE:SetScoreBombDistance(distance) - self.scorebombdistance=distance or 1000 +function RANGE:SetScoreBombDistance( distance ) + self.scorebombdistance = distance or 1000 return self end @@ -965,8 +965,8 @@ end -- @param #RANGE self -- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default SMOKECOLOR.Green. -- @return #RANGE self -function RANGE:SetStrafeTargetSmokeColor(colorid) - self.StrafeSmokeColor=colorid or SMOKECOLOR.Green +function RANGE:SetStrafeTargetSmokeColor( colorid ) + self.StrafeSmokeColor = colorid or SMOKECOLOR.Green return self end @@ -974,8 +974,8 @@ end -- @param #RANGE self -- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default SMOKECOLOR.White. -- @return #RANGE self -function RANGE:SetStrafePitSmokeColor(colorid) - self.StrafePitSmokeColor=colorid or SMOKECOLOR.White +function RANGE:SetStrafePitSmokeColor( colorid ) + self.StrafePitSmokeColor = colorid or SMOKECOLOR.White return self end @@ -983,8 +983,8 @@ end -- @param #RANGE self -- @param #number delay Time delay in seconds. Default is 3 seconds. -- @return #RANGE self -function RANGE:SetSmokeTimeDelay(delay) - self.TdelaySmoke=delay or RANGE.Defaults.TdelaySmoke +function RANGE:SetSmokeTimeDelay( delay ) + self.TdelaySmoke = delay or RANGE.Defaults.TdelaySmoke return self end @@ -992,7 +992,7 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:DebugON() - self.Debug=true + self.Debug = true return self end @@ -1000,7 +1000,7 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:DebugOFF() - self.Debug=false + self.Debug = false return self end @@ -1008,7 +1008,7 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:SetMessagesOFF() - self.messages=false + self.messages = false return self end @@ -1016,7 +1016,7 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:SetMessagesON() - self.messages=true + self.messages = true return self end @@ -1024,7 +1024,7 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:TrackBombsON() - self.trackbombs=true + self.trackbombs = true return self end @@ -1032,7 +1032,7 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:TrackBombsOFF() - self.trackbombs=false + self.trackbombs = false return self end @@ -1040,7 +1040,7 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:TrackRocketsON() - self.trackrockets=true + self.trackrockets = true return self end @@ -1048,7 +1048,7 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:TrackRocketsOFF() - self.trackrockets=false + self.trackrockets = false return self end @@ -1056,7 +1056,7 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:TrackMissilesON() - self.trackmissiles=true + self.trackmissiles = true return self end @@ -1064,7 +1064,7 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:TrackMissilesOFF() - self.trackmissiles=false + self.trackmissiles = false return self end @@ -1073,9 +1073,9 @@ end -- @param #number frequency Frequency in MHz. Default 256 MHz. -- @param #string relayunitname Name of the unit used for transmission. -- @return #RANGE self -function RANGE:SetRangeControl(frequency, relayunitname) - self.rangecontrolfreq=frequency or 256 - self.rangecontrolrelayname=relayunitname +function RANGE:SetRangeControl( frequency, relayunitname ) + self.rangecontrolfreq = frequency or 256 + self.rangecontrolrelayname = relayunitname return self end @@ -1084,9 +1084,9 @@ end -- @param #number frequency Frequency in MHz. Default 305 MHz. -- @param #string relayunitname Name of the unit used for transmission. -- @return #RANGE self -function RANGE:SetInstructorRadio(frequency, relayunitname) - self.instructorfreq=frequency or 305 - self.instructorrelayname=relayunitname +function RANGE:SetInstructorRadio( frequency, relayunitname ) + self.instructorfreq = frequency or 305 + self.instructorrelayname = relayunitname return self end @@ -1094,9 +1094,9 @@ end -- @param #RANGE self -- @param #string path Path for sound files. Default "ATIS Soundfiles/". Mind the slash "/" at the end! -- @return #RANGE self -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)) +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 @@ -1112,130 +1112,130 @@ end -- @param #number goodpass (Optional) Number of hits for a "good" strafing pass. Default is 20. -- @param #number foulline (Optional) Foul line distance. Hits from closer than this distance are not counted. Default is 610 m = 2000 ft. Set to 0 for no foul line. -- @return #RANGE self -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}) +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 } ) -- Create table if necessary. - if type(targetnames) ~= "table" then - targetnames={targetnames} + if type( targetnames ) ~= "table" then + targetnames = { targetnames } end -- Make targets - local _targets={} - local center=nil --Wrapper.Unit#UNIT - local ntargets=0 + local _targets = {} + local center = nil -- Wrapper.Unit#UNIT + local ntargets = 0 - for _i,_name in ipairs(targetnames) do + for _i, _name in ipairs( targetnames ) do -- Check if we have a static or unit object. - local _isstatic=self:_CheckStatic(_name) + local _isstatic = self:_CheckStatic( _name ) - local unit=nil + local unit = nil if _isstatic == true then -- Add static object. - self:T(self.id..string.format("Adding STATIC object %s as strafe target #%d.", _name, _i)) - unit=STATIC:FindByName(_name, false) + 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 -- Add unit object. - self:T(self.id..string.format("Adding UNIT object %s as strafe target #%d.", _name, _i)) - unit=UNIT:FindByName(_name) + self:T( self.id .. string.format( "Adding UNIT object %s as strafe target #%d.", _name, _i ) ) + unit = UNIT:FindByName( _name ) else -- Neither unit nor static object with this name could be found. - local text=string.format("ERROR! Could not find ANY strafe target object with name %s.", _name) - self:E(self.id..text) + local text = string.format( "ERROR! Could not find ANY strafe target object with name %s.", _name ) + self:E( self.id .. text ) end -- Add object to targets. if unit then - table.insert(_targets, unit) + table.insert( _targets, unit ) -- Define center as the first unit we find if center == nil then - center=unit + center = unit end - ntargets=ntargets+1 + ntargets = ntargets + 1 end end -- Check if at least one target could be found. - 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) + 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 -- Approach box dimensions. - local l=boxlength or RANGE.Defaults.boxlength - local w=(boxwidth or RANGE.Defaults.boxwidth)/2 + local l = boxlength or RANGE.Defaults.boxlength + local w = (boxwidth or RANGE.Defaults.boxwidth) / 2 -- Heading: either manually entered or automatically taken from unit heading. - local heading=heading or center:GetHeading() + local heading = heading or center:GetHeading() -- Invert the heading since some units point in the "wrong" direction. In particular the strafe pit from 476th range objects. if inverseheading ~= nil then if inverseheading then - heading=heading-180 + heading = heading - 180 end end if heading < 0 then - heading=heading+360 + heading = heading + 360 end if heading >= 360 then - heading=heading-360 + heading = heading - 360 end -- Number of hits called a "good" pass. - goodpass=goodpass or RANGE.Defaults.goodpass + goodpass = goodpass or RANGE.Defaults.goodpass -- Foule line distance. - foulline=foulline or RANGE.Defaults.foulline + foulline = foulline or RANGE.Defaults.foulline -- Coordinate of the range. - local Ccenter=center:GetCoordinate() + local Ccenter = center:GetCoordinate() -- Name of the target defined as its unit name. - local _name=center:GetName() + local _name = center:GetName() -- Points defining the approach area. - 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 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} + local pv2 = {} + for i, p in ipairs( p ) do + pv2[i] = { x = p.x, y = p.z } end -- Create polygon zone. - local _polygon=ZONE_POLYGON_BASE:New(_name, pv2) + local _polygon = ZONE_POLYGON_BASE:New( _name, pv2 ) -- Create tires - --_polygon:BoundZone() + -- _polygon:BoundZone() - local st={} --#RANGE.StrafeTarget - st.name=_name - st.polygon=_polygon - st.coordinate=Ccenter - st.goodPass=goodpass - st.targets=_targets - st.foulline=foulline - st.smokepoints=p - st.heading=heading + local st = {} -- #RANGE.StrafeTarget + st.name = _name + st.polygon = _polygon + st.coordinate = Ccenter + st.goodPass = goodpass + st.targets = _targets + st.foulline = foulline + st.smokepoints = p + st.heading = heading -- Add zone to table. - table.insert(self.strafeTargets, st) + table.insert( self.strafeTargets, st ) -- Debug info - 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) + 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 @@ -1253,29 +1253,29 @@ end -- @param #number goodpass (Optional) Number of hits for a "good" strafing pass. Default is 20. -- @param #number foulline (Optional) Foul line distance. Hits from closer than this distance are not counted. Default 610 m = 2000 ft. Set to 0 for no foul line. -- @return #RANGE self -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}) +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 -- Get units of group. - local _units=group:GetUnits() + local _units = group:GetUnits() -- Make table of unit names. - local _names={} - for _,_unit in ipairs(_units) do + local _names = {} + for _, _unit in ipairs( _units ) do - local _unit=_unit --Wrapper.Unit#UNIT + local _unit = _unit -- Wrapper.Unit#UNIT if _unit and _unit:IsAlive() then - local _name=_unit:GetName() - table.insert(_names,_name) + local _name = _unit:GetName() + table.insert( _names, _name ) end end -- Add strafe pit. - self:AddStrafePit(_names, boxlength, boxwidth, heading, inverseheading, goodpass, foulline) + self:AddStrafePit( _names, boxlength, boxwidth, heading, inverseheading, goodpass, foulline ) end return self @@ -1287,32 +1287,32 @@ end -- @param #number goodhitrange (Optional) Max hit distance from target unit in meters which is considered as a good hit. Default is 25 m. -- @param #boolean randommove (Optional) If true, unit will move randomly within the range. Default is false. -- @return #RANGE self -function RANGE:AddBombingTargets(targetnames, goodhitrange, randommove) - self:F({targetnames=targetnames, goodhitrange=goodhitrange, randommove=randommove}) +function RANGE:AddBombingTargets( targetnames, goodhitrange, randommove ) + self:F( { targetnames = targetnames, goodhitrange = goodhitrange, randommove = randommove } ) -- Create a table if necessary. - if type(targetnames) ~= "table" then - targetnames={targetnames} + if type( targetnames ) ~= "table" then + targetnames = { targetnames } end -- Default range is 25 m. - goodhitrange=goodhitrange or RANGE.Defaults.goodhitrange + goodhitrange = goodhitrange or RANGE.Defaults.goodhitrange - for _,name in pairs(targetnames) do + for _, name in pairs( targetnames ) do -- Check if we have a static or unit object. - local _isstatic=self:_CheckStatic(name) + 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) + 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) + 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)) + self:E( self.id .. string.format( "ERROR! Could not find bombing target %s.", name ) ) end end @@ -1326,53 +1326,53 @@ end -- @param #number goodhitrange (Optional) Max hit distance from target unit in meters which is considered as a good hit. Default is 25 m. -- @param #boolean randommove (Optional) If true, unit will move randomly within the range. Default is false. -- @return #RANGE self -function RANGE:AddBombingTargetUnit(unit, goodhitrange, randommove) - self:F({unit=unit, goodhitrange=goodhitrange, randommove=randommove}) +function RANGE:AddBombingTargetUnit( unit, goodhitrange, randommove ) + self:F( { unit = unit, goodhitrange = goodhitrange, randommove = randommove } ) -- Get name of positionable. - local name=unit:GetName() + local name = unit:GetName() -- Check if we have a static or unit object. - local _isstatic=self:_CheckStatic(name) + local _isstatic = self:_CheckStatic( name ) -- Default range is 25 m. - goodhitrange=goodhitrange or RANGE.Defaults.goodhitrange + goodhitrange = goodhitrange or RANGE.Defaults.goodhitrange -- Set randommove to false if it was not specified. if randommove == nil or _isstatic == true then - randommove=false + randommove = false end -- Debug or error output. - 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))) + 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)) + 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 -- Get max speed of unit in km/h. - local speed=0 + local speed = 0 if _isstatic == false then - speed=self:_GetSpeed(unit) + speed = self:_GetSpeed( unit ) end - local target={} --#RANGE.BombTarget - target.name=name - target.target=unit - target.goodhitrange=goodhitrange - target.move=randommove - target.speed=speed - target.coordinate=unit:GetCoordinate() + local target = {} -- #RANGE.BombTarget + 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 + target.type = RANGE.TargetType.STATIC else - target.type=RANGE.TargetType.UNIT + target.type = RANGE.TargetType.UNIT end -- Insert target to table. - table.insert(self.bombingTargets, target) + table.insert( self.bombingTargets, target ) return self end @@ -1394,19 +1394,19 @@ end -- -- Or, add the coordinate of the STATIC target object as a bomb target using default values (name will be "Bomb Target", goodhitrange will be 25 m) -- RangeOne:AddBombingTargetCoordinate( RangeOneBombTarget:GetCoordinate() ) -- -function RANGE:AddBombingTargetCoordinate(coord, name, goodhitrange) +function RANGE:AddBombingTargetCoordinate( coord, name, goodhitrange ) - local target={} --#RANGE.BombTarget - 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 + local target = {} -- #RANGE.BombTarget + 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 -- Insert target to table. - table.insert(self.bombingTargets, target) + table.insert( self.bombingTargets, target ) return self end @@ -1417,16 +1417,16 @@ end -- @param #number goodhitrange (Optional) Max hit distance from target unit in meters which is considered as a good hit. Default is 25 m. -- @param #boolean randommove (Optional) If true, unit will move randomly within the range. Default is false. -- @return #RANGE self -function RANGE:AddBombingTargetGroup(group, goodhitrange, randommove) - self:F({group=group, goodhitrange=goodhitrange, randommove=randommove}) +function RANGE:AddBombingTargetGroup( group, goodhitrange, randommove ) + self:F( { group = group, goodhitrange = goodhitrange, randommove = randommove } ) if group then - local _units=group:GetUnits() + local _units = group:GetUnits() - for _,_unit in pairs(_units) do + for _, _unit in pairs( _units ) do if _unit and _unit:IsAlive() then - self:AddBombingTargetUnit(_unit, goodhitrange, randommove) + self:AddBombingTargetUnit( _unit, goodhitrange, randommove ) end end end @@ -1439,42 +1439,42 @@ end -- @param #string namepit Name of the strafe pit target object. -- @param #string namefoulline Name of the foul line distance marker object. -- @return #number Foul line distance in meters. -function RANGE:GetFoullineDistance(namepit, namefoulline) - self:F({namepit=namepit, namefoulline=namefoulline}) +function RANGE:GetFoullineDistance( namepit, namefoulline ) + self:F( { namepit = namepit, namefoulline = namefoulline } ) -- Check if we have units or statics. - local _staticpit=self:_CheckStatic(namepit) - local _staticfoul=self:_CheckStatic(namefoulline) + local _staticpit = self:_CheckStatic( namepit ) + local _staticfoul = self:_CheckStatic( namefoulline ) -- Get the unit or static pit object. - local pit=nil + local pit = nil if _staticpit == true then - pit=STATIC:FindByName(namepit, false) - elseif _staticpit==false then - pit=UNIT:FindByName(namepit) + 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)) + 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 -- Get the unit or static foul line object. - local foul=nil + local foul = nil if _staticfoul == true then - foul=STATIC:FindByName(namefoulline, false) + foul = STATIC:FindByName( namefoulline, false ) elseif _staticfoul == false then - foul=UNIT:FindByName(namefoulline) + 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)) + 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 -- Get the distance between the two objects. - local fouldist=0 + local fouldist = 0 if pit ~= nil and foul ~= nil then - fouldist=pit:GetCoordinate():Get2DDistance(foul:GetCoordinate()) + 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)) + 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)) + self:T( self.id .. string.format( "Foul line distance = %.1f m.", fouldist ) ) return fouldist end @@ -1485,66 +1485,66 @@ end --- General event handler. -- @param #RANGE self -- @param #table Event DCS event table. -function RANGE:onEvent(Event) - self:F3(Event) +function RANGE:onEvent( Event ) + self:F3( Event ) if Event == nil or Event.initiator == nil then - self:T3("Skipping onEvent. Event or Event.initiator unknown.") + 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.") + 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 DCSweapon = Event.weapon - local EventData={} - local _playerunit=nil - local _playername=nil + local EventData = {} + local _playerunit = nil + local _playername = nil if Event.initiator then - EventData.IniUnitName = Event.initiator:getName() - EventData.IniDCSGroup = Event.initiator:getGroup() + EventData.IniUnitName = Event.initiator:getName() + EventData.IniDCSGroup = Event.initiator:getGroup() EventData.IniGroupName = Event.initiator:getGroup():getName() -- Get player unit and name. This returns nil,nil if the event was not fired by a player unit. And these are the only events we are interested in. - _playerunit, _playername = self:_GetPlayerUnitAndName(EventData.IniUnitName) + _playerunit, _playername = self:_GetPlayerUnitAndName( EventData.IniUnitName ) end if Event.target then - EventData.TgtUnitName = Event.target:getName() - EventData.TgtUnit = UNIT:FindByName(EventData.TgtUnitName) + 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.Weapon = Event.weapon + EventData.weapon = Event.weapon EventData.WeaponTypeName = Event.weapon:getTypeName() end -- Event info. - 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))) + 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 ) ) ) -- Call event Birth function. - if Event.id==world.event.S_EVENT_BIRTH and _playername then - self:OnEventBirth(EventData) + if Event.id == world.event.S_EVENT_BIRTH and _playername then + self:OnEventBirth( EventData ) end -- Call event Shot function. - if Event.id==world.event.S_EVENT_SHOT and _playername and Event.weapon then - self:OnEventShot(EventData) + if Event.id == world.event.S_EVENT_SHOT and _playername and Event.weapon then + self:OnEventShot( EventData ) end -- Call event Hit function. - if Event.id==world.event.S_EVENT_HIT and _playername and DCStgtunit then - self:OnEventHit(EventData) + if Event.id == world.event.S_EVENT_HIT and _playername and DCStgtunit then + self:OnEventHit( EventData ) end end @@ -1552,51 +1552,51 @@ end --- Range event handler for event birth. -- @param #RANGE self -- @param Core.Event#EVENTDATA EventData -function RANGE:OnEventBirth(EventData) - self:F({eventbirth = EventData}) +function RANGE:OnEventBirth( EventData ) + self:F( { eventbirth = EventData } ) - local _unitName=EventData.IniUnitName - local _unit, _playername=self:_GetPlayerUnitAndName(_unitName) + 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)) + 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 _uid = _unit:GetID() + local _group = _unit:GetGroup() + local _gid = _group:GetID() + local _callsign = _unit:GetCallsign() -- Debug output. - 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) + 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 ) -- Reset current strafe status. self.strafeStatus[_uid] = nil -- Add Menu commands after a delay of 0.1 seconds. - --SCHEDULER:New(nil, self._AddF10Commands, {self,_unitName}, 0.1) - self:ScheduleOnce(0.1, self._AddF10Commands, self, _unitName) + -- SCHEDULER:New(nil, self._AddF10Commands, {self,_unitName}, 0.1) + self:ScheduleOnce( 0.1, self._AddF10Commands, self, _unitName ) -- By default, some bomb impact points and do not flare each hit on target. - self.PlayerSettings[_playername]={} --#RANGE.PlayerData - 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 + self.PlayerSettings[_playername] = {} -- #RANGE.PlayerData + 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 -- Start check in zone timer. if self.planes[_uid] ~= true then - --SCHEDULER:New(nil, self._CheckInZone, {self, EventData.IniUnitName}, 1, 1) - self.timerCheckZone=TIMER:New(self._CheckInZone, self, EventData.IniUnitName):Start(1, 1) + -- SCHEDULER:New(nil, self._CheckInZone, {self, EventData.IniUnitName}, 1, 1) + self.timerCheckZone = TIMER:New( self._CheckInZone, self, EventData.IniUnitName ):Start( 1, 1 ) self.planes[_uid] = true end end @@ -1606,17 +1606,17 @@ end --- Range event handler for event hit. -- @param #RANGE self -- @param Core.Event#EVENTDATA EventData -function RANGE:OnEventHit(EventData) - self:F({eventhit = EventData}) +function RANGE:OnEventHit( EventData ) + self:F( { eventhit = EventData } ) -- Debug info. - 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)) + 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 ) ) -- Player info local _unitName = EventData.IniUnitName - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) if _unit == nil or _playername == nil then return end @@ -1625,7 +1625,7 @@ function RANGE:OnEventHit(EventData) local _unitID = _unit:GetID() -- Target - local target = EventData.TgtUnit + local target = EventData.TgtUnit local targetname = EventData.TgtUnitName -- Current strafe target of player. @@ -1638,30 +1638,30 @@ function RANGE:OnEventHit(EventData) local targetPos = target:GetCoordinate() -- Loop over valid targets for this run. - for _,_target in pairs(_currentTarget.zone.targets) do + for _, _target in pairs( _currentTarget.zone.targets ) do -- Check the the target is the same that was actually hit. - if _target and _target:IsAlive() and _target:GetName() == targetname then + if _target and _target:IsAlive() and _target:GetName() == targetname then -- Get distance between player and target. - local dist=playerPos:Get2DDistance(targetPos) + local dist = playerPos:Get2DDistance( targetPos ) if dist > _currentTarget.zone.foulline then -- Increase hit counter of this run. - _currentTarget.hits = _currentTarget.hits + 1 + _currentTarget.hits = _currentTarget.hits + 1 -- Flare target. if _unit and _playername and self.PlayerSettings[_playername].flaredirecthits then - targetPos:Flare(self.PlayerSettings[_playername].flarecolor) + targetPos:Flare( self.PlayerSettings[_playername].flarecolor ) end else -- Too close to the target. - 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 + 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 @@ -1670,9 +1670,9 @@ function RANGE:OnEventHit(EventData) end -- Bombing Targets - for _,_bombtarget in pairs(self.bombingTargets) do + for _, _bombtarget in pairs( self.bombingTargets ) do - local _target=_bombtarget.target --Wrapper.Positionable#POSITIONABLE + local _target = _bombtarget.target -- Wrapper.Positionable#POSITIONABLE -- Check if one of the bomb targets was hit. if _target and _target:IsAlive() and _bombtarget.name == targetname then @@ -1685,7 +1685,7 @@ function RANGE:OnEventHit(EventData) -- Position of target. local targetPos = _target:GetCoordinate() - targetPos:Flare(self.PlayerSettings[_playername].flarecolor) + targetPos:Flare( self.PlayerSettings[_playername].flarecolor ) end end @@ -1697,8 +1697,8 @@ end --- Range event handler for event shot (when a unit releases a rocket or bomb (but not a fast firing gun). -- @param #RANGE self -- @param Core.Event#EVENTDATA EventData -function RANGE:OnEventShot(EventData) - self:F({eventshot = EventData}) +function RANGE:OnEventShot( EventData ) + self:F( { eventshot = EventData } ) -- Nil checks. if EventData.Weapon == nil then @@ -1709,28 +1709,28 @@ function RANGE:OnEventShot(EventData) end -- Weapon data. - local _weapon = EventData.Weapon:getTypeName() -- should be the same as Event.WeaponTypeName - local _weaponStrArray = UTILS.Split(_weapon,"%.") + local _weapon = EventData.Weapon:getTypeName() -- should be the same as Event.WeaponTypeName + local _weaponStrArray = UTILS.Split( _weapon, "%." ) local _weaponName = _weaponStrArray[#_weaponStrArray] -- Weapon descriptor. - local desc=EventData.Weapon:getDesc() + local desc = EventData.Weapon:getDesc() -- Weapon category: 0=SHELL, 1=MISSILE, 2=ROCKET, 3=BOMB (Weapon.Category.X) - local weaponcategory=desc.category + local weaponcategory = desc.category -- Debug info. - 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) + 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 ) -- Tracking conditions for bombs, rockets and missiles. - local _bombs = weaponcategory==Weapon.Category.BOMB --string.match(_weapon, "weapons.bombs") - local _rockets = weaponcategory==Weapon.Category.ROCKET --string.match(_weapon, "weapons.nurs") - local _missiles = weaponcategory==Weapon.Category.MISSILE --string.match(_weapon, "weapons.missiles") or _viggen + local _bombs = weaponcategory == Weapon.Category.BOMB -- string.match(_weapon, "weapons.bombs") + local _rockets = weaponcategory == Weapon.Category.ROCKET -- string.match(_weapon, "weapons.nurs") + local _missiles = weaponcategory == Weapon.Category.MISSILE -- string.match(_weapon, "weapons.missiles") or _viggen -- Check if any condition applies here. local _track = (_bombs and self.trackbombs) or (_rockets and self.trackrockets) or (_missiles and self.trackmissiles) @@ -1739,38 +1739,37 @@ function RANGE:OnEventShot(EventData) local _unitName = EventData.IniUnitName -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) -- Set this to larger value than the threshold. - local dPR=self.BombtrackThreshold*2 + local dPR = self.BombtrackThreshold * 2 -- Distance player to range. 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)) + 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 -- Only track if distance player to range is < 25 km. Also check that a player shot. No need to track AI weapons. - if _track and dPR<=self.BombtrackThreshold and _unit and _playername then + if _track and dPR <= self.BombtrackThreshold and _unit and _playername then -- Player data. - local playerData=self.PlayerSettings[_playername] --#RANGE.PlayerData + local playerData = self.PlayerSettings[_playername] -- #RANGE.PlayerData -- Tracking info and init of last bomb position. - self:T(self.id..string.format("RANGE %s: Tracking %s - %s.", self.rangename, _weapon, EventData.weapon:getName())) + self:T( self.id .. string.format( "RANGE %s: Tracking %s - %s.", self.rangename, _weapon, EventData.weapon:getName() ) ) -- Init bomb position. - local _lastBombPos = {x=0,y=0,z=0} --DCS#Vec3 + local _lastBombPos = { x = 0, y = 0, z = 0 } -- DCS#Vec3 -- Function monitoring the position of a bomb until impact. - local function trackBomb(_ordnance) + local function trackBomb( _ordnance ) -- When the pcall returns a failure the weapon has hit. - local _status,_bombPos = pcall( - function() + local _status, _bombPos = pcall( function() return _ordnance:getPoint() - end) + end ) - self:T2(self.id..string.format("Range %s: Bomb still in air: %s", self.rangename, tostring(_status))) + self:T2( self.id .. string.format( "Range %s: Bomb still in air: %s", self.rangename, tostring( _status ) ) ) if _status then ---------------------------- @@ -1778,7 +1777,7 @@ function RANGE:OnEventShot(EventData) ---------------------------- -- Remember this position. - _lastBombPos = {x = _bombPos.x, y = _bombPos.y, z= _bombPos.z } + _lastBombPos = { x = _bombPos.x, y = _bombPos.y, z = _bombPos.z } -- Check again in ~0.005 seconds ==> 200 checks per second. return timer.getTime() + self.dtBombtrack @@ -1790,55 +1789,55 @@ function RANGE:OnEventShot(EventData) ----------------------------- -- Get closet target to last position. - local _closetTarget=nil --#RANGE.BombTarget - local _distance=nil - local _closeCoord=nil - local _hitquality="POOR" + local _closetTarget = nil -- #RANGE.BombTarget + local _distance = nil + local _closeCoord = nil + local _hitquality = "POOR" -- Get callsign. - local _callsign=self:_myname(_unitName) + local _callsign = self:_myname( _unitName ) -- Coordinate of impact point. - local impactcoord=COORDINATE:NewFromVec3(_lastBombPos) + local impactcoord = COORDINATE:NewFromVec3( _lastBombPos ) -- Check if impact happened in range zone. - local insidezone=self.rangezone:IsCoordinateInZone(impactcoord) + local insidezone = self.rangezone:IsCoordinateInZone( impactcoord ) -- Impact point of bomb. if self.Debug then - impactcoord:MarkToAll("Bomb impact point") + impactcoord:MarkToAll( "Bomb impact point" ) end -- Smoke impact point of bomb. if playerData.smokebombimpact and insidezone then if playerData.delaysmoke then - timer.scheduleFunction(self._DelayedSmoke, {coord=impactcoord, color=playerData.smokecolor}, timer.getTime() + self.TdelaySmoke) + timer.scheduleFunction( self._DelayedSmoke, { coord = impactcoord, color = playerData.smokecolor }, timer.getTime() + self.TdelaySmoke ) else - impactcoord:Smoke(playerData.smokecolor) + impactcoord:Smoke( playerData.smokecolor ) end end -- Loop over defined bombing targets. - for _,_bombtarget in pairs(self.bombingTargets) do + for _, _bombtarget in pairs( self.bombingTargets ) do -- Get target coordinate. - local targetcoord=self:_GetBombTargetCoordinate(_bombtarget) + local targetcoord = self:_GetBombTargetCoordinate( _bombtarget ) if targetcoord then -- Distance between bomb and target. - local _temp = impactcoord:Get2DDistance(targetcoord) + local _temp = impactcoord:Get2DDistance( targetcoord ) -- Find closest target to last known position of the bomb. if _distance == nil or _temp < _distance then _distance = _temp _closetTarget = _bombtarget - _closeCoord=targetcoord - if _distance <= 0.5*_bombtarget.goodhitrange then + _closeCoord = targetcoord + if _distance <= 0.5 * _bombtarget.goodhitrange then _hitquality = "EXCELLENT" elseif _distance <= _bombtarget.goodhitrange then _hitquality = "GOOD" - elseif _distance <= 2*_bombtarget.goodhitrange then + elseif _distance <= 2 * _bombtarget.goodhitrange then _hitquality = "INEFFECTIVE" else _hitquality = "POOR" @@ -1852,44 +1851,44 @@ function RANGE:OnEventShot(EventData) if _distance and _distance <= self.scorebombdistance then -- Init bomb player results. if not self.bombPlayerResults[_playername] then - self.bombPlayerResults[_playername]={} + self.bombPlayerResults[_playername] = {} end -- Local results. - local _results=self.bombPlayerResults[_playername] + local _results = self.bombPlayerResults[_playername] - local result={} --#RANGE.BombResult - 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 + local result = {} -- #RANGE.BombResult + 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 -- Add to table. - table.insert(_results, result) + table.insert( _results, result ) -- Call impact. - self:Impact(result, playerData) + self:Impact( result, playerData ) elseif insidezone then -- Send message. - 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) + 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) + 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.") + self:T( self.id .. "Weapon impacted outside range zone." ) end - --Terminate the timer - self:T(self.id..string.format("Range %s, player %s: Terminating bomb track timer.", self.rangename, _playername)) + -- Terminate the timer + self:T( self.id .. string.format( "Range %s, player %s: Terminating bomb track timer.", self.rangename, _playername ) ) return nil end -- _status check @@ -1897,10 +1896,10 @@ function RANGE:OnEventShot(EventData) end -- end function trackBomb -- Weapon is not yet "alife" just yet. Start timer in one second. - 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) + 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 --if _track (string.match) and player-range distance < threshold. + end -- if _track (string.match) and player-range distance < threshold. end @@ -1913,38 +1912,38 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function RANGE:onafterStatus(From, Event, To) +function RANGE:onafterStatus( From, Event, To ) - if self.verbose>0 then + if self.verbose > 0 then - local fsmstate=self:GetState() + local fsmstate = self:GetState() - local text=string.format("Range status: %s", fsmstate) + local text = string.format( "Range status: %s", fsmstate ) if self.instructor then - local alive="N/A" + local alive = "N/A" if self.instructorrelayname then - local relay=UNIT:FindByName(self.instructorrelayname) + local relay = UNIT:FindByName( self.instructorrelayname ) if relay then - alive=tostring(relay:IsAlive()) + alive = tostring( relay:IsAlive() ) end end - text=text..string.format(", Instructor %.3f MHz (Relay=%s alive=%s)", self.instructorfreq, tostring(self.instructorrelayname), alive) + 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.rangecontrol then + local alive = "N/A" if self.rangecontrolrelayname then - local relay=UNIT:FindByName(self.rangecontrolrelayname) + local relay = UNIT:FindByName( self.rangecontrolrelayname ) if relay then - alive=tostring(relay:IsAlive()) + alive = tostring( relay:IsAlive() ) end end - text=text..string.format(", Control %.3f MHz (Relay=%s alive=%s)", self.rangecontrolfreq, tostring(self.rangecontrolrelayname), alive) + text = text .. string.format( ", Control %.3f MHz (Relay=%s alive=%s)", self.rangecontrolfreq, tostring( self.rangecontrolrelayname ), alive ) end -- Check range status. - self:I(self.id..text) + self:I( self.id .. text ) end @@ -1952,7 +1951,7 @@ function RANGE:onafterStatus(From, Event, To) self:_CheckPlayers() -- Check back in ~10 seconds. - self:__Status(-10) + self:__Status( -10 ) end --- Function called after player enters the range zone. @@ -1961,21 +1960,21 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param #RANGE.PlayerData player Player data. -function RANGE:onafterEnterRange(From, Event, To, player) +function RANGE:onafterEnterRange( From, Event, To, player ) if self.instructor and self.rangecontrol then -- Range control radio frequency split. - local RF=UTILS.Split(string.format("%.3f", self.rangecontrolfreq), ".") + local RF = UTILS.Split( string.format( "%.3f", self.rangecontrolfreq ), "." ) -- Radio message that player entered the range - 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]) + 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) + self.instructor:NewTransmission( RANGE.Sound.IRMegaHertz.filename, RANGE.Sound.IRMegaHertz.duration, self.soundpath ) end end @@ -1986,10 +1985,10 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param #RANGE.PlayerData player Player data. -function RANGE:onafterExitRange(From, Event, To, player) +function RANGE:onafterExitRange( From, Event, To, player ) if self.instructor then - self.instructor:NewTransmission(RANGE.Sound.IRExitRange.filename, RANGE.Sound.IRExitRange.duration, self.soundpath) + self.instructor:NewTransmission( RANGE.Sound.IRExitRange.filename, RANGE.Sound.IRExitRange.duration, self.soundpath ) end end @@ -2001,48 +2000,43 @@ end -- @param #string To To state. -- @param #RANGE.BombResult result Result of bomb impact. -- @param #RANGE.PlayerData player Player data table. -function RANGE:onafterImpact(From, Event, To, result, player) - - -- Only display target name if there is more than one bomb target. - local targetname=nil - if #self.bombingTargets>1 then - local targetname=result.name - end +function RANGE:onafterImpact( From, Event, To, result, player ) -- Send message to player. - 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.") + local text = string.format( "%s, impact %03d° for %d m (%d ft)", player.playername, result.radial, result.distance, UTILS.MetersToFeet( result.distance ) ) + -- Only display target name if there is more than one bomb target. + if #self.bombingTargets > 1 then + text = text .. string.format( " from bulls of target %s.", result.name ) else - text=text.."." + text = text .. "." end - text=text..string.format(" %s hit.", result.quality) + 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) + 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 -- Unit. - local unit=UNIT:FindByName(player.unitname) + local unit = UNIT:FindByName( player.unitname ) -- Send message. - self:_DisplayMessageToGroup(unit, text, nil, true) - self:T(self.id..text) + self:_DisplayMessageToGroup( unit, text, nil, true ) + self:T( self.id .. text ) -- Save results. if self.autosave then @@ -2056,11 +2050,11 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function RANGE:onbeforeSave(From, Event, To) +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.")) + self:E( self.id .. string.format( "WARNING: io and/or lfs not desanitized. Cannot save player results." ) ) return false end end @@ -2070,50 +2064,50 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function RANGE:onafterSave(From, Event, To) +function RANGE:onafterSave( From, Event, To ) - local function _savefile(filename, data) - local f=io.open(filename, "wb") + local function _savefile( filename, data ) + local f = io.open( filename, "wb" ) if f then - f:write(data) + f:write( data ) f:close() - self:I(self.id..string.format("Saving player results to file %s", tostring(filename))) + 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))) + self:E( self.id .. string.format( "ERROR: Could not save results to file %s", tostring( filename ) ) ) end end -- Path. - local path=lfs.writedir()..[[Logs\]] + local path = lfs.writedir() .. [[Logs\]] -- Set file name. - local filename=path..string.format("RANGE-%s_BombingResults.csv", self.rangename) + local filename = path .. string.format( "RANGE-%s_BombingResults.csv", self.rangename ) -- Header line. - local scores="Name,Pass,Target,Distance,Radial,Quality,Weapon,Airframe,Mission Time" + local scores = "Name,Pass,Target,Distance,Radial,Quality,Weapon,Airframe,Mission Time" -- Loop over all players. - for playername,results in pairs(self.bombPlayerResults) do + for playername, results in pairs( self.bombPlayerResults ) do -- Loop over player grades table. - for i,_result in pairs(results) do - local result=_result --#RANGE.BombResult - 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" + for i, _result in pairs( results ) do + local result = _result -- #RANGE.BombResult + 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() + 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) + 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) + _savefile( filename, scores ) end --- Function called before save event. Checks that io and lfs are desanitized. @@ -2121,11 +2115,11 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function RANGE:onbeforeLoad(From, Event, To) +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.")) + self:E( self.id .. string.format( "WARNING: io and/or lfs not desanitized. Cannot load player results." ) ) return false end end @@ -2135,74 +2129,74 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function RANGE:onafterLoad(From, Event, To) +function RANGE:onafterLoad( From, Event, To ) --- Function that load data from a file. - local function _loadfile(filename) - local f=io.open(filename, "rb") + local function _loadfile( filename ) + local f = io.open( filename, "rb" ) if f then - --self:I(self.id..string.format("Loading player results from file %s", tostring(filename))) - local data=f:read("*all") + -- self:I(self.id..string.format("Loading player results from file %s", tostring(filename))) + 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))) + 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 -- Path in DCS log file. - local path=lfs.writedir()..[[Logs\]] + local path = lfs.writedir() .. [[Logs\]] -- Set file name. - local filename=path..string.format("RANGE-%s_BombingResults.csv", self.rangename) + local filename = path .. string.format( "RANGE-%s_BombingResults.csv", self.rangename ) -- Info message. - local text=string.format("Loading player bomb results from file %s", filename) - self:I(self.id..text) + local text = string.format( "Loading player bomb results from file %s", filename ) + self:I( self.id .. text ) -- Load asset data from file. - local data=_loadfile(filename) + local data = _loadfile( filename ) if data then -- Split by line break. - local results=UTILS.Split(data,"\n") + local results = UTILS.Split( data, "\n" ) -- Remove first header line. - table.remove(results, 1) + table.remove( results, 1 ) -- Init player scores table. - self.bombPlayerResults={} + self.bombPlayerResults = {} -- Loop over all lines. - for _,_result in pairs(results) do + for _, _result in pairs( results ) do -- Parameters are separated by commata. - local resultdata=UTILS.Split(_result, ",") + local resultdata = UTILS.Split( _result, "," ) -- Grade table - local result={} --#RANGE.BombResult + local result = {} -- #RANGE.BombResult -- Player name. - local playername=resultdata[1] - result.player=playername + local playername = resultdata[1] + result.player = playername -- Results data. - 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" + 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" -- Create player array if necessary. - self.bombPlayerResults[playername]=self.bombPlayerResults[playername] or {} + self.bombPlayerResults[playername] = self.bombPlayerResults[playername] or {} -- Add result to table. - table.insert(self.bombPlayerResults[playername], result) + table.insert( self.bombPlayerResults[playername], result ) end end end @@ -2213,50 +2207,52 @@ end --- Start smoking a coordinate with a delay. -- @param #table _args Argements passed. -function RANGE._DelayedSmoke(_args) - trigger.action.smoke(_args.coord:GetVec3(), _args.color) +function RANGE._DelayedSmoke( _args ) + trigger.action.smoke( _args.coord:GetVec3(), _args.color ) end --- Display top 10 stafing results of a specific player. -- @param #RANGE self -- @param #string _unitName Name of the player unit. -function RANGE:_DisplayMyStrafePitResults(_unitName) - self:F(_unitName) +function RANGE:_DisplayMyStrafePitResults( _unitName ) + self:F( _unitName ) -- Get player unit and name - local _unit,_playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) if _unit and _playername then -- Message header. - local _message = string.format("My Top %d Strafe Pit Results:\n", self.ndisplayresult) + local _message = string.format( "My Top %d Strafe Pit Results:\n", self.ndisplayresult ) -- Get player results. local _results = self.strafePlayerResults[_playername] -- Create message. if _results == nil then - -- No score yet. - _message = string.format("%s: No Score yet.", _playername) + -- No score yet. + _message = string.format( "%s: No Score yet.", _playername ) else -- Sort results table wrt number of hits. - local _sort = function( a,b ) return a.hits > b.hits end - table.sort(_results,_sort) + local _sort = function( a, b ) + return a.hits > b.hits + end + table.sort( _results, _sort ) -- Prepare message of best results. local _bestMsg = "" local _count = 1 -- Loop over results - for _,_result in pairs(_results) do + for _, _result in pairs( _results ) do -- Message text. - _message = _message..string.format("\n[%d] Hits %d - %s - %s", _count, _result.hits, _result.zone.name, _result.text) + _message = _message .. string.format( "\n[%d] Hits %d - %s - %s", _count, _result.hits, _result.zone.name, _result.text ) -- Best result. if _bestMsg == "" then - _bestMsg = string.format("Hits %d - %s - %s", _result.hits, _result.zone.name, _result.text) + _bestMsg = string.format( "Hits %d - %s - %s", _result.hits, _result.zone.name, _result.text ) end -- 10 runs @@ -2265,26 +2261,26 @@ function RANGE:_DisplayMyStrafePitResults(_unitName) end -- Increase counter - _count = _count+1 + _count = _count + 1 end -- Message text. - _message = _message .."\n\nBEST: ".._bestMsg + _message = _message .. "\n\nBEST: " .. _bestMsg end -- Send message to group. - self:_DisplayMessageToGroup(_unit, _message, nil, true, true) + self:_DisplayMessageToGroup( _unit, _message, nil, true, true ) end end --- Display top 10 strafing results of all players. -- @param #RANGE self -- @param #string _unitName Name fo the player unit. -function RANGE:_DisplayStrafePitResults(_unitName) - self:F(_unitName) +function RANGE:_DisplayStrafePitResults( _unitName ) + self:F( _unitName ) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) -- Check if we have a unit which is a player. if _unit and _playername then @@ -2293,14 +2289,14 @@ function RANGE:_DisplayStrafePitResults(_unitName) local _playerResults = {} -- Message text. - local _message = string.format("Strafe Pit Results - Top %d Players:\n", self.ndisplayresult) + local _message = string.format( "Strafe Pit Results - Top %d Players:\n", self.ndisplayresult ) -- Loop over player results. - for _playerName,_results in pairs(self.strafePlayerResults) do + for _playerName, _results in pairs( self.strafePlayerResults ) do -- Get the best result of the player. local _best = nil - for _,_result in pairs(_results) do + for _, _result in pairs( _results ) do if _best == nil or _result.hits > _best.hits then _best = _result end @@ -2308,68 +2304,72 @@ function RANGE:_DisplayStrafePitResults(_unitName) -- Add best result to table. 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}) + 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 - --Sort list! - local _sort = function( a,b ) return a.hits > b.hits end - table.sort(_playerResults,_sort) + -- Sort list! + local _sort = function( a, b ) + return a.hits > b.hits + end + table.sort( _playerResults, _sort ) -- Add top 10 results. - for _i = 1, math.min(#_playerResults, self.ndisplayresult) do - _message = _message..string.format("\n[%d] %s", _i, _playerResults[_i].msg) + for _i = 1, math.min( #_playerResults, self.ndisplayresult ) do + _message = _message .. string.format( "\n[%d] %s", _i, _playerResults[_i].msg ) end -- In case there are no scores yet. - if #_playerResults<1 then - _message = _message.."No player scored yet." + if #_playerResults < 1 then + _message = _message .. "No player scored yet." end -- Send message. - self:_DisplayMessageToGroup(_unit, _message, nil, true, true) + self:_DisplayMessageToGroup( _unit, _message, nil, true, true ) end end --- Display top 10 bombing run results of specific player. -- @param #RANGE self -- @param #string _unitName Name of the player unit. -function RANGE:_DisplayMyBombingResults(_unitName) - self:F(_unitName) +function RANGE:_DisplayMyBombingResults( _unitName ) + self:F( _unitName ) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) if _unit and _playername then -- Init message. - local _message = string.format("My Top %d Bombing Results:\n", self.ndisplayresult) + local _message = string.format( "My Top %d Bombing Results:\n", self.ndisplayresult ) -- Results from player. local _results = self.bombPlayerResults[_playername] -- No score so far. if _results == nil then - _message = _playername..": No Score yet." + _message = _playername .. ": No Score yet." else -- Sort results wrt to distance. - local _sort = function( a,b ) return a.distance < b.distance end - table.sort(_results,_sort) + local _sort = function( a, b ) + return a.distance < b.distance + end + table.sort( _results, _sort ) -- Loop over results. local _bestMsg = "" - for i,_result in pairs(_results) do - local result=_result --#RANGE.BombResult + for i, _result in pairs( _results ) do + local result = _result -- #RANGE.BombResult -- Message with name, weapon and distance. - _message = _message.."\n"..string.format("[%d] %d m %03d° - %s - %s - %s hit", i, result.distance, result.radial, result.name, result.weapon, result.quality) + _message = _message .. "\n" .. string.format( "[%d] %d m %03d° - %s - %s - %s hit", i, result.distance, result.radial, result.name, result.weapon, result.quality ) -- Store best/first result. if _bestMsg == "" then - _bestMsg = string.format("%d m %03d° - %s - %s - %s hit", result.distance, result.radial, result.name, result.weapon, result.quality) + _bestMsg = string.format( "%d m %03d° - %s - %s - %s hit", result.distance, result.radial, result.name, result.weapon, result.quality ) end -- Best 10 runs only. @@ -2380,154 +2380,156 @@ function RANGE:_DisplayMyBombingResults(_unitName) end -- Message. - _message = _message .."\n\nBEST: ".._bestMsg + _message = _message .. "\n\nBEST: " .. _bestMsg end -- Send message. - self:_DisplayMessageToGroup(_unit, _message, nil, true, true) + self:_DisplayMessageToGroup( _unit, _message, nil, true, true ) end end --- Display best bombing results of top 10 players. -- @param #RANGE self -- @param #string _unitName Name of player unit. -function RANGE:_DisplayBombingResults(_unitName) - self:F(_unitName) +function RANGE:_DisplayBombingResults( _unitName ) + self:F( _unitName ) -- Results table. local _playerResults = {} -- Get player unit and name. - local _unit, _player = self:_GetPlayerUnitAndName(_unitName) + local _unit, _player = self:_GetPlayerUnitAndName( _unitName ) -- Check if we have a unit with a player. if _unit and _player then -- Message header. - local _message = string.format("Bombing Results - Top %d Players:\n", self.ndisplayresult) + local _message = string.format( "Bombing Results - Top %d Players:\n", self.ndisplayresult ) -- Loop over players. - for _playerName,_results in pairs(self.bombPlayerResults) do + for _playerName, _results in pairs( self.bombPlayerResults ) do -- Find best result of player. local _best = nil - for _,_result in pairs(_results) do + for _, _result in pairs( _results ) do if _best == nil or _result.distance < _best.distance then - _best = _result + _best = _result end end -- Put best result of player into table. if _best ~= nil then - local bestres=string.format("%s: %d m - %s - %s - %s hit", _playerName, _best.distance, _best.name, _best.weapon, _best.quality) - table.insert(_playerResults, {msg = bestres, distance = _best.distance}) + local bestres = string.format( "%s: %d m - %s - %s - %s hit", _playerName, _best.distance, _best.name, _best.weapon, _best.quality ) + table.insert( _playerResults, { msg = bestres, distance = _best.distance } ) end end -- Sort list of player results. - local _sort = function( a,b ) return a.distance < b.distance end - table.sort(_playerResults,_sort) + local _sort = function( a, b ) + return a.distance < b.distance + end + table.sort( _playerResults, _sort ) -- Loop over player results. - for _i = 1, math.min(#_playerResults, self.ndisplayresult) do - _message = _message..string.format("\n[%d] %s", _i, _playerResults[_i].msg) + for _i = 1, math.min( #_playerResults, self.ndisplayresult ) do + _message = _message .. string.format( "\n[%d] %s", _i, _playerResults[_i].msg ) end -- In case there are no scores yet. - if #_playerResults<1 then - _message = _message.."No player scored yet." + if #_playerResults < 1 then + _message = _message .. "No player scored yet." end -- Send message. - self:_DisplayMessageToGroup(_unit, _message, nil, true, true) + self:_DisplayMessageToGroup( _unit, _message, nil, true, true ) end end --- Report information like bearing and range from player unit to range. -- @param #RANGE self -- @param #string _unitname Name of the player unit. -function RANGE:_DisplayRangeInfo(_unitname) - self:F(_unitname) +function RANGE:_DisplayRangeInfo( _unitname ) + self:F( _unitname ) -- Get player unit and player name. - local unit, playername = self:_GetPlayerUnitAndName(_unitname) + local unit, playername = self:_GetPlayerUnitAndName( _unitname ) -- Check if we have a player. if unit and playername then -- Message text. - local text="" + local text = "" -- Current coordinates. - local coord=unit:GetCoordinate() + local coord = unit:GetCoordinate() if self.location then - local settings=_DATABASE:GetPlayerSettings(playername) or _SETTINGS --Core.Settings#SETTINGS + local settings = _DATABASE:GetPlayerSettings( playername ) or _SETTINGS -- Core.Settings#SETTINGS -- Direction vector from current position (coord) to target (position). - local position=self.location --Core.Point#COORDINATE - local bulls=position:ToStringBULLS(unit:GetCoalition(), settings) - local lldms=position:ToStringLLDMS(settings) - local llddm=position:ToStringLLDDM(settings) - local rangealt=position:GetLandHeight() - local vec3=coord:GetDirectionVec3(position) - local angle=coord:GetAngleDegrees(vec3) - local range=coord:Get2DDistance(position) + local position = self.location -- Core.Point#COORDINATE + local bulls = position:ToStringBULLS( unit:GetCoalition(), settings ) + local lldms = position:ToStringLLDMS( settings ) + local llddm = position:ToStringLLDDM( settings ) + local rangealt = position:GetLandHeight() + local vec3 = coord:GetDirectionVec3( position ) + local angle = coord:GetAngleDegrees( vec3 ) + local range = coord:Get2DDistance( position ) -- Bearing string. - local Bs=string.format('%03d°', angle) + local Bs = string.format( "%03d°", angle ) local texthit if self.PlayerSettings[playername].flaredirecthits then - texthit=string.format("Flare direct hits: ON (flare color %s)\n", self:_flarecolor2text(self.PlayerSettings[playername].flarecolor)) + texthit = string.format( "Flare direct hits: ON (flare color %s)\n", self:_flarecolor2text( self.PlayerSettings[playername].flarecolor ) ) else - texthit=string.format("Flare direct hits: OFF\n") + texthit = string.format( "Flare direct hits: OFF\n" ) end local textbomb if self.PlayerSettings[playername].smokebombimpact then - textbomb=string.format("Smoke bomb impact points: ON (smoke color %s)\n", self:_smokecolor2text(self.PlayerSettings[playername].smokecolor)) + textbomb = string.format( "Smoke bomb impact points: ON (smoke color %s)\n", self:_smokecolor2text( self.PlayerSettings[playername].smokecolor ) ) else - textbomb=string.format("Smoke bomb impact points: OFF\n") + textbomb = string.format( "Smoke bomb impact points: OFF\n" ) end local textdelay if self.PlayerSettings[playername].delaysmoke then - textdelay=string.format("Smoke bomb delay: ON (delay %.1f seconds)", self.TdelaySmoke) + textdelay = string.format( "Smoke bomb delay: ON (delay %.1f seconds)", self.TdelaySmoke ) else - textdelay=string.format("Smoke bomb delay: OFF") + textdelay = string.format( "Smoke bomb delay: OFF" ) end -- Player unit settings. - local trange=string.format("%.1f km", range/1000) - local trangealt=string.format("%d m", rangealt) - local tstrafemaxalt=string.format("%d m", self.strafemaxalt) + local trange = string.format( "%.1f km", range / 1000 ) + local trangealt = string.format( "%d m", rangealt ) + local tstrafemaxalt = string.format( "%d m", self.strafemaxalt ) if settings:IsImperial() then - trange=string.format("%.1f NM", UTILS.MetersToNM(range)) - trangealt=string.format("%d feet", UTILS.MetersToFeet(rangealt)) - tstrafemaxalt=string.format("%d feet", UTILS.MetersToFeet(self.strafemaxalt)) + trange = string.format( "%.1f NM", UTILS.MetersToNM( range ) ) + trangealt = string.format( "%d feet", UTILS.MetersToFeet( rangealt ) ) + tstrafemaxalt = string.format( "%d feet", UTILS.MetersToFeet( self.strafemaxalt ) ) end -- Message. - text=text..string.format("Information on %s:\n", self.rangename) - text=text..string.format("-------------------------------------------------------\n") - text=text..string.format("Bearing %s, Range %s\n", Bs, trange) - text=text..string.format("%s\n", bulls) - text=text..string.format("%s\n", lldms) - text=text..string.format("%s\n", llddm) - text=text..string.format("Altitude ASL: %s\n", trangealt) - text=text..string.format("Max strafing alt AGL: %s\n", tstrafemaxalt) - text=text..string.format("# of strafe targets: %d\n", self.nstrafetargets) - text=text..string.format("# of bomb targets: %d\n", self.nbombtargets) - text=text..texthit - text=text..textbomb - text=text..textdelay + text = text .. string.format( "Information on %s:\n", self.rangename ) + text = text .. string.format( "-------------------------------------------------------\n" ) + text = text .. string.format( "Bearing %s, Range %s\n", Bs, trange ) + text = text .. string.format( "%s\n", bulls ) + text = text .. string.format( "%s\n", lldms ) + text = text .. string.format( "%s\n", llddm ) + text = text .. string.format( "Altitude ASL: %s\n", trangealt ) + text = text .. string.format( "Max strafing alt AGL: %s\n", tstrafemaxalt ) + text = text .. string.format( "# of strafe targets: %d\n", self.nstrafetargets ) + text = text .. string.format( "# of bomb targets: %d\n", self.nbombtargets ) + text = text .. texthit + text = text .. textbomb + text = text .. textdelay -- Send message to player group. - self:_DisplayMessageToGroup(unit, text, nil, true, true) + self:_DisplayMessageToGroup( unit, text, nil, true, true ) -- Debug output. - self:T2(self.id..text) + self:T2( self.id .. text ) end end end @@ -2535,148 +2537,148 @@ end --- Display bombing target locations to player. -- @param #RANGE self -- @param #string _unitname Name of the player unit. -function RANGE:_DisplayBombTargets(_unitname) - self:F(_unitname) +function RANGE:_DisplayBombTargets( _unitname ) + self:F( _unitname ) -- Get player unit and player name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitname) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitname ) -- Check if we have a player. if _unit and _playername then -- Player settings. - local _settings=_DATABASE:GetPlayerSettings(_playername) or _SETTINGS --Core.Settings#SETTINGS + local _settings = _DATABASE:GetPlayerSettings( _playername ) or _SETTINGS -- Core.Settings#SETTINGS -- Message text. - local _text="Bomb Target Locations:" + local _text = "Bomb Target Locations:" - for _,_bombtarget in pairs(self.bombingTargets) do - local bombtarget=_bombtarget --#RANGE.BombTarget + for _, _bombtarget in pairs( self.bombingTargets ) do + local bombtarget = _bombtarget -- #RANGE.BombTarget -- Coordinate of bombtarget. - local coord=self:_GetBombTargetCoordinate(bombtarget) + local coord = self:_GetBombTargetCoordinate( bombtarget ) if coord then -- Get elevation - local elevation=coord:GetLandHeight() - local eltxt=string.format("%d m", elevation) + local elevation = coord:GetLandHeight() + local eltxt = string.format( "%d m", elevation ) if not _settings:IsMetric() then - elevation=UTILS.MetersToFeet(elevation) - eltxt=string.format("%d ft", elevation) + elevation = UTILS.MetersToFeet( elevation ) + eltxt = string.format( "%d ft", elevation ) end - local ca2g=coord:ToStringA2G(_unit,_settings) - _text=_text..string.format("\n- %s:\n%s @ %s", bombtarget.name or "unknown", ca2g, eltxt) + local ca2g = coord:ToStringA2G( _unit, _settings ) + _text = _text .. string.format( "\n- %s:\n%s @ %s", bombtarget.name or "unknown", ca2g, eltxt ) end end - self:_DisplayMessageToGroup(_unit,_text, 60, true, true) + self:_DisplayMessageToGroup( _unit, _text, 60, true, true ) end end --- Display pit location and heading to player. -- @param #RANGE self -- @param #string _unitname Name of the player unit. -function RANGE:_DisplayStrafePits(_unitname) - self:F(_unitname) +function RANGE:_DisplayStrafePits( _unitname ) + self:F( _unitname ) -- Get player unit and player name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitname) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitname ) -- Check if we have a player. if _unit and _playername then -- Player settings. - local _settings=_DATABASE:GetPlayerSettings(_playername) or _SETTINGS --Core.Settings#SETTINGS + local _settings = _DATABASE:GetPlayerSettings( _playername ) or _SETTINGS -- Core.Settings#SETTINGS -- Message text. - local _text="Strafe Target Locations:" + local _text = "Strafe Target Locations:" - for _,_strafepit in pairs(self.strafeTargets) do - local _target=_strafepit --Wrapper.Positionable#POSITIONABLE + for _, _strafepit in pairs( self.strafeTargets ) do + local _target = _strafepit -- Wrapper.Positionable#POSITIONABLE -- Pit parameters. - local coord=_strafepit.coordinate --Core.Point#COORDINATE - local heading=_strafepit.heading + local coord = _strafepit.coordinate -- Core.Point#COORDINATE + local heading = _strafepit.heading -- Turn heading around ==> approach heading. - if heading>180 then - heading=heading-180 + if heading > 180 then + heading = heading - 180 else - heading=heading+180 + heading = heading + 180 end - local mycoord=coord:ToStringA2G(_unit, _settings) - _text=_text..string.format("\n- %s: heading %03d°\n%s",_strafepit.name, heading, mycoord) + 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) + self:_DisplayMessageToGroup( _unit, _text, nil, true, true ) end end --- Report weather conditions at range. Temperature, QFE pressure and wind data. -- @param #RANGE self -- @param #string _unitname Name of the player unit. -function RANGE:_DisplayRangeWeather(_unitname) - self:F(_unitname) +function RANGE:_DisplayRangeWeather( _unitname ) + self:F( _unitname ) -- Get player unit and player name. - local unit, playername = self:_GetPlayerUnitAndName(_unitname) + local unit, playername = self:_GetPlayerUnitAndName( _unitname ) -- Check if we have a player. if unit and playername then -- Message text. - local text="" + local text = "" -- Current coordinates. - local coord=unit:GetCoordinate() + local coord = unit:GetCoordinate() if self.location then -- Get atmospheric data at range location. - local position=self.location --Core.Point#COORDINATE - local T=position:GetTemperature() - local P=position:GetPressure() - local Wd,Ws=position:GetWind() + local position = self.location -- Core.Point#COORDINATE + local T = position:GetTemperature() + local P = position:GetPressure() + local Wd, Ws = position:GetWind() -- Get Beaufort wind scale. - local Bn,Bd=UTILS.BeaufortScale(Ws) + local Bn, Bd = UTILS.BeaufortScale( Ws ) - local WD=string.format('%03d°', Wd) - local Ts=string.format("%d°C",T) + local WD = string.format( "%03d°", Wd ) + local Ts = string.format( "%d°C", T ) - local hPa2inHg=0.0295299830714 - local hPa2mmHg=0.7500615613030 + local hPa2inHg = 0.0295299830714 + local hPa2mmHg = 0.7500615613030 - local settings=_DATABASE:GetPlayerSettings(playername) or _SETTINGS --Core.Settings#SETTINGS - local tT=string.format("%d°C",T) - local tW=string.format("%.1f m/s", Ws) - local tP=string.format("%.1f mmHg", P*hPa2mmHg) + local settings = _DATABASE:GetPlayerSettings( playername ) or _SETTINGS -- Core.Settings#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 - --tT=string.format("%d°F", UTILS.CelciusToFarenheit(T)) - tW=string.format("%.1f knots", UTILS.MpsToKnots(Ws)) - tP=string.format("%.2f inHg", P*hPa2inHg) + -- tT=string.format("%d°F", UTILS.CelciusToFarenheit(T)) + tW = string.format( "%.1f knots", UTILS.MpsToKnots( Ws ) ) + tP = string.format( "%.2f inHg", P * hPa2inHg ) end -- Message text. - 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) + 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) + text = string.format( "No range location defined for range %s.", self.rangename ) end -- Send message to player group. - self:_DisplayMessageToGroup(unit, text, nil, true, true) + self:_DisplayMessageToGroup( unit, text, nil, true, true ) -- Debug output. - self:T2(self.id..text) + self:T2( self.id .. text ) else - self:T(self.id..string.format("ERROR! Could not find player unit in RangeInfo! Name = %s", _unitname)) + self:T( self.id .. string.format( "ERROR! Could not find player unit in RangeInfo! Name = %s", _unitname ) ) end end @@ -2688,23 +2690,23 @@ end -- @param #string _unitName Name of player unit. function RANGE:_CheckPlayers() - for playername,_playersettings in pairs(self.PlayerSettings) do - local playersettings=_playersettings --#RANGE.PlayerData + for playername, _playersettings in pairs( self.PlayerSettings ) do + local playersettings = _playersettings -- #RANGE.PlayerData - local unitname=playersettings.unitname - local unit=UNIT:FindByName(unitname) + local unitname = playersettings.unitname + local unit = UNIT:FindByName( unitname ) if unit and unit:IsAlive() then - if unit:IsInZone(self.rangezone) then + if unit:IsInZone( self.rangezone ) then ------------------------------ -- Player INSIDE Range Zone -- ------------------------------ if not playersettings.inzone then - playersettings.inzone=true - self:EnterRange(playersettings) + playersettings.inzone = true + self:EnterRange( playersettings ) end else @@ -2713,9 +2715,9 @@ function RANGE:_CheckPlayers() -- Player OUTSIDE Range Zone -- ------------------------------- - if playersettings.inzone==true then - playersettings.inzone=false - self:ExitRange(playersettings) + if playersettings.inzone == true then + playersettings.inzone = false + self:ExitRange( playersettings ) end end @@ -2727,33 +2729,33 @@ end --- Check if player is inside a strafing zone. If he is, we start looking for hits. If he was and left the zone again, the result is stored. -- @param #RANGE self -- @param #string _unitName Name of player unit. -function RANGE:_CheckInZone(_unitName) - self:F2(_unitName) +function RANGE:_CheckInZone( _unitName ) + self:F2( _unitName ) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) if _unit and _playername then --- Function to check if unit is in zone and facing in the right direction and is below the max alt. - local function checkme(targetheading, _zone) - local zone=_zone --Core.Zone#ZONE + local function checkme( targetheading, _zone ) + local zone = _zone -- Core.Zone#ZONE -- Heading check. - local unitheading = _unit:GetHeading() - local pitheading = targetheading-180 - local deltaheading = unitheading-pitheading - local towardspit = math.abs(deltaheading)<=90 or math.abs(deltaheading-360)<=90 + 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} --DCS#Vec2 - local landheight=land.getHeight(vec2) - local unitalt=vec3.y-landheight + local vec3 = _unit:GetVec3() + local vec2 = { x = vec3.x, y = vec3.z } -- DCS#Vec2 + local landheight = land.getHeight( vec2 ) + local unitalt = vec3.y - landheight - if unitalt<=self.strafemaxalt then - local unitinzone=zone:IsVec2InZone(vec2) + if unitalt <= self.strafemaxalt then + local unitinzone = zone:IsVec2InZone( vec2 ) return unitinzone end end @@ -2762,29 +2764,29 @@ function RANGE:_CheckInZone(_unitName) end -- Current position of player unit. - local _unitID = _unit:GetID() + local _unitID = _unit:GetID() -- Currently strafing? (strafeStatus is nil if not) local _currentStrafeRun = self.strafeStatus[_unitID] - if _currentStrafeRun then -- player has already registered for a strafing run. + if _currentStrafeRun then -- player has already registered for a strafing run. -- Get the current approach zone and check if player is inside. - local zone=_currentStrafeRun.zone.polygon --Core.Zone#ZONE_POLYGON_BASE + local zone = _currentStrafeRun.zone.polygon -- Core.Zone#ZONE_POLYGON_BASE -- Check if unit in zone and facing the right direction. - local unitinzone=checkme(_currentStrafeRun.zone.heading, zone) + local unitinzone = checkme( _currentStrafeRun.zone.heading, zone ) -- Check if player is in strafe zone and below max alt. if unitinzone then -- Still in zone, keep counting hits. Increase counter. - _currentStrafeRun.time = _currentStrafeRun.time+1 + _currentStrafeRun.time = _currentStrafeRun.time + 1 else -- Increase counter - _currentStrafeRun.time = _currentStrafeRun.time+1 + _currentStrafeRun.time = _currentStrafeRun.time + 1 if _currentStrafeRun.time <= 3 then @@ -2792,69 +2794,71 @@ function RANGE:_CheckInZone(_unitName) self.strafeStatus[_unitID] = nil -- Message text. - local _msg = string.format("%s left strafing zone %s too quickly. No Score.", _playername, _currentStrafeRun.zone.name) + local _msg = string.format( "%s left strafing zone %s too quickly. No Score.", _playername, _currentStrafeRun.zone.name ) -- Send message. - self:_DisplayMessageToGroup(_unit, _msg, nil, true) + self:_DisplayMessageToGroup( _unit, _msg, nil, true ) if self.rangecontrol then - self.rangecontrol:NewTransmission(RANGE.Sound.RCLeftStrafePitTooQuickly.filename, RANGE.Sound.RCLeftStrafePitTooQuickly.duration, self.soundpath) + self.rangecontrol:NewTransmission( RANGE.Sound.RCLeftStrafePitTooQuickly.filename, RANGE.Sound.RCLeftStrafePitTooQuickly.duration, self.soundpath ) end else -- Get current ammo. - local _ammo=self:_GetAmmo(_unitName) + local _ammo = self:_GetAmmo( _unitName ) -- Result. local _result = self.strafeStatus[_unitID] - local _sound = nil --#RANGE.Soundfile + local _sound = nil -- #RANGE.Soundfile -- Judge this pass. Text is displayed on summary. - if _result.hits >= _result.zone.goodPass*2 then + if _result.hits >= _result.zone.goodPass * 2 then _result.text = "EXCELLENT PASS" - _sound=RANGE.Sound.RCExcellentPass + _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 + _sound = RANGE.Sound.RCGoodPass + elseif _result.hits >= _result.zone.goodPass / 2 then _result.text = "INEFFECTIVE PASS" - _sound=RANGE.Sound.RCIneffectivePass + _sound = RANGE.Sound.RCIneffectivePass else _result.text = "POOR PASS" - _sound=RANGE.Sound.RCPoorPass + _sound = RANGE.Sound.RCPoorPass end -- Calculate accuracy of run. Number of hits wrt number of rounds fired. - local shots=_result.ammo-_ammo - local accur=0 - if shots>0 then - accur=_result.hits/shots*100 - if accur > 100 then accur = 100 end + local shots = _result.ammo - _ammo + local accur = 0 + if shots > 0 then + accur = _result.hits / shots * 100 + if accur > 100 then + accur = 100 + end end -- Message text. - local _text=string.format("%s, hits on target %s: %d", self:_myname(_unitName), _result.zone.name, _result.hits) + 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) + _text = _text .. string.format( "\nTotal rounds fired %d. Accuracy %.1f %%.", shots, accur ) end - _text=_text..string.format("\n%s", _result.text) + _text = _text .. string.format( "\n%s", _result.text ) -- Send message. - self:_DisplayMessageToGroup(_unit, _text) + self:_DisplayMessageToGroup( _unit, _text ) -- Voice over. - 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 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) + 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) + self.rangecontrol:NewTransmission( _sound.filename, _sound.duration, self.soundpath, nil, 0.5 ) end -- Set strafe status to nil. @@ -2862,7 +2866,7 @@ function RANGE:_CheckInZone(_unitName) -- Save stats so the player can retrieve them. local _stats = self.strafePlayerResults[_playername] or {} - table.insert(_stats, _result) + table.insert( _stats, _result ) self.strafePlayerResults[_playername] = _stats end @@ -2871,32 +2875,32 @@ function RANGE:_CheckInZone(_unitName) else -- Check to see if we're in any of the strafing zones (first time). - for _,_targetZone in pairs(self.strafeTargets) do + for _, _targetZone in pairs( self.strafeTargets ) do -- Get the current approach zone and check if player is inside. - local zone=_targetZone.polygon --Core.Zone#ZONE_POLYGON_BASE + local zone = _targetZone.polygon -- Core.Zone#ZONE_POLYGON_BASE -- Check if unit in zone and facing the right direction. - local unitinzone=checkme(_targetZone.heading, zone) + local unitinzone = checkme( _targetZone.heading, zone ) -- Player is inside zone. if unitinzone then -- Get ammo at the beginning of the run. - local _ammo=self:_GetAmmo(_unitName) + local _ammo = self:_GetAmmo( _unitName ) -- Init strafe status for this player. - self.strafeStatus[_unitID] = {hits = 0, zone = _targetZone, time = 1, ammo=_ammo, pastfoulline=false} + self.strafeStatus[_unitID] = { hits = 0, zone = _targetZone, time = 1, ammo = _ammo, pastfoulline = false } -- Rolling in! - local _msg=string.format("%s, rolling in on strafe pit %s.", self:_myname(_unitName), _targetZone.name) + 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) + self.rangecontrol:NewTransmission( RANGE.Sound.RCRollingInOnStrafeTarget.filename, RANGE.Sound.RCRollingInOnStrafeTarget.duration, self.soundpath ) end -- Send message. - self:_DisplayMessageToGroup(_unit, _msg, 10, true) + self:_DisplayMessageToGroup( _unit, _msg, 10, true ) -- We found our player. Skip remaining checks. break @@ -2916,18 +2920,18 @@ end --- Add menu commands for player. -- @param #RANGE self -- @param #string _unitName Name of player unit. -function RANGE:_AddF10Commands(_unitName) - self:F(_unitName) +function RANGE:_AddF10Commands( _unitName ) + self:F( _unitName ) -- Get player unit and name. - local _unit, playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, playername = self:_GetPlayerUnitAndName( _unitName ) -- Check for player unit. if _unit and playername then -- Get group and ID. - local group=_unit:GetGroup() - local _gid=group:GetID() + local group = _unit:GetGroup() + local _gid = group:GetID() if group and _gid then @@ -2937,7 +2941,7 @@ function RANGE:_AddF10Commands(_unitName) self.MenuAddedTo[_gid] = true -- Range root menu path. - local _rangePath=nil + local _rangePath = nil if RANGE.MenuF10Root then @@ -2945,7 +2949,7 @@ function RANGE:_AddF10Commands(_unitName) -- MISSION LEVEL -- ------------------- - _rangePath = missionCommands.addSubMenuForGroup(_gid, self.rangename, RANGE.MenuF10Root) + _rangePath = missionCommands.addSubMenuForGroup( _gid, self.rangename, RANGE.MenuF10Root ) else @@ -2955,61 +2959,60 @@ function RANGE:_AddF10Commands(_unitName) -- Main F10 menu: F10/On the Range// if RANGE.MenuF10[_gid] == nil then - RANGE.MenuF10[_gid]=missionCommands.addSubMenuForGroup(_gid, "On the Range") + RANGE.MenuF10[_gid] = missionCommands.addSubMenuForGroup( _gid, "On the Range" ) end - _rangePath = missionCommands.addSubMenuForGroup(_gid, self.rangename, RANGE.MenuF10[_gid]) + _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 _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 ) -- F10/On the Range//My Settings/ - local _mysmokePath = missionCommands.addSubMenuForGroup(_gid, "Smoke Color", _settingsPath) - local _myflarePath = missionCommands.addSubMenuForGroup(_gid, "Flare Color", _settingsPath) + local _mysmokePath = missionCommands.addSubMenuForGroup( _gid, "Smoke Color", _settingsPath ) + local _myflarePath = missionCommands.addSubMenuForGroup( _gid, "Flare Color", _settingsPath ) -- F10/On the Range//Mark Targets/ - 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, "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 ) -- F10/On the Range//Stats/ - 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, "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 ) -- F10/On the Range//My Settings/Smoke Color/ - 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, "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 ) -- F10/On the Range//My Settings/Flare Color/ - 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, "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 ) -- F10/On the Range//My Settings/ - 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, "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 ) -- F10/On the Range//Range Information - 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) + 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) + 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) + self:E( self.id .. "Player unit does not exist in AddF10Menu() function. Unit name: " .. _unitName ) end end @@ -3022,34 +3025,34 @@ end -- @param #RANGE self -- @param #RANGE.BombTarget target Bomb target data. -- @return Core.Point#COORDINATE Target coordinate. -function RANGE:_GetBombTargetCoordinate(target) +function RANGE:_GetBombTargetCoordinate( target ) - local coord=nil --Core.Point#COORDINATE + local coord = nil -- Core.Point#COORDINATE if target.type == RANGE.TargetType.UNIT then if not target.move then -- Target should not move. - coord=target.coordinate + coord = target.coordinate else -- Moving target. Check if alive and get current position if target.target and target.target:IsAlive() then - coord=target.target:GetCoordinate() + coord = target.target:GetCoordinate() end end elseif target.type == RANGE.TargetType.STATIC then -- Static targets dont move. - coord=target.coordinate + coord = target.coordinate elseif target.type == RANGE.TargetType.COORD then -- Coordinates dont move. - coord=target.coordinate + coord = target.coordinate else - self:E(self.id.."ERROR: Unknown target type.") + self:E( self.id .. "ERROR: Unknown target type." ) end return coord @@ -3059,42 +3062,42 @@ end -- @param #RANGE self -- @param #string unitname Name of the player unit. -- @return Number of shells left -function RANGE:_GetAmmo(unitname) - self:F2(unitname) +function RANGE:_GetAmmo( unitname ) + self:F2( unitname ) -- Init counter. - local ammo=0 + local ammo = 0 - local unit, playername = self:_GetPlayerUnitAndName(unitname) + local unit, playername = self:_GetPlayerUnitAndName( unitname ) if unit and playername then - local has_ammo=false + local has_ammo = false - local ammotable=unit:GetAmmo() - self:T2({ammotable=ammotable}) + 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)) + local weapons = #ammotable + self:T2( self.id .. string.format( "Number of weapons %d.", weapons ) ) - for w=1,weapons do + for w = 1, weapons do - local Nammo=ammotable[w]["count"] - local Tammo=ammotable[w]["desc"]["typeName"] + local Nammo = ammotable[w]["count"] + local Tammo = ammotable[w]["desc"]["typeName"] -- We are specifically looking for shells here. - if string.match(Tammo, "shell") then + if string.match( Tammo, "shell" ) then -- Add up all shells - ammo=ammo+Nammo + ammo = ammo + Nammo - local text=string.format("Player %s has %d rounds ammo of type %s", playername, Nammo, Tammo) - self:T(self.id..text) + 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) + local text = string.format( "Player %s has %d ammo of type %s", playername, Nammo, Tammo ) + self:T( self.id .. text ) end end end @@ -3106,46 +3109,46 @@ end --- Mark targets on F10 map. -- @param #RANGE self -- @param #string _unitName Name of the player unit. -function RANGE:_MarkTargetsOnMap(_unitName) - self:F(_unitName) +function RANGE:_MarkTargetsOnMap( _unitName ) + self:F( _unitName ) -- Get group. - local group=nil --Wrapper.Group#GROUP + local group = nil -- Wrapper.Group#GROUP if _unitName then - group=UNIT:FindByName(_unitName):GetGroup() + group = UNIT:FindByName( _unitName ):GetGroup() end -- Mark bomb targets. - for _,_bombtarget in pairs(self.bombingTargets) do - local bombtarget=_bombtarget --#RANGE.BombTarget - local coord=self:_GetBombTargetCoordinate(_bombtarget) + for _, _bombtarget in pairs( self.bombingTargets ) do + local bombtarget = _bombtarget -- #RANGE.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) + 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)) + coord:MarkToAll( string.format( "Bomb target %s", bombtarget.name ) ) end end -- Mark strafe targets. - for _,_strafepit in pairs(self.strafeTargets) do - for _,_target in pairs(_strafepit.targets) do - local _target=_target --Wrapper.Positionable#POSITIONABLE + for _, _strafepit in pairs( self.strafeTargets ) do + for _, _target in pairs( _strafepit.targets ) do + local _target = _target -- Wrapper.Positionable#POSITIONABLE if _target and _target:IsAlive() then - local coord=_target:GetCoordinate() --Core.Point#COORDINATE + local coord = _target:GetCoordinate() -- Core.Point#COORDINATE if group then - --coord:MarkToGroup("Strafe target ".._target:GetName(), group) - coord:MarkToGroup(string.format("Strafe target %s:\n%s\n%s", _target:GetName(), coord:ToStringLLDMS(), coord:ToStringBULLS(group:GetCoalition())), group) + -- coord:MarkToGroup("Strafe target ".._target:GetName(), group) + 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()) + 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) + 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 @@ -3153,67 +3156,67 @@ end --- Illuminate targets. Fires illumination bombs at one random bomb and one random strafe target at a random altitude between 400 and 800 m. -- @param #RANGE self -- @param #string _unitName (Optional) Name of the player unit. -function RANGE:_IlluminateBombTargets(_unitName) - self:F(_unitName) +function RANGE:_IlluminateBombTargets( _unitName ) + self:F( _unitName ) -- All bombing target coordinates. - local bomb={} + local bomb = {} - for _,_bombtarget in pairs(self.bombingTargets) do - local _target=_bombtarget.target --Wrapper.Positionable#POSITIONABLE - local coord=self:_GetBombTargetCoordinate(_bombtarget) + for _, _bombtarget in pairs( self.bombingTargets ) do + local _target = _bombtarget.target -- Wrapper.Positionable#POSITIONABLE + local coord = self:_GetBombTargetCoordinate( _bombtarget ) if coord then - table.insert(bomb, coord) + table.insert( bomb, coord ) end end - if #bomb>0 then - local coord=bomb[math.random(#bomb)] --Core.Point#COORDINATE - local c=COORDINATE:New(coord.x,coord.y+math.random(self.illuminationminalt,self.illuminationmaxalt),coord.z) + if #bomb > 0 then + local coord = bomb[math.random( #bomb )] -- Core.Point#COORDINATE + local c = COORDINATE:New( coord.x, coord.y + math.random( self.illuminationminalt, self.illuminationmaxalt ), coord.z ) c:IlluminationBomb() end -- All strafe target coordinates. - local strafe={} + local strafe = {} - for _,_strafepit in pairs(self.strafeTargets) do - for _,_target in pairs(_strafepit.targets) do - local _target=_target --Wrapper.Positionable#POSITIONABLE + for _, _strafepit in pairs( self.strafeTargets ) do + for _, _target in pairs( _strafepit.targets ) do + local _target = _target -- Wrapper.Positionable#POSITIONABLE if _target and _target:IsAlive() then - local coord=_target:GetCoordinate() --Core.Point#COORDINATE - table.insert(strafe, coord) + local coord = _target:GetCoordinate() -- Core.Point#COORDINATE + table.insert( strafe, coord ) end end end -- Pick a random strafe target. - if #strafe>0 then - local coord=strafe[math.random(#strafe)] --Core.Point#COORDINATE - local c=COORDINATE:New(coord.x,coord.y+math.random(self.illuminationminalt,self.illuminationmaxalt),coord.z) + if #strafe > 0 then + local coord = strafe[math.random( #strafe )] -- Core.Point#COORDINATE + 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) + 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 --- Reset player statistics. -- @param #RANGE self -- @param #string _unitName Name of the player unit. -function RANGE:_ResetRangeStats(_unitName) - self:F(_unitName) +function RANGE:_ResetRangeStats( _unitName ) + self:F( _unitName ) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName(_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) + local text = string.format( "%s, %s, your range stats were cleared.", self.rangename, _playername ) + self:DisplayMessageToGroup( _unit, text, 5, false, true ) end end @@ -3224,19 +3227,19 @@ end -- @param #number _time Duration how long the message is displayed. -- @param #boolean _clear Clear up old messages. -- @param #boolean display If true, display message regardless of player setting "Messages Off". -function RANGE:_DisplayMessageToGroup(_unit, _text, _time, _clear, display) - self:F({unit=_unit, text=_text, time=_time, clear=_clear}) +function RANGE:_DisplayMessageToGroup( _unit, _text, _time, _clear, display ) + self:F( { unit = _unit, text = _text, time = _time, clear = _clear } ) -- Defaults - _time=_time or self.Tmsg - if _clear==nil or _clear==false then - _clear=false + _time = _time or self.Tmsg + if _clear == nil or _clear == false then + _clear = false else - _clear=true + _clear = true end -- Messages globally disabled. - if self.messages==false then + if self.messages == false then return end @@ -3244,22 +3247,22 @@ function RANGE:_DisplayMessageToGroup(_unit, _text, _time, _clear, display) if _unit and _unit:IsAlive() then -- Group ID. - local _gid=_unit:GetGroup():GetID() + local _gid = _unit:GetGroup():GetID() -- Get playername and player settings - local _, playername=self:_GetPlayerUnitAndName(_unit:GetName()) - local playermessage=self.PlayerSettings[playername].messages + local _, playername = self:_GetPlayerUnitAndName( _unit:GetName() ) + local playermessage = self.PlayerSettings[playername].messages -- Send message to player if messages enabled and not only for the examiner. - if _gid and (playermessage==true or display) and (not self.examinerexclusive) then - trigger.action.outTextForGroup(_gid, _text, _time, _clear) + if _gid and (playermessage == true or display) and (not self.examinerexclusive) then + trigger.action.outTextForGroup( _gid, _text, _time, _clear ) end -- Send message to examiner. - if self.examinergroupname~=nil then - local _examinerid=GROUP:FindByName(self.examinergroupname):GetID() + if self.examinergroupname ~= nil then + local _examinerid = GROUP:FindByName( self.examinergroupname ):GetID() if _examinerid then - trigger.action.outTextForGroup(_examinerid, _text, _time, _clear) + trigger.action.outTextForGroup( _examinerid, _text, _time, _clear ) end end end @@ -3269,20 +3272,20 @@ end --- Toggle status of smoking bomb impact points. -- @param #RANGE self -- @param #string unitname Name of the player unit. -function RANGE:_SmokeBombImpactOnOff(unitname) - self:F(unitname) +function RANGE:_SmokeBombImpactOnOff( unitname ) + self:F( unitname ) - local unit, playername = self:_GetPlayerUnitAndName(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) + 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) + 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) + self:_DisplayMessageToGroup( unit, text, 5, false, true ) end end @@ -3290,20 +3293,20 @@ end --- Toggle status of time delay for smoking bomb impact points -- @param #RANGE self -- @param #string unitname Name of the player unit. -function RANGE:_SmokeBombDelayOnOff(unitname) - self:F(unitname) +function RANGE:_SmokeBombDelayOnOff( unitname ) + self:F( unitname ) - local unit, playername = self:_GetPlayerUnitAndName(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) + 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) + 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) + self:_DisplayMessageToGroup( unit, text, 5, false, true ) end end @@ -3311,19 +3314,19 @@ end --- Toggle display messages to player. -- @param #RANGE self -- @param #string unitname Name of the player unit. -function RANGE:_MessagesToPlayerOnOff(unitname) - self:F(unitname) +function RANGE:_MessagesToPlayerOnOff( unitname ) + self:F( unitname ) - local unit, playername = self:_GetPlayerUnitAndName(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) + 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) + 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 + self:_DisplayMessageToGroup( unit, text, 5, false, true ) + self.PlayerSettings[playername].messages = not self.PlayerSettings[playername].messages end end @@ -3331,20 +3334,20 @@ end --- Toggle status of flaring direct hits of range targets. -- @param #RANGE self -- @param #string unitname Name of the player unit. -function RANGE:_FlareDirectHitsOnOff(unitname) - self:F(unitname) +function RANGE:_FlareDirectHitsOnOff( unitname ) + self:F( unitname ) - local unit, playername = self:_GetPlayerUnitAndName(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) + 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) + 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) + self:_DisplayMessageToGroup( unit, text, 5, false, true ) end end @@ -3352,21 +3355,21 @@ end --- Mark bombing targets with smoke. -- @param #RANGE self -- @param #string unitname Name of the player unit. -function RANGE:_SmokeBombTargets(unitname) - self:F(unitname) +function RANGE:_SmokeBombTargets( unitname ) + self:F( unitname ) - for _,_bombtarget in pairs(self.bombingTargets) do - local _target=_bombtarget.target --Wrapper.Positionable#POSITIONABLE - local coord=self:_GetBombTargetCoordinate(_bombtarget) + for _, _bombtarget in pairs( self.bombingTargets ) do + local _target = _bombtarget.target -- Wrapper.Positionable#POSITIONABLE + local coord = self:_GetBombTargetCoordinate( _bombtarget ) if coord then - coord:Smoke(self.BombSmokeColor) + 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) + 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 @@ -3374,17 +3377,17 @@ end --- Mark strafing targets with smoke. -- @param #RANGE self -- @param #string unitname Name of the player unit. -function RANGE:_SmokeStrafeTargets(unitname) - self:F(unitname) +function RANGE:_SmokeStrafeTargets( unitname ) + self:F( unitname ) - for _,_target in pairs(self.strafeTargets) do - _target.coordinate:Smoke(self.StrafeSmokeColor) + 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) + 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 @@ -3392,21 +3395,21 @@ end --- Mark approach boxes of strafe targets with smoke. -- @param #RANGE self -- @param #string unitname Name of the player unit. -function RANGE:_SmokeStrafeTargetBoxes(unitname) - self:F(unitname) +function RANGE:_SmokeStrafeTargetBoxes( unitname ) + self:F( unitname ) - for _,_target in pairs(self.strafeTargets) do - local zone=_target.polygon --Core.Zone#ZONE - zone:SmokeZone(self.StrafePitSmokeColor, 4) - for _,_point in pairs(_target.smokepoints) do - _point:SmokeOrange() --Corners are smoked orange. + for _, _target in pairs( self.strafeTargets ) do + local zone = _target.polygon -- Core.Zone#ZONE + zone:SmokeZone( self.StrafePitSmokeColor, 4 ) + for _, _point in pairs( _target.smokepoints ) do + _point:SmokeOrange() -- Corners are smoked orange. 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) + 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 @@ -3415,14 +3418,14 @@ end -- @param #RANGE self -- @param #string _unitName Name of the player unit. -- @param Utilities.Utils#SMOKECOLOR color ID of the smoke color. -function RANGE:_playersmokecolor(_unitName, color) - self:F({unitname=_unitName, color=color}) +function RANGE:_playersmokecolor( _unitName, color ) + self:F( { unitname = _unitName, color = color } ) - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + 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) + 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 @@ -3431,14 +3434,14 @@ end -- @param #RANGE self -- @param #string _unitName Name of the player unit. -- @param Utilities.Utils#FLARECOLOR color ID of flare color. -function RANGE:_playerflarecolor(_unitName, color) - self:F({unitname=_unitName, color=color}) +function RANGE:_playerflarecolor( _unitName, color ) + self:F( { unitname = _unitName, color = color } ) - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + 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) + 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 @@ -3447,22 +3450,22 @@ end -- @param #RANGE self -- @param Utilities.Utils#SMOKECOLOR color Color Id. -- @return #string Color text. -function RANGE:_smokecolor2text(color) - self:F(color) +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" + 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)) + txt = string.format( "unknown color (%s)", tostring( color ) ) end return txt @@ -3472,20 +3475,20 @@ end -- @param #RANGE self -- @param Utilities.Utils#FLARECOLOR color Color Id. -- @return #string Color text. -function RANGE:_flarecolor2text(color) - self:F(color) +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" + 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)) + txt = string.format( "unknown color (%s)", tostring( color ) ) end return txt @@ -3495,33 +3498,33 @@ end -- @param #RANGE self -- @param #string name Name of the potential static object. -- @return #boolean Returns true if a static with this name exists. Retruns false if a unit with this name exists. Returns nil if neither unit or static exist. -function RANGE:_CheckStatic(name) - self:F2(name) +function RANGE:_CheckStatic( name ) + self:F2( name ) -- Get DCS static object. - local _DCSstatic=StaticObject.getByName(name) + local _DCSstatic = StaticObject.getByName( name ) if _DCSstatic and _DCSstatic:isExist() then - --Static does exist at least in DCS. Check if it also in the MOOSE DB. - local _MOOSEstatic=STATIC:FindByName(name, false) + -- Static does exist at least in DCS. Check if it also in the MOOSE DB. + local _MOOSEstatic = STATIC:FindByName( name, false ) -- If static is not yet in MOOSE DB, we add it. Can happen for cargo statics! if not _MOOSEstatic then - self:T(self.id..string.format("Adding DCS static to MOOSE database. Name = %s.", name)) - _DATABASE:AddStatic(name) + 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)) + self:T3( self.id .. string.format( "No static object with name %s exists.", name ) ) end -- Check if a unit has this name. - if UNIT:FindByName(name) then + if UNIT:FindByName( name ) then return false else - self:T3(self.id..string.format("No unit object with name %s exists.", name)) + self:T3( self.id .. string.format( "No unit object with name %s exists.", name ) ) end -- If not unit or static exist, we return nil. @@ -3532,17 +3535,17 @@ end -- @param #RANGE self -- @param Wrapper.Controllable#CONTROLLABLE controllable -- @return Maximum speed in km/h. -function RANGE:_GetSpeed(controllable) - self:F2(controllable) +function RANGE:_GetSpeed( controllable ) + self:F2( controllable ) -- Get DCS descriptors - local desc=controllable:GetDesc() + local desc = controllable:GetDesc() -- Get speed - local speed=0 + local speed = 0 if desc then - speed=desc.speedMax*3.6 - self:T({speed=speed}) + speed = desc.speedMax * 3.6 + self:T( { speed = speed } ) end return speed @@ -3554,20 +3557,20 @@ end -- @return Wrapper.Unit#UNIT Unit of player. -- @return #string Name of the player. -- @return nil If player does not exist. -function RANGE:_GetPlayerUnitAndName(_unitName) - self:F2(_unitName) +function RANGE:_GetPlayerUnitAndName( _unitName ) + self:F2( _unitName ) if _unitName ~= nil then -- Get DCS unit from its name. - local DCSunit=Unit.getByName(_unitName) + local DCSunit = Unit.getByName( _unitName ) if DCSunit then - local playername=DCSunit:getPlayerName() - local unit=UNIT:Find(DCSunit) + local playername = DCSunit:getPlayerName() + local unit = UNIT:Find( DCSunit ) - self:T2({DCSunit=DCSunit, unit=unit, playername=playername}) + self:T2( { DCSunit = DCSunit, unit = unit, playername = playername } ) if DCSunit and unit and playername then return unit, playername end @@ -3577,23 +3580,23 @@ function RANGE:_GetPlayerUnitAndName(_unitName) end -- Return nil if we could not find a player. - return nil,nil + return nil, nil end --- Returns a string which consists of the player name. -- @param #RANGE self -- @param #string unitname Name of the player unit. -function RANGE:_myname(unitname) - self:F2(unitname) +function RANGE:_myname( unitname ) + self:F2( unitname ) - local unit=UNIT:FindByName(unitname) - local pname=unit:GetPlayerName() - - --TODO: Either remove these leftovers, or implement them. - --local csign=unit:GetCallsign() - --return string.format("%s (%s)", csign, pname) - - return string.format("%s", pname) + local unit = UNIT:FindByName( unitname ) + local pname = unit:GetPlayerName() + + -- TODO: Either remove these leftovers, or implement them. + -- local csign=unit:GetCallsign() + -- return string.format("%s (%s)", csign, pname) + + return string.format( "%s", pname ) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 18685d1a946caec6f0f7f8eb795558dc4d2bb55e Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 6 Dec 2021 14:57:49 +0100 Subject: [PATCH 023/200] Update Utils.lua (#1655) Small bugfix for UTILS.LoadSetOfGroups --- Moose Development/Moose/Utilities/Utils.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index c9bbe6856..e66f5b57f 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2137,7 +2137,7 @@ end -- @return Core.Set#SET_GROUP Set of GROUP objects. -- Returns nil when file cannot be read. Returns a table of data entries if Spawn is false: `{ groupname=groupname, size=size, coordinate=coordinate }` function UTILS.LoadSetOfGroups(Path,Filename,Spawn) - local spawn = SPAWN==false and false or true + local spawn = Spawn==false and false or true local filename = Filename or "SetOfGroups" local setdata = SET_GROUP:New() local datatable = {} From 249a6af45603f4bb8a230042d73d5161d651b638 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 6 Dec 2021 15:16:01 +0100 Subject: [PATCH 024/200] Some false values seem to be in need of being set explicitly --- Moose Development/Moose/Core/Set.lua | 3 ++- Moose Development/Moose/Utilities/Utils.lua | 10 +++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index c6de50afb..964eb5d25 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -209,7 +209,8 @@ do -- SET_BASE function SET_BASE:Remove( ObjectName, NoTriggerEvent ) self:F2( { ObjectName = ObjectName } ) - local TriggerEvent = NoTriggerEvent==nil and true or (not NoTriggerEvent) + local TriggerEvent = true + if NoTriggerEvent == false then TriggerEvent = false end local Object = self.Set[ObjectName] diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index e66f5b57f..ea55e3357 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2091,7 +2091,8 @@ end -- @param #boolean Reduce If false, existing loaded groups will not be reduced to fit the saved number. -- @return #table Table of data objects (tables) containing groupname, coordinate and group object. Returns nil when file cannot be read. function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce) - local reduce = Reduce==false and false or true + local reduce = true + if Reduce == false then reduce = false end local filename = Filename or "StateListofGroups" local datatable = {} if UTILS.CheckFileExists(Path,filename) then @@ -2137,7 +2138,9 @@ end -- @return Core.Set#SET_GROUP Set of GROUP objects. -- Returns nil when file cannot be read. Returns a table of data entries if Spawn is false: `{ groupname=groupname, size=size, coordinate=coordinate }` function UTILS.LoadSetOfGroups(Path,Filename,Spawn) - local spawn = Spawn==false and false or true + local spawn = true + if Spawn == false then spawn = false end + BASE:I("Spawn = "..tostring(spawn)) local filename = Filename or "SetOfGroups" local setdata = SET_GROUP:New() local datatable = {} @@ -2223,7 +2226,8 @@ end -- @return #table Table of data objects (tables) containing staticname, size (0=dead else 1), coordinate and the static object. -- Returns nil when file cannot be read. function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce) - local reduce = Reduce==false and false or true + local reduce = true + if Reduce == false then reduce = false end local filename = Filename or "StateListofStatics" local datatable = {} if UTILS.CheckFileExists(Path,filename) then From a57b9a90810dbcb0dad9259b633513f38bca5d84 Mon Sep 17 00:00:00 2001 From: TommyC81 Date: Tue, 7 Dec 2021 21:13:51 +0400 Subject: [PATCH 025/200] Update Range.lua (#1658) Adjust some minor spelling and figure out the quirks of luadocumentor... --- Moose Development/Moose/Functional/Range.lua | 27 +++++++++----------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index 0f180e9aa..f086917c7 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -49,7 +49,7 @@ -- === -- @module Functional.Range -- @image Range.JPG -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + --- RANGE class -- @type RANGE -- @field #string ClassName Name of the Class. @@ -91,15 +91,15 @@ -- @field #boolean trackmissiles If true (default), all missile types are tracked and impact point to closest bombing target is evaluated. -- @field #boolean defaultsmokebomb If true, initialize player settings to smoke bomb. -- @field #boolean autosave If true, automatically save results every X seconds. --- @field #number instructorfreq Frequency on which the range control transmitts. +-- @field #number instructorfreq Frequency on which the range control transmits. -- @field Sound.RadioQueue#RADIOQUEUE instructor Instructor radio queue. --- @field #number rangecontrolfreq Frequency on which the range control transmitts. +-- @field #number rangecontrolfreq Frequency on which the range control transmits. -- @field Sound.RadioQueue#RADIOQUEUE rangecontrol Range control radio queue. -- @field #string rangecontrolrelayname Name of relay unit. -- @field #string instructorrelayname Name of relay unit. -- @field #string soundpath Path inside miz file where the sound files are located. Default is "Range Soundfiles/". -- @extends Core.Fsm#FSM -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + --- *Don't only practice your art, but force your way into its secrets; art deserves that, for it and knowledge can raise man to the Divine.* - Ludwig van Beethoven -- -- === @@ -121,7 +121,7 @@ -- -- Due to a DCS bug, it is not possible to directly monitor when a player enters a plane. So in a mission with client slots, it is vital that -- a player first enters as spectator or hits ESC twice and **after that** jumps into the slot of his aircraft! --- If that is not done, the script is not started correctly. This can be checked by looking at the radio menues. If the mission was entered correctly, +-- If that is not done, the script is not started correctly. This can be checked by looking at the radio menus. If the mission was entered correctly, -- there should be an "On the Range" menu items in the "F10. Other..." menu. -- -- # Strafe Pits @@ -284,9 +284,8 @@ -- Note that it can happen that the RANGE radio menu is not shown. Check that the range object is defined as a **global** variable rather than a local one. -- The could avoid the lua garbage collection to accidentally/falsely deallocate the RANGE objects. -- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- @field #RANGE -RANGE = { -- LuaFormatter off +RANGE = { ClassName = "RANGE", Debug = false, verbose = 0, @@ -331,11 +330,11 @@ RANGE = { -- LuaFormatter off rangecontrolfreq = nil, rangecontrol = nil, soundpath = "Range Soundfiles/" -} -- LuaFormatter on +} --- Default range parameters. -- @list Defaults -RANGE.Defaults = { -- LuaFormatter off +RANGE.Defaults = { goodhitrange = 25, -- meters strafemaxalt = 914, -- meters AGL dtBombtrack = 0.005, -- seconds @@ -347,18 +346,18 @@ RANGE.Defaults = { -- LuaFormatter off boxwidth = 300, -- meters goodpass = 20, -- targethits per pass foulline = 610 -- meters -} -- LuaFormatter on +} --- Target type, i.e. unit, static, or coordinate. -- @type RANGE.TargetType -- @field #string UNIT Target is a unit. -- @field #string STATIC Target is a static. -- @field #string COORD Target is a coordinate. -RANGE.TargetType = { -- LuaFormatter off +RANGE.TargetType = { UNIT = "Unit", STATIC = "Static", COORD = "Coordinate", -} -- LuaFormatter on +} --- Player settings. -- @type RANGE.PlayerData @@ -563,7 +562,6 @@ function RANGE:New( rangename ) -- Start State. self:SetStartState( "Stopped" ) - -- LuaFormatter off --- -- Add FSM transitions. -- From State --> Event --> To State @@ -574,7 +572,6 @@ function RANGE:New( rangename ) self:AddTransition("*", "ExitRange", "*") -- Player leaves the range. self:AddTransition("*", "Save", "*") -- Save player results. self:AddTransition("*", "Load", "*") -- Load player results. --- LuaFormatter on ------------------------ --- Pseudo Functions --- @@ -3497,7 +3494,7 @@ end --- Checks if a static object with a certain name exists. It also added it to the MOOSE data base, if it is not already in there. -- @param #RANGE self -- @param #string name Name of the potential static object. --- @return #boolean Returns true if a static with this name exists. Retruns false if a unit with this name exists. Returns nil if neither unit or static exist. +-- @return #boolean Returns true if a static with this name exists. Returns false if a unit with this name exists. Returns nil if neither unit or static exist. function RANGE:_CheckStatic( name ) self:F2( name ) From a4ca4bdc996bfd26d35e42b52e8077fe03673eae Mon Sep 17 00:00:00 2001 From: TommyC81 Date: Wed, 8 Dec 2021 22:52:29 +0400 Subject: [PATCH 026/200] Code and documentation fixes (#1659) * Update .lua-format Adjust for observed coding standards. * Update ATIS.lua Correct measurement units and spelling (also changed in Utils.lua). * Update Utils.lua Format the file, fix typos, adjust minor text. Rename "celcius" to "celsius". Rename "farenheit" to "fahrenheit". * Update Warehouse.lua Adjust measurement unit text. * Update STTS.lua Adjust formatting, minor typos, and fix error in documentation (missing blank rows) introduced in previous update. * Update Range.lua Adjust minor typos and code formatting. Adjust for celsius/fahrenheit typo correction. * Update PseudoATC.lua Adjust for celsius/fahrenheit typo correction in utils.lua. * Update Point.lua Code formatting, fix minor typos, adjust for celsius/fahrenheit corrrection in utils.lua. * Update Range.lua Minor documentation fix. --- Moose Development/Moose/.lua-format | 4 +- Moose Development/Moose/Core/Point.lua | 1962 ++++++++--------- .../Moose/Functional/PseudoATC.lua | 2 +- Moose Development/Moose/Functional/Range.lua | 37 +- .../Moose/Functional/Warehouse.lua | 2 +- Moose Development/Moose/Ops/ATIS.lua | 10 +- Moose Development/Moose/Utilities/STTS.lua | 20 +- Moose Development/Moose/Utilities/Utils.lua | 1842 ++++++++-------- 8 files changed, 1924 insertions(+), 1955 deletions(-) diff --git a/Moose Development/Moose/.lua-format b/Moose Development/Moose/.lua-format index 26b4f5897..6fa8431a8 100644 --- a/Moose Development/Moose/.lua-format +++ b/Moose Development/Moose/.lua-format @@ -21,9 +21,9 @@ chop_down_table: true chop_down_kv_table: true column_table_limit: 500 table_sep: ',' -extra_sep_at_table_end: false +extra_sep_at_table_end: true break_after_operator: true -single_quote_to_double_quote: true +single_quote_to_double_quote: false double_quote_to_single_quote: false spaces_before_call: 1 spaces_inside_functiondef_parens: true diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 1851db807..87fbc64fd 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -1,11 +1,11 @@ --- **Core** - Defines an extensive API to manage 3D points in the DCS World 3D simulation space. -- -- ## Features: --- +-- -- * Provides a COORDINATE class, which allows to manage points in 3D space and perform various operations on it. -- * Provides a POINT\_VEC2 class, which is derived from COORDINATE, and allows to manage points in 3D space, but from a Lat/Lon and Altitude perspective. -- * Provides a POINT\_VEC3 class, which is derived from COORDINATE, and allows to manage points in 3D space, but from a X, Z and Y vector perspective. --- +-- -- === -- -- # Demo Missions @@ -33,15 +33,11 @@ -- @module Core.Point -- @image Core_Coordinate.JPG - - - do -- COORDINATE --- @type COORDINATE -- @extends Core.Base#BASE - - + --- Defines a 3D point in the simulator and with its methods, you can use or manipulate the point in 3D space. -- -- # 1) Create a COORDINATE object. @@ -52,7 +48,6 @@ do -- COORDINATE -- * @{#COORDINATE.NewFromVec2}(): from a @{DCS#Vec2} and possible altitude. -- * @{#COORDINATE.NewFromVec3}(): from a @{DCS#Vec3}. -- - -- -- # 2) Smoke, flare, explode, illuminate at the coordinate. -- -- At the point a smoke, flare, explosion and illumination bomb can be triggered. Use the following methods: @@ -82,19 +77,18 @@ do -- COORDINATE -- -- * @{#COORDINATE.IlluminationBomb}(): To illuminate the point. -- - -- -- # 3) Create markings on the map. - -- + -- -- Place markers (text boxes with clarifications for briefings, target locations or any other reference point) -- on the map for all players, coalitions or specific groups: - -- + -- -- * @{#COORDINATE.MarkToAll}(): Place a mark to all players. -- * @{#COORDINATE.MarkToCoalition}(): Place a mark to a coalition. -- * @{#COORDINATE.MarkToCoalitionRed}(): Place a mark to the red coalition. -- * @{#COORDINATE.MarkToCoalitionBlue}(): Place a mark to the blue coalition. -- * @{#COORDINATE.MarkToGroup}(): Place a mark to a group (needs to have a client in it or a CA group (CA group is bugged)). -- * @{#COORDINATE.RemoveMark}(): Removes a mark from the map. - -- + -- -- # 4) Coordinate calculation methods. -- -- Various calculation methods exist to use or manipulate 3D space. Find below a short description of each method: @@ -124,37 +118,35 @@ do -- COORDINATE -- -- * @{#COORDINATE.GetRandomVec2InRadius}(): Provides a random 2D vector around the current 3D point, in the given inner to outer band. -- * @{#COORDINATE.GetRandomVec3InRadius}(): Provides a random 3D vector around the current 3D point, in the given inner to outer band. - -- + -- -- ## 4.6) LOS between coordinates. - -- + -- -- Calculate if the coordinate has Line of Sight (LOS) with the other given coordinate. - -- Mountains, trees and other objects can be positioned between the two 3D points, preventing visibilty in a straight continuous line. - -- The method @{#COORDINATE.IsLOS}() returns if the two coodinates have LOS. - -- + -- Mountains, trees and other objects can be positioned between the two 3D points, preventing visibility in a straight continuous line. + -- The method @{#COORDINATE.IsLOS}() returns if the two coordinates have LOS. + -- -- ## 4.7) Check the coordinate position. - -- + -- -- Various methods are available that allow to check if a coordinate is: - -- + -- -- * @{#COORDINATE.IsInRadius}(): in a give radius. -- * @{#COORDINATE.IsInSphere}(): is in a given sphere. -- * @{#COORDINATE.IsAtCoordinate2D}(): is in a given coordinate within a specific precision. - -- - -- -- -- # 5) Measure the simulation environment at the coordinate. - -- + -- -- ## 5.1) Weather specific. - -- + -- -- Within the DCS simulator, a coordinate has specific environmental properties, like wind, temperature, humidity etc. - -- + -- -- * @{#COORDINATE.GetWind}(): Retrieve the wind at the specific coordinate within the DCS simulator. -- * @{#COORDINATE.GetTemperature}(): Retrieve the temperature at the specific height within the DCS simulator. -- * @{#COORDINATE.GetPressure}(): Retrieve the pressure at the specific height within the DCS simulator. - -- + -- -- ## 5.2) Surface specific. - -- + -- -- Within the DCS simulator, the surface can have various objects placed at the coordinate, and the surface height will vary. - -- + -- -- * @{#COORDINATE.GetLandHeight}(): Retrieve the height of the surface (on the ground) within the DCS simulator. -- * @{#COORDINATE.GetSurfaceType}(): Retrieve the surface type (on the ground) within the DCS simulator. -- @@ -169,13 +161,13 @@ do -- COORDINATE -- Route points can be used in the Route methods of the @{Wrapper.Group#GROUP} class. -- -- ## 7) Manage the roads. - -- + -- -- Important for ground vehicle transportation and movement, the method @{#COORDINATE.GetClosestPointToRoad}() will calculate -- the closest point on the nearest road. - -- + -- -- In order to use the most optimal road system to transport vehicles, the method @{#COORDINATE.GetPathOnRoad}() will calculate -- the most optimal path following the road between two coordinates. - -- + -- -- ## 8) Metric or imperial system -- -- * @{#COORDINATE.IsMetric}(): Returns if the 3D point is Metric or Nautical Miles. @@ -183,12 +175,12 @@ do -- COORDINATE -- -- -- ## 9) Coordinate text generation - -- + -- -- * @{#COORDINATE.ToStringBR}(): Generates a Bearing & Range text in the format of DDD for DI where DDD is degrees and DI is distance. - -- * @{#COORDINATE.ToStringLL}(): Generates a Latutude & Longutude text. + -- * @{#COORDINATE.ToStringLL}(): Generates a Latitude & Longitude text. -- -- ## 10) Drawings on F10 map - -- + -- -- * @{#COORDINATE.CircleToAll}(): Draw a circle on the F10 map. -- * @{#COORDINATE.LineToAll}(): Draw a line on the F10 map. -- * @{#COORDINATE.RectToAll}(): Draw a rectangle on the F10 map. @@ -201,13 +193,13 @@ do -- COORDINATE ClassName = "COORDINATE", } - --- @field COORDINATE.WaypointAltType + --- @field COORDINATE.WaypointAltType COORDINATE.WaypointAltType = { - BARO = "BARO", + BARO = "BARO", RADIO = "RADIO", } - - --- @field COORDINATE.WaypointAction + + --- @field COORDINATE.WaypointAction COORDINATE.WaypointAction = { TurningPoint = "Turning Point", FlyoverPoint = "Fly Over Point", @@ -218,7 +210,7 @@ do -- COORDINATE LandingReFuAr = "LandingReFuAr", } - --- @field COORDINATE.WaypointType + --- @field COORDINATE.WaypointType COORDINATE.WaypointType = { TakeOffParking = "TakeOffParking", TakeOffParkingHot = "TakeOffParkingHot", @@ -228,21 +220,20 @@ do -- COORDINATE LandingReFuAr = "LandingReFuAr", } - --- COORDINATE constructor. -- @param #COORDINATE self -- @param DCS#Distance x The x coordinate of the Vec3 point, pointing to the North. -- @param DCS#Distance y The y coordinate of the Vec3 point, pointing to the Right. -- @param DCS#Distance z The z coordinate of the Vec3 point, pointing to the Right. -- @return #COORDINATE - function COORDINATE:New( x, y, z ) + function COORDINATE:New( x, y, z ) - --env.info("FF COORDINATE New") + -- env.info("FF COORDINATE New") local self = BASE:Inherit( self, BASE:New() ) -- #COORDINATE self.x = x self.y = y self.z = z - + return self end @@ -250,13 +241,13 @@ do -- COORDINATE -- @param #COORDINATE self -- @param #COORDINATE Coordinate. -- @return #COORDINATE - function COORDINATE:NewFromCoordinate( Coordinate ) + function COORDINATE:NewFromCoordinate( Coordinate ) local self = BASE:Inherit( self, BASE:New() ) -- #COORDINATE self.x = Coordinate.x self.y = Coordinate.y self.z = Coordinate.z - + return self end @@ -265,10 +256,10 @@ do -- COORDINATE -- @param DCS#Vec2 Vec2 The Vec2 point. -- @param DCS#Distance LandHeightAdd (optional) The default height if required to be evaluated will be the land height of the x, y coordinate. You can specify an extra height to be added to the land height. -- @return #COORDINATE - function COORDINATE:NewFromVec2( Vec2, LandHeightAdd ) + function COORDINATE:NewFromVec2( Vec2, LandHeightAdd ) local LandHeight = land.getHeight( Vec2 ) - + LandHeightAdd = LandHeightAdd or 0 LandHeight = LandHeight + LandHeightAdd @@ -284,7 +275,7 @@ do -- COORDINATE -- @param #COORDINATE self -- @param DCS#Vec3 Vec3 The Vec3 point. -- @return #COORDINATE - function COORDINATE:NewFromVec3( Vec3 ) + function COORDINATE:NewFromVec3( Vec3 ) local self = self:New( Vec3.x, Vec3.y, Vec3.z ) -- #COORDINATE @@ -292,7 +283,6 @@ do -- COORDINATE return self end - --- Return the coordinates of the COORDINATE in Vec3 format. -- @param #COORDINATE self @@ -301,7 +291,6 @@ do -- COORDINATE return { x = self.x, y = self.y, z = self.z } end - --- Return the coordinates of the COORDINATE in Vec2 format. -- @param #COORDINATE self -- @return DCS#Vec2 The Vec2 format coordinate. @@ -313,11 +302,11 @@ do -- COORDINATE -- @param #COORDINATE self -- @param DCS#Vec3 Vec3 The 3D vector with x,y,z components. -- @return #COORDINATE The modified COORDINATE itself. - function COORDINATE:UpdateFromVec3(Vec3) + function COORDINATE:UpdateFromVec3( Vec3 ) - self.x=Vec3.x - self.y=Vec3.y - self.z=Vec3.z + self.x = Vec3.x + self.y = Vec3.y + self.z = Vec3.z return self end @@ -326,11 +315,11 @@ do -- COORDINATE -- @param #COORDINATE self -- @param #COORDINATE Coordinate The coordinate with the new x,y,z positions. -- @return #COORDINATE The modified COORDINATE itself. - function COORDINATE:UpdateFromCoordinate(Coordinate) + function COORDINATE:UpdateFromCoordinate( Coordinate ) - self.x=Coordinate.x - self.y=Coordinate.y - self.z=Coordinate.z + self.x = Coordinate.x + self.y = Coordinate.y + self.z = Coordinate.z return self end @@ -339,10 +328,10 @@ do -- COORDINATE -- @param #COORDINATE self -- @param DCS#Vec2 Vec2 The 2D vector with x,y components. x is overwriting COORDINATE.x while y is overwriting COORDINATE.z. -- @return #COORDINATE The modified COORDINATE itself. - function COORDINATE:UpdateFromVec2(Vec2) + function COORDINATE:UpdateFromVec2( Vec2 ) - self.x=Vec2.x - self.z=Vec2.y + self.x = Vec2.x + self.z = Vec2.y return self end @@ -353,19 +342,19 @@ do -- COORDINATE -- @param #number longitude Longitude in decimal degrees. -- @param #number altitude (Optional) Altitude in meters. Default is the land height at the coordinate. -- @return #COORDINATE - function COORDINATE:NewFromLLDD( latitude, longitude, altitude) + function COORDINATE:NewFromLLDD( latitude, longitude, altitude ) -- Returns a point from latitude and longitude in the vec3 format. - local vec3=coord.LLtoLO(latitude, longitude) + local vec3 = coord.LLtoLO( latitude, longitude ) -- Convert vec3 to coordinate object. - local _coord=self:NewFromVec3(vec3) + local _coord = self:NewFromVec3( vec3 ) -- Adjust height - if altitude==nil then - _coord.y=self:GetLandHeight() + if altitude == nil then + _coord.y = self:GetLandHeight() else - _coord.y=altitude + _coord.y = altitude end return _coord @@ -384,7 +373,7 @@ do -- COORDINATE local x = Coordinate.x local z = Coordinate.z - return x - Precision <= self.x and x + Precision >= self.x and z - Precision <= self.z and z + Precision >= self.z + return x - Precision <= self.x and x + Precision >= self.x and z - Precision <= self.z and z + Precision >= self.z end --- Scan/find objects (units, statics, scenery) within a certain radius around the coordinate using the world.searchObjects() DCS API function. @@ -399,50 +388,50 @@ do -- COORDINATE -- @return #table Table of MOOSE @[#Wrapper.Unit#UNIT} objects found. -- @return #table Table of DCS static objects found. -- @return #table Table of DCS scenery objects found. - function COORDINATE:ScanObjects(radius, scanunits, scanstatics, scanscenery) - self:F(string.format("Scanning in radius %.1f m.", radius or 100)) + 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 = { + params = { point = self:GetVec3(), - radius = radius, - } + radius = radius } + } -- Defaults - radius=radius or 100 - if scanunits==nil then - scanunits=true + radius = radius or 100 + if scanunits == nil then + scanunits = true end - if scanstatics==nil then - scanstatics=true + if scanstatics == nil then + scanstatics = true end - if scanscenery==nil then - scanscenery=false + if scanscenery == nil then + scanscenery = false end - --{Object.Category.UNIT, Object.Category.STATIC, Object.Category.SCENERY} - local scanobjects={} + -- {Object.Category.UNIT, Object.Category.STATIC, Object.Category.SCENERY} + local scanobjects = {} if scanunits then - table.insert(scanobjects, Object.Category.UNIT) + table.insert( scanobjects, Object.Category.UNIT ) end if scanstatics then - table.insert(scanobjects, Object.Category.STATIC) + table.insert( scanobjects, Object.Category.STATIC ) end if scanscenery then - table.insert(scanobjects, Object.Category.SCENERY) + table.insert( scanobjects, Object.Category.SCENERY ) end -- Found stuff. local Units = {} local Statics = {} local Scenery = {} - local gotstatics=false - local gotunits=false - local gotscenery=false + local gotstatics = false + local gotunits = false + local gotscenery = false - local function EvaluateZone(ZoneObject) + local function EvaluateZone( ZoneObject ) if ZoneObject then @@ -450,20 +439,20 @@ do -- COORDINATE local ObjectCategory = ZoneObject:getCategory() -- Check for unit or static objects - if ObjectCategory==Object.Category.UNIT and ZoneObject:isExist() then + if ObjectCategory == Object.Category.UNIT and ZoneObject:isExist() then - table.insert(Units, UNIT:Find(ZoneObject)) - gotunits=true + table.insert( Units, UNIT:Find( ZoneObject ) ) + gotunits = true - elseif ObjectCategory==Object.Category.STATIC and ZoneObject:isExist() then + elseif ObjectCategory == Object.Category.STATIC and ZoneObject:isExist() then - table.insert(Statics, ZoneObject) - gotstatics=true + table.insert( Statics, ZoneObject ) + gotstatics = true - elseif ObjectCategory==Object.Category.SCENERY then + elseif ObjectCategory == Object.Category.SCENERY then - table.insert(Scenery, ZoneObject) - gotscenery=true + table.insert( Scenery, ZoneObject ) + gotscenery = true end @@ -473,64 +462,63 @@ do -- COORDINATE end -- Search the world. - world.searchObjects(scanobjects, SphereSearch, EvaluateZone) + world.searchObjects( scanobjects, SphereSearch, EvaluateZone ) - for _,unit in pairs(Units) do - self:T(string.format("Scan found unit %s", unit:GetName())) + 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()) + 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())) - SCENERY:Register(scenery:getName(), scenery) + for _, scenery in pairs( Scenery ) do + self:T( string.format( "Scan found scenery %s typename=%s", scenery:getName(), scenery:getTypeName() ) ) + SCENERY:Register( scenery:getName(), scenery ) end - + return gotunits, gotstatics, gotscenery, Units, Statics, Scenery end - + --- Scan/find UNITS within a certain radius around the coordinate using the world.searchObjects() DCS API function. -- @param #COORDINATE self -- @param #number radius (Optional) Scan radius in meters. Default 100 m. -- @return Core.Set#SET_UNIT Set of units. - 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) + 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 - + --- Find the closest unit to the COORDINATE within a certain radius. -- @param #COORDINATE self -- @param #number radius Scan radius in meters. Default 100 m. -- @return Wrapper.Unit#UNIT The closest unit or #nil if no unit is inside the given radius. - function COORDINATE:FindClosestUnit(radius) - - local units=self:ScanUnits(radius) - - local umin=nil --Wrapper.Unit#UNIT - local dmin=math.huge - for _,_unit in pairs(units.Set) do - local unit=_unit --Wrapper.Unit#UNIT - local coordinate=unit:GetCoordinate() - local d=self:Get2DDistance(coordinate) - if d 180 then - direction = direction-180 + direction = direction - 180 else - direction = direction+180 + direction = direction + 180 end - local strength=math.sqrt((wind.x)^2+(wind.z)^2) + local strength = math.sqrt( (wind.x) ^ 2 + (wind.z) ^ 2 ) -- Return wind direction and strength km/h. return direction, strength end - + --- Returns the wind direction (from) and strength. -- @param #COORDINATE self -- @param height (Optional) parameter specifying the height ASL. The minimum height will be always be the land height since the wind is zero below the ground. -- @return Direction the wind is blowing from in degrees. - function COORDINATE:GetWindWithTurbulenceVec3(height) - - -- AGL height if - local landheight=self:GetLandHeight()+0.1 -- we at 0.1 meters to be sure to be above ground since wind is zero below ground level. - - -- Point at which the wind is evaluated. - local point={x=self.x, y=math.max(height or self.y, landheight), z=self.z} - - -- Get wind velocity vector including turbulences. - local vec3 = atmosphere.getWindWithTurbulence(point) - - return vec3 - end + function COORDINATE:GetWindWithTurbulenceVec3( height ) + -- AGL height if + local landheight = self:GetLandHeight() + 0.1 -- we at 0.1 meters to be sure to be above ground since wind is zero below ground level. + + -- Point at which the wind is evaluated. + local point = { x = self.x, y = math.max( height or self.y, landheight ), z = self.z } + + -- Get wind velocity vector including turbulences. + local vec3 = atmosphere.getWindWithTurbulence( point ) + + return vec3 + end --- Returns a text documenting the wind direction (from) and strength according the measurement system @{Settings}. -- The text will reflect the wind like this: - -- + -- -- - For Russian and European aircraft using the metric system - Wind direction in degrees (°) and wind speed in meters per second (mps). - -- - For Americain aircraft we link to the imperial system - Wind direction in degrees (°) and wind speed in knots per second (kps). - -- - -- A text containing a pressure will look like this: - -- - -- - `Wind: %n ° at n.d mps` + -- - For American aircraft we link to the imperial system - Wind direction in degrees (°) and wind speed in knots per second (kps). + -- + -- A text containing a pressure will look like this: + -- + -- - `Wind: %n ° at n.d mps` -- - `Wind: %n ° at n.d kps` - -- + -- -- @param #COORDINATE self -- @param height (Optional) parameter specifying the height ASL. The minimum height will be always be the land height since the wind is zero below the ground. -- @return #string Wind direction and strength according the measurement system @{Settings}. @@ -969,7 +945,7 @@ do -- COORDINATE else return " no wind" end - + return nil end @@ -980,13 +956,12 @@ do -- COORDINATE 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 + return ((TargetVec3.x - SourceVec3.x) ^ 2 + (TargetVec3.y - SourceVec3.y) ^ 2 + (TargetVec3.z - SourceVec3.z) ^ 2) ^ 0.5 end - --- Provides a bearing text in degrees. -- @param #COORDINATE self - -- @param #number AngleRadians The angle in randians. + -- @param #number AngleRadians The angle in radians. -- @param #number Precision The precision. -- @param Core.Settings#SETTINGS Settings -- @return #string The bearing text in degrees. @@ -995,9 +970,9 @@ do -- COORDINATE local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS local AngleDegrees = UTILS.Round( UTILS.ToDegree( AngleRadians ), Precision ) - - local s = string.format( '%03d°', AngleDegrees ) - + + local s = string.format( '%03d°', AngleDegrees ) + return s end @@ -1014,19 +989,19 @@ do -- COORDINATE local DistanceText if Settings:IsMetric() then - if Language == "EN" 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 + 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 @@ -1037,16 +1012,16 @@ do -- COORDINATE 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 + 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 + if Language == "EN" then return " at " .. UTILS.Round( UTILS.MetersToFeet( self.y ), -3 ) .. " feet" elseif Language == "RU" then return " в " .. UTILS.Round( self.y, -3 ) .. " ноги" @@ -1057,8 +1032,6 @@ do -- COORDINATE end end - - --- Return the velocity text of the COORDINATE. -- @param #COORDINATE self -- @return #string Velocity text. @@ -1076,7 +1049,6 @@ do -- COORDINATE end end - --- Return the heading text of the COORDINATE. -- @param #COORDINATE self -- @return #string Heading text. @@ -1089,10 +1061,9 @@ do -- COORDINATE end end - --- Provides a Bearing / Range string -- @param #COORDINATE self - -- @param #number AngleRadians The angle in randians + -- @param #number AngleRadians The angle in radians -- @param #number Distance The distance -- @param Core.Settings#SETTINGS Settings -- @return #string The BR Text @@ -1102,7 +1073,7 @@ do -- COORDINATE local BearingText = self:GetBearingText( AngleRadians, 0, Settings, Language ) local DistanceText = self:GetDistanceText( Distance, Settings, Language ) - + local BRText = BearingText .. DistanceText return BRText @@ -1110,7 +1081,7 @@ do -- COORDINATE --- Provides a Bearing / Range / Altitude string -- @param #COORDINATE self - -- @param #number AngleRadians The angle in randians + -- @param #number AngleRadians The angle in radians -- @param #number Distance The distance -- @param Core.Settings#SETTINGS Settings -- @return #string The BRA Text @@ -1119,28 +1090,27 @@ do -- COORDINATE local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS local BearingText = self:GetBearingText( AngleRadians, 0, Settings, Language ) - local DistanceText = self:GetDistanceText( Distance, Settings, Language ) - local AltitudeText = self:GetAltitudeText( Settings, Language ) + local DistanceText = self:GetDistanceText( Distance, Settings, Language ) + local AltitudeText = self:GetAltitudeText( Settings, Language ) local BRAText = BearingText .. DistanceText .. AltitudeText -- When the POINT is a VEC2, there will be no altitude shown. return BRAText end - --- Set altitude. -- @param #COORDINATE self -- @param #number altitude New altitude in meters. -- @param #boolean asl Altitude above sea level. Default is above ground level. -- @return #COORDINATE The COORDINATE with adjusted altitude. - function COORDINATE:SetAltitude(altitude, asl) - local alt=altitude + function COORDINATE:SetAltitude( altitude, asl ) + local alt = altitude if asl then - alt=altitude + alt = altitude else - alt=self:GetLandHeight()+altitude + alt = self:GetLandHeight() + altitude end - self.y=alt + self.y = alt return self end @@ -1158,44 +1128,44 @@ do -- COORDINATE -- @return #table The route point. function COORDINATE:WaypointAir( AltType, Type, Action, Speed, SpeedLocked, airbase, DCSTasks, description, timeReFuAr ) self:F2( { AltType, Type, Action, Speed, SpeedLocked } ) - + -- Set alttype or "RADIO" which is AGL. - AltType=AltType or "RADIO" - + AltType = AltType or "RADIO" + -- Speedlocked by default - if SpeedLocked==nil then - SpeedLocked=true + if SpeedLocked == nil then + SpeedLocked = true end - + -- Speed or default 500 km/h. - Speed=Speed or 500 - + Speed = Speed or 500 + -- Waypoint array. local RoutePoint = {} - + -- Coordinates. RoutePoint.x = self.x RoutePoint.y = self.z - + -- Altitude. RoutePoint.alt = self.y RoutePoint.alt_type = AltType - + -- Waypoint type. RoutePoint.type = Type or nil - RoutePoint.action = Action or nil - + RoutePoint.action = Action or nil + -- Speed. - RoutePoint.speed = Speed/3.6 + RoutePoint.speed = Speed / 3.6 RoutePoint.speed_locked = SpeedLocked - + -- ETA. - RoutePoint.ETA=0 - RoutePoint.ETA_locked=false - + RoutePoint.ETA = 0 + RoutePoint.ETA_locked = false + -- Waypoint description. - RoutePoint.name=description - + RoutePoint.name = description + -- Airbase parameters for takeoff and landing points. if airbase then local AirbaseID = airbase:GetID() @@ -1206,34 +1176,33 @@ do -- COORDINATE elseif AirbaseCategory == Airbase.Category.AIRDROME then RoutePoint.airdromeId = AirbaseID else - self:E("ERROR: Unknown airbase category in COORDINATE:WaypointAir()!") + self:E( "ERROR: Unknown airbase category in COORDINATE:WaypointAir()!" ) end end - + -- Time in minutes to stay at the airbase before resuming route. - if Type==COORDINATE.WaypointType.LandingReFuAr then - RoutePoint.timeReFuAr=timeReFuAr or 10 + if Type == COORDINATE.WaypointType.LandingReFuAr then + RoutePoint.timeReFuAr = timeReFuAr or 10 end - + -- Waypoint tasks. RoutePoint.task = {} RoutePoint.task.id = "ComboTask" RoutePoint.task.params = {} RoutePoint.task.params.tasks = DCSTasks or {} - - --RoutePoint.properties={} - --RoutePoint.properties.addopt={} - - --RoutePoint.formation_template="" + + -- RoutePoint.properties={} + -- RoutePoint.properties.addopt={} + + -- RoutePoint.formation_template="" -- Debug. - self:T({RoutePoint=RoutePoint}) - + self:T( { RoutePoint = RoutePoint } ) + -- Return waypoint. return RoutePoint end - --- Build a Waypoint Air "Turning Point". -- @param #COORDINATE self -- @param #COORDINATE.WaypointAltType AltType The altitude type. @@ -1245,7 +1214,6 @@ do -- COORDINATE return self:WaypointAir( AltType, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, Speed, true, nil, DCSTasks, description ) end - --- Build a Waypoint Air "Fly Over Point". -- @param #COORDINATE self -- @param #COORDINATE.WaypointAltType AltType The altitude type. @@ -1254,8 +1222,7 @@ do -- COORDINATE function COORDINATE:WaypointAirFlyOverPoint( AltType, Speed ) return self:WaypointAir( AltType, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.FlyoverPoint, Speed ) end - - + --- Build a Waypoint Air "Take Off Parking Hot". -- @param #COORDINATE self -- @param #COORDINATE.WaypointAltType AltType The altitude type. @@ -1264,7 +1231,6 @@ do -- COORDINATE function COORDINATE:WaypointAirTakeOffParkingHot( AltType, Speed ) return self:WaypointAir( AltType, COORDINATE.WaypointType.TakeOffParkingHot, COORDINATE.WaypointAction.FromParkingAreaHot, Speed ) end - --- Build a Waypoint Air "Take Off Parking". -- @param #COORDINATE self @@ -1274,8 +1240,7 @@ do -- COORDINATE function COORDINATE:WaypointAirTakeOffParking( AltType, Speed ) return self:WaypointAir( AltType, COORDINATE.WaypointType.TakeOffParking, COORDINATE.WaypointAction.FromParkingArea, Speed ) end - - + --- Build a Waypoint Air "Take Off Runway". -- @param #COORDINATE self -- @param #COORDINATE.WaypointAltType AltType The altitude type. @@ -1284,8 +1249,7 @@ do -- COORDINATE function COORDINATE:WaypointAirTakeOffRunway( AltType, Speed ) return self:WaypointAir( AltType, COORDINATE.WaypointType.TakeOff, COORDINATE.WaypointAction.FromRunway, Speed ) end - - + --- Build a Waypoint Air "Landing". -- @param #COORDINATE self -- @param DCS#Speed Speed Airspeed in km/h. @@ -1294,16 +1258,16 @@ do -- COORDINATE -- @param #string description A text description of the waypoint, which will be shown on the F10 map. -- @return #table The route point. -- @usage - -- + -- -- LandingZone = ZONE:New( "LandingZone" ) -- LandingCoord = LandingZone:GetCoordinate() -- LandingWaypoint = LandingCoord:WaypointAirLanding( 60 ) -- HeliGroup:Route( { LandWaypoint }, 1 ) -- Start landing the helicopter in one second. - -- + -- function COORDINATE:WaypointAirLanding( Speed, airbase, DCSTasks, description ) - return self:WaypointAir(nil, COORDINATE.WaypointType.Land, COORDINATE.WaypointAction.Landing, Speed, false, airbase, DCSTasks, description) + return self:WaypointAir( nil, COORDINATE.WaypointType.Land, COORDINATE.WaypointAction.Landing, Speed, false, airbase, DCSTasks, description ) end - + --- Build a Waypoint Air "LandingReFuAr". Mimics the aircraft ReFueling and ReArming. -- @param #COORDINATE self -- @param DCS#Speed Speed Airspeed in km/h. @@ -1313,10 +1277,9 @@ do -- COORDINATE -- @param #string description A text description of the waypoint, which will be shown on the F10 map. -- @return #table The route point. 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 - - + return self:WaypointAir( nil, COORDINATE.WaypointType.LandingReFuAr, COORDINATE.WaypointAction.LandingReFuAr, Speed, false, airbase, DCSTasks, description, timeReFuAr or 10 ) + end + --- Build an ground type route point. -- @param #COORDINATE self -- @param #number Speed (Optional) Speed in km/h. The default speed is 20 km/h. @@ -1327,32 +1290,32 @@ do -- COORDINATE self:F2( { Speed, Formation, DCSTasks } ) local RoutePoint = {} - - RoutePoint.x = self.x - RoutePoint.y = self.z - - RoutePoint.alt = self:GetLandHeight()+1 - RoutePoint.alt_type = COORDINATE.WaypointAltType.BARO - - RoutePoint.type = "Turning Point" - - RoutePoint.action = Formation or "Off Road" - RoutePoint.formation_template="" - - RoutePoint.ETA=0 - RoutePoint.ETA_locked=false - RoutePoint.speed = ( Speed or 20 ) / 3.6 + RoutePoint.x = self.x + RoutePoint.y = self.z + + RoutePoint.alt = self:GetLandHeight() + 1 + RoutePoint.alt_type = COORDINATE.WaypointAltType.BARO + + RoutePoint.type = "Turning Point" + + RoutePoint.action = Formation or "Off Road" + RoutePoint.formation_template = "" + + RoutePoint.ETA = 0 + RoutePoint.ETA_locked = false + + RoutePoint.speed = (Speed or 20) / 3.6 RoutePoint.speed_locked = true RoutePoint.task = {} RoutePoint.task.id = "ComboTask" RoutePoint.task.params = {} RoutePoint.task.params.tasks = DCSTasks or {} - + return RoutePoint end - + --- Build route waypoint point for Naval units. -- @param #COORDINATE self -- @param #number Speed (Optional) Speed in km/h. The default speed is 20 km/h. @@ -1363,21 +1326,21 @@ do -- COORDINATE self:F2( { Speed, Depth, DCSTasks } ) local RoutePoint = {} - - RoutePoint.x = self.x - RoutePoint.y = self.z - - RoutePoint.alt = Depth or self.y -- Depth is for submarines only. Ships should have alt=0. + + RoutePoint.x = self.x + RoutePoint.y = self.z + + RoutePoint.alt = Depth or self.y -- Depth is for submarines only. Ships should have alt=0. RoutePoint.alt_type = "BARO" - RoutePoint.type = "Turning Point" + RoutePoint.type = "Turning Point" RoutePoint.action = "Turning Point" RoutePoint.formation_template = "" - RoutePoint.ETA=0 - RoutePoint.ETA_locked=false + RoutePoint.ETA = 0 + RoutePoint.ETA_locked = false - RoutePoint.speed = ( Speed or 20 ) / 3.6 + RoutePoint.speed = (Speed or 20) / 3.6 RoutePoint.speed_locked = true RoutePoint.task = {} @@ -1394,38 +1357,38 @@ do -- COORDINATE -- @param #number Coalition (Optional) Coalition of the airbase. -- @return Wrapper.Airbase#AIRBASE Closest Airbase to the given coordinate. -- @return #number Distance to the closest airbase in meters. - function COORDINATE:GetClosestAirbase2(Category, Coalition) - + function COORDINATE:GetClosestAirbase2( Category, Coalition ) + -- Get all airbases of the map. - local airbases=AIRBASE.GetAllAirbases(Coalition) - - local closest=nil - local distmin=nil + local airbases = AIRBASE.GetAllAirbases( Coalition ) + + local closest = nil + local distmin = nil -- Loop over all airbases. - for _,_airbase in pairs(airbases) do - local airbase=_airbase --Wrapper.Airbase#AIRBASE + for _, _airbase in pairs( airbases ) do + local airbase = _airbase -- Wrapper.Airbase#AIRBASE if airbase then - local category=airbase:GetAirbaseCategory() - if Category and Category==category or Category==nil then + local category = airbase:GetAirbaseCategory() + if Category and Category == category or Category == nil then -- Distance to airbase. - local dist=self:Get2DDistance(airbase:GetCoordinate()) - - if closest==nil then - distmin=dist - closest=airbase + 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]) + if #Path >= 2 then + for i = 1, #Path - 1 do + Way = Way + Path[i + 1]:Get2DDistance( Path[i] ) end else -- There are cases where no path on road can be found. - return nil,nil,false - end - + return nil, nil, false + end + return Path, Way, GotPath end @@ -1645,8 +1607,8 @@ do -- COORDINATE -- @param #COORDINATE self -- @return DCS#SurfaceType Surface type. function COORDINATE:GetSurfaceType() - local vec2=self:GetVec2() - local surface=land.getSurfaceType(vec2) + local vec2 = self:GetVec2() + local surface = land.getSurfaceType( vec2 ) return surface end @@ -1654,46 +1616,44 @@ do -- COORDINATE -- @param #COORDINATE self -- @return #boolean If true, the surface type at the coordinate is land. function COORDINATE:IsSurfaceTypeLand() - return self:GetSurfaceType()==land.SurfaceType.LAND + return self:GetSurfaceType() == land.SurfaceType.LAND end --- Checks if the surface type is road. -- @param #COORDINATE self -- @return #boolean If true, the surface type at the coordinate is land. function COORDINATE:IsSurfaceTypeLand() - return self:GetSurfaceType()==land.SurfaceType.LAND + return self:GetSurfaceType() == land.SurfaceType.LAND end - --- Checks if the surface type is road. -- @param #COORDINATE self -- @return #boolean If true, the surface type at the coordinate is a road. function COORDINATE:IsSurfaceTypeRoad() - return self:GetSurfaceType()==land.SurfaceType.ROAD + return self:GetSurfaceType() == land.SurfaceType.ROAD end --- Checks if the surface type is runway. -- @param #COORDINATE self -- @return #boolean If true, the surface type at the coordinate is a runway or taxi way. function COORDINATE:IsSurfaceTypeRunway() - return self:GetSurfaceType()==land.SurfaceType.RUNWAY + return self:GetSurfaceType() == land.SurfaceType.RUNWAY end --- Checks if the surface type is shallow water. -- @param #COORDINATE self -- @return #boolean If true, the surface type at the coordinate is a shallow water. function COORDINATE:IsSurfaceTypeShallowWater() - return self:GetSurfaceType()==land.SurfaceType.SHALLOW_WATER + return self:GetSurfaceType() == land.SurfaceType.SHALLOW_WATER end --- Checks if the surface type is water. -- @param #COORDINATE self -- @return #boolean If true, the surface type at the coordinate is a deep water. function COORDINATE:IsSurfaceTypeWater() - return self:GetSurfaceType()==land.SurfaceType.WATER + return self:GetSurfaceType() == land.SurfaceType.WATER end - --- Creates an explosion at the point of a certain intensity. -- @param #COORDINATE self -- @param #number ExplosionIntensity Intensity of the explosion in kg TNT. Default 100 kg. @@ -1701,9 +1661,9 @@ do -- COORDINATE -- @return #COORDINATE self function COORDINATE:Explosion( ExplosionIntensity, Delay ) self:F2( { ExplosionIntensity } ) - ExplosionIntensity=ExplosionIntensity or 100 - if Delay and Delay>0 then - SCHEDULER:New(nil, self.Explosion, {self,ExplosionIntensity}, Delay) + ExplosionIntensity = ExplosionIntensity or 100 + if Delay and Delay > 0 then + SCHEDULER:New( nil, self.Explosion, { self, ExplosionIntensity }, Delay ) else trigger.action.explosion( self:GetVec3(), ExplosionIntensity ) end @@ -1714,12 +1674,11 @@ do -- COORDINATE -- @param #COORDINATE self -- @param #number power Power of illumination bomb in Candela. -- @return #COORDINATE self - function COORDINATE:IlluminationBomb(power) + function COORDINATE:IlluminationBomb( power ) self:F2() trigger.action.illuminationBomb( self:GetVec3(), power ) end - --- Smokes the point in a color. -- @param #COORDINATE self -- @param Utilities.Utils#SMOKECOLOR SmokeColor @@ -1768,8 +1727,8 @@ do -- COORDINATE -- @param Utilities.Utils#BIGSMOKEPRESET preset Smoke preset (0=small smoke and fire, 1=medium smoke and fire, 2=large smoke and fire, 3=huge smoke and fire, 4=small smoke, 5=medium smoke, 6=large smoke, 7=huge smoke). -- @param #number density (Optional) Smoke density. Number in [0,...,1]. Default 0.5. function COORDINATE:BigSmokeAndFire( preset, density ) - self:F2( { preset=preset, density=density } ) - density=density or 0.5 + self:F2( { preset = preset, density = density } ) + density = density or 0.5 trigger.action.effectSmokeBig( self:GetVec3(), preset, density ) end @@ -1777,73 +1736,73 @@ do -- COORDINATE -- @param #COORDINATE self -- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. function COORDINATE:BigSmokeAndFireSmall( density ) - self:F2( { density=density } ) - density=density or 0.5 - self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmokeAndFire, density) + self:F2( { density = density } ) + density = density or 0.5 + self:BigSmokeAndFire( BIGSMOKEPRESET.SmallSmokeAndFire, density ) end --- Medium smoke and fire at the coordinate. -- @param #COORDINATE self -- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. function COORDINATE:BigSmokeAndFireMedium( density ) - self:F2( { density=density } ) - density=density or 0.5 - self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmokeAndFire, density) + self:F2( { density = density } ) + density = density or 0.5 + self:BigSmokeAndFire( BIGSMOKEPRESET.MediumSmokeAndFire, density ) end - + --- Large smoke and fire at the coordinate. -- @param #COORDINATE self -- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. function COORDINATE:BigSmokeAndFireLarge( density ) - self:F2( { density=density } ) - density=density or 0.5 - self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmokeAndFire, density) + self:F2( { density = density } ) + density = density or 0.5 + self:BigSmokeAndFire( BIGSMOKEPRESET.LargeSmokeAndFire, density ) end --- Huge smoke and fire at the coordinate. -- @param #COORDINATE self -- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. function COORDINATE:BigSmokeAndFireHuge( density ) - self:F2( { density=density } ) - density=density or 0.5 - self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmokeAndFire, density) + self:F2( { density = density } ) + density = density or 0.5 + self:BigSmokeAndFire( BIGSMOKEPRESET.HugeSmokeAndFire, density ) end - + --- Small smoke at the coordinate. -- @param #COORDINATE self -- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. function COORDINATE:BigSmokeSmall( density ) - self:F2( { density=density } ) - density=density or 0.5 - self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmoke, density) + self:F2( { density = density } ) + density = density or 0.5 + self:BigSmokeAndFire( BIGSMOKEPRESET.SmallSmoke, density ) end - + --- Medium smoke at the coordinate. -- @param #COORDINATE self -- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. function COORDINATE:BigSmokeMedium( density ) - self:F2( { density=density } ) - density=density or 0.5 - self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmoke, density) + self:F2( { density = density } ) + density = density or 0.5 + self:BigSmokeAndFire( BIGSMOKEPRESET.MediumSmoke, density ) end --- Large smoke at the coordinate. -- @param #COORDINATE self -- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. function COORDINATE:BigSmokeLarge( density ) - self:F2( { density=density } ) - density=density or 0.5 - self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmoke, density) + self:F2( { density = density } ) + density = density or 0.5 + self:BigSmokeAndFire( BIGSMOKEPRESET.LargeSmoke, density ) end - + --- Huge smoke at the coordinate. -- @param #COORDINATE self -- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. function COORDINATE:BigSmokeHuge( density ) - self:F2( { density=density } ) - density=density or 0.5 - self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmoke, density) - end + self:F2( { density = density } ) + density = density or 0.5 + self:BigSmokeAndFire( BIGSMOKEPRESET.HugeSmoke, density ) + end --- Flares the point in a color. -- @param #COORDINATE self @@ -1884,9 +1843,9 @@ do -- COORDINATE self:F2( Azimuth ) self:Flare( FLARECOLOR.Red, Azimuth ) end - + do -- Markings - + --- Mark to All -- @param #COORDINATE self -- @param #string MarkText Free format text that shows the marking clarification. @@ -1898,11 +1857,11 @@ do -- COORDINATE -- local MarkID = TargetCoord:MarkToAll( "This is a target for all players" ) function COORDINATE:MarkToAll( MarkText, ReadOnly, Text ) local MarkID = UTILS.GetMarkID() - if ReadOnly==nil then - ReadOnly=false + if ReadOnly == nil then + ReadOnly = false end - local text=Text or "" - trigger.action.markToAll( MarkID, MarkText, self:GetVec3(), ReadOnly, text) + local text = Text or "" + trigger.action.markToAll( MarkID, MarkText, self:GetVec3(), ReadOnly, text ) return MarkID end @@ -1918,10 +1877,10 @@ do -- COORDINATE -- local MarkID = TargetCoord:MarkToCoalition( "This is a target for the red coalition", coalition.side.RED ) function COORDINATE:MarkToCoalition( MarkText, Coalition, ReadOnly, Text ) local MarkID = UTILS.GetMarkID() - if ReadOnly==nil then - ReadOnly=false + if ReadOnly == nil then + ReadOnly = false end - local text=Text or "" + local text = Text or "" trigger.action.markToCoalition( MarkID, MarkText, self:GetVec3(), Coalition, ReadOnly, text ) return MarkID end @@ -1965,14 +1924,14 @@ do -- COORDINATE -- local MarkID = TargetCoord:MarkToGroup( "This is a target for the attack group", AttackGroup ) function COORDINATE:MarkToGroup( MarkText, MarkGroup, ReadOnly, Text ) local MarkID = UTILS.GetMarkID() - if ReadOnly==nil then - ReadOnly=false + if ReadOnly == nil then + ReadOnly = false end - local text=Text or "" + local text = Text or "" trigger.action.markToGroup( MarkID, MarkText, self:GetVec3(), MarkGroup:GetID(), ReadOnly, text ) return MarkID end - + --- Remove a mark -- @param #COORDINATE self -- @param #number MarkID The ID of the mark to be removed. @@ -1985,36 +1944,36 @@ do -- COORDINATE function COORDINATE:RemoveMark( MarkID ) trigger.action.removeMark( MarkID ) end - + --- Line to all. -- Creates a line on the F10 map from one point to another. -- @param #COORDINATE self - -- @param #COORDINATE Endpoint COORDIANTE to where the line is drawn. + -- @param #COORDINATE Endpoint COORDINATE to where the line is drawn. -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). -- @param #number Alpha Transparency [0,1]. Default 1. - -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. + -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. -- @param #string Text (Optional) Text displayed when mark is added. Default none. - -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. - function COORDINATE:LineToAll(Endpoint, Coalition, Color, Alpha, LineType, ReadOnly, Text) + -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. + function COORDINATE:LineToAll( Endpoint, Coalition, Color, Alpha, LineType, ReadOnly, Text ) local MarkID = UTILS.GetMarkID() - if ReadOnly==nil then - ReadOnly=false + 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 "") + 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 - + --- Circle to all. -- Creates a circle on the map with a given radius, color, fill color, and outline. -- @param #COORDINATE self - -- @param #numberr Radius Radius in meters. Default 1000 m. + -- @param #number Radius Radius in meters. Default 1000 m. -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). -- @param #number Alpha Transparency [0,1]. Default 1. @@ -2023,205 +1982,205 @@ do -- COORDINATE -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. -- @param #string Text (Optional) Text displayed when mark is added. Default none. - -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. - function COORDINATE:CircleToAll(Radius, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text) + -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. + function COORDINATE:CircleToAll( Radius, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text ) local MarkID = UTILS.GetMarkID() - if ReadOnly==nil then - ReadOnly=false + 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 Color - FillColor[4]=FillAlpha or 0.15 - trigger.action.circleToAll(Coalition, MarkID, vec3, Radius, Color, FillColor, LineType, ReadOnly, Text or "") + 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 Color + FillColor[4] = FillAlpha or 0.15 + trigger.action.circleToAll( Coalition, MarkID, vec3, Radius, Color, FillColor, LineType, ReadOnly, Text or "" ) return MarkID - end - + end + end -- Markings - --- Rectangle to all. Creates a rectangle on the map from the COORDINATE in one corner to the end COORDINATE in the opposite corner. - -- Creates a line on the F10 map from one point to another. - -- @param #COORDINATE self - -- @param #COORDINATE Endpoint COORDIANTE in the opposite corner. - -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. - -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). - -- @param #number Alpha Transparency [0,1]. Default 1. - -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. - -- @param #number FillAlpha Transparency [0,1]. Default 0.15. - -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. - -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. - -- @param #string Text (Optional) Text displayed when mark is added. Default none. - -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. - function COORDINATE:RectToAll(Endpoint, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text) - local MarkID = UTILS.GetMarkID() - if ReadOnly==nil then - ReadOnly=false - end - local vec3=Endpoint:GetVec3() - Coalition=Coalition or -1 - Color=Color or {1,0,0} - Color[4]=Alpha or 1.0 - LineType=LineType or 1 - FillColor=FillColor or Color - FillColor[4]=FillAlpha or 0.15 - trigger.action.rectToAll(Coalition, MarkID, self:GetVec3(), vec3, Color, FillColor, LineType, ReadOnly, Text or "") - return MarkID + --- Rectangle to all. Creates a rectangle on the map from the COORDINATE in one corner to the end COORDINATE in the opposite corner. + -- Creates a line on the F10 map from one point to another. + -- @param #COORDINATE self + -- @param #COORDINATE Endpoint COORDINATE in the opposite corner. + -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. + -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). + -- @param #number Alpha Transparency [0,1]. Default 1. + -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. + -- @param #number FillAlpha Transparency [0,1]. Default 0.15. + -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. + -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. + -- @param #string Text (Optional) Text displayed when mark is added. Default none. + -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. + function COORDINATE:RectToAll( Endpoint, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text ) + local MarkID = UTILS.GetMarkID() + if ReadOnly == nil then + ReadOnly = false end - - --- Creates a shape defined by 4 points on the F10 map. The first point is the current COORDINATE. The remaining three points need to be specified. - -- @param #COORDINATE self - -- @param #COORDINATE Coord2 Second COORDIANTE of the quad shape. - -- @param #COORDINATE Coord3 Third COORDIANTE of the quad shape. - -- @param #COORDINATE Coord4 Fourth COORDIANTE of the quad shape. - -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. - -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). - -- @param #number Alpha Transparency [0,1]. Default 1. - -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. - -- @param #number FillAlpha Transparency [0,1]. Default 0.15. - -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. - -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. - -- @param #string Text (Optional) Text displayed when mark is added. Default none. - -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. - function COORDINATE:QuadToAll(Coord2, Coord3, Coord4, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text) - local MarkID = UTILS.GetMarkID() - if ReadOnly==nil then - ReadOnly=false - end - local point1=self:GetVec3() - local point2=Coord2:GetVec3() - local point3=Coord3:GetVec3() - local point4=Coord4:GetVec3() - Coalition=Coalition or -1 - Color=Color or {1,0,0} - Color[4]=Alpha or 1.0 - LineType=LineType or 1 - FillColor=FillColor or Color - FillColor[4]=FillAlpha or 0.15 - trigger.action.quadToAll(Coalition, MarkID, self:GetVec3(), point2, point3, point4, Color, FillColor, LineType, ReadOnly, Text or "") - return MarkID - end - - --- Creates a free form shape on the F10 map. The first point is the current COORDINATE. The remaining points need to be specified. - -- **NOTE**: A free form polygon must have **at least three points** in total and currently only **up to 10 points** in total are supported. - -- @param #COORDINATE self - -- @param #table Coordinates Table of coordinates of the remaining points of the shape. - -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. - -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). - -- @param #number Alpha Transparency [0,1]. Default 1. - -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. - -- @param #number FillAlpha Transparency [0,1]. Default 0.15. - -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. - -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. - -- @param #string Text (Optional) Text displayed when mark is added. Default none. - -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. - function COORDINATE:MarkupToAllFreeForm(Coordinates, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text) - - local MarkID = UTILS.GetMarkID() - if ReadOnly==nil then - ReadOnly=false - end - - Coalition=Coalition or -1 - - Color=Color or {1,0,0} - Color[4]=Alpha or 1.0 - - LineType=LineType or 1 - - FillColor=FillColor or UTILS.DeepCopy(Color) - FillColor[4]=FillAlpha or 0.15 - - local vecs={} - vecs[1]=self:GetVec3() - for i,coord in ipairs(Coordinates) do - vecs[i+1]=coord:GetVec3() - end + local vec3 = Endpoint:GetVec3() + Coalition = Coalition or -1 + Color = Color or { 1, 0, 0 } + Color[4] = Alpha or 1.0 + LineType = LineType or 1 + FillColor = FillColor or Color + FillColor[4] = FillAlpha or 0.15 + trigger.action.rectToAll( Coalition, MarkID, self:GetVec3(), vec3, Color, FillColor, LineType, ReadOnly, Text or "" ) + return MarkID + end - if #vecs<3 then - self:E("ERROR: A free form polygon needs at least three points!") - elseif #vecs==3 then - trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], Color, FillColor, LineType, ReadOnly, Text or "") - elseif #vecs==4 then - trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], Color, FillColor, LineType, ReadOnly, Text or "") - elseif #vecs==5 then - trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], Color, FillColor, LineType, ReadOnly, Text or "") - elseif #vecs==6 then - trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], Color, FillColor, LineType, Text or "") - elseif #vecs==7 then - trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], Color, FillColor, LineType, ReadOnly, Text or "") - elseif #vecs==8 then - trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], vecs[8], Color, FillColor, LineType, ReadOnly, Text or "") - elseif #vecs==9 then - trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], vecs[8], vecs[9], Color, FillColor, LineType, ReadOnly, Text or "") - elseif #vecs==10 then - trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], vecs[8], vecs[9], vecs[10], Color, FillColor, LineType, ReadOnly, Text or "") - else - self:E("ERROR: Currently a free form polygon can only have 10 points in total!") - -- Unfortunately, unpack(vecs) does not work! So no idea how to generalize this :( - trigger.action.markupToAll(7, Coalition, MarkID, unpack(vecs), Color, FillColor, LineType, ReadOnly, Text or "") - end - - return MarkID - end - - --- Text to all. Creates a text imposed on the map at the COORDINATE. Text scales with the map. - -- @param #COORDINATE self - -- @param #string Text Text displayed on the F10 map. - -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. - -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). - -- @param #number Alpha Transparency [0,1]. Default 1. - -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. - -- @param #number FillAlpha Transparency [0,1]. Default 0.3. - -- @param #number FontSize Font size. Default 14. - -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. - -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. - function COORDINATE:TextToAll(Text, Coalition, Color, Alpha, FillColor, FillAlpha, FontSize, ReadOnly) - local MarkID = UTILS.GetMarkID() - if ReadOnly==nil then - ReadOnly=false - end - Coalition=Coalition or -1 - Color=Color or {1,0,0} - Color[4]=Alpha or 1.0 - FillColor=FillColor or Color - FillColor[4]=FillAlpha or 0.3 - FontSize=FontSize or 14 - trigger.action.textToAll(Coalition, MarkID, self:GetVec3(), Color, FillColor, FontSize, ReadOnly, Text or "Hello World") - return MarkID + --- Creates a shape defined by 4 points on the F10 map. The first point is the current COORDINATE. The remaining three points need to be specified. + -- @param #COORDINATE self + -- @param #COORDINATE Coord2 Second COORDIANTE of the quad shape. + -- @param #COORDINATE Coord3 Third COORDIANTE of the quad shape. + -- @param #COORDINATE Coord4 Fourth COORDIANTE of the quad shape. + -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. + -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). + -- @param #number Alpha Transparency [0,1]. Default 1. + -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. + -- @param #number FillAlpha Transparency [0,1]. Default 0.15. + -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. + -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. + -- @param #string Text (Optional) Text displayed when mark is added. Default none. + -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. + function COORDINATE:QuadToAll( Coord2, Coord3, Coord4, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text ) + local MarkID = UTILS.GetMarkID() + if ReadOnly == nil then + ReadOnly = false end - - --- Arrow to all. Creates an arrow from the COORDINATE to the endpoint COORDINATE on the F10 map. There is no control over other dimensions of the arrow. - -- @param #COORDINATE self - -- @param #COORDINATE Endpoint COORDINATE where the tip of the arrow is pointing at. - -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. - -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). - -- @param #number Alpha Transparency [0,1]. Default 1. - -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. - -- @param #number FillAlpha Transparency [0,1]. Default 0.15. - -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. - -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. - -- @param #string Text (Optional) Text displayed when mark is added. Default none. - -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. - function COORDINATE:ArrowToAll(Endpoint, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text) - local MarkID = UTILS.GetMarkID() - if ReadOnly==nil then - ReadOnly=false - end - local vec3=Endpoint:GetVec3() - Coalition=Coalition or -1 - Color=Color or {1,0,0} - Color[4]=Alpha or 1.0 - LineType=LineType or 1 - FillColor=FillColor or Color - FillColor[4]=FillAlpha or 0.15 - --trigger.action.textToAll(Coalition, MarkID, self:GetVec3(), Color, FillColor, FontSize, ReadOnly, Text or "Hello World") - trigger.action.arrowToAll(Coalition, MarkID, vec3, self:GetVec3(), Color, FillColor, LineType, ReadOnly, Text or "") - return MarkID - end + local point1 = self:GetVec3() + local point2 = Coord2:GetVec3() + local point3 = Coord3:GetVec3() + local point4 = Coord4:GetVec3() + Coalition = Coalition or -1 + Color = Color or { 1, 0, 0 } + Color[4] = Alpha or 1.0 + LineType = LineType or 1 + FillColor = FillColor or Color + FillColor[4] = FillAlpha or 0.15 + trigger.action.quadToAll( Coalition, MarkID, self:GetVec3(), point2, point3, point4, Color, FillColor, LineType, ReadOnly, Text or "" ) + return MarkID + end + + --- Creates a free form shape on the F10 map. The first point is the current COORDINATE. The remaining points need to be specified. + -- **NOTE**: A free form polygon must have **at least three points** in total and currently only **up to 10 points** in total are supported. + -- @param #COORDINATE self + -- @param #table Coordinates Table of coordinates of the remaining points of the shape. + -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. + -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). + -- @param #number Alpha Transparency [0,1]. Default 1. + -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. + -- @param #number FillAlpha Transparency [0,1]. Default 0.15. + -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. + -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. + -- @param #string Text (Optional) Text displayed when mark is added. Default none. + -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. + function COORDINATE:MarkupToAllFreeForm( Coordinates, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text ) + + local MarkID = UTILS.GetMarkID() + if ReadOnly == nil then + ReadOnly = false + end + + Coalition = Coalition or -1 + + Color = Color or { 1, 0, 0 } + Color[4] = Alpha or 1.0 + + LineType = LineType or 1 + + FillColor = FillColor or UTILS.DeepCopy( Color ) + FillColor[4] = FillAlpha or 0.15 + + local vecs = {} + vecs[1] = self:GetVec3() + for i, coord in ipairs( Coordinates ) do + vecs[i + 1] = coord:GetVec3() + end + + if #vecs < 3 then + self:E( "ERROR: A free form polygon needs at least three points!" ) + elseif #vecs == 3 then + trigger.action.markupToAll( 7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], Color, FillColor, LineType, ReadOnly, Text or "" ) + elseif #vecs == 4 then + trigger.action.markupToAll( 7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], Color, FillColor, LineType, ReadOnly, Text or "" ) + elseif #vecs == 5 then + trigger.action.markupToAll( 7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], Color, FillColor, LineType, ReadOnly, Text or "" ) + elseif #vecs == 6 then + trigger.action.markupToAll( 7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], Color, FillColor, LineType, Text or "" ) + elseif #vecs == 7 then + trigger.action.markupToAll( 7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], Color, FillColor, LineType, ReadOnly, Text or "" ) + elseif #vecs == 8 then + trigger.action.markupToAll( 7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], vecs[8], Color, FillColor, LineType, ReadOnly, Text or "" ) + elseif #vecs == 9 then + trigger.action.markupToAll( 7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], vecs[8], vecs[9], Color, FillColor, LineType, ReadOnly, Text or "" ) + elseif #vecs == 10 then + trigger.action.markupToAll( 7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], vecs[8], vecs[9], vecs[10], Color, FillColor, LineType, ReadOnly, Text or "" ) + else + self:E( "ERROR: Currently a free form polygon can only have 10 points in total!" ) + -- Unfortunately, unpack(vecs) does not work! So no idea how to generalize this :( + trigger.action.markupToAll( 7, Coalition, MarkID, unpack( vecs ), Color, FillColor, LineType, ReadOnly, Text or "" ) + end + + return MarkID + end + + --- Text to all. Creates a text imposed on the map at the COORDINATE. Text scales with the map. + -- @param #COORDINATE self + -- @param #string Text Text displayed on the F10 map. + -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. + -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). + -- @param #number Alpha Transparency [0,1]. Default 1. + -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. + -- @param #number FillAlpha Transparency [0,1]. Default 0.3. + -- @param #number FontSize Font size. Default 14. + -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. + -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. + function COORDINATE:TextToAll( Text, Coalition, Color, Alpha, FillColor, FillAlpha, FontSize, ReadOnly ) + local MarkID = UTILS.GetMarkID() + if ReadOnly == nil then + ReadOnly = false + end + Coalition = Coalition or -1 + Color = Color or { 1, 0, 0 } + Color[4] = Alpha or 1.0 + FillColor = FillColor or Color + FillColor[4] = FillAlpha or 0.3 + FontSize = FontSize or 14 + trigger.action.textToAll( Coalition, MarkID, self:GetVec3(), Color, FillColor, FontSize, ReadOnly, Text or "Hello World" ) + return MarkID + end + + --- Arrow to all. Creates an arrow from the COORDINATE to the endpoint COORDINATE on the F10 map. There is no control over other dimensions of the arrow. + -- @param #COORDINATE self + -- @param #COORDINATE Endpoint COORDINATE where the tip of the arrow is pointing at. + -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. + -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). + -- @param #number Alpha Transparency [0,1]. Default 1. + -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. + -- @param #number FillAlpha Transparency [0,1]. Default 0.15. + -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. + -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. + -- @param #string Text (Optional) Text displayed when mark is added. Default none. + -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. + function COORDINATE:ArrowToAll( Endpoint, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text ) + local MarkID = UTILS.GetMarkID() + if ReadOnly == nil then + ReadOnly = false + end + local vec3 = Endpoint:GetVec3() + Coalition = Coalition or -1 + Color = Color or { 1, 0, 0 } + Color[4] = Alpha or 1.0 + LineType = LineType or 1 + FillColor = FillColor or Color + FillColor[4] = FillAlpha or 0.15 + -- trigger.action.textToAll(Coalition, MarkID, self:GetVec3(), Color, FillColor, FontSize, ReadOnly, Text or "Hello World") + trigger.action.arrowToAll( Coalition, MarkID, vec3, self:GetVec3(), Color, FillColor, LineType, ReadOnly, Text or "" ) + return MarkID + end --- Returns if a Coordinate has Line of Sight (LOS) with the ToCoordinate. -- @param #COORDINATE self @@ -2229,8 +2188,8 @@ do -- COORDINATE -- @param #number Offset Height offset in meters. Default 2 m. -- @return #boolean true If the ToCoordinate has LOS with the Coordinate, otherwise false. function COORDINATE:IsLOS( ToCoordinate, Offset ) - - Offset=Offset or 2 + + Offset = Offset or 2 -- Measurement of visibility should not be from the ground, so Adding a hypotethical 2 meters to each Coordinate. local FromVec3 = self:GetVec3() @@ -2244,7 +2203,6 @@ do -- COORDINATE return IsLOS end - --- Returns if a Coordinate is in a certain Radius of this Coordinate in 2D plane using the X and Z axis. -- @param #COORDINATE self -- @param #COORDINATE Coordinate The coordinate that will be tested if it is in the radius of this coordinate. @@ -2254,13 +2212,12 @@ do -- COORDINATE local InVec2 = self:GetVec2() local Vec2 = Coordinate:GetVec2() - - local InRadius = UTILS.IsInRadius( InVec2, Vec2, Radius) + + local InRadius = UTILS.IsInRadius( InVec2, Vec2, Radius ) return InRadius end - --- Returns if a Coordinate is in a certain radius of this Coordinate in 3D space using the X, Y and Z axis. -- So Radius defines the radius of the a Sphere in 3D space around this coordinate. -- @param #COORDINATE self @@ -2271,8 +2228,8 @@ do -- COORDINATE local InVec3 = self:GetVec3() local Vec3 = Coordinate:GetVec3() - - local InSphere = UTILS.IsInSphere( InVec3, Vec3, Radius) + + local InSphere = UTILS.IsInSphere( InVec3, Vec3, Radius ) return InSphere end @@ -2282,185 +2239,185 @@ do -- COORDINATE -- @param #number Day The day. -- @param #number Month The month. -- @param #number Year The year. - -- @param #boolean InSeconds If true, return the sun rise time in seconds. + -- @param #boolean InSeconds If true, return the sun rise time in seconds. -- @return #string Sunrise time, e.g. "05:41". - function COORDINATE:GetSunriseAtDate(Day, Month, Year, InSeconds) - + function COORDINATE:GetSunriseAtDate( Day, Month, Year, InSeconds ) + -- Day of the year. - 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) + 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) + return UTILS.SecondsToClock( sunrise, true ) end - + end - + --- Get sun rise time for a specific day of the year at the coordinate. -- @param #COORDINATE self -- @param #number DayOfYear The day of the year. - -- @param #boolean InSeconds If true, return the sun rise time in seconds. + -- @param #boolean InSeconds If true, return the sun rise time in seconds. -- @return #string Sunrise time, e.g. "05:41". - function COORDINATE:GetSunriseAtDayOfYear(DayOfYear, InSeconds) - - local Latitude, Longitude=self:GetLLDDM() - - local Tdiff=UTILS.GMTToLocalTimeDifference() - - local sunrise=UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, true, Tdiff) + 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) + return UTILS.SecondsToClock( sunrise, true ) end - + end - + --- Get todays sun rise time. -- @param #COORDINATE self - -- @param #boolean InSeconds If true, return the sun rise time in seconds. + -- @param #boolean InSeconds If true, return the sun rise time in seconds. -- @return #string Sunrise time, e.g. "05:41". - function COORDINATE:GetSunrise(InSeconds) - + function COORDINATE:GetSunrise( InSeconds ) + -- Get current day of the year. - local DayOfYear=UTILS.GetMissionDayOfYear() - + local DayOfYear = UTILS.GetMissionDayOfYear() + -- Lat and long at this point. - local Latitude, Longitude=self:GetLLDDM() - + local Latitude, Longitude = self:GetLLDDM() + -- GMT time diff. - local Tdiff=UTILS.GMTToLocalTimeDifference() - + local Tdiff = UTILS.GMTToLocalTimeDifference() + -- Sunrise in seconds of the day. - local sunrise=UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, true, Tdiff) - - local date=UTILS.GetDCSMissionDate() - + local sunrise = UTILS.GetSunRiseAndSet( DayOfYear, Latitude, Longitude, true, Tdiff ) + + local date = UTILS.GetDCSMissionDate() + -- Debug output. - --self:I(string.format("Sun rise at lat=%.3f long=%.3f on %s (DayOfYear=%d): %s (%d sec of the day) (GMT %d)", Latitude, Longitude, date, DayOfYear, tostring(UTILS.SecondsToClock(sunrise)), sunrise, Tdiff)) - + -- self:I(string.format("Sun rise at lat=%.3f long=%.3f on %s (DayOfYear=%d): %s (%d sec of the day) (GMT %d)", Latitude, Longitude, date, DayOfYear, tostring(UTILS.SecondsToClock(sunrise)), sunrise, Tdiff)) + if InSeconds then return sunrise else - return UTILS.SecondsToClock(sunrise, true) + return UTILS.SecondsToClock( sunrise, true ) end - + end --- Get minutes until the next sun rise at this coordinate. -- @param #COORDINATE self -- @param OnlyToday If true, only calculate the sun rise of today. If sun has already risen, the time in negative minutes since sunrise is reported. -- @return #number Minutes to the next sunrise. - function COORDINATE:GetMinutesToSunrise(OnlyToday) - + function COORDINATE:GetMinutesToSunrise( OnlyToday ) + -- Seconds of today - local time=UTILS.SecondsOfToday() + local time = UTILS.SecondsOfToday() -- Next Sunrise in seconds. - local sunrise=nil - + local sunrise = nil + -- Time to sunrise. - local delta=nil - + local delta = nil + if OnlyToday then - + --- -- Sunrise of today --- - - sunrise=self:GetSunrise(true) - - delta=sunrise-time - + + sunrise = self:GetSunrise( true ) + + delta = sunrise - time + else --- -- Sunrise of tomorrow --- - - -- Tomorrows day of the year. - 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() + -- Tomorrows day of the year. + 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 + return delta / 60 end - + --- Check if it is day, i.e. if the sun has risen about the horizon at this coordinate. -- @param #COORDINATE self -- @param #string Clock (Optional) Time in format "HH:MM:SS+D", e.g. "05:40:00+3" to check if is day at 5:40 at third day after mission start. Default is to check right now. -- @return #boolean If true, it is day. If false, it is night time. - function COORDINATE:IsDay(Clock) - - if Clock then - - local Time=UTILS.ClockToSeconds(Clock) - - local clock=UTILS.Split(Clock, "+")[1] - - -- Tomorrows day of the year. - local DayOfYear=UTILS.GetMissionDayOfYear(Time) + function COORDINATE:IsDay( Clock ) + + if Clock then + + local Time = UTILS.ClockToSeconds( Clock ) + + local clock = UTILS.Split( Clock, "+" )[1] + + -- Tomorrows day of the year. + 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 ) - 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) - -- Check if time is between sunrise and sunset. - if time>sunrise and time<=sunset then - return true - else - return false - end - - else - - -- Todays sun rise in sec. - local sunrise=self:GetSunrise(true) - - -- Todays sun set in sec. - local sunset=self:GetSunset(true) - - -- Seconds passed since midnight. - local time=UTILS.SecondsOfToday() - - -- Check if time is between sunrise and sunset. - if time>sunrise and time<=sunset then + if time > sunrise and time <= sunset then return true else return false end - - end - + + else + + -- Todays sun rise in sec. + local sunrise = self:GetSunrise( true ) + + -- Todays sun set in sec. + local sunset = self:GetSunset( true ) + + -- Seconds passed since midnight. + local time = UTILS.SecondsOfToday() + + -- Check if time is between sunrise and sunset. + if time > sunrise and time <= sunset then + return true + else + return false + end + + end + end - + --- Check if it is night, i.e. if the sun has set below the horizon at this coordinate. - -- @param #COORDINATE self + -- @param #COORDINATE self -- @param #string Clock (Optional) Time in format "HH:MM:SS+D", e.g. "05:40:00+3" to check if is night at 5:40 at third day after mission start. Default is to check right now. -- @return #boolean If true, it is night. If false, it is day time. - function COORDINATE:IsNight(Clock) - return not self:IsDay(Clock) + function COORDINATE:IsNight( Clock ) + return not self:IsDay( Clock ) end --- Get sun set time for a specific date at the coordinate. @@ -2468,106 +2425,105 @@ do -- COORDINATE -- @param #number Day The day. -- @param #number Month The month. -- @param #number Year The year. - -- @param #boolean InSeconds If true, return the sun rise time in seconds. + -- @param #boolean InSeconds If true, return the sun rise time in seconds. -- @return #string Sunset time, e.g. "20:41". - function COORDINATE:GetSunsetAtDate(Day, Month, Year, InSeconds) - - -- Day of the year. - 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) + function COORDINATE:GetSunsetAtDate( Day, Month, Year, InSeconds ) + + -- Day of the year. + 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) + return UTILS.SecondsToClock( sunset, true ) end - + end --- Get todays sun set time. -- @param #COORDINATE self - -- @param #boolean InSeconds If true, return the sun set time in seconds. + -- @param #boolean InSeconds If true, return the sun set time in seconds. -- @return #string Sunrise time, e.g. "20:41". - function COORDINATE:GetSunset(InSeconds) - + function COORDINATE:GetSunset( InSeconds ) + -- Get current day of the year. - local DayOfYear=UTILS.GetMissionDayOfYear() - + local DayOfYear = UTILS.GetMissionDayOfYear() + -- Lat and long at this point. - local Latitude, Longitude=self:GetLLDDM() - + local Latitude, Longitude = self:GetLLDDM() + -- GMT time diff. - local Tdiff=UTILS.GMTToLocalTimeDifference() - + local Tdiff = UTILS.GMTToLocalTimeDifference() + -- Sunrise in seconds of the day. - local sunrise=UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, false, Tdiff) - - local date=UTILS.GetDCSMissionDate() - + local sunrise = UTILS.GetSunRiseAndSet( DayOfYear, Latitude, Longitude, false, Tdiff ) + + local date = UTILS.GetDCSMissionDate() + -- Debug output. - --self:I(string.format("Sun set at lat=%.3f long=%.3f on %s (DayOfYear=%d): %s (%d sec of the day) (GMT %d)", Latitude, Longitude, date, DayOfYear, tostring(UTILS.SecondsToClock(sunrise)), sunrise, Tdiff)) - + -- self:I(string.format("Sun set at lat=%.3f long=%.3f on %s (DayOfYear=%d): %s (%d sec of the day) (GMT %d)", Latitude, Longitude, date, DayOfYear, tostring(UTILS.SecondsToClock(sunrise)), sunrise, Tdiff)) + if InSeconds then return sunrise else - return UTILS.SecondsToClock(sunrise, true) + return UTILS.SecondsToClock( sunrise, true ) end - + end - + --- Get minutes until the next sun set at this coordinate. -- @param #COORDINATE self -- @param OnlyToday If true, only calculate the sun set of today. If sun has already set, the time in negative minutes since sunset is reported. -- @return #number Minutes to the next sunrise. - function COORDINATE:GetMinutesToSunset(OnlyToday) - + function COORDINATE:GetMinutesToSunset( OnlyToday ) + -- Seconds of today - local time=UTILS.SecondsOfToday() + local time = UTILS.SecondsOfToday() -- Next Sunset in seconds. - local sunset=nil - + local sunset = nil + -- Time to sunrise. - local delta=nil - + local delta = nil + if OnlyToday then - + --- -- Sunset of today --- - - sunset=self:GetSunset(true) - - delta=sunset-time - + + sunset = self:GetSunset( true ) + + delta = sunset - time + else --- -- Sunset of tomorrow --- - - -- Tomorrows day of the year. - 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() + -- Tomorrows day of the year. + 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 + return delta / 60 end - --- Return a BR string from a COORDINATE to the COORDINATE. -- @param #COORDINATE self -- @param #COORDINATE FromCoordinate The coordinate to measure the distance and the bearing from. @@ -2575,7 +2531,7 @@ do -- COORDINATE -- @return #string The BR text. function COORDINATE:ToStringBR( FromCoordinate, Settings ) local DirectionVec3 = FromCoordinate:GetDirectionVec3( self ) - local AngleRadians = self:GetAngleRadians( DirectionVec3 ) + local AngleRadians = self:GetAngleRadians( DirectionVec3 ) local Distance = self:Get2DDistance( FromCoordinate ) return "BR, " .. self:GetBRText( AngleRadians, Distance, Settings ) end @@ -2587,7 +2543,7 @@ do -- COORDINATE -- @return #string The BR text. function COORDINATE:ToStringBRA( FromCoordinate, Settings, Language ) local DirectionVec3 = FromCoordinate:GetDirectionVec3( self ) - local AngleRadians = self:GetAngleRadians( DirectionVec3 ) + local AngleRadians = self:GetAngleRadians( DirectionVec3 ) local Distance = FromCoordinate:Get2DDistance( self ) local Altitude = self:GetAltitudeText() return "BRA, " .. self:GetBRAText( AngleRadians, Distance, Settings, Language ) @@ -2601,7 +2557,7 @@ do -- COORDINATE function COORDINATE:ToStringBULLS( Coalition, Settings ) local BullsCoordinate = COORDINATE:NewFromVec3( coalition.getMainRefPoint( Coalition ) ) local DirectionVec3 = BullsCoordinate:GetDirectionVec3( self ) - local AngleRadians = self:GetAngleRadians( DirectionVec3 ) + local AngleRadians = self:GetAngleRadians( DirectionVec3 ) local Distance = self:Get2DDistance( BullsCoordinate ) local Altitude = self:GetAltitudeText() return "BULLS, " .. self:GetBRText( AngleRadians, Distance, Settings ) @@ -2615,7 +2571,7 @@ do -- COORDINATE 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 @@ -2638,7 +2594,7 @@ do -- COORDINATE -- @param #COORDINATE self -- @return #number Latitude in DDM. -- @return #number Lontitude in DDM. - function COORDINATE:GetLLDDM() + function COORDINATE:GetLLDDM() return coord.LOtoLL( self:GetVec3() ) end @@ -2646,7 +2602,7 @@ do -- COORDINATE -- @param #COORDINATE self -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @return #string The LL DMS Text - function COORDINATE:ToStringLLDMS( Settings ) + function COORDINATE:ToStringLLDMS( Settings ) local LL_Accuracy = Settings and Settings.LL_Accuracy or _SETTINGS.LL_Accuracy local lat, lon = coord.LOtoLL( self:GetVec3() ) @@ -2668,7 +2624,7 @@ do -- COORDINATE -- @param #COORDINATE self -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @return #string The MGRS Text - function COORDINATE:ToStringMGRS( Settings ) --R2.1 Fixes issue #424. + function COORDINATE:ToStringMGRS( Settings ) -- R2.1 Fixes issue #424. local MGRS_Accuracy = Settings and Settings.MGRS_Accuracy or _SETTINGS.MGRS_Accuracy local lat, lon = coord.LOtoLL( self:GetVec3() ) @@ -2686,25 +2642,25 @@ do -- COORDINATE -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @return #string The coordinate Text in the configured coordinate system. 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 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 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 AngleRadians = self:GetAngleRadians( DirectionVec3 ) local Distance = self:Get2DDistance( ReferenceCoord ) return "Target are located " .. self:GetBRText( AngleRadians, Distance, Settings ) .. " from " .. ReferenceName end - + return nil end @@ -2714,13 +2670,13 @@ do -- COORDINATE -- @param Wrapper.Controllable#CONTROLLABLE Controllable -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @return #string The coordinate Text in the configured coordinate system. - function COORDINATE:ToStringA2G( Controllable, Settings ) - + function COORDINATE:ToStringA2G( Controllable, Settings ) + self:F2( { Controllable = Controllable and Controllable:GetName() } ) - local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS + local Settings = Settings or (Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() )) or _SETTINGS - if Settings:IsA2G_BR() then + if Settings:IsA2G_BR() then -- If no Controllable is given to calculate the BR from, then MGRS will be used!!! if Controllable then local Coordinate = Controllable:GetCoordinate() @@ -2729,10 +2685,10 @@ do -- COORDINATE return self:ToStringMGRS( Settings ) end end - if Settings:IsA2G_LL_DMS() then + if Settings:IsA2G_LL_DMS() then return self:ToStringLLDMS( Settings ) end - if Settings:IsA2G_LL_DDM() then + if Settings:IsA2G_LL_DDM() then return self:ToStringLLDDM( Settings ) end if Settings:IsA2G_MGRS() then @@ -2743,22 +2699,21 @@ do -- COORDINATE end - --- Provides a coordinate string of the point, based on the A2A coordinate format system. -- @param #COORDINATE self -- @param Wrapper.Controllable#CONTROLLABLE Controllable -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @return #string The coordinate Text in the configured coordinate system. function COORDINATE:ToStringA2A( Controllable, Settings, Language ) -- R2.2 - + self:F2( { Controllable = Controllable and Controllable:GetName() } ) - local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS + local Settings = Settings or (Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() )) or _SETTINGS - if Settings:IsA2A_BRAA() then + if Settings:IsA2A_BRAA() then if Controllable then local Coordinate = Controllable:GetCoordinate() - return self:ToStringBRA( Coordinate, Settings, Language ) + return self:ToStringBRA( Coordinate, Settings, Language ) else return self:ToStringMGRS( Settings, Language ) end @@ -2767,10 +2722,10 @@ do -- COORDINATE local Coalition = Controllable:GetCoalition() return self:ToStringBULLS( Coalition, Settings, Language ) end - if Settings:IsA2A_LL_DMS() then + if Settings:IsA2A_LL_DMS() then return self:ToStringLLDMS( Settings, Language ) end - if Settings:IsA2A_LL_DDM() then + if Settings:IsA2A_LL_DDM() then return self:ToStringLLDDM( Settings, Language ) end if Settings:IsA2A_MGRS() then @@ -2790,13 +2745,13 @@ do -- COORDINATE -- @param Tasking.Task#TASK Task The task for which coordinates need to be calculated. -- @return #string The coordinate Text in the configured coordinate system. function COORDINATE:ToString( Controllable, Settings, Task ) - --- self:E( { Controllable = Controllable and Controllable:GetName() } ) - local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS + -- self:E( { Controllable = Controllable and Controllable:GetName() } ) + + 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 @@ -2807,30 +2762,28 @@ do -- COORDINATE if Task:IsInstanceOf( TASK_CARGO ) then ModeA2A = false end - if Task:IsInstanceOf( TASK_CAPTURE_ZONE ) 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 + 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 @@ -2843,10 +2796,10 @@ do -- COORDINATE -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @return #string The pressure text in the configured measurement system. function COORDINATE:ToStringPressure( Controllable, Settings ) -- R2.3 - + self:F2( { Controllable = Controllable and Controllable:GetName() } ) - local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS + local Settings = Settings or (Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() )) or _SETTINGS return self:GetPressureText( nil, Settings ) end @@ -2859,10 +2812,10 @@ do -- COORDINATE -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @return #string The wind text in the configured measurement system. function COORDINATE:ToStringWind( Controllable, Settings ) - + self:F2( { Controllable = Controllable and Controllable:GetName() } ) - local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS + local Settings = Settings or (Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() )) or _SETTINGS return self:GetWindText( nil, Settings ) end @@ -2875,10 +2828,10 @@ do -- COORDINATE -- @param Core.Settings#SETTINGS -- @return #string The temperature text in the configured measurement system. function COORDINATE:ToStringTemperature( Controllable, Settings ) - + self:F2( { Controllable = Controllable and Controllable:GetName() } ) - local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS + local Settings = Settings or (Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() )) or _SETTINGS return self:GetTemperatureText( nil, Settings ) end @@ -2898,8 +2851,7 @@ do -- POINT_VEC3 -- @field #POINT_VEC3.RoutePointType RoutePointType -- @field #POINT_VEC3.RoutePointAction RoutePointAction -- @extends #COORDINATE - - + --- Defines a 3D point in the simulator and with its methods, you can use or manipulate the point in 3D space. -- -- **Important Note:** Most of the functions in this section were taken from MIST, and reworked to OO concepts. @@ -2907,7 +2859,6 @@ do -- POINT_VEC3 -- I want to emphasize that the formulas embedded in the MIST framework were created by Grimes or previous authors, -- who you can find on the Eagle Dynamics Forums. -- - -- -- ## POINT_VEC3 constructor -- -- A new POINT_VEC3 object can be created with: @@ -2929,19 +2880,16 @@ do -- POINT_VEC3 -- -- local Vec3 = PointVec3:AddX( 100 ):AddZ( 150 ):GetVec3() -- - -- -- ## 3D calculation methods -- -- Various calculation methods exist to use or manipulate 3D space. Find below a short description of each method: -- - -- -- ## Point Randomization -- -- Various methods exist to calculate random locations around a given 3D point. -- -- * @{#POINT_VEC3.GetRandomPointVec3InRadius}(): Provides a random 3D point around the current 3D point, in the given inner to outer band. -- - -- -- @field #POINT_VEC3 POINT_VEC3 = { ClassName = "POINT_VEC3", @@ -3085,7 +3033,7 @@ do -- POINT_VEC3 -- @param #number z The z coordinate value to add to the current z coodinate. -- @return #POINT_VEC3 function POINT_VEC3:AddZ( z ) - self.z = self.z +z + self.z = self.z + z return self end @@ -3107,7 +3055,7 @@ do -- POINT_VEC2 -- @field DCS#Distance x The x coordinate in meters. -- @field DCS#Distance y the y coordinate in meters. -- @extends Core.Point#COORDINATE - + --- Defines a 2D point in the simulator. The height coordinate (if needed) will be the land height + an optional added height specified. -- -- ## POINT_VEC2 constructor @@ -3135,8 +3083,6 @@ do -- POINT_VEC2 POINT_VEC2 = { ClassName = "POINT_VEC2", } - - --- POINT_VEC2 constructor. -- @param #POINT_VEC2 self @@ -3289,11 +3235,10 @@ do -- POINT_VEC2 -- @param #number Altitude The Altitude to add. If nothing (nil) is given, then the current land altitude is set. -- @return #POINT_VEC2 function POINT_VEC2:AddAlt( Altitude ) - self.y = land.getHeight( { x = self.x, y = self.z } ) + Altitude or 0 + self.y = land.getHeight( { x = self.x, y = self.z } ) + (Altitude or 0) return self end - --- Return a random POINT_VEC2 within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC2. -- @param #POINT_VEC2 self -- @param DCS#Distance OuterRadius @@ -3313,7 +3258,7 @@ do -- POINT_VEC2 function POINT_VEC2:DistanceFromPointVec2( PointVec2Reference ) self:F2( PointVec2Reference ) - local Distance = ( ( PointVec2Reference.x - self.x ) ^ 2 + ( PointVec2Reference.z - self.z ) ^2 ) ^ 0.5 + local Distance = ((PointVec2Reference.x - self.x) ^ 2 + (PointVec2Reference.z - self.z) ^ 2) ^ 0.5 self:T2( Distance ) return Distance @@ -3321,4 +3266,3 @@ do -- POINT_VEC2 end - diff --git a/Moose Development/Moose/Functional/PseudoATC.lua b/Moose Development/Moose/Functional/PseudoATC.lua index 1143b00e8..7358dda2c 100644 --- a/Moose Development/Moose/Functional/PseudoATC.lua +++ b/Moose Development/Moose/Functional/PseudoATC.lua @@ -742,7 +742,7 @@ function PSEUDOATC:ReportWeather(GID, UID, position, location) local T=position:GetTemperature() -- Correct unit system. - local _T=string.format('%d°F', UTILS.CelciusToFarenheit(T)) + local _T=string.format('%d°F', UTILS.CelsiusToFahrenheit(T)) if settings:IsMetric() then _T=string.format('%d°C', T) end diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index f086917c7..845f062bf 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -260,7 +260,7 @@ -- -- Add bombing targets. A good hit is if the bomb falls less then 50 m from the target. -- GoldwaterRange:AddBombingTargets(bombtargets, 50) -- --- -- Start range. +-- -- Start Range. -- GoldwaterRange:Start() -- -- The [476th - Air Weapons Range Objects mod](http://www.476vfightergroup.com/downloads.php?do=file&id=287) is (implicitly) used in this example. @@ -288,7 +288,7 @@ RANGE = { ClassName = "RANGE", Debug = false, - verbose = 0, + verbose = 0, id = nil, rangename = nil, location = nil, @@ -329,7 +329,7 @@ RANGE = { instructor = nil, rangecontrolfreq = nil, rangecontrol = nil, - soundpath = "Range Soundfiles/" + soundpath = "Range Soundfiles/", } --- Default range parameters. @@ -345,7 +345,7 @@ RANGE.Defaults = { boxlength = 3000, -- meters boxwidth = 300, -- meters goodpass = 20, -- targethits per pass - foulline = 610 -- meters + foulline = 610, -- meters } --- Target type, i.e. unit, static, or coordinate. @@ -500,7 +500,7 @@ RANGE.Sound = { 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 } + IRExitRange = { filename = "IR-ExitRange.ogg", duration = 3.10 }, } --- Global list of all defined range names. @@ -1089,7 +1089,7 @@ end --- Set sound files folder within miz file. -- @param #RANGE self --- @param #string path Path for sound files. Default "ATIS Soundfiles/". Mind the slash "/" at the end! +-- @param #string path Path for sound files. Default "Range Soundfiles/". Mind the slash "/" at the end! -- @return #RANGE self function RANGE:SetSoundfilesPath( path ) self.soundpath = tostring( path or "Range Soundfiles/" ) @@ -1384,12 +1384,14 @@ end -- -- -- Setup a Range -- RangeOne = RANGE:New( "Range One" ) --- -- Find the STATIC target object as setup in the ME --- RangeOneBombTarget = STATIC:FindByName( "RangeOneBombTarget" ): --- -- Add the coordinate of the STATIC target object as a bomb target (thus keeping the bomb function active, even if the STATIC target is destroyed) +-- -- Find the STATIC target object as setup in the ME. +-- RangeOneBombTarget = STATIC:FindByName( "RangeOneBombTarget" ) +-- -- Add the coordinate of the STATIC target object as a bomb target (thus keeping the bomb function active, even if the STATIC target is destroyed). -- RangeOne:AddBombingTargetCoordinate( RangeOneBombTarget:GetCoordinate(), "RangeOneBombTarget", 50) --- -- Or, add the coordinate of the STATIC target object as a bomb target using default values (name will be "Bomb Target", goodhitrange will be 25 m) +-- -- Or, add the coordinate of the STATIC target object as a bomb target using default values (name will be "Bomb Target", goodhitrange will be 25 m). -- RangeOne:AddBombingTargetCoordinate( RangeOneBombTarget:GetCoordinate() ) +-- -- Start Range. +-- RangeOne:Start() -- function RANGE:AddBombingTargetCoordinate( coord, name, goodhitrange ) @@ -1436,6 +1438,17 @@ end -- @param #string namepit Name of the strafe pit target object. -- @param #string namefoulline Name of the foul line distance marker object. -- @return #number Foul line distance in meters. +-- @usage +-- +-- -- Setup a Range +-- RangeOne = RANGE:New( "Range One" ) +-- -- Get distance between strafe target objext and foul line distance marker object. +-- RangeOneFoulDistance = RangeOne:GetFoullineDistance( "RangeOneStrafeTarget" , "RangeOneFoulLineObject" ) +-- -- Add a strafe pit using the measured foul line distance. Where nil is used, strafe pit default values will be used - adjust as required. +-- RangeOne:AddStrafePit( "RangeOneStrafeTarget", nil, nil, nil, nil, nil, RangeOneFoulDistance ) +-- -- Start Range. +-- RangeOne:Start() +-- function RANGE:GetFoullineDistance( namepit, namefoulline ) self:F( { namepit = namepit, namefoulline = namefoulline } ) @@ -2107,7 +2120,7 @@ function RANGE:onafterSave( From, Event, To ) _savefile( filename, scores ) end ---- Function called before save event. Checks that io and lfs are desanitized. +--- Function called before load event. Checks that io and lfs are desanitized. -- @param #RANGE self -- @param #string From From state. -- @param #string Event Event. @@ -2654,7 +2667,7 @@ function RANGE:_DisplayRangeWeather( _unitname ) local tW = string.format( "%.1f m/s", Ws ) local tP = string.format( "%.1f mmHg", P * hPa2mmHg ) if settings:IsImperial() then - -- tT=string.format("%d°F", UTILS.CelciusToFarenheit(T)) + -- tT=string.format("%d°F", UTILS.CelsiusToFahrenheit(T)) tW = string.format( "%.1f knots", UTILS.MpsToKnots( Ws ) ) tP = string.format( "%.2f inHg", P * hPa2inHg ) end diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 3aaf868f1..e96feb3b5 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -1601,7 +1601,7 @@ WAREHOUSE = { -- @field #number range Range of the unit in meters. -- @field #number speedmax Maximum speed in km/h the group can do. -- @field #number size Maximum size in length and with of the asset in meters. --- @field #number weight The weight of the whole asset group in kilo gramms. +-- @field #number weight The weight of the whole asset group in kilograms. -- @field DCS#Object.Desc DCSdesc All DCS descriptors. -- @field #WAREHOUSE.Attribute attribute Generalized attribute of the group. -- @field #table cargobay Array of cargo bays of all units in an asset group. diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index f865f98be..a225ce5c5 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -67,7 +67,7 @@ -- @field #string activerunway The active runway specified by the user. -- @field #number subduration Duration how long subtitles are displayed in seconds. -- @field #boolean metric If true, use metric units. If false, use imperial (default). --- @field #boolean PmmHg If true, give pressure in millimeters of Mercury. Default is inHg for imperial and hecto Pascal (=mili Bars) for metric units. +-- @field #boolean PmmHg If true, give pressure in millimeters of Mercury. Default is inHg for imperial and hectopascal (hPa, which is the same as millibar - mbar) for metric units. -- @field #boolean qnhonly If true, suppresses reporting QFE. Default is to report both QNH and QFE. -- @field #boolean TDegF If true, give temperature in degrees Fahrenheit. Default is in degrees Celsius independent of chosen unit system. -- @field #number zuludiff Time difference local vs. zulu in hours. @@ -239,7 +239,7 @@ -- -- atisBatumi:SetMetricUnits() -- --- With this, wind speed is given in meters per second, pressure in hecto Pascal (mbar), visibility in kilometers etc. +-- With this, wind speed is given in meters per second, pressure in hectopascal (hPa, which is the same as millibar - mbar), visibility in kilometers etc. -- -- # Sound Files -- @@ -1459,8 +1459,8 @@ function ATIS:onafterBroadcast(From, Event, To) -- Convert to °F. if self.TDegF then - temperature=UTILS.CelciusToFarenheit(temperature) - dewpoint=UTILS.CelciusToFarenheit(dewpoint) + temperature=UTILS.CelsiusToFahrenheit(temperature) + dewpoint=UTILS.CelsiusToFahrenheit(dewpoint) end local TEMPERATURE=string.format("%d", math.abs(temperature)) @@ -2280,7 +2280,7 @@ function ATIS:onafterReport(From, Event, To, Text) local text=string.gsub(text, "°F", "degrees Fahrenheit") local text=string.gsub(text, "inHg", "inches of Mercury") local text=string.gsub(text, "mmHg", "millimeters of Mercury") - local text=string.gsub(text, "hPa", "hecto Pascals") + local text=string.gsub(text, "hPa", "hectopascals") local text=string.gsub(text, "m/s", "meters per second") -- Replace ";" by "." diff --git a/Moose Development/Moose/Utilities/STTS.lua b/Moose Development/Moose/Utilities/STTS.lua index 37a6b3035..5c28dd329 100644 --- a/Moose Development/Moose/Utilities/STTS.lua +++ b/Moose Development/Moose/Utilities/STTS.lua @@ -3,9 +3,11 @@ -- -- @module Utils.STTS -- @image MOOSE.JPG + --- [DCS Enum world](https://wiki.hoggitworld.com/view/DCS_enum_world) -- @type STTS -- @field #string DIRECTORY Path of the SRS directory. + --- Simple Text-To-Speech -- -- Version 0.4 - Compatible with SRS version 1.9.6.0+ @@ -40,7 +42,7 @@ -- * OPTIONAL - Speed -10 to +10 -- * OPTIONAL - Gender male, female or neuter -- * OPTIONAL - Culture - en-US, en-GB etc --- * OPTIONAL - Voice - a specfic voice by name. Run DCS-SR-ExternalAudio.exe with --help to get the ones you can use on the command line +-- * OPTIONAL - Voice - a specific voice by name. Run DCS-SR-ExternalAudio.exe with --help to get the ones you can use on the command line -- * OPTIONAL - Google TTS - Switch to Google Text To Speech - Requires STTS.GOOGLE_CREDENTIALS path and Google project setup correctly -- -- @@ -73,11 +75,11 @@ -- -- @field #STTS STTS = { - ClassName = "STTS", - DIRECTORY = "", - SRS_PORT = 5002, - GOOGLE_CREDENTIALS = "C:\\Users\\Ciaran\\Downloads\\googletts.json", - EXECUTABLE = "DCS-SR-ExternalAudio.exe" + ClassName = "STTS", + DIRECTORY = "", + SRS_PORT = 5002, + GOOGLE_CREDENTIALS = "C:\\Users\\Ciaran\\Downloads\\googletts.json", + EXECUTABLE = "DCS-SR-ExternalAudio.exe" } --- FULL Path to the FOLDER containing DCS-SR-ExternalAudio.exe - EDIT TO CORRECT FOLDER @@ -89,7 +91,7 @@ STTS.SRS_PORT = 5002 --- Google credentials file STTS.GOOGLE_CREDENTIALS = "C:\\Users\\Ciaran\\Downloads\\googletts.json" ---- DONT CHANGE THIS UNLESS YOU KNOW WHAT YOU'RE DOING +--- DON'T CHANGE THIS UNLESS YOU KNOW WHAT YOU'RE DOING STTS.EXECUTABLE = "DCS-SR-ExternalAudio.exe" --- Function for UUID. @@ -117,11 +119,11 @@ function STTS.round( x, n ) end --- Function returns estimated speech time in seconds. --- Assumptions for time calc: 100 Words per min, avarage of 5 letters for english word so +-- Assumptions for time calc: 100 Words per min, average of 5 letters for english word so -- -- * 5 chars * 100wpm = 500 characters per min = 8.3 chars per second -- --- So lengh of msg / 8.3 = number of seconds needed to read it. rounded down to 8 chars per sec map function: +-- So length of msg / 8.3 = number of seconds needed to read it. rounded down to 8 chars per sec map function: -- -- * (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min -- diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index ea55e3357..cac4f9af7 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -11,7 +11,6 @@ -- @module Utils -- @image MOOSE.JPG - --- @type SMOKECOLOR -- @field Green -- @field Red @@ -32,14 +31,14 @@ FLARECOLOR = trigger.flareColor -- #FLARECOLOR --- Big smoke preset enum. -- @type BIGSMOKEPRESET BIGSMOKEPRESET = { - SmallSmokeAndFire=1, - MediumSmokeAndFire=2, - LargeSmokeAndFire=3, - HugeSmokeAndFire=4, - SmallSmoke=5, - MediumSmoke=6, - LargeSmoke=7, - HugeSmoke=8, + SmallSmokeAndFire = 1, + MediumSmokeAndFire = 2, + LargeSmokeAndFire = 3, + HugeSmokeAndFire = 4, + SmallSmoke = 5, + MediumSmoke = 6, + LargeSmoke = 7, + HugeSmoke = 8, } --- DCS map as returned by env.mission.theatre. @@ -52,85 +51,84 @@ BIGSMOKEPRESET = { -- @field #string Syria Syria map. -- @field #string MarianaIslands Mariana Islands map. DCSMAP = { - Caucasus="Caucasus", - NTTR="Nevada", - Normandy="Normandy", - PersianGulf="PersianGulf", - TheChannel="TheChannel", - Syria="Syria", - MarianaIslands="MarianaIslands" + Caucasus = "Caucasus", + NTTR = "Nevada", + Normandy = "Normandy", + PersianGulf = "PersianGulf", + TheChannel = "TheChannel", + Syria = "Syria", + MarianaIslands = "MarianaIslands", } - --- See [DCS_enum_callsigns](https://wiki.hoggitworld.com/view/DCS_enum_callsigns) -- @type CALLSIGN -CALLSIGN={ +CALLSIGN = { -- Aircraft - Aircraft={ - Enfield=1, - Springfield=2, - Uzi=3, - Colt=4, - Dodge=5, - Ford=6, - Chevy=7, - Pontiac=8, + Aircraft = { + Enfield = 1, + Springfield = 2, + Uzi = 3, + Colt = 4, + Dodge = 5, + Ford = 6, + Chevy = 7, + Pontiac = 8, -- A-10A or A-10C - Hawg=9, - Boar=10, - Pig=11, - Tusk=12, + Hawg = 9, + Boar = 10, + Pig = 11, + Tusk = 12, }, -- AWACS - AWACS={ - Overlord=1, - Magic=2, - Wizard=3, - Focus=4, - Darkstar=5, + AWACS = { + Overlord = 1, + Magic = 2, + Wizard = 3, + Focus = 4, + Darkstar = 5, }, -- Tanker - Tanker={ - Texaco=1, - Arco=2, - Shell=3, + Tanker = { + Texaco = 1, + Arco = 2, + Shell = 3, }, -- JTAC - 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, + 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 - FARP={ - London=1, - Dallas=2, - Paris=3, - Moscow=4, - Berlin=5, - Rome=6, - Madrid=7, - Warsaw=8, - Dublin=9, - Perth=10, + FARP = { + London = 1, + Dallas = 2, + Paris = 3, + Moscow = 4, + Berlin = 5, + Rome = 6, + Madrid = 7, + Warsaw = 8, + Dublin = 9, + Perth = 10, }, -} --#CALLSIGN +} -- #CALLSIGN --- Utilities static class. -- @type UTILS @@ -170,11 +168,11 @@ UTILS.IsInstanceOf = function( object, className ) -- Get the name of the Moose class as a string className = className.ClassName - -- className is neither a string nor a Moose class, throw an error + -- className is neither a string nor a Moose class, throw an error else -- I'm not sure if this should take advantage of MOOSE logging function, or throw an error for pcall - local err_str = 'className parameter should be a string; parameter received: '..type( className ) + local err_str = 'className parameter should be a string; parameter received: ' .. type( className ) return false -- error( err_str ) @@ -201,17 +199,16 @@ UTILS.IsInstanceOf = function( object, className ) return false end - --- Deep copy a table. See http://lua-users.org/wiki/CopyTable -- @param #table object The input table. -- @return #table Copy of the input table. -UTILS.DeepCopy = function(object) +UTILS.DeepCopy = function( object ) local lookup_table = {} -- Copy function. - local function _copy(object) - if type(object) ~= "table" then + local function _copy( object ) + if type( object ) ~= "table" then return object elseif lookup_table[object] then return lookup_table[object] @@ -221,28 +218,27 @@ UTILS.DeepCopy = function(object) lookup_table[object] = new_table - for index, value in pairs(object) do - new_table[_copy(index)] = _copy(value) + for index, value in pairs( object ) do + new_table[_copy( index )] = _copy( value ) end - return setmetatable(new_table, getmetatable(object)) + return setmetatable( new_table, getmetatable( object ) ) end - local objectreturn = _copy(object) + local objectreturn = _copy( object ) return objectreturn end - --- Porting in Slmod's serialize_slmod2. -- @param #table tbl Input table. -UTILS.OneLineSerialize = function( tbl ) -- serialization of a table all on a single line, no comments, made to replace old get_table_string function +UTILS.OneLineSerialize = function( tbl ) -- serialization of a table all on a single line, no comments, made to replace old get_table_string function lookup_table = {} local function _Serialize( tbl ) - if type(tbl) == 'table' then --function only works for tables! + if type( tbl ) == 'table' then -- function only works for tables! if lookup_table[tbl] then return lookup_table[object] @@ -254,128 +250,127 @@ UTILS.OneLineSerialize = function( tbl ) -- serialization of a table all on a s tbl_str[#tbl_str + 1] = '{' - for ind,val in pairs(tbl) do -- serialize its fields + for ind, val in pairs( tbl ) do -- serialize its fields local ind_str = {} - if type(ind) == "number" then + if type( ind ) == "number" then ind_str[#ind_str + 1] = '[' - ind_str[#ind_str + 1] = tostring(ind) + ind_str[#ind_str + 1] = tostring( ind ) ind_str[#ind_str + 1] = ']=' - else --must be a string + else -- must be a string ind_str[#ind_str + 1] = '[' - ind_str[#ind_str + 1] = routines.utils.basicSerialize(ind) + 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) + 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) + 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 -- won't ever happen, right? + tbl_str[#tbl_str + 1] = table.concat( ind_str ) + tbl_str[#tbl_str + 1] = table.concat( val_str ) + elseif type( val ) == 'nil' then -- won't ever happen, right? 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 + 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 - -- tbl_str[#tbl_str + 1] = "__index" - -- tbl_str[#tbl_str + 1] = ',' --I think this is right, I just added it + -- tbl_str[#tbl_str + 1] = "__index" + -- tbl_str[#tbl_str + 1] = ',' --I think this is right, I just added it else - val_str[#val_str + 1] = _Serialize(val) - val_str[#val_str + 1] = ',' --I think this is right, I just added it - tbl_str[#tbl_str + 1] = table.concat(ind_str) - tbl_str[#tbl_str + 1] = table.concat(val_str) + val_str[#val_str + 1] = _Serialize( val ) + val_str[#val_str + 1] = ',' -- I think this is right, I just added it + 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] = ',' --I think this is right, I just added it + elseif type( val ) == 'function' then + tbl_str[#tbl_str + 1] = "f() " .. tostring( ind ) + tbl_str[#tbl_str + 1] = ',' -- I think this is right, I just added it else - env.info('unable to serialize value type ' .. routines.utils.basicSerialize(type(val)) .. ' at index ' .. tostring(ind)) + 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) + return table.concat( tbl_str ) else - return tostring(tbl) + return tostring( tbl ) end end - local objectreturn = _Serialize(tbl) + local objectreturn = _Serialize( tbl ) return objectreturn end ---porting in Slmod's "safestring" basic serialize -UTILS.BasicSerialize = function(s) +-- porting in Slmod's "safestring" basic serialize +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) + 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 +UTILS.ToDegree = function( angle ) + return angle * 180 / math.pi end -UTILS.ToRadian = function(angle) - return angle*math.pi/180 +UTILS.ToRadian = function( angle ) + return angle * math.pi / 180 end -UTILS.MetersToNM = function(meters) - return meters/1852 +UTILS.MetersToNM = function( meters ) + return meters / 1852 end -UTILS.KiloMetersToNM = function(kilometers) - return kilometers/1852*1000 +UTILS.KiloMetersToNM = function( kilometers ) + return kilometers / 1852 * 1000 end -UTILS.MetersToSM = function(meters) - return meters/1609.34 +UTILS.MetersToSM = function( meters ) + return meters / 1609.34 end -UTILS.KiloMetersToSM = function(kilometers) - return kilometers/1609.34*1000 +UTILS.KiloMetersToSM = function( kilometers ) + return kilometers / 1609.34 * 1000 end -UTILS.MetersToFeet = function(meters) - return meters/0.3048 +UTILS.MetersToFeet = function( meters ) + return meters / 0.3048 end -UTILS.KiloMetersToFeet = function(kilometers) - return kilometers/0.3048*1000 +UTILS.KiloMetersToFeet = function( kilometers ) + return kilometers / 0.3048 * 1000 end -UTILS.NMToMeters = function(NM) - return NM*1852 +UTILS.NMToMeters = function( NM ) + return NM * 1852 end -UTILS.NMToKiloMeters = function(NM) - return NM*1852/1000 +UTILS.NMToKiloMeters = function( NM ) + return NM * 1852 / 1000 end -UTILS.FeetToMeters = function(feet) - return feet*0.3048 +UTILS.FeetToMeters = function( feet ) + return feet * 0.3048 end -UTILS.KnotsToKmph = function(knots) +UTILS.KnotsToKmph = function( knots ) return knots * 1.852 end -UTILS.KmphToKnots = function(knots) +UTILS.KmphToKnots = function( knots ) return knots / 1.852 end @@ -402,31 +397,31 @@ end -- @param #number mps Speed in m/s. -- @return #number Speed in knots. UTILS.MpsToKnots = function( mps ) - return mps * 1.94384 --3600 / 1852 + return mps * 1.94384 -- 3600 / 1852 end --- Convert knots to meters per second. -- @param #number knots Speed in knots. -- @return #number Speed in m/s. UTILS.KnotsToMps = function( knots ) - return knots / 1.94384 --* 1852 / 3600 + return knots / 1.94384 -- * 1852 / 3600 end ---- Convert temperature from Celsius to Farenheit. --- @param #number Celcius Temperature in degrees Celsius. --- @return #number Temperature in degrees Farenheit. -UTILS.CelciusToFarenheit = function( Celcius ) - return Celcius * 9/5 + 32 +--- Convert temperature from Celsius to Fahrenheit. +-- @param #number Celsius Temperature in degrees Celsius. +-- @return #number Temperature in degrees Fahrenheit. +UTILS.CelsiusToFahrenheit = function( Celsius ) + return Celsius * 9 / 5 + 32 end ---- Convert pressure from hecto Pascal (hPa) to inches of mercury (inHg). +--- Convert pressure from hectopascal (hPa) to inches of mercury (inHg). -- @param #number hPa Pressure in hPa. -- @return #number Pressure in inHg. UTILS.hPa2inHg = function( hPa ) return hPa * 0.0295299830714 end ---- Convert knots to alitude corrected KIAS, e.g. for tankers. +--- Convert knots to altitude corrected KIAS, e.g. for tankers. -- @param #number knots Speed in knots. -- @param #number altitude Altitude in feet -- @return #number Corrected KIAS @@ -434,14 +429,14 @@ UTILS.KnotsToAltKIAS = function( knots, altitude ) return (knots * 0.018 * (altitude / 1000)) + knots end ---- Convert pressure from hecto Pascal (hPa) to millimeters of mercury (mmHg). +--- Convert pressure from hectopascal (hPa) to millimeters of mercury (mmHg). -- @param #number hPa Pressure in hPa. -- @return #number Pressure in mmHg. UTILS.hPa2mmHg = function( hPa ) return hPa * 0.7500615613030 end ---- Convert kilo gramms (kg) to pounds (lbs). +--- Convert kilograms (kg) to pounds (lbs). -- @param #number kg Mass in kg. -- @return #number Mass in lbs. UTILS.kg2lbs = function( kg ) @@ -455,7 +450,7 @@ position after the decimal of the least significant digit: So: 42.32 - acc of 2. ]] -UTILS.tostringLL = function( lat, lon, acc, DMS) +UTILS.tostringLL = function( lat, lon, acc, DMS ) local latHemi, lonHemi if lat > 0 then @@ -470,23 +465,23 @@ UTILS.tostringLL = function( lat, lon, acc, DMS) lonHemi = 'W' end - lat = math.abs(lat) - lon = math.abs(lon) + lat = math.abs( lat ) + lon = math.abs( lon ) - local latDeg = math.floor(lat) - local latMin = (lat - latDeg)*60 + local latDeg = math.floor( lat ) + local latMin = (lat - latDeg) * 60 - local lonDeg = math.floor(lon) - local lonMin = (lon - lonDeg)*60 + local lonDeg = math.floor( lon ) + local lonMin = (lon - lonDeg) * 60 - if DMS then -- degrees, minutes, and seconds. + if DMS then -- degrees, minutes, and seconds. local oldLatMin = latMin - latMin = math.floor(latMin) - local latSec = UTILS.Round((oldLatMin - latMin)*60, acc) + 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) + lonMin = math.floor( lonMin ) + local lonSec = UTILS.Round( (oldLonMin - lonMin) * 60, acc ) if latSec == 60 then latSec = 0 @@ -500,20 +495,19 @@ UTILS.tostringLL = function( lat, lon, acc, DMS) local secFrmtStr -- create the formatting string for the seconds place secFrmtStr = '%02d' - if acc <= 0 then -- no decimal place. + if acc <= 0 then -- no decimal place. secFrmtStr = '%02d' else - local width = 3 + acc -- 01.310 - that's a width of 6, for example. Acc is limited to 2 for DMS! + local width = 3 + acc -- 01.310 - that's a width of 6, for example. Acc is limited to 2 for DMS! secFrmtStr = '%0' .. width .. '.' .. acc .. 'f' end -- 024° 23' 12"N or 024° 23' 12.03"N - 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 + 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 -- degrees, decimal minutes. - latMin = UTILS.Round(latMin, acc) - lonMin = UTILS.Round(lonMin, acc) + else -- degrees, decimal minutes. + latMin = UTILS.Round( latMin, acc ) + lonMin = UTILS.Round( lonMin, acc ) if latMin == 60 then latMin = 0 @@ -526,54 +520,56 @@ UTILS.tostringLL = function( lat, lon, acc, DMS) end local minFrmtStr -- create the formatting string for the minutes place - if acc <= 0 then -- no decimal place. + if acc <= 0 then -- no decimal place. minFrmtStr = '%02d' else - local width = 3 + acc -- 01.310 - that's a width of 6, for example. + local width = 3 + acc -- 01.310 - that's a width of 6, for example. minFrmtStr = '%0' .. width .. '.' .. acc .. 'f' end -- 024 23'N or 024 23.123'N - return string.format('%03d°', latDeg) .. ' ' .. string.format(minFrmtStr, latMin) .. '\'' .. latHemi .. ' ' - .. string.format('%03d°', lonDeg) .. ' ' .. string.format(minFrmtStr, lonMin) .. '\'' .. lonHemi + return string.format( '%03d°', latDeg ) .. ' ' .. string.format( minFrmtStr, latMin ) .. '\'' .. latHemi .. ' ' .. string.format( '%03d°', lonDeg ) .. ' ' .. string.format( minFrmtStr, lonMin ) .. '\'' .. lonHemi end end -- acc- the accuracy of each easting/northing. 0, 1, 2, 3, 4, or 5. -UTILS.tostringMGRS = function(MGRS, acc) --R2.1 +UTILS.tostringMGRS = function( MGRS, acc ) -- R2.1 if acc == 0 then return MGRS.UTMZone .. ' ' .. MGRS.MGRSDigraph else -- Test if Easting/Northing have less than 4 digits. - --MGRS.Easting=123 -- should be 00123 - --MGRS.Northing=5432 -- should be 05432 + -- MGRS.Easting=123 -- should be 00123 + -- MGRS.Northing=5432 -- should be 05432 -- Truncate rather than round MGRS grid! - local Easting=tostring(MGRS.Easting) - local Northing=tostring(MGRS.Northing) + local Easting = tostring( MGRS.Easting ) + local Northing = tostring( MGRS.Northing ) -- Count number of missing digits. Easting/Northing should have 5 digits. However, it is passed as a number. Therefore, any leading zeros would not be displayed by lua. - local nE=5-string.len(Easting) - local nN=5-string.len(Northing) + local nE = 5 - string.len( Easting ) + local nN = 5 - string.len( Northing ) -- Get leading zeros (if any). - for i=1,nE do Easting="0"..Easting end - for i=1,nN do Northing="0"..Northing end + for i = 1, nE do + Easting = "0" .. Easting + end + for i = 1, nN do + Northing = "0" .. Northing + end -- Return MGRS string. - return string.format("%s %s %s %s", MGRS.UTMZone, MGRS.MGRSDigraph, string.sub(Easting, 1, acc), string.sub(Northing, 1, acc)) + return string.format( "%s %s %s %s", MGRS.UTMZone, MGRS.MGRSDigraph, string.sub( Easting, 1, acc ), string.sub( Northing, 1, acc ) ) end end - --- From http://lua-users.org/wiki/SimpleRound -- use negative idp for rounding ahead of decimal place, positive for rounding after decimal place function UTILS.Round( num, idp ) - local mult = 10 ^ ( idp or 0 ) + local mult = 10 ^ (idp or 0) return math.floor( num * mult + 0.5 ) / mult end @@ -589,77 +585,87 @@ end -- Here is a customized version of pairs, which I called spairs because it iterates over the table in a sorted order. function UTILS.spairs( t, order ) - -- collect the keys - local keys = {} - for k in pairs(t) do keys[#keys+1] = k end + -- collect the keys + local keys = {} + for k in pairs( t ) do + keys[#keys + 1] = k + end - -- if order function given, sort by it by passing the table and keys a, b, - -- otherwise just sort the keys - if order then - table.sort(keys, function(a,b) return order(t, a, b) end) - else - table.sort(keys) - end + -- if order function given, sort by it by passing the table and keys a, b, + -- otherwise just sort the keys + if order then + table.sort( keys, function( a, b ) + return order( t, a, b ) + end ) + else + table.sort( keys ) + end - -- return the iterator function - local i = 0 - return function() - i = i + 1 - if keys[i] then - return keys[i], t[keys[i]] - end + -- return the iterator function + local i = 0 + return function() + i = i + 1 + if keys[i] then + return keys[i], t[keys[i]] end + end end - -- Here is a customized version of pairs, which I called kpairs because it iterates over the table in a sorted order, based on a function that will determine the keys as reference first. function UTILS.kpairs( t, getkey, order ) - -- collect the keys - local keys = {} - local keyso = {} - for k, o in pairs(t) do keys[#keys+1] = k keyso[#keyso+1] = getkey( o ) end + -- collect the keys + local keys = {} + local keyso = {} + for k, o in pairs( t ) do + keys[#keys + 1] = k + keyso[#keyso + 1] = getkey( o ) + end - -- if order function given, sort by it by passing the table and keys a, b, - -- otherwise just sort the keys - if order then - table.sort(keys, function(a,b) return order(t, a, b) end) - else - table.sort(keys) - end + -- if order function given, sort by it by passing the table and keys a, b, + -- otherwise just sort the keys + if order then + table.sort( keys, function( a, b ) + return order( t, a, b ) + end ) + else + table.sort( keys ) + end - -- return the iterator function - local i = 0 - return function() - i = i + 1 - if keys[i] then - return keyso[i], t[keys[i]] - end + -- return the iterator function + local i = 0 + return function() + i = i + 1 + if keys[i] then + return keyso[i], t[keys[i]] end + end end -- Here is a customized version of pairs, which I called rpairs because it iterates over the table in a random order. function UTILS.rpairs( t ) - -- collect the keys + -- collect the keys - local keys = {} - for k in pairs(t) do keys[#keys+1] = k end + 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 - - -- return the iterator function - local i = 0 - return function() - i = i + 1 - if random[i] then - return random[i], t[random[i]] - 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 + + -- return the iterator function + local i = 0 + return function() + i = i + 1 + if random[i] then + return random[i], t[random[i]] end + end end -- get a new mark ID for markings @@ -673,19 +679,18 @@ end --- Remove an object (marker, circle, arrow, text, quad, ...) on the F10 map. -- @param #number MarkID Unique ID of the object. -- @param #number Delay (Optional) Delay in seconds before the mark is removed. -function UTILS.RemoveMark(MarkID, Delay) - if Delay and Delay>0 then - TIMER:New(UTILS.RemoveMark, MarkID):Start(Delay) +function UTILS.RemoveMark( MarkID, Delay ) + if Delay and Delay > 0 then + TIMER:New( UTILS.RemoveMark, MarkID ):Start( Delay ) else - trigger.action.removeMark(MarkID) + trigger.action.removeMark( MarkID ) end end - -- Test if a Vec2 is in a radius of another Vec2 function UTILS.IsInRadius( InVec2, Vec2, Radius ) - local InRadius = ( ( InVec2.x - Vec2.x ) ^2 + ( InVec2.y - Vec2.y ) ^2 ) ^ 0.5 <= Radius + local InRadius = ((InVec2.x - Vec2.x) ^ 2 + (InVec2.y - Vec2.y) ^ 2) ^ 0.5 <= Radius return InRadius end @@ -693,7 +698,7 @@ end -- Test if a Vec3 is in the sphere of another Vec3 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 + local InSphere = ((InVec3.x - Vec3.x) ^ 2 + (InVec3.y - Vec3.y) ^ 2 + (InVec3.z - Vec3.z) ^ 2) ^ 0.5 <= Radius return InSphere end @@ -702,61 +707,61 @@ end -- @param #number speed Wind speed in m/s. -- @return #number Beaufort number. -- @return #string Beauford wind description. -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" +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" + bn = 12 + bd = "Hurricane" end - return bn,bd + return bn, bd end ---- Split string at seperators. C.f. http://stackoverflow.com/questions/1426954/split-string-in-lua +--- Split string at separators. C.f. http://stackoverflow.com/questions/1426954/split-string-in-lua -- @param #string str Sting to split. --- @param #string sep Speparator for split. +-- @param #string sep Separator for split. -- @return #table Split text. -function UTILS.Split(str, sep) +function UTILS.Split( str, sep ) local result = {} - local regex = ("([^%s]+)"):format(sep) - for each in str:gmatch(regex) do - table.insert(result, each) + local regex = ("([^%s]+)"):format( sep ) + for each in str:gmatch( regex ) do + table.insert( result, each ) end return result end @@ -764,13 +769,13 @@ end --- Get a table of all characters in a string. -- @param #string str Sting. -- @return #table Individual characters. -function UTILS.GetCharacters(str) +function UTILS.GetCharacters( str ) - local chars={} + local chars = {} - for i=1,#str do - local c=str:sub(i,i) - table.insert(chars, c) + for i = 1, #str do + local c = str:sub( i, i ) + table.insert( chars, c ) end return chars @@ -780,33 +785,33 @@ end -- @param #number seconds Time in seconds, e.g. from timer.getAbsTime() function. -- @param #boolean short (Optional) If true, use short output, i.e. (HH:)MM:SS without day. -- @return #string Time in format Hours:Minutes:Seconds+Days (HH:MM:SS+D). -function UTILS.SecondsToClock(seconds, short) +function UTILS.SecondsToClock( seconds, short ) -- Nil check. - if seconds==nil then + if seconds == nil then return nil end -- Seconds - local seconds = tonumber(seconds) + local seconds = tonumber( seconds ) -- Seconds of this day. - local _seconds=seconds%(60*60*24) + local _seconds = seconds % (60 * 60 * 24) - if seconds<0 then + 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 + 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=mins..":"..secs - clock=hours..":"..mins..":"..secs + if hours == "00" then + -- clock=mins..":"..secs + clock = hours .. ":" .. mins .. ":" .. secs else - clock=hours..":"..mins..":"..secs + clock = hours .. ":" .. mins .. ":" .. secs end end return clock @@ -817,60 +822,60 @@ end -- @return #number Seconds passed since last midnight. function UTILS.SecondsOfToday() - -- Time in seconds. - local time=timer.getAbsTime() + -- Time in seconds. + local time = timer.getAbsTime() - -- Short format without days since mission start. - local clock=UTILS.SecondsToClock(time, true) + -- Short format without days since mission start. + local clock = UTILS.SecondsToClock( time, true ) - -- Time is now the seconds passed since last midnight. - return UTILS.ClockToSeconds(clock) + -- Time is now the seconds passed since last midnight. + return UTILS.ClockToSeconds( clock ) end ---- Cound seconds until next midnight. +--- Count seconds until next midnight. -- @return #number Seconds to midnight. function UTILS.SecondsToMidnight() - return 24*60*60-UTILS.SecondsOfToday() + return 24 * 60 * 60 - UTILS.SecondsOfToday() end --- Convert clock time from hours, minutes and seconds to seconds. -- @param #string clock String of clock time. E.g., "06:12:35" or "5:1:30+1". Format is (H)H:(M)M:((S)S)(+D) H=Hours, M=Minutes, S=Seconds, D=Days. -- @return #number Seconds. Corresponds to what you cet from timer.getAbsTime() function. -function UTILS.ClockToSeconds(clock) +function UTILS.ClockToSeconds( clock ) -- Nil check. - if clock==nil then + if clock == nil then return nil end -- Seconds init. - local seconds=0 + local seconds = 0 -- Split additional days. - local dsplit=UTILS.Split(clock, "+") + local dsplit = UTILS.Split( clock, "+" ) -- Convert days to seconds. - if #dsplit>1 then - seconds=seconds+tonumber(dsplit[2])*60*60*24 + if #dsplit > 1 then + seconds = seconds + tonumber( dsplit[2] ) * 60 * 60 * 24 end -- Split hours, minutes, seconds - local tsplit=UTILS.Split(dsplit[1], ":") + local tsplit = UTILS.Split( dsplit[1], ":" ) -- Get time in seconds - local i=1 - for _,time in ipairs(tsplit) do - if i==1 then + local i = 1 + for _, time in ipairs( tsplit ) do + if i == 1 then -- Hours - seconds=seconds+tonumber(time)*60*60 - elseif i==2 then + seconds = seconds + tonumber( time ) * 60 * 60 + elseif i == 2 then -- Minutes - seconds=seconds+tonumber(time)*60 - elseif i==3 then + seconds = seconds + tonumber( time ) * 60 + elseif i == 3 then -- Seconds - seconds=seconds+tonumber(time) + seconds = seconds + tonumber( time ) end - i=i+1 + i = i + 1 end return seconds @@ -878,24 +883,24 @@ end --- Display clock and mission time on screen as a message to all. -- @param #number duration Duration in seconds how long the time is displayed. Default is 5 seconds. -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() +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 --- Replace illegal characters [<>|/?*:\\] in a string. -- @param #string Text Input text. -- @param #string ReplaceBy Replace illegal characters by this character or string. Default underscore "_". -- @return #string The input text with illegal chars replaced. -function UTILS.ReplaceIllegalCharacters(Text, ReplaceBy) - ReplaceBy=ReplaceBy or "_" - local text=Text:gsub("[<>|/?*:\\]", ReplaceBy) +function UTILS.ReplaceIllegalCharacters( Text, ReplaceBy ) + ReplaceBy = ReplaceBy or "_" + local text = Text:gsub( "[<>|/?*:\\]", ReplaceBy ) return text end @@ -906,29 +911,29 @@ end -- @param #number xmax (Optional) Upper cut-off value. -- @param #number imax (Optional) Max number of tries to get a value between xmin and xmax (if specified). Default 100. -- @return #number Gaussian random number. -function UTILS.RandomGaussian(x0, sigma, xmin, xmax, imax) +function UTILS.RandomGaussian( x0, sigma, xmin, xmax, imax ) -- Standard deviation. Default 10 if not given. - sigma=sigma or 10 + sigma = sigma or 10 -- Max attempts. - imax=imax or 100 + imax = imax or 100 local r - local gotit=false - local i=0 + local gotit = false + local i = 0 while not gotit do -- Uniform numbers in [0,1). We need two. - local x1=math.random() - local x2=math.random() + local x1 = math.random() + local x2 = math.random() -- Transform to Gaussian exp(-(x-x0)°/(2*sigma°). - r = math.sqrt(-2*sigma*sigma * math.log(x1)) * math.cos(2*math.pi * x2) + x0 + 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 + i = i + 1 + if (r >= xmin and r <= xmax) or i > imax then + gotit = true end end @@ -943,21 +948,21 @@ end -- @return #number Randomized value. -- @usage UTILS.Randomize(100, 0.1) returns a value between 90 and 110, i.e. a plus/minus ten percent variation. -- @usage UTILS.Randomize(100, 0.5, nil, 120) returns a value between 50 and 120, i.e. a plus/minus fivty percent variation with upper bound 120. -function UTILS.Randomize(value, fac, lower, upper) +function UTILS.Randomize( value, fac, lower, upper ) local min if lower then - min=math.max(value-value*fac, lower) + min = math.max( value - value * fac, lower ) else - min=value-value*fac + min = value - value * fac end local max if upper then - max=math.min(value+value*fac, upper) + max = math.min( value + value * fac, upper ) else - max=value+value*fac + max = value + value * fac end - local r=math.random(min, max) + local r = math.random( min, max ) return r end @@ -966,56 +971,54 @@ end -- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @param DCS#Vec3 b Vector in 3D with x, y, z components. -- @return #number Scalar product of the two vectors a*b. -function UTILS.VecDot(a, b) - return a.x*b.x + a.y*b.y + a.z*b.z +function UTILS.VecDot( a, b ) + return a.x * b.x + a.y * b.y + a.z * b.z end --- Calculate the [dot product](https://en.wikipedia.org/wiki/Dot_product) of two 2D vectors. The result is a number. -- @param DCS#Vec2 a Vector in 2D with x, y components. -- @param DCS#Vec2 b Vector in 2D with x, y components. -- @return #number Scalar product of the two vectors a*b. -function UTILS.Vec2Dot(a, b) - return a.x*b.x + a.y*b.y +function UTILS.Vec2Dot( a, b ) + return a.x * b.x + a.y * b.y end - --- Calculate the [euclidean norm](https://en.wikipedia.org/wiki/Euclidean_distance) (length) of a 3D vector. -- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @return #number Norm of the vector. -function UTILS.VecNorm(a) - return math.sqrt(UTILS.VecDot(a, a)) +function UTILS.VecNorm( a ) + return math.sqrt( UTILS.VecDot( a, a ) ) end --- Calculate the [euclidean norm](https://en.wikipedia.org/wiki/Euclidean_distance) (length) of a 2D vector. -- @param DCS#Vec2 a Vector in 2D with x, y components. -- @return #number Norm of the vector. -function UTILS.Vec2Norm(a) - return math.sqrt(UTILS.Vec2Dot(a, a)) +function UTILS.Vec2Norm( a ) + return math.sqrt( UTILS.Vec2Dot( a, a ) ) end --- Calculate the distance between two 2D vectors. -- @param DCS#Vec2 a Vector in 3D with x, y components. -- @param DCS#Vec2 b Vector in 3D with x, y components. -- @return #number Distance between the vectors. -function UTILS.VecDist2D(a, b) +function UTILS.VecDist2D( a, b ) - local c={x=b.x-a.x, y=b.y-a.y} + local c = { x = b.x - a.x, y = b.y - a.y } - local d=math.sqrt(c.x*c.x+c.y*c.y) + local d = math.sqrt( c.x * c.x + c.y * c.y ) return d end - --- Calculate the distance between two 3D vectors. -- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @param DCS#Vec3 b Vector in 3D with x, y, z components. -- @return #number Distance between the vectors. -function UTILS.VecDist3D(a, b) +function UTILS.VecDist3D( a, b ) - local c={x=b.x-a.x, y=b.y-a.y, z=b.z-a.z} + 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)) + local d = math.sqrt( UTILS.VecDot( c, c ) ) return d end @@ -1024,53 +1027,53 @@ end -- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @param DCS#Vec3 b Vector in 3D with x, y, z components. -- @return DCS#Vec3 Vector -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} +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 --- Calculate the difference between two 3D vectors by substracting the x,y,z components from each other. -- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @param DCS#Vec3 b Vector in 3D with x, y, z components. -- @return DCS#Vec3 Vector c=a-b with c(i)=a(i)-b(i), i=x,y,z. -function UTILS.VecSubstract(a, b) - return {x=a.x-b.x, y=a.y-b.y, z=a.z-b.z} +function UTILS.VecSubstract( a, b ) + return { x = a.x - b.x, y = a.y - b.y, z = a.z - b.z } end --- Calculate the total vector of two 3D vectors by adding the x,y,z components of each other. -- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @param DCS#Vec3 b Vector in 3D with x, y, z components. -- @return DCS#Vec3 Vector c=a+b with c(i)=a(i)+b(i), i=x,y,z. -function UTILS.VecAdd(a, b) - return {x=a.x+b.x, y=a.y+b.y, z=a.z+b.z} +function UTILS.VecAdd( a, b ) + return { x = a.x + b.x, y = a.y + b.y, z = a.z + b.z } end --- Calculate the angle between two 3D vectors. -- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @param DCS#Vec3 b Vector in 3D with x, y, z components. -- @return #number Angle alpha between and b in degrees. alpha=acos(a*b)/(|a||b|), (* denotes the dot product). -function UTILS.VecAngle(a, b) +function UTILS.VecAngle( a, b ) - local cosalpha=UTILS.VecDot(a,b)/(UTILS.VecNorm(a)*UTILS.VecNorm(b)) + local cosalpha = UTILS.VecDot( a, b ) / (UTILS.VecNorm( a ) * UTILS.VecNorm( b )) - local alpha=0 - if cosalpha>=0.9999999999 then --acos(1) is not defined. - alpha=0 - elseif cosalpha<=-0.999999999 then --acos(-1) is not defined. - alpha=math.pi + local alpha = 0 + if cosalpha >= 0.9999999999 then -- acos(1) is not defined. + alpha = 0 + elseif cosalpha <= -0.999999999 then -- acos(-1) is not defined. + alpha = math.pi else - alpha=math.acos(cosalpha) + alpha = math.acos( cosalpha ) end - return math.deg(alpha) + return math.deg( alpha ) end --- Calculate "heading" of a 3D vector in the X-Z plane. -- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @return #number Heading in degrees in [0,360). -function UTILS.VecHdg(a) - local h=math.deg(math.atan2(a.z, a.x)) - if h<0 then - h=h+360 +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 @@ -1078,10 +1081,10 @@ end --- Calculate "heading" of a 2D vector in the X-Y plane. -- @param DCS#Vec2 a Vector in "D with x, y components. -- @return #number Heading in degrees in [0,360). -function UTILS.Vec2Hdg(a) - local h=math.deg(math.atan2(a.y, a.x)) - if h<0 then - h=h+360 +function UTILS.Vec2Hdg( a ) + local h = math.deg( math.atan2( a.y, a.x ) ) + if h < 0 then + h = h + 360 end return h end @@ -1090,36 +1093,35 @@ end -- @param #number h1 Heading one. -- @param #number h2 Heading two. -- @return #number Heading difference in degrees. -function UTILS.HdgDiff(h1, h2) +function UTILS.HdgDiff( h1, h2 ) -- Angle in rad. - local alpha= math.rad(tonumber(h1)) - local beta = math.rad(tonumber(h2)) + local alpha = math.rad( tonumber( h1 ) ) + local beta = math.rad( tonumber( h2 ) ) -- Runway vector. - 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 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) + local delta = UTILS.VecAngle( v1, v2 ) - return math.abs(delta) + return math.abs( delta ) end - --- Translate 3D vector in the 2D (x,z) plane. y-component (usually altitude) unchanged. -- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @param #number distance The distance to translate. -- @param #number angle Rotation angle in degrees. -- @return DCS#Vec3 Vector rotated in the (x,z) plane. -function UTILS.VecTranslate(a, distance, angle) +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 + 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} + return { x = TX, y = a.y, z = TY } end --- Translate 2D vector in the 2D (x,z) plane. @@ -1127,33 +1129,33 @@ end -- @param #number distance The distance to translate. -- @param #number angle Rotation angle in degrees. -- @return DCS#Vec2 Translated vector. -function UTILS.Vec2Translate(a, distance, angle) +function UTILS.Vec2Translate( a, distance, angle ) local SX = a.x local SY = a.y - local Radians=math.rad(angle or 0) - local TX=distance*math.cos(Radians)+SX - local TY=distance*math.sin(Radians)+SY + local Radians = math.rad( angle or 0 ) + local TX = distance * math.cos( Radians ) + SX + local TY = distance * math.sin( Radians ) + SY - return {x=TX, y=TY} + return { x = TX, y = TY } end --- Rotate 3D vector in the 2D (x,z) plane. y-component (usually altitude) unchanged. -- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @param #number angle Rotation angle in degrees. -- @return DCS#Vec3 Vector rotated in the (x,z) plane. -function UTILS.Rotate2D(a, angle) +function UTILS.Rotate2D( a, angle ) - local phi=math.rad(angle) + local phi = math.rad( angle ) - local x=a.z - local y=a.x + 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 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} + local A = { x = X, y = Y, z = Z } return A end @@ -1162,39 +1164,38 @@ end -- @param DCS#Vec2 a Vector in 2D with x, y components. -- @param #number angle Rotation angle in degrees. -- @return DCS#Vec2 Vector rotated in the (x,y) plane. -function UTILS.Vec2Rotate2D(a, angle) +function UTILS.Vec2Rotate2D( a, angle ) - local phi=math.rad(angle) + local phi = math.rad( angle ) - local x=a.x - local y=a.y + local x = a.x + local y = a.y - local X=x*math.cos(phi)-y*math.sin(phi) - local Y=x*math.sin(phi)+y*math.cos(phi) + local X = x * math.cos( phi ) - y * math.sin( phi ) + local Y = x * math.sin( phi ) + y * math.cos( phi ) - local A={x=X, y=Y} + local A = { x = X, y = Y } return A end - --- Converts a TACAN Channel/Mode couple into a frequency in Hz. -- @param #number TACANChannel The TACAN channel, i.e. the 10 in "10X". -- @param #string TACANMode The TACAN mode, i.e. the "X" in "10X". -- @return #number Frequency in Hz or #nil if parameters are invalid. -function UTILS.TACANToFrequency(TACANChannel, TACANMode) +function UTILS.TACANToFrequency( TACANChannel, TACANMode ) - if type(TACANChannel) ~= "number" then + if type( TACANChannel ) ~= "number" then return nil -- error in arguments end if TACANMode ~= "X" and TACANMode ~= "Y" then return nil -- error in arguments end --- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137. --- I have no idea what it does but it seems to work + -- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137. + -- I have no idea what it does but it seems to work local A = 1151 -- 'X', channel >= 64 - local B = 64 -- channel >= 64 + local B = 64 -- channel >= 64 if TACANChannel < 64 then B = 1 @@ -1214,7 +1215,6 @@ function UTILS.TACANToFrequency(TACANChannel, TACANMode) return (A + TACANChannel - B) * 1000000 end - --- Returns the DCS map/theatre as optained by env.mission.theatre -- @return #string DCS map name. function UTILS.GetDCSMap() @@ -1227,22 +1227,22 @@ end -- @return #number The month. -- @return #number The day. 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) + 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 --- Returns the day of the mission. -- @param #number Time (Optional) Abs. time in seconds. Default now, i.e. the value return from timer.getAbsTime(). -- @return #number Day of the mission. Mission starts on day 0. -function UTILS.GetMissionDay(Time) +function UTILS.GetMissionDay( Time ) - Time=Time or timer.getAbsTime() + Time = Time or timer.getAbsTime() - local clock=UTILS.SecondsToClock(Time, false) + local clock = UTILS.SecondsToClock( Time, false ) - local x=tonumber(UTILS.Split(clock, "+")[2]) + local x = tonumber( UTILS.Split( clock, "+" )[2] ) return x end @@ -1250,13 +1250,13 @@ end --- Returns the current day of the year of the mission. -- @param #number Time (Optional) Abs. time in seconds. Default now, i.e. the value return from timer.getAbsTime(). -- @return #number Current day of year of the mission. For example, January 1st returns 0, January 2nd returns 1 etc. -function UTILS.GetMissionDayOfYear(Time) +function UTILS.GetMissionDayOfYear( Time ) - local Date, Year, Month, Day=UTILS.GetDCSMissionDate() + local Date, Year, Month, Day = UTILS.GetDCSMissionDate() - local d=UTILS.GetMissionDay(Time) + local d = UTILS.GetMissionDay( Time ) - return UTILS.GetDayOfYear(Year, Month, Day)+d + return UTILS.GetDayOfYear( Year, Month, Day ) + d end @@ -1268,15 +1268,15 @@ end function UTILS.GetDate() -- Mission start date - local date, year, month, day=UTILS.GetDCSMissionDate() + local date, year, month, day = UTILS.GetDCSMissionDate() - local time=timer.getAbsTime() + local time = timer.getAbsTime() - local clock=UTILS.SecondsToClock(time, false) + local clock = UTILS.SecondsToClock( time, false ) - local x=tonumber(UTILS.Split(clock, "+")[2]) + local x = tonumber( UTILS.Split( clock, "+" )[2] ) - local day=day+x + local day = day + x end @@ -1292,28 +1292,28 @@ end -- * Mariana Islands +2 (East) -- @param #string map (Optional) Map for which the declination is returned. Default is from env.mission.theatre -- @return #number Declination in degrees. -function UTILS.GetMagneticDeclination(map) +function UTILS.GetMagneticDeclination( map ) -- Map. - map=map or UTILS.GetDCSMap() + map = map or UTILS.GetDCSMap() - local declination=0 - if map==DCSMAP.Caucasus then - declination=6 - elseif map==DCSMAP.NTTR then - declination=12 - elseif map==DCSMAP.Normandy then - declination=-10 - elseif map==DCSMAP.PersianGulf then - declination=2 - elseif map==DCSMAP.TheChannel then - declination=-10 - elseif map==DCSMAP.Syria then - declination=5 - elseif map==DCSMAP.MarianaIslands then - declination=2 + local declination = 0 + if map == DCSMAP.Caucasus then + declination = 6 + elseif map == DCSMAP.NTTR then + declination = 12 + elseif map == DCSMAP.Normandy then + declination = -10 + elseif map == DCSMAP.PersianGulf then + declination = 2 + elseif map == DCSMAP.TheChannel then + declination = -10 + elseif map == DCSMAP.Syria then + declination = 5 + elseif map == DCSMAP.MarianaIslands then + declination = 2 else - declination=0 + declination = 0 end return declination @@ -1322,11 +1322,11 @@ end --- Checks if a file exists or not. This requires **io** to be desanitized. -- @param #string file File that should be checked. -- @return #boolean True if the file exists, false if the file does not exist or nil if the io module is not available and the check could not be performed. -function UTILS.FileExists(file) +function UTILS.FileExists( file ) if io then - local f=io.open(file, "r") - if f~=nil then - io.close(f) + local f = io.open( file, "r" ) + if f ~= nil then + io.close( f ) return true else return false @@ -1339,28 +1339,27 @@ end --- Checks the current memory usage collectgarbage("count"). Info is printed to the DCS log file. Time stamp is the current mission runtime. -- @param #boolean output If true, print to DCS log file. -- @return #number Memory usage in kByte. -function UTILS.CheckMemory(output) - local time=timer.getTime() - local clock=UTILS.SecondsToClock(time) - local mem=collectgarbage("count") +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)) + env.info( string.format( "T=%s Memory usage %d kByte = %.2f MByte", clock, mem, mem / 1024 ) ) end return mem end - --- Get the coalition name from its numerical ID, e.g. coaliton.side.RED. -- @param #number Coalition The coalition ID. -- @return #string The coalition name, i.e. "Neutral", "Red" or "Blue" (or "Unknown"). -function UTILS.GetCoalitionName(Coalition) +function UTILS.GetCoalitionName( Coalition ) if Coalition then - if Coalition==coalition.side.BLUE then + if Coalition == coalition.side.BLUE then return "Blue" - elseif Coalition==coalition.side.RED then + elseif Coalition == coalition.side.RED then return "Red" - elseif Coalition==coalition.side.NEUTRAL then + elseif Coalition == coalition.side.NEUTRAL then return "Neutral" else return "Unknown" @@ -1374,12 +1373,12 @@ end --- Get the modulation name from its numerical value. -- @param #number Modulation The modulation enumerator number. Can be either 0 or 1. -- @return #string The modulation name, i.e. "AM"=0 or "FM"=1. Anything else will return "Unknown". -function UTILS.GetModulationName(Modulation) +function UTILS.GetModulationName( Modulation ) if Modulation then - if Modulation==0 then + if Modulation == 0 then return "AM" - elseif Modulation==1 then + elseif Modulation == 1 then return "FM" else return "Unknown" @@ -1393,28 +1392,28 @@ end --- Get the callsign name from its enumerator value -- @param #number Callsign The enumerator callsign. -- @return #string The callsign name or "Ghostrider". -function UTILS.GetCallsignName(Callsign) +function UTILS.GetCallsignName( Callsign ) - for name, value in pairs(CALLSIGN.Aircraft) do - if value==Callsign then + 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 + 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 + 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 + for name, value in pairs( CALLSIGN.Tanker ) do + if value == Callsign then return name end end @@ -1426,44 +1425,43 @@ end -- @return #number Local time difference in hours compared to GMT. E.g. Dubai is GMT+4 ==> +4 is returned. function UTILS.GMTToLocalTimeDifference() - local theatre=UTILS.GetDCSMap() + local theatre = UTILS.GetDCSMap() - if theatre==DCSMAP.Caucasus then - return 4 -- Caucasus UTC+4 hours - elseif theatre==DCSMAP.PersianGulf then - return 4 -- Abu Dhabi UTC+4 hours - elseif theatre==DCSMAP.NTTR then - return -8 -- Las Vegas UTC-8 hours - elseif theatre==DCSMAP.Normandy then - return 0 -- Calais UTC+1 hour - elseif theatre==DCSMAP.TheChannel then - return 2 -- This map currently needs +2 - elseif theatre==DCSMAP.Syria then - return 3 -- Damascus is UTC+3 hours - elseif theatre==DCSMAP.MarianaIslands then - return 10 -- Guam is UTC+10 hours. + if theatre == DCSMAP.Caucasus then + return 4 -- Caucasus UTC+4 hours + elseif theatre == DCSMAP.PersianGulf then + return 4 -- Abu Dhabi UTC+4 hours + elseif theatre == DCSMAP.NTTR then + return -8 -- Las Vegas UTC-8 hours + elseif theatre == DCSMAP.Normandy then + return 0 -- Calais UTC+1 hour + elseif theatre == DCSMAP.TheChannel then + return 2 -- This map currently needs +2 + elseif theatre == DCSMAP.Syria then + return 3 -- Damascus is UTC+3 hours + elseif theatre == DCSMAP.MarianaIslands then + return 10 -- Guam is UTC+10 hours. else - BASE:E(string.format("ERROR: Unknown Map %s in UTILS.GMTToLocal function. Returning 0", tostring(theatre))) + BASE:E( string.format( "ERROR: Unknown Map %s in UTILS.GMTToLocal function. Returning 0", tostring( theatre ) ) ) return 0 end end - --- Get the day of the year. Counting starts on 1st of January. -- @param #number Year The year. -- @param #number Month The month. -- @param #number Day The day. -- @return #number The day of the year. -function UTILS.GetDayOfYear(Year, Month, Day) +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)) + 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 + return n1 - (n2 * n3) + Day - 30 end --- Get sunrise or sun set of a specific day of the year at a specific location. @@ -1473,100 +1471,113 @@ end -- @param #boolean Rising If true, calc sun rise, or sun set otherwise. -- @param #number Tlocal Local time offset in hours. E.g. +4 for a location which has GMT+4. -- @return #number Sun rise/set in seconds of the day. -function UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, Rising, Tlocal) +function UTILS.GetSunRiseAndSet( DayOfYear, Latitude, Longitude, Rising, Tlocal ) -- Defaults - local zenith=90.83 - local latitude=Latitude - local longitude=Longitude - local rising=Rising - local n=DayOfYear - Tlocal=Tlocal or 0 - + local zenith = 90.83 + local latitude = Latitude + local longitude = Longitude + local rising = Rising + local n = DayOfYear + Tlocal = Tlocal or 0 -- Short cuts. local rad = math.rad local deg = math.deg local floor = math.floor - local frac = function(n) return n - floor(n) end - local cos = function(d) return math.cos(rad(d)) end - local acos = function(d) return deg(math.acos(d)) end - local sin = function(d) return math.sin(rad(d)) end - local asin = function(d) return deg(math.asin(d)) end - local tan = function(d) return math.tan(rad(d)) end - local atan = function(d) return deg(math.atan(d)) end - - local function fit_into_range(val, min, max) - local range = max - min - local count - if val < min then - count = floor((min - val) / range) + 1 - return val + count * range - elseif val >= max then - count = floor((val - max) / range) + 1 - return val - count * range - else - return val - end + 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 - -- Convert the longitude to hour value and calculate an approximate time - local lng_hour = longitude / 15 + local function fit_into_range( val, min, max ) + local range = max - min + local count + if val < min then + count = floor( (min - val) / range ) + 1 + return val + count * range + elseif val >= max then + count = floor( (val - max) / range ) + 1 + return val - count * range + else + return val + end + end - local t - if rising then -- Rising time is desired - t = n + ((6 - lng_hour) / 24) - else -- Setting time is desired - t = n + ((18 - lng_hour) / 24) - end + -- Convert the longitude to hour value and calculate an approximate time + local lng_hour = longitude / 15 - -- Calculate the Sun's mean anomaly - local M = (0.9856 * t) - 3.289 + local t + if rising then -- Rising time is desired + t = n + ((6 - lng_hour) / 24) + else -- Setting time is desired + t = n + ((18 - lng_hour) / 24) + end - -- Calculate the Sun's true longitude - local L = fit_into_range(M + (1.916 * sin(M)) + (0.020 * sin(2 * M)) + 282.634, 0, 360) + -- Calculate the Sun's mean anomaly + local M = (0.9856 * t) - 3.289 - -- Calculate the Sun's right ascension - local RA = fit_into_range(atan(0.91764 * tan(L)), 0, 360) + -- Calculate the Sun's true longitude + local L = fit_into_range( M + (1.916 * sin( M )) + (0.020 * sin( 2 * M )) + 282.634, 0, 360 ) - -- Right ascension value needs to be in the same quadrant as L - local Lquadrant = floor(L / 90) * 90 - local RAquadrant = floor(RA / 90) * 90 - RA = RA + Lquadrant - RAquadrant + -- Calculate the Sun's right ascension + local RA = fit_into_range( atan( 0.91764 * tan( L ) ), 0, 360 ) - -- Right ascension value needs to be converted into hours - RA = RA / 15 + -- Right ascension value needs to be in the same quadrant as L + local Lquadrant = floor( L / 90 ) * 90 + local RAquadrant = floor( RA / 90 ) * 90 + RA = RA + Lquadrant - RAquadrant - -- Calculate the Sun's declination - local sinDec = 0.39782 * sin(L) - local cosDec = cos(asin(sinDec)) + -- Right ascension value needs to be converted into hours + RA = RA / 15 - -- Calculate the Sun's local hour angle - local cosH = (cos(zenith) - (sinDec * sin(latitude))) / (cosDec * cos(latitude)) + -- Calculate the Sun's declination + local sinDec = 0.39782 * sin( L ) + local cosDec = cos( asin( sinDec ) ) - if rising and cosH > 1 then - return "N/R" -- The sun never rises on this location on the specified date - elseif cosH < -1 then - return "N/S" -- The sun never sets on this location on the specified date - end + -- Calculate the Sun's local hour angle + local cosH = (cos( zenith ) - (sinDec * sin( latitude ))) / (cosDec * cos( latitude )) - -- Finish calculating H and convert into hours - local H - if rising then - H = 360 - acos(cosH) - else - H = acos(cosH) - end - H = H / 15 + if rising and cosH > 1 then + return "N/R" -- The sun never rises on this location on the specified date + elseif cosH < -1 then + return "N/S" -- The sun never sets on this location on the specified date + end - -- Calculate local mean time of rising/setting - local T = H + RA - (0.06571 * t) - 6.622 + -- Finish calculating H and convert into hours + local H + if rising then + H = 360 - acos( cosH ) + else + H = acos( cosH ) + end + H = H / 15 - -- Adjust back to UTC - local UT = fit_into_range(T - lng_hour +Tlocal, 0, 24) + -- Calculate local mean time of rising/setting + local T = H + RA - (0.06571 * t) - 6.622 - return floor(UT)*60*60+frac(UT)*60*60--+Tlocal*60*60 - end + -- Adjust back to UTC + local UT = fit_into_range( T - lng_hour + Tlocal, 0, 24 ) + + return floor( UT ) * 60 * 60 + frac( UT ) * 60 * 60 -- +Tlocal*60*60 +end --- Get sun rise of a specific day of the year at a specific location. -- @param #number Day Day of the year. @@ -1577,11 +1588,11 @@ function UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, Rising, Tlocal) -- @param #boolean Rising If true, calc sun rise, or sun set otherwise. -- @param #number Tlocal Local time offset in hours. E.g. +4 for a location which has GMT+4. Default 0. -- @return #number Sun rise in seconds of the day. -function UTILS.GetSunrise(Day, Month, Year, Latitude, Longitude, Tlocal) +function UTILS.GetSunrise( Day, Month, Year, Latitude, Longitude, Tlocal ) - local DayOfYear=UTILS.GetDayOfYear(Year, Month, Day) + local DayOfYear = UTILS.GetDayOfYear( Year, Month, Day ) - return UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, true, Tlocal) + return UTILS.GetSunRiseAndSet( DayOfYear, Latitude, Longitude, true, Tlocal ) end --- Get sun set of a specific day of the year at a specific location. @@ -1593,11 +1604,11 @@ end -- @param #boolean Rising If true, calc sun rise, or sun set otherwise. -- @param #number Tlocal Local time offset in hours. E.g. +4 for a location which has GMT+4. Default 0. -- @return #number Sun rise in seconds of the day. -function UTILS.GetSunset(Day, Month, Year, Latitude, Longitude, Tlocal) +function UTILS.GetSunset( Day, Month, Year, Latitude, Longitude, Tlocal ) - local DayOfYear=UTILS.GetDayOfYear(Year, Month, Day) + local DayOfYear = UTILS.GetDayOfYear( Year, Month, Day ) - return UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, false, Tlocal) + return UTILS.GetSunRiseAndSet( DayOfYear, Latitude, Longitude, false, Tlocal ) end --- Get OS time. Needs os to be desanitized! @@ -1611,11 +1622,11 @@ function UTILS.GetOSTime() end --- Shuffle a table accoring to Fisher Yeates algorithm ---@param #table t Table to be shuffled ---@return #table -function UTILS.ShuffleTable(t) - if t == nil or type(t) ~= "table" then - BASE:I("Error in ShuffleTable: Missing or wrong type of Argument") +-- @param #table t Table to be shuffled +-- @return #table +function UTILS.ShuffleTable( t ) + if t == nil or type( t ) ~= "table" then + BASE:I( "Error in ShuffleTable: Missing or wrong type of Argument" ) return end math.random() @@ -1623,67 +1634,67 @@ function UTILS.ShuffleTable(t) math.random() local TempTable = {} for i = 1, #t do - local r = math.random(1,#t) + local r = math.random( 1, #t ) TempTable[i] = t[r] - table.remove(t,r) + table.remove( t, r ) end return TempTable end --- (Helicopter) Check if one loading door is open. ---@param #string unit_name Unit name to be checked ---@return #boolean Outcome - true if a (loading door) is open, false if not, nil if none exists. +-- @param #string unit_name Unit name to be checked +-- @return #boolean Outcome - true if a (loading door) is open, false if not, nil if none exists. function UTILS.IsLoadingDoorOpen( unit_name ) local ret_val = false - local unit = Unit.getByName(unit_name) + local unit = Unit.getByName( unit_name ) if unit ~= nil then - local type_name = unit:getTypeName() + local type_name = unit:getTypeName() - if type_name == "Mi-8MT" and unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(86) == 1 or unit:getDrawArgumentValue(250) < 0 then - BASE:T(unit_name .. " Cargo doors are open or cargo door not present") - ret_val = true - end + if type_name == "Mi-8MT" and unit:getDrawArgumentValue( 38 ) == 1 or unit:getDrawArgumentValue( 86 ) == 1 or unit:getDrawArgumentValue( 250 ) < 0 then + BASE:T( unit_name .. " Cargo doors are open or cargo door not present" ) + ret_val = true + end - if type_name == "Mi-24P" and unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(86) == 1 then - BASE:T(unit_name .. " a side door is open") - ret_val = true - end + if type_name == "Mi-24P" and unit:getDrawArgumentValue( 38 ) == 1 or unit:getDrawArgumentValue( 86 ) == 1 then + BASE:T( unit_name .. " a side door is open" ) + ret_val = true + end - if type_name == "UH-1H" and unit:getDrawArgumentValue(43) == 1 or unit:getDrawArgumentValue(44) == 1 then - BASE:T(unit_name .. " a side door is open ") - ret_val = true - end + if type_name == "UH-1H" and unit:getDrawArgumentValue( 43 ) == 1 or unit:getDrawArgumentValue( 44 ) == 1 then + BASE:T( unit_name .. " a side door is open " ) + ret_val = true + end - if string.find(type_name, "SA342" ) and unit:getDrawArgumentValue(34) == 1 or unit:getDrawArgumentValue(38) == 1 then - BASE:T(unit_name .. " front door(s) are open") - ret_val = true - end + if string.find( type_name, "SA342" ) and unit:getDrawArgumentValue( 34 ) == 1 or unit:getDrawArgumentValue( 38 ) == 1 then + BASE:T( unit_name .. " front door(s) are open" ) + ret_val = true + end - if string.find(type_name, "Hercules") and unit:getDrawArgumentValue(1215) == 1 and unit:getDrawArgumentValue(1216) == 1 then - BASE:T(unit_name .. " rear doors are open") - ret_val = true - end + if string.find( type_name, "Hercules" ) and unit:getDrawArgumentValue( 1215 ) == 1 and unit:getDrawArgumentValue( 1216 ) == 1 then + BASE:T( unit_name .. " rear doors are open" ) + ret_val = true + end - if string.find(type_name, "Hercules") and (unit:getDrawArgumentValue(1220) == 1 or unit:getDrawArgumentValue(1221) == 1) then - BASE:T(unit_name .. " para doors are open") - ret_val = true - end + if string.find( type_name, "Hercules" ) and (unit:getDrawArgumentValue( 1220 ) == 1 or unit:getDrawArgumentValue( 1221 ) == 1) then + BASE:T( unit_name .. " para doors are open" ) + ret_val = true + end - if string.find(type_name, "Hercules") and unit:getDrawArgumentValue(1217) == 1 then - BASE:T(unit_name .. " side door is open") - ret_val = true - end + if string.find( type_name, "Hercules" ) and unit:getDrawArgumentValue( 1217 ) == 1 then + BASE:T( unit_name .. " side door is open" ) + ret_val = true + end - if string.find(type_name, "Bell-47") then -- bell aint got no doors so always ready to load injured soldiers - BASE:T(unit_name .. " door is open") - ret_val = true - end + if string.find( type_name, "Bell-47" ) then -- bell aint got no doors so always ready to load injured soldiers + BASE:T( unit_name .. " door is open" ) + ret_val = true + end - if ret_val == false then - BASE:T(unit_name .. " all doors are closed") - end - return ret_val + if ret_val == false then + BASE:T( unit_name .. " all doors are closed" ) + end + return ret_val end -- nil @@ -1693,16 +1704,16 @@ end --- Function to generate valid FM frequencies in mHz for radio beacons (FM). -- @return #table Table of frequencies. function UTILS.GenerateFMFrequencies() - local FreeFMFrequencies = {} - for _first = 3, 7 do - for _second = 0, 5 do - for _third = 0, 9 do - local _frequency = ((100 * _first) + (10 * _second) + _third) * 100000 --extra 0 because we didnt bother with 4th digit - table.insert(FreeFMFrequencies, _frequency) - end - end + local FreeFMFrequencies = {} + for _first = 3, 7 do + for _second = 0, 5 do + for _third = 0, 9 do + local _frequency = ((100 * _first) + (10 * _second) + _third) * 100000 -- extra 0 because we didnt bother with 4th digit + table.insert( FreeFMFrequencies, _frequency ) + end end - return FreeFMFrequencies + end + return FreeFMFrequencies end --- Function to generate valid VHF frequencies in kHz for radio beacons (FM). @@ -1711,73 +1722,73 @@ function UTILS.GenerateVHFrequencies() -- known and sorted map-wise NDBs in kHz local _skipFrequencies = { - 214,274,291.5,295,297.5, - 300.5,304,307,309.5,311,312,312.5,316, - 320,324,328,329,330,332,336,337, - 342,343,348,351,352,353,358, - 363,365,368,372.5,374, - 380,381,384,385,389,395,396, - 414,420,430,432,435,440,450,455,462,470,485, - 507,515,520,525,528,540,550,560,570,577,580, - 602,625,641,662,670,680,682,690, - 705,720,722,730,735,740,745,750,770,795, - 822,830,862,866, - 905,907,920,935,942,950,995, - 1000,1025,1030,1050,1065,1116,1175,1182,1210 + 214,274,291.5,295,297.5, + 300.5,304,307,309.5,311,312,312.5,316, + 320,324,328,329,330,332,336,337, + 342,343,348,351,352,353,358, + 363,365,368,372.5,374, + 380,381,384,385,389,395,396, + 414,420,430,432,435,440,450,455,462,470,485, + 507,515,520,525,528,540,550,560,570,577,580, + 602,625,641,662,670,680,682,690, + 705,720,722,730,735,740,745,750,770,795, + 822,830,862,866, + 905,907,920,935,942,950,995, + 1000,1025,1030,1050,1065,1116,1175,1182,1210 } local FreeVHFFrequencies = {} - -- first range + -- first range local _start = 200000 while _start < 400000 do - -- skip existing NDB frequencies# - local _found = false - for _, value in pairs(_skipFrequencies) do - if value * 1000 == _start then - _found = true - break - end + -- skip existing NDB frequencies# + local _found = false + for _, value in pairs( _skipFrequencies ) do + if value * 1000 == _start then + _found = true + break end - if _found == false then - table.insert(FreeVHFFrequencies, _start) - end - _start = _start + 10000 + end + if _found == false then + table.insert( FreeVHFFrequencies, _start ) + end + _start = _start + 10000 end - -- second range + -- second range _start = 400000 while _start < 850000 do - -- skip existing NDB frequencies - local _found = false - for _, value in pairs(_skipFrequencies) do - if value * 1000 == _start then - _found = true - break - end + -- skip existing NDB frequencies + local _found = false + for _, value in pairs( _skipFrequencies ) do + if value * 1000 == _start then + _found = true + break end - if _found == false then - table.insert(FreeVHFFrequencies, _start) - end - _start = _start + 10000 + end + if _found == false then + table.insert( FreeVHFFrequencies, _start ) + end + _start = _start + 10000 end -- third range _start = 850000 while _start <= 999000 do -- adjusted for Gazelle - -- skip existing NDB frequencies - local _found = false - for _, value in pairs(_skipFrequencies) do - if value * 1000 == _start then - _found = true - break - end + -- skip existing NDB frequencies + local _found = false + for _, value in pairs( _skipFrequencies ) do + if value * 1000 == _start then + _found = true + break end - if _found == false then - table.insert(FreeVHFFrequencies, _start) - end - _start = _start + 50000 + end + if _found == false then + table.insert( FreeVHFFrequencies, _start ) + end + _start = _start + 50000 end return FreeVHFFrequencies @@ -1787,52 +1798,50 @@ end -- @return #table UHF Frequencies function UTILS.GenerateUHFrequencies() - local FreeUHFFrequencies = {} - local _start = 220000000 + local FreeUHFFrequencies = {} + local _start = 220000000 - while _start < 399000000 do - table.insert(FreeUHFFrequencies, _start) - _start = _start + 500000 - end + while _start < 399000000 do + table.insert( FreeUHFFrequencies, _start ) + _start = _start + 500000 + end - return FreeUHFFrequencies + return FreeUHFFrequencies end --- Function to generate valid laser codes for JTAC. -- @return #table Laser Codes. function UTILS.GenerateLaserCodes() - local jtacGeneratedLaserCodes = {} + local jtacGeneratedLaserCodes = {} - -- helper function - 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 + -- helper function + 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 - return false end + return false + end - -- generate list of laser codes - local _code = 1111 - local _count = 1 - while _code < 1777 and _count < 30 do - while true do - _code = _code + 1 - if not ContainsDigit(_code, 8) - and not ContainsDigit(_code, 9) - and not ContainsDigit(_code, 0) then - table.insert(jtacGeneratedLaserCodes, _code) - break - end - end - _count = _count + 1 + -- generate list of laser codes + local _code = 1111 + local _count = 1 + while _code < 1777 and _count < 30 do + while true do + _code = _code + 1 + if not ContainsDigit( _code, 8 ) and not ContainsDigit( _code, 9 ) and not ContainsDigit( _code, 0 ) then + table.insert( jtacGeneratedLaserCodes, _code ) + break + end end - return jtacGeneratedLaserCodes + _count = _count + 1 + end + return jtacGeneratedLaserCodes end --- Function to save an object to a file @@ -1840,34 +1849,34 @@ end -- @param #string Filename The name of the file. Existing file will be overwritten. -- @param #table Data The LUA data structure to save. This will be e.g. a table of text lines with an \\n at the end of each line. -- @return #boolean outcome True if saving is possible, else false. -function UTILS.SaveToFile(Path,Filename,Data) +function UTILS.SaveToFile( Path, Filename, Data ) -- Thanks to @FunkyFranky -- Check io module is available. if not io then - BASE:E("ERROR: io not desanitized. Can't save current file.") + BASE:E( "ERROR: io not desanitized. Can't save current file." ) return false end - + -- Check default path. - if Path==nil and not lfs then - BASE:E("WARNING: lfs not desanitized. File will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") + if Path == nil and not lfs then + BASE:E( "WARNING: lfs not desanitized. File will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder." ) end - + -- Set path or default. local path = nil if lfs then - path=Path or lfs.writedir() + path = Path or lfs.writedir() end - + -- Set file name. - local filename=Filename - if path~=nil then - filename=path.."\\"..filename + local filename = Filename + if path ~= nil then + filename = path .. "\\" .. filename end - + -- write - local f = assert(io.open(filename, "wb")) - f:write(Data) + local f = assert( io.open( filename, "wb" ) ) + f:write( Data ) f:close() return true end @@ -1877,43 +1886,43 @@ end -- @param #string Filename The name of the file. -- @return #boolean outcome True if reading is possible and successful, else false. -- @return #table data The data read from the filesystem (table of lines of text). Each line is one single #string! -function UTILS.LoadFromFile(Path,Filename) +function UTILS.LoadFromFile( Path, Filename ) -- Thanks to @FunkyFranky -- Check io module is available. if not io then - BASE:E("ERROR: io not desanitized. Can't save current state.") + BASE:E( "ERROR: io not desanitized. Can't save current state." ) return false end - + -- Check default path. - if Path==nil and not lfs then - BASE:E("WARNING: lfs not desanitized. Loading will look into your DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") + if Path == nil and not lfs then + BASE:E( "WARNING: lfs not desanitized. Loading will look into your DCS installation root directory rather than your \"Saved Games\\DCS\" folder." ) end - + -- Set path or default. local path = nil if lfs then - path=Path or lfs.writedir() + path = Path or lfs.writedir() end - + -- Set file name. - local filename=Filename - if path~=nil then - filename=path.."\\"..filename + local filename = Filename + if path ~= nil then + filename = path .. "\\" .. filename end - + -- Check if file exists. - local exists=UTILS.CheckFileExists(Path,Filename) + local exists = UTILS.CheckFileExists( Path, Filename ) if not exists then - BASE:E(string.format("ERROR: File %s does not exist!",filename)) + BASE:E( string.format( "ERROR: File %s does not exist!", filename ) ) return false end - + -- read - local file=assert(io.open(filename, "rb")) + local file = assert( io.open( filename, "rb" ) ) local loadeddata = {} for line in file:lines() do - loadeddata[#loadeddata+1] = line + loadeddata[#loadeddata + 1] = line end file:close() return true, loadeddata @@ -1923,46 +1932,46 @@ end -- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems. -- @param #string Filename The name of the file. -- @return #boolean outcome True if reading is possible, else false. -function UTILS.CheckFileExists(Path,Filename) +function UTILS.CheckFileExists( Path, Filename ) -- Thanks to @FunkyFranky -- Function that check if a file exists. - local function _fileexists(name) - local f=io.open(name,"r") - if f~=nil then - io.close(f) + local function _fileexists( name ) + local f = io.open( name, "r" ) + if f ~= nil then + io.close( f ) return true else return false end end - + -- Check io module is available. if not io then - BASE:E("ERROR: io not desanitized. Can't save current state.") + BASE:E( "ERROR: io not desanitized. Can't save current state." ) return false end - + -- Check default path. - if Path==nil and not lfs then - BASE:E("WARNING: lfs not desanitized. Loading will look into your DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") + if Path == nil and not lfs then + BASE:E( "WARNING: lfs not desanitized. Loading will look into your DCS installation root directory rather than your \"Saved Games\\DCS\" folder." ) end - + -- Set path or default. local path = nil if lfs then - path=Path or lfs.writedir() + path = Path or lfs.writedir() end - + -- Set file name. - local filename=Filename - if path~=nil then - filename=path.."\\"..filename + local filename = Filename + if path ~= nil then + filename = path .. "\\" .. filename end - + -- Check if file exists. - local exists=_fileexists(filename) + local exists = _fileexists( filename ) if not exists then - BASE:E(string.format("ERROR: File %s does not exist!",filename)) + BASE:E( string.format( "ERROR: File %s does not exist!", filename ) ) return false else return true @@ -1980,21 +1989,21 @@ end -- Position is still saved for your usage. -- The idea is to reduce the number of units when reloading the data again to restart the saved mission. -- The data will be a simple comma separated list of groupname and size, with one header line. -function UTILS.SaveStationaryListOfGroups(List,Path,Filename) +function UTILS.SaveStationaryListOfGroups( List, Path, Filename ) local filename = Filename or "StateListofGroups" - local data = "--Save Stationary List of Groups: "..Filename .."\n" - for _,_group in pairs (List) do - local group = GROUP:FindByName(_group) -- Wrapper.Group#GROUP + local data = "--Save Stationary List of Groups: " .. Filename .. "\n" + for _, _group in pairs( List ) do + local group = GROUP:FindByName( _group ) -- Wrapper.Group#GROUP if group and group:IsAlive() then local units = group:CountAliveUnits() local position = group:GetVec3() - data = string.format("%s%s,%d,%d,%d,%d\n",data,_group,units,position.x,position.y,position.z) + data = string.format( "%s%s,%d,%d,%d,%d\n", data, _group, units, position.x, position.y, position.z ) else - data = string.format("%s%s,0,0,0,0\n",data,_group) + data = string.format( "%s%s,0,0,0,0\n", data, _group ) end end -- save the data - local outcome = UTILS.SaveToFile(Path,Filename,data) + local outcome = UTILS.SaveToFile( Path, Filename, data ) return outcome end @@ -2011,25 +2020,25 @@ end -- **Note** Do NOT use dashes or hashes in group template names (-,#)! -- The data will be a simple comma separated list of groupname and size, with one header line. -- The current task/waypoint/etc cannot be restored. -function UTILS.SaveSetOfGroups(Set,Path,Filename) +function UTILS.SaveSetOfGroups( Set, Path, Filename ) local filename = Filename or "SetOfGroups" - local data = "--Save SET of groups: "..Filename .."\n" + local data = "--Save SET of groups: " .. Filename .. "\n" local List = Set:GetSetObjects() - for _,_group in pairs (List) do + for _, _group in pairs( List ) do local group = _group -- Wrapper.Group#GROUP if group and group:IsAlive() then local name = group:GetName() - local template = string.gsub(name,"-(.+)$","") - if string.find(template,"#") then - template = string.gsub(name,"#(%d+)$","") - end + local template = string.gsub( name, "-(.+)$", "" ) + if string.find( template, "#" ) then + template = string.gsub( name, "#(%d+)$", "" ) + end local units = group:CountAliveUnits() local position = group:GetVec3() - data = string.format("%s%s,%s,%d,%d,%d,%d\n",data,name,template,units,position.x,position.y,position.z) + data = string.format( "%s%s,%s,%d,%d,%d,%d\n", data, name, template, units, position.x, position.y, position.z ) end end -- save the data - local outcome = UTILS.SaveToFile(Path,Filename,data) + local outcome = UTILS.SaveToFile( Path, Filename, data ) return outcome end @@ -2041,20 +2050,20 @@ end -- @usage -- We will go through the set and find the corresponding static and save the current name and postion when alive. -- The data will be a simple comma separated list of name and state etc, with one header line. -function UTILS.SaveSetOfStatics(Set,Path,Filename) +function UTILS.SaveSetOfStatics( Set, Path, Filename ) local filename = Filename or "SetOfStatics" - local data = "--Save SET of statics: "..Filename .."\n" + local data = "--Save SET of statics: " .. Filename .. "\n" local List = Set:GetSetObjects() - for _,_group in pairs (List) do + for _, _group in pairs( List ) do local group = _group -- Wrapper.Static#STATIC if group and group:IsAlive() then local name = group:GetName() local position = group:GetVec3() - data = string.format("%s%s,%d,%d,%d\n",data,name,position.x,position.y,position.z) + data = string.format( "%s%s,%d,%d,%d\n", data, name, position.x, position.y, position.z ) end end -- save the data - local outcome = UTILS.SaveToFile(Path,Filename,data) + local outcome = UTILS.SaveToFile( Path, Filename, data ) return outcome end @@ -2068,20 +2077,20 @@ end -- Position is saved for your usage. **Note** this works on UNIT-name level. -- The idea is to reduce the number of units when reloading the data again to restart the saved mission. -- The data will be a simple comma separated list of name and state etc, with one header line. -function UTILS.SaveStationaryListOfStatics(List,Path,Filename) +function UTILS.SaveStationaryListOfStatics( List, Path, Filename ) local filename = Filename or "StateListofStatics" - local data = "--Save Stationary List of Statics: "..Filename .."\n" - for _,_group in pairs (List) do - local group = STATIC:FindByName(_group,false) -- Wrapper.Static#STATIC + local data = "--Save Stationary List of Statics: " .. Filename .. "\n" + for _, _group in pairs( List ) do + local group = STATIC:FindByName( _group, false ) -- Wrapper.Static#STATIC if group and group:IsAlive() then local position = group:GetVec3() - data = string.format("%s%s,1,%d,%d,%d\n",data,_group,position.x,position.y,position.z) + data = string.format( "%s%s,1,%d,%d,%d\n", data, _group, position.x, position.y, position.z ) else - data = string.format("%s%s,0,0,0,0\n",data,_group) + data = string.format( "%s%s,0,0,0,0\n", data, _group ) end end -- save the data - local outcome = UTILS.SaveToFile(Path,Filename,data) + local outcome = UTILS.SaveToFile( Path, Filename, data ) return outcome end @@ -2090,41 +2099,43 @@ end -- @param #string Filename The name of the file. -- @param #boolean Reduce If false, existing loaded groups will not be reduced to fit the saved number. -- @return #table Table of data objects (tables) containing groupname, coordinate and group object. Returns nil when file cannot be read. -function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce) +function UTILS.LoadStationaryListOfGroups( Path, Filename, Reduce ) local reduce = true - if Reduce == false then reduce = false end + if Reduce == false then + reduce = false + end local filename = Filename or "StateListofGroups" local datatable = {} - if UTILS.CheckFileExists(Path,filename) then - local outcome,loadeddata = UTILS.LoadFromFile(Path,Filename) + if UTILS.CheckFileExists( Path, filename ) then + local outcome, loadeddata = UTILS.LoadFromFile( Path, Filename ) -- remove header - table.remove(loadeddata, 1) - for _id,_entry in pairs (loadeddata) do - local dataset = UTILS.Split(_entry,",") + table.remove( loadeddata, 1 ) + for _id, _entry in pairs( loadeddata ) do + local dataset = UTILS.Split( _entry, "," ) -- groupname,units,position.x,position.y,position.z local groupname = dataset[1] - local size = tonumber(dataset[2]) - local posx = tonumber(dataset[3]) - local posy = tonumber(dataset[4]) - local posz = tonumber(dataset[5]) - local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz}) - local data = { groupname=groupname, size=size, coordinate=coordinate, group=GROUP:FindByName(groupname) } + local size = tonumber( dataset[2] ) + local posx = tonumber( dataset[3] ) + local posy = tonumber( dataset[4] ) + local posz = tonumber( dataset[5] ) + local coordinate = COORDINATE:NewFromVec3( { x = posx, y = posy, z = posz } ) + local data = { groupname = groupname, size = size, coordinate = coordinate, group = GROUP:FindByName( groupname ) } if reduce then - local actualgroup = GROUP:FindByName(groupname) + local actualgroup = GROUP:FindByName( groupname ) local actualsize = actualgroup:CountAliveUnits() if actualsize > size then - local reduction = actualsize-size - BASE:I("Reducing groupsize by ".. reduction .. " units!") + local reduction = actualsize - size + BASE:I( "Reducing groupsize by " .. reduction .. " units!" ) -- reduce existing group local units = actualgroup:GetUnits() - local units2 = UTILS.ShuffleTable(units) -- randomize table - for i=1,reduction do - units2[i]:Destroy(false) + local units2 = UTILS.ShuffleTable( units ) -- randomize table + for i = 1, reduction do + units2[i]:Destroy( false ) end end end - table.insert(datatable,data) - end + table.insert( datatable, data ) + end else return nil end @@ -2137,58 +2148,55 @@ end -- @param #boolean Spawn If set to false, do not re-spawn the groups loaded in location and reduce to size. -- @return Core.Set#SET_GROUP Set of GROUP objects. -- Returns nil when file cannot be read. Returns a table of data entries if Spawn is false: `{ groupname=groupname, size=size, coordinate=coordinate }` -function UTILS.LoadSetOfGroups(Path,Filename,Spawn) +function UTILS.LoadSetOfGroups( Path, Filename, Spawn ) local spawn = true - if Spawn == false then spawn = false end - BASE:I("Spawn = "..tostring(spawn)) + if Spawn == false then + spawn = false + end + BASE:I( "Spawn = " .. tostring( spawn ) ) local filename = Filename or "SetOfGroups" local setdata = SET_GROUP:New() local datatable = {} - if UTILS.CheckFileExists(Path,filename) then - local outcome,loadeddata = UTILS.LoadFromFile(Path,Filename) + if UTILS.CheckFileExists( Path, filename ) then + local outcome, loadeddata = UTILS.LoadFromFile( Path, Filename ) -- remove header - table.remove(loadeddata, 1) - for _id,_entry in pairs (loadeddata) do - local dataset = UTILS.Split(_entry,",") + table.remove( loadeddata, 1 ) + for _id, _entry in pairs( loadeddata ) do + local dataset = UTILS.Split( _entry, "," ) -- groupname,template,units,position.x,position.y,position.z local groupname = dataset[1] local template = dataset[2] - local size = tonumber(dataset[3]) - local posx = tonumber(dataset[4]) - local posy = tonumber(dataset[5]) - local posz = tonumber(dataset[6]) - local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz}) - local group=nil - local data = { groupname=groupname, size=size, coordinate=coordinate } - table.insert(datatable,data) + local size = tonumber( dataset[3] ) + local posx = tonumber( dataset[4] ) + local posy = tonumber( dataset[5] ) + local posz = tonumber( dataset[6] ) + local coordinate = COORDINATE:NewFromVec3( { x = posx, y = posy, z = posz } ) + local group = nil + local data = { groupname = groupname, size = size, coordinate = coordinate } + table.insert( datatable, data ) if spawn then - local group = SPAWN:New(groupname) - :InitDelayOff() - :OnSpawnGroup( - function(spwndgrp) - setdata:AddObject(spwndgrp) - local actualsize = spwndgrp:CountAliveUnits() - if actualsize > size then - local reduction = actualsize-size - -- reduce existing group - local units = spwndgrp:GetUnits() - local units2 = UTILS.ShuffleTable(units) -- randomize table - for i=1,reduction do - units2[i]:Destroy(false) - end - end + local group = SPAWN:New( groupname ):InitDelayOff():OnSpawnGroup( function( spwndgrp ) + setdata:AddObject( spwndgrp ) + local actualsize = spwndgrp:CountAliveUnits() + if actualsize > size then + local reduction = actualsize - size + -- reduce existing group + local units = spwndgrp:GetUnits() + local units2 = UTILS.ShuffleTable( units ) -- randomize table + for i = 1, reduction do + units2[i]:Destroy( false ) end - ) - :SpawnFromCoordinate(coordinate) + end + end ):SpawnFromCoordinate( coordinate ) end - end + end else return nil end if spawn then return setdata else - return datatable + return datatable end end @@ -2196,23 +2204,23 @@ end -- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems. -- @param #string Filename The name of the file. -- @return Core.Set#SET_STATIC Set SET_STATIC containing the static objects. -function UTILS.LoadSetOfStatics(Path,Filename) +function UTILS.LoadSetOfStatics( Path, Filename ) local filename = Filename or "SetOfStatics" local datatable = SET_STATIC:New() - if UTILS.CheckFileExists(Path,filename) then - local outcome,loadeddata = UTILS.LoadFromFile(Path,Filename) + if UTILS.CheckFileExists( Path, filename ) then + local outcome, loadeddata = UTILS.LoadFromFile( Path, Filename ) -- remove header - table.remove(loadeddata, 1) - for _id,_entry in pairs (loadeddata) do - local dataset = UTILS.Split(_entry,",") + table.remove( loadeddata, 1 ) + for _id, _entry in pairs( loadeddata ) do + local dataset = UTILS.Split( _entry, "," ) -- staticname,position.x,position.y,position.z local staticname = dataset[1] - local posx = tonumber(dataset[2]) - local posy = tonumber(dataset[3]) - local posz = tonumber(dataset[4]) - local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz}) - datatable:AddObject(STATIC:FindByName(staticname,false)) - end + local posx = tonumber( dataset[2] ) + local posy = tonumber( dataset[3] ) + local posz = tonumber( dataset[4] ) + local coordinate = COORDINATE:NewFromVec3( { x = posx, y = posy, z = posz } ) + datatable:AddObject( STATIC:FindByName( staticname, false ) ) + end else return nil end @@ -2225,33 +2233,35 @@ end -- @param #boolean Reduce If false, do not destroy the units with size=0. -- @return #table Table of data objects (tables) containing staticname, size (0=dead else 1), coordinate and the static object. -- Returns nil when file cannot be read. -function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce) +function UTILS.LoadStationaryListOfStatics( Path, Filename, Reduce ) local reduce = true - if Reduce == false then reduce = false end + if Reduce == false then + reduce = false + end local filename = Filename or "StateListofStatics" local datatable = {} - if UTILS.CheckFileExists(Path,filename) then - local outcome,loadeddata = UTILS.LoadFromFile(Path,Filename) + if UTILS.CheckFileExists( Path, filename ) then + local outcome, loadeddata = UTILS.LoadFromFile( Path, Filename ) -- remove header - table.remove(loadeddata, 1) - for _id,_entry in pairs (loadeddata) do - local dataset = UTILS.Split(_entry,",") + table.remove( loadeddata, 1 ) + for _id, _entry in pairs( loadeddata ) do + local dataset = UTILS.Split( _entry, "," ) -- staticname,units(1/0),position.x,position.y,position.z) local staticname = dataset[1] - local size = tonumber(dataset[2]) - local posx = tonumber(dataset[3]) - local posy = tonumber(dataset[4]) - local posz = tonumber(dataset[5]) - local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz}) - local data = { staticname=staticname, size=size, coordinate=coordinate, static=STATIC:FindByName(staticname,false) } - table.insert(datatable,data) - if size==0 and reduce then - local static = STATIC:FindByName(staticname,false) + local size = tonumber( dataset[2] ) + local posx = tonumber( dataset[3] ) + local posy = tonumber( dataset[4] ) + local posz = tonumber( dataset[5] ) + local coordinate = COORDINATE:NewFromVec3( { x = posx, y = posy, z = posz } ) + local data = { staticname = staticname, size = size, coordinate = coordinate, static = STATIC:FindByName( staticname, false ) } + table.insert( datatable, data ) + if size == 0 and reduce then + local static = STATIC:FindByName( staticname, false ) if static then - static:Destroy(false) + static:Destroy( false ) end end - end + end else return nil end From a59343b987c8dbf76b8b95783bbac767da79fdc7 Mon Sep 17 00:00:00 2001 From: TommyC81 Date: Thu, 9 Dec 2021 20:51:38 +0400 Subject: [PATCH 027/200] Code formatting, spelling and documentation fixes. (#1661) * Update Point.lua Minor code formatting fix. * Update Airboss.lua Minor code formatting and documentation fixes. * Update Set.lua Code formatting, spelling and documentation fixes. * Update ATIS.lua Code formatting, spelling and documentation fixes. * Update Task_A2A_Dispatcher.lua Minor code formatting and documentation fixes. Added TODO re. possible unused variables. --- Moose Development/Moose/Core/Point.lua | 4 +- Moose Development/Moose/Core/Set.lua | 1025 ++++------ Moose Development/Moose/Ops/ATIS.lua | 1784 ++++++++--------- Moose Development/Moose/Ops/Airboss.lua | 115 +- .../Moose/Tasking/Task_A2A_Dispatcher.lua | 8 +- 5 files changed, 1366 insertions(+), 1570 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 87fbc64fd..c0565afc9 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -395,8 +395,8 @@ do -- COORDINATE id = world.VolumeType.SPHERE, params = { point = self:GetVec3(), - radius = radius - } + radius = radius, + }, } -- Defaults diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 964eb5d25..88c1e8760 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -20,9 +20,9 @@ -- Various types of SET_ classes are available: -- -- * @{#SET_GROUP}: Defines a collection of @{Wrapper.Group}s filtered by filter criteria. --- * @{#SET_UNIT}: Defines a colleciton of @{Wrapper.Unit}s filtered by filter criteria. +-- * @{#SET_UNIT}: Defines a collection of @{Wrapper.Unit}s filtered by filter criteria. -- * @{#SET_STATIC}: Defines a collection of @{Wrapper.Static}s filtered by filter criteria. --- * @{#SET_CLIENT}: Defines a collection of @{Client}s filterd by filter criteria. +-- * @{#SET_CLIENT}: Defines a collection of @{Client}s filtered by filter criteria. -- * @{#SET_AIRBASE}: Defines a collection of @{Wrapper.Airbase}s filtered by filter criteria. -- * @{#SET_CARGO}: Defines a collection of @{Cargo.Cargo}s filtered by filter criteria. -- * @{#SET_ZONE}: Defines a collection of @{Core.Zone}s filtered by filter criteria. @@ -44,20 +44,18 @@ -- @module Core.Set -- @image Core_Sets.JPG - do -- SET_BASE --- @type SET_BASE -- @field #table Filter Table of filters. -- @field #table Set Table of objects. - -- @field #table Index Table of indicies. + -- @field #table Index Table of indices. -- @field #table List Unused table. -- @field Core.Scheduler#SCHEDULER CallScheduler -- @extends Core.Base#BASE - --- The @{Core.Set#SET_BASE} class defines the core functions that define a collection of objects. - -- A SET provides iterators to iterate the SET, but will **temporarily** yield the ForEach interator loop at defined **"intervals"** to the mail simulator loop. + -- A SET provides iterators to iterate the SET, but will **temporarily** yield the ForEach iterator loop at defined **"intervals"** to the mail simulator loop. -- In this way, large loops can be done while not blocking the simulator main processing loop. -- The default **"yield interval"** is after 10 objects processed. -- The default **"time interval"** is after 0.001 seconds. @@ -68,7 +66,7 @@ do -- SET_BASE -- -- ## Define the SET iterator **"yield interval"** and the **"time interval"** -- - -- Modify the iterator intervals with the @{Core.Set#SET_BASE.SetInteratorIntervals} method. + -- Modify the iterator intervals with the @{Core.Set#SET_BASE.SetIteratorIntervals} method. -- You can set the **"yield interval"**, and the **"time interval"**. (See above). -- -- @field #SET_BASE SET_BASE @@ -79,12 +77,11 @@ do -- SET_BASE List = {}, Index = {}, Database = nil, - CallScheduler=nil, - TimeInterval=nil, - YieldInterval=nil, + CallScheduler = nil, + TimeInterval = nil, + YieldInterval = nil, } - --- Creates a new SET_BASE object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. -- @param #SET_BASE self -- @return #SET_BASE @@ -109,8 +106,7 @@ do -- SET_BASE -- @param #string ObjectName The name of the object. -- @param Object The object. - - self:AddTransition( "*", "Added", "*" ) + self:AddTransition( "*", "Added", "*" ) --- Removed Handler OnAfter for SET_BASE -- @function [parent=#SET_BASE] OnAfterRemoved @@ -121,7 +117,7 @@ do -- SET_BASE -- @param #string ObjectName The name of the object. -- @param Object The object. - self:AddTransition( "*", "Removed", "*" ) + self:AddTransition( "*", "Removed", "*" ) self.YieldInterval = 10 self.TimeInterval = 0.001 @@ -148,8 +144,6 @@ do -- SET_BASE return self end - - --- Finds an @{Core.Base#BASE} object based on the object Name. -- @param #SET_BASE self -- @param #string ObjectName @@ -160,7 +154,6 @@ do -- SET_BASE return ObjectFound end - --- Gets the Set. -- @param #SET_BASE self -- @return #SET_BASE self @@ -173,7 +166,7 @@ do -- SET_BASE --- Gets a list of the Names of the Objects in the Set. -- @param #SET_BASE self -- @return #SET_BASE self - function SET_BASE:GetSetNames() -- R2.3 + function SET_BASE:GetSetNames() -- R2.3 self:F2() local Names = {} @@ -185,11 +178,10 @@ do -- SET_BASE return Names end - --- Gets a list of the Objects in the Set. -- @param #SET_BASE self -- @return #SET_BASE self - function SET_BASE:GetSetObjects() -- R2.3 + function SET_BASE:GetSetObjects() -- R2.3 self:F2() local Objects = {} @@ -201,17 +193,18 @@ do -- SET_BASE return Objects end - --- Removes a @{Core.Base#BASE} object from the @{Core.Set#SET_BASE} and derived classes, based on the Object Name. -- @param #SET_BASE self -- @param #string ObjectName - -- @param NoTriggerEvent (optional) When `true`, the :Remove() method will not trigger a **Removed** event. + -- @param NoTriggerEvent (Optional) When `true`, the :Remove() method will not trigger a **Removed** event. function SET_BASE:Remove( ObjectName, NoTriggerEvent ) self:F2( { ObjectName = ObjectName } ) - + local TriggerEvent = true - if NoTriggerEvent == false then TriggerEvent = false end - + if NoTriggerEvent == false then + TriggerEvent = false + end + local Object = self.Set[ObjectName] if Object then @@ -229,7 +222,6 @@ do -- SET_BASE end end - --- Adds a @{Core.Base#BASE} object in the @{Core.Set#SET_BASE}, using a given ObjectName as the index. -- @param #SET_BASE self -- @param #string ObjectName The name of the object. @@ -266,21 +258,20 @@ do -- SET_BASE end - --- Get the *union* of two sets. -- @param #SET_BASE self -- @param Core.Set#SET_BASE SetB Set *B*. -- @return Core.Set#SET_BASE The union set, i.e. contains objects that are in set *A* **or** in set *B*. - function SET_BASE:GetSetUnion(SetB) + function SET_BASE:GetSetUnion( SetB ) - local union=SET_BASE:New() + local union = SET_BASE:New() - for _,ObjectA in pairs(self.Set) do - union:AddObject(ObjectA) + for _, ObjectA in pairs( self.Set ) do + union:AddObject( ObjectA ) end - for _,ObjectB in pairs(SetB.Set) do - union:AddObject(ObjectB) + for _, ObjectB in pairs( SetB.Set ) do + union:AddObject( ObjectB ) end return union @@ -290,15 +281,15 @@ do -- SET_BASE -- @param #SET_BASE self -- @param Core.Set#SET_BASE SetB Set other set, called *B*. -- @return Core.Set#SET_BASE A set of objects that is included in set *A* **and** in set *B*. - function SET_BASE:GetSetIntersection(SetB) + function SET_BASE:GetSetIntersection( SetB ) - local intersection=SET_BASE:New() + local intersection = SET_BASE:New() - local union=self:GetSetUnion(SetB) + local union = self:GetSetUnion( SetB ) - for _,Object in pairs(union.Set) do - if self:IsIncludeObject(Object) and SetB:IsIncludeObject(Object) then - intersection:AddObject(intersection) + for _, Object in pairs( union.Set ) do + if self:IsIncludeObject( Object ) and SetB:IsIncludeObject( Object ) then + intersection:AddObject( intersection ) end end @@ -309,38 +300,34 @@ do -- SET_BASE -- @param #SET_BASE self -- @param Core.Set#SET_BASE SetB Set other set, called *B*. -- @return Core.Set#SET_BASE The set of objects that are in set *B* but **not** in this set *A*. - function SET_BASE:GetSetComplement(SetB) + function SET_BASE:GetSetComplement( SetB ) - local complement = self:GetSetUnion(SetB) - local intersection = self:GetSetIntersection(SetB) + local complement = self:GetSetUnion( SetB ) + local intersection = self:GetSetIntersection( SetB ) - for _,Object in pairs(intersection.Set) do - complement:Remove(Object.ObjectName,true) + for _, Object in pairs( intersection.Set ) do + complement:Remove( Object.ObjectName, true ) end return complement end - --- Compare two sets. -- @param #SET_BASE self -- @param Core.Set#SET_BASE SetA First set. -- @param Core.Set#SET_BASE SetB Set to be merged into first set. -- @return Core.Set#SET_BASE The set of objects that are included in SetA and SetB. - function SET_BASE:CompareSets(SetA, SetB) + function SET_BASE:CompareSets( SetA, SetB ) - for _,ObjectB in pairs(SetB.Set) do - if SetA:IsIncludeObject(ObjectB) then - SetA:Add(ObjectB) + for _, ObjectB in pairs( SetB.Set ) do + if SetA:IsIncludeObject( ObjectB ) then + SetA:Add( ObjectB ) end end return SetA end - - - --- Gets a @{Core.Base#BASE} object from the @{Core.Set#SET_BASE} and derived classes, based on the Object Name. -- @param #SET_BASE self -- @param #string ObjectName @@ -381,12 +368,11 @@ do -- SET_BASE -- @return Core.Base#BASE function SET_BASE:GetRandom() - local RandomItem = self.Set[self.Index[math.random(#self.Index)]] + local RandomItem = self.Set[self.Index[math.random( #self.Index )]] self:T3( { RandomItem } ) return RandomItem end - --- Retrieves the amount of objects in the @{Core.Set#SET_BASE} and derived classes. -- @param #SET_BASE self -- @return #number Count @@ -395,7 +381,6 @@ do -- SET_BASE return self.Index and #self.Index or 0 end - --- Copies the Filter criteria from a given Set (for rebuilding a new Set based on an existing Set). -- @param #SET_BASE self -- @param #SET_BASE BaseSet @@ -411,8 +396,6 @@ do -- SET_BASE return self end - - --- Define the SET iterator **"yield interval"** and the **"time interval"**. -- @param #SET_BASE self -- @param #number YieldInterval Sets the frequency when the iterator loop will yield after the number of objects processed. The default frequency is 10 objects processed. @@ -437,7 +420,7 @@ do -- SET_BASE return self end - --- Get the SET iterator **"limit"**. + --- Get the SET iterator **"limit"**. -- @param #SET_BASE self -- @return #number Defines how many objects are evaluated of the set as part of the Some iterators. function SET_BASE:GetSomeIteratorLimit() @@ -445,7 +428,6 @@ do -- SET_BASE return self.SomeIteratorLimit or self:Count() end - --- Filters for the defined collection. -- @param #SET_BASE self -- @return #SET_BASE self @@ -474,9 +456,8 @@ do -- SET_BASE end -- Follow alive players and clients - --self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventOnPlayerEnterUnit ) - --self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventOnPlayerLeaveUnit ) - + -- self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventOnPlayerEnterUnit ) + -- self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventOnPlayerLeaveUnit ) return self end @@ -484,7 +465,7 @@ do -- SET_BASE --- Starts the filtering of the Dead events for the collection. -- @param #SET_BASE self -- @return #SET_BASE self - function SET_BASE:FilterDeads() --R2.1 allow deads to be filtered to automatically handle deads in the collection. + function SET_BASE:FilterDeads() -- R2.1 allow deads to be filtered to automatically handle deads in the collection. self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) @@ -494,7 +475,7 @@ do -- SET_BASE --- Starts the filtering of the Crash events for the collection. -- @param #SET_BASE self -- @return #SET_BASE self - function SET_BASE:FilterCrashes() --R2.1 allow crashes to be filtered to automatically handle crashes in the collection. + function SET_BASE:FilterCrashes() -- R2.1 allow crashes to be filtered to automatically handle crashes in the collection. self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) @@ -539,12 +520,10 @@ do -- SET_BASE return NearestObject end - - ----- Private method that registers all alive players in the mission. ---- @param #SET_BASE self ---- @return #SET_BASE self - --function SET_BASE:_RegisterPlayers() + -- function SET_BASE:_RegisterPlayers() -- -- local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ) } -- for CoalitionId, CoalitionData in pairs( CoalitionsData ) do @@ -561,7 +540,7 @@ do -- SET_BASE -- end -- -- return self - --end + -- end --- Events @@ -576,7 +555,7 @@ do -- SET_BASE self:T3( ObjectName, Object ) if Object and self:IsIncludeObject( Object ) then self:Add( ObjectName, Object ) - --self:_EventOnPlayerEnterUnit( Event ) + -- self:_EventOnPlayerEnterUnit( Event ) end end end @@ -598,7 +577,7 @@ do -- SET_BASE --- Handles the OnPlayerEnterUnit event to fill the active players table (with the unit filter applied). -- @param #SET_BASE self -- @param Core.Event#EVENTDATA Event - --function SET_BASE:_EventOnPlayerEnterUnit( Event ) + -- function SET_BASE:_EventOnPlayerEnterUnit( Event ) -- self:F3( { Event } ) -- -- if Event.IniDCSUnit then @@ -609,12 +588,12 @@ do -- SET_BASE -- --self:_EventOnPlayerEnterUnit( Event ) -- end -- end - --end + -- end --- Handles the OnPlayerLeaveUnit event to clean the active players table. -- @param #SET_BASE self -- @param Core.Event#EVENTDATA Event - --function SET_BASE:_EventOnPlayerLeaveUnit( Event ) + -- function SET_BASE:_EventOnPlayerLeaveUnit( Event ) -- self:F3( { Event } ) -- -- local ObjectName = Event.IniDCSUnit @@ -635,7 +614,7 @@ do -- SET_BASE -- end -- end -- end - --end + -- end -- Iterators @@ -657,28 +636,28 @@ do -- SET_BASE local Count = 0 for ObjectID, ObjectData in pairs( Set ) do local Object = ObjectData - self:T3( Object ) - if Function then - if Function( unpack( FunctionArguments or {} ), Object ) == true then - IteratorFunction( Object, unpack( arg ) ) - end - else + self:T3( Object ) + if Function then + if Function( unpack( FunctionArguments or {} ), Object ) == true then IteratorFunction( Object, unpack( arg ) ) end - Count = Count + 1 - -- if Count % self.YieldInterval == 0 then - -- coroutine.yield( false ) - -- end + else + IteratorFunction( Object, unpack( arg ) ) + end + Count = Count + 1 + -- if Count % self.YieldInterval == 0 then + -- coroutine.yield( false ) + -- end end return true end - -- local co = coroutine.create( CoRoutine ) + -- local co = coroutine.create( CoRoutine ) local co = CoRoutine local function Schedule() - -- local status, res = coroutine.resume( co ) + -- local status, res = coroutine.resume( co ) local status, res = co() self:T3( { status, res } ) @@ -692,7 +671,7 @@ do -- SET_BASE return false end - --self.CallScheduler:Schedule( self, Schedule, {}, self.TimeInterval, self.TimeInterval, 0 ) + -- self.CallScheduler:Schedule( self, Schedule, {}, self.TimeInterval, self.TimeInterval, 0 ) Schedule() return self @@ -714,31 +693,31 @@ do -- SET_BASE local Count = 0 for ObjectID, ObjectData in pairs( Set ) do local Object = ObjectData - self:T3( Object ) - if Function then - if Function( unpack( FunctionArguments ), Object ) == true then - IteratorFunction( Object, unpack( arg ) ) - end - else + self:T3( Object ) + if Function then + if Function( unpack( FunctionArguments ), Object ) == true then IteratorFunction( Object, unpack( arg ) ) end - Count = Count + 1 - if Count >= Limit then - break - end - -- if Count % self.YieldInterval == 0 then - -- coroutine.yield( false ) - -- end + else + IteratorFunction( Object, unpack( arg ) ) + end + Count = Count + 1 + if Count >= Limit then + break + end + -- if Count % self.YieldInterval == 0 then + -- coroutine.yield( false ) + -- end end return true end - -- local co = coroutine.create( CoRoutine ) + -- local co = coroutine.create( CoRoutine ) local co = CoRoutine local function Schedule() - -- local status, res = coroutine.resume( co ) + -- local status, res = coroutine.resume( co ) local status, res = co() self:T3( { status, res } ) @@ -752,50 +731,48 @@ do -- SET_BASE return false end - --self.CallScheduler:Schedule( self, Schedule, {}, self.TimeInterval, self.TimeInterval, 0 ) + -- self.CallScheduler:Schedule( self, Schedule, {}, self.TimeInterval, self.TimeInterval, 0 ) Schedule() return self end - - ----- Iterate the SET_BASE and call an interator function for each **alive** unit, providing the Unit and optional parameters. + ----- Iterate the SET_BASE and call an iterator function for each **alive** unit, providing the Unit and optional parameters. ---- @param #SET_BASE self ---- @param #function IteratorFunction The function that will be called when there is an alive unit in the SET_BASE. The function needs to accept a UNIT parameter. ---- @return #SET_BASE self - --function SET_BASE:ForEachDCSUnitAlive( IteratorFunction, ... ) + -- function SET_BASE:ForEachDCSUnitAlive( IteratorFunction, ... ) -- self:F3( arg ) -- -- self:ForEach( IteratorFunction, arg, self.DCSUnitsAlive ) -- -- return self - --end + -- end -- - ----- Iterate the SET_BASE and call an interator function for each **alive** player, providing the Unit of the player and optional parameters. + ----- Iterate the SET_BASE and call an iterator function for each **alive** player, providing the Unit of the player and optional parameters. ---- @param #SET_BASE self ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_BASE. The function needs to accept a UNIT parameter. ---- @return #SET_BASE self - --function SET_BASE:ForEachPlayer( IteratorFunction, ... ) + -- function SET_BASE:ForEachPlayer( IteratorFunction, ... ) -- self:F3( arg ) -- -- self:ForEach( IteratorFunction, arg, self.PlayersAlive ) -- -- return self - --end + -- end -- -- - ----- Iterate the SET_BASE and call an interator function for each client, providing the Client to the function and optional parameters. + ----- Iterate the SET_BASE and call an iterator function for each client, providing the Client to the function and optional parameters. ---- @param #SET_BASE self ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_BASE. The function needs to accept a CLIENT parameter. ---- @return #SET_BASE self - --function SET_BASE:ForEachClient( IteratorFunction, ... ) + -- function SET_BASE:ForEachClient( IteratorFunction, ... ) -- self:F3( arg ) -- -- self:ForEach( IteratorFunction, arg, self.Clients ) -- -- return self - --end - + -- end --- Decides whether to include the Object. -- @param #SET_BASE self @@ -811,7 +788,7 @@ do -- SET_BASE -- @param #SET_BASE self -- @param #table Object -- @return #SET_BASE self - function SET_BASE:IsInSet(ObjectName) + function SET_BASE:IsInSet( ObjectName ) self:F3( Object ) return true @@ -833,7 +810,7 @@ do -- SET_BASE --- Flushes the current SET_BASE contents in the log ... (for debugging reasons). -- @param #SET_BASE self - -- @param Core.Base#BASE MasterObject (optional) The master object as a reference. + -- @param Core.Base#BASE MasterObject (Optional) The master object as a reference. -- @return #string A string with the names of the objects. function SET_BASE:Flush( MasterObject ) self:F3() @@ -849,7 +826,6 @@ do -- SET_BASE end - do -- SET_GROUP --- @type SET_GROUP @@ -921,7 +897,7 @@ do -- SET_GROUP -- ### When a GROUP object crashes or is dead, the SET_GROUP will trigger a **Dead** event. -- -- You can handle the event using the OnBefore and OnAfter event handlers. - -- The event handlers need to have the paramters From, Event, To, GroupObject. + -- The event handlers need to have the parameters From, Event, To, GroupObject. -- The GroupObject is the GROUP object that is dead and within the SET_GROUP, and is passed as a parameter to the event handler. -- See the following example: -- @@ -936,7 +912,7 @@ do -- SET_GROUP -- end -- -- While this is a good example, there is a catch. - -- Imageine you want to execute the code above, the the self would need to be from the object declared outside (above) the OnAfterDead method. + -- Imagine you want to execute the code above, the the self would need to be from the object declared outside (above) the OnAfterDead method. -- So, the self would need to contain another object. Fortunately, this can be done, but you must use then the **`.`** notation for the method. -- See the modified example: -- @@ -985,7 +961,6 @@ do -- SET_GROUP }, } - --- Creates a new SET_GROUP object, building a set of groups belonging to a coalitions, categories, countries, types or with defined prefix names. -- @param #SET_GROUP self -- @return #SET_GROUP @@ -1012,7 +987,7 @@ do -- SET_GROUP -- Clean the Set before returning with only the alive Groups. for GroupName, GroupObject in pairs( self.Set ) do - local GroupObject=GroupObject --Wrapper.Group#GROUP + local GroupObject = GroupObject -- Wrapper.Group#GROUP if GroupObject then if GroupObject:IsAlive() then AliveSet:Add( GroupName, GroupObject ) @@ -1079,7 +1054,7 @@ do -- SET_GROUP -- @return Core.Set#SET_GROUP self function SET_GROUP:AddGroupsByName( AddGroupNames ) - local AddGroupNamesArray = ( type( AddGroupNames ) == "table" ) and AddGroupNames or { AddGroupNames } + local AddGroupNamesArray = (type( AddGroupNames ) == "table") and AddGroupNames or { AddGroupNames } for AddGroupID, AddGroupName in pairs( AddGroupNamesArray ) do self:Add( AddGroupName, GROUP:FindByName( AddGroupName ) ) @@ -1094,7 +1069,7 @@ do -- SET_GROUP -- @return Core.Set#SET_GROUP self function SET_GROUP:RemoveGroupsByName( RemoveGroupNames ) - local RemoveGroupNamesArray = ( type( RemoveGroupNames ) == "table" ) and RemoveGroupNames or { RemoveGroupNames } + local RemoveGroupNamesArray = (type( RemoveGroupNames ) == "table") and RemoveGroupNames or { RemoveGroupNames } for RemoveGroupID, RemoveGroupName in pairs( RemoveGroupNamesArray ) do self:Remove( RemoveGroupName ) @@ -1103,9 +1078,6 @@ do -- SET_GROUP return self end - - - --- Finds a Group based on the Group Name. -- @param #SET_GROUP self -- @param #string GroupName @@ -1123,7 +1095,7 @@ do -- SET_GROUP function SET_GROUP:FindNearestGroupFromPointVec2( PointVec2 ) self:F2( PointVec2 ) - local NearestGroup = nil --Wrapper.Group#GROUP + local NearestGroup = nil -- Wrapper.Group#GROUP local ClosestDistance = nil for ObjectID, ObjectData in pairs( self.Set ) do @@ -1142,7 +1114,6 @@ do -- SET_GROUP return NearestGroup end - --- Builds a set of groups of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_GROUP self @@ -1161,7 +1132,6 @@ do -- SET_GROUP return self end - --- Builds a set of groups out of categories. -- Possible current categories are plane, helicopter, ground, ship. -- @param #SET_GROUP self @@ -1220,8 +1190,6 @@ do -- SET_GROUP return self end - - --- Builds a set of groups of defined countries. -- Possible current countries are those known within DCS world. -- @param #SET_GROUP self @@ -1240,7 +1208,6 @@ do -- SET_GROUP return self end - --- Builds a set of groups that contain the given string in their group name. -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all groups that **contain** the string. -- @param #SET_GROUP self @@ -1262,7 +1229,7 @@ do -- SET_GROUP --- Builds a set of groups that are only active. -- Only the groups that are active will be included within the set. -- @param #SET_GROUP self - -- @param #boolean Active (optional) Include only active groups to the set. + -- @param #boolean Active (Optional) Include only active groups to the set. -- Include inactive groups if you provide false. -- @return #SET_GROUP self -- @usage @@ -1280,12 +1247,11 @@ do -- SET_GROUP -- GroupSet = SET_GROUP:New():FilterActive( false ):FilterCoalition( "blue" ):FilterOnce() -- function SET_GROUP:FilterActive( Active ) - Active = Active or not ( Active == false ) + Active = Active or not (Active == false) self.Filter.Active = Active return self end - --- Starts the filtering. -- @param #SET_GROUP self -- @return #SET_GROUP self @@ -1299,8 +1265,6 @@ do -- SET_GROUP self:HandleEvent( EVENTS.RemoveUnit, self._EventOnDeadOrCrash ) end - - return self end @@ -1408,16 +1372,15 @@ do -- SET_GROUP function SET_GROUP:ForEachGroupCompletelyInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - self:ForEach( IteratorFunction, arg, self:GetSet(), - --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Group#GROUP GroupObject - function( ZoneObject, GroupObject ) - if GroupObject:IsCompletelyInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) + self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Group#GROUP GroupObject + function( ZoneObject, GroupObject ) + if GroupObject:IsCompletelyInZone( ZoneObject ) then + return true + else + return false + end + end, { ZoneObject } ) return self end @@ -1430,16 +1393,15 @@ do -- SET_GROUP function SET_GROUP:ForEachGroupPartlyInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - self:ForEach( IteratorFunction, arg, self:GetSet(), - --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Group#GROUP GroupObject - function( ZoneObject, GroupObject ) - if GroupObject:IsPartlyInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) + self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Group#GROUP GroupObject + function( ZoneObject, GroupObject ) + if GroupObject:IsPartlyInZone( ZoneObject ) then + return true + else + return false + end + end, { ZoneObject } ) return self end @@ -1452,16 +1414,15 @@ do -- SET_GROUP function SET_GROUP:ForEachGroupNotInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - self:ForEach( IteratorFunction, arg, self:GetSet(), - --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Group#GROUP GroupObject - function( ZoneObject, GroupObject ) - if GroupObject:IsNotInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) + self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Group#GROUP GroupObject + function( ZoneObject, GroupObject ) + if GroupObject:IsNotInZone( ZoneObject ) then + return true + else + return false + end + end, { ZoneObject } ) return self end @@ -1469,7 +1430,7 @@ do -- SET_GROUP --- Iterate the SET_GROUP and return true if all the @{Wrapper.Group#GROUP} are completely in the @{Core.Zone#ZONE} -- @param #SET_GROUP self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. - -- @return #boolean true if all the @{Wrapper.Group#GROUP} are completly in the @{Core.Zone#ZONE}, false otherwise + -- @return #boolean true if all the @{Wrapper.Group#GROUP} are completely in the @{Core.Zone#ZONE}, false otherwise -- @usage -- local MyZone = ZONE:New("Zone1") -- local MySetGroup = SET_GROUP:New() @@ -1480,11 +1441,11 @@ do -- SET_GROUP -- else -- MESSAGE:New("Some or all SET's GROUP are outside zone !", 10):ToAll() -- end - function SET_GROUP:AllCompletelyInZone(Zone) - self:F2(Zone) + function SET_GROUP:AllCompletelyInZone( Zone ) + self:F2( Zone ) local Set = self:GetSet() - for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP - if not GroupData:IsCompletelyInZone(Zone) then + for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP + if not GroupData:IsCompletelyInZone( Zone ) then return false end end @@ -1499,25 +1460,23 @@ do -- SET_GROUP function SET_GROUP:ForEachGroupAnyInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - self:ForEach( IteratorFunction, arg, self:GetSet(), - --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Group#GROUP GroupObject - function( ZoneObject, GroupObject ) - if GroupObject:IsAnyInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) + self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Group#GROUP GroupObject + function( ZoneObject, GroupObject ) + if GroupObject:IsAnyInZone( ZoneObject ) then + return true + else + return false + end + end, { ZoneObject } ) return self end - --- Iterate the SET_GROUP and return true if at least one of the @{Wrapper.Group#GROUP} is completely inside the @{Core.Zone#ZONE} -- @param #SET_GROUP self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. - -- @return #boolean true if at least one of the @{Wrapper.Group#GROUP} is completly inside the @{Core.Zone#ZONE}, false otherwise. + -- @return #boolean true if at least one of the @{Wrapper.Group#GROUP} is completely inside the @{Core.Zone#ZONE}, false otherwise. -- @usage -- local MyZone = ZONE:New("Zone1") -- local MySetGroup = SET_GROUP:New() @@ -1528,11 +1487,11 @@ do -- SET_GROUP -- else -- MESSAGE:New("No GROUP is completely in zone !", 10):ToAll() -- end - function SET_GROUP:AnyCompletelyInZone(Zone) - self:F2(Zone) + function SET_GROUP:AnyCompletelyInZone( Zone ) + self:F2( Zone ) local Set = self:GetSet() - for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP - if GroupData:IsCompletelyInZone(Zone) then + for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP + if GroupData:IsCompletelyInZone( Zone ) then return true end end @@ -1542,7 +1501,7 @@ do -- SET_GROUP --- Iterate the SET_GROUP and return true if at least one @{#UNIT} of one @{GROUP} of the @{SET_GROUP} is in @{ZONE} -- @param #SET_GROUP self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. - -- @return #boolean true if at least one of the @{Wrapper.Group#GROUP} is partly or completly inside the @{Core.Zone#ZONE}, false otherwise. + -- @return #boolean true if at least one of the @{Wrapper.Group#GROUP} is partly or completely inside the @{Core.Zone#ZONE}, false otherwise. -- @usage -- local MyZone = ZONE:New("Zone1") -- local MySetGroup = SET_GROUP:New() @@ -1553,11 +1512,11 @@ do -- SET_GROUP -- else -- MESSAGE:New("No UNIT of any GROUP is in zone !", 10):ToAll() -- end - function SET_GROUP:AnyInZone(Zone) - self:F2(Zone) + function SET_GROUP:AnyInZone( Zone ) + self:F2( Zone ) local Set = self:GetSet() - for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP - if GroupData:IsPartlyInZone(Zone) or GroupData:IsCompletelyInZone(Zone) then + for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP + if GroupData:IsPartlyInZone( Zone ) or GroupData:IsCompletelyInZone( Zone ) then return true end end @@ -1568,7 +1527,7 @@ do -- SET_GROUP -- Will return false if a @{GROUP} is fully in the @{ZONE} -- @param #SET_GROUP self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. - -- @return #boolean true if at least one of the @{Wrapper.Group#GROUP} is partly or completly inside the @{Core.Zone#ZONE}, false otherwise. + -- @return #boolean true if at least one of the @{Wrapper.Group#GROUP} is partly or completely inside the @{Core.Zone#ZONE}, false otherwise. -- @usage -- local MyZone = ZONE:New("Zone1") -- local MySetGroup = SET_GROUP:New() @@ -1579,14 +1538,14 @@ do -- SET_GROUP -- else -- MESSAGE:New("No GROUP are in zone, or one (or more) GROUP is completely in it !", 10):ToAll() -- end - function SET_GROUP:AnyPartlyInZone(Zone) - self:F2(Zone) + function SET_GROUP:AnyPartlyInZone( Zone ) + self:F2( Zone ) local IsPartlyInZone = false local Set = self:GetSet() - for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP - if GroupData:IsCompletelyInZone(Zone) then + for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP + if GroupData:IsCompletelyInZone( Zone ) then return false - elseif GroupData:IsPartlyInZone(Zone) then + elseif GroupData:IsPartlyInZone( Zone ) then IsPartlyInZone = true -- at least one GROUP is partly in zone end end @@ -1614,11 +1573,11 @@ do -- SET_GROUP -- else -- MESSAGE:New("No UNIT of any GROUP is in zone !", 10):ToAll() -- end - function SET_GROUP:NoneInZone(Zone) - self:F2(Zone) + function SET_GROUP:NoneInZone( Zone ) + self:F2( Zone ) local Set = self:GetSet() - for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP - if not GroupData:IsNotInZone(Zone) then -- If the GROUP is in Zone in any way + for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP + if not GroupData:IsNotInZone( Zone ) then -- If the GROUP is in Zone in any way return false end end @@ -1637,12 +1596,12 @@ do -- SET_GROUP -- MySetGroup:AddGroupsByName({"Group1", "Group2"}) -- -- MESSAGE:New("There are " .. MySetGroup:CountInZone(MyZone) .. " GROUPs in the Zone !", 10):ToAll() - function SET_GROUP:CountInZone(Zone) - self:F2(Zone) + function SET_GROUP:CountInZone( Zone ) + self:F2( Zone ) local Count = 0 local Set = self:GetSet() - for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP - if GroupData:IsCompletelyInZone(Zone) then + for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP + if GroupData:IsCompletelyInZone( Zone ) then Count = Count + 1 end end @@ -1659,12 +1618,12 @@ do -- SET_GROUP -- MySetGroup:AddGroupsByName({"Group1", "Group2"}) -- -- MESSAGE:New("There are " .. MySetGroup:CountUnitInZone(MyZone) .. " UNITs in the Zone !", 10):ToAll() - function SET_GROUP:CountUnitInZone(Zone) - self:F2(Zone) + function SET_GROUP:CountUnitInZone( Zone ) + self:F2( Zone ) local Count = 0 local Set = self:GetSet() - for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP - Count = Count + GroupData:CountInZone(Zone) + for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP + Count = Count + GroupData:CountInZone( Zone ) end return Count end @@ -1679,50 +1638,49 @@ do -- SET_GROUP local Set = self:GetSet() - for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP + for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP if GroupData and GroupData:IsAlive() then CountG = CountG + 1 - --Count Units. - for _,_unit in pairs(GroupData:GetUnits()) do - local unit=_unit --Wrapper.Unit#UNIT + -- Count Units. + for _, _unit in pairs( GroupData:GetUnits() ) do + local unit = _unit -- Wrapper.Unit#UNIT if unit and unit:IsAlive() then - CountU=CountU+1 + CountU = CountU + 1 end end end end - return CountG,CountU + return CountG, CountU end - ----- Iterate the SET_GROUP and call an interator function for each **alive** player, providing the Group of the player and optional parameters. + ----- Iterate the SET_GROUP and call an iterator function for each **alive** player, providing the Group of the player and optional parameters. ---- @param #SET_GROUP self ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_GROUP. The function needs to accept a GROUP parameter. ---- @return #SET_GROUP self - --function SET_GROUP:ForEachPlayer( IteratorFunction, ... ) + -- function SET_GROUP:ForEachPlayer( IteratorFunction, ... ) -- self:F2( arg ) -- -- self:ForEach( IteratorFunction, arg, self.PlayersAlive ) -- -- return self - --end + -- end -- -- - ----- Iterate the SET_GROUP and call an interator function for each client, providing the Client to the function and optional parameters. + ----- Iterate the SET_GROUP and call an iterator function for each client, providing the Client to the function and optional parameters. ---- @param #SET_GROUP self ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_GROUP. The function needs to accept a CLIENT parameter. ---- @return #SET_GROUP self - --function SET_GROUP:ForEachClient( IteratorFunction, ... ) + -- function SET_GROUP:ForEachClient( IteratorFunction, ... ) -- self:F2( arg ) -- -- self:ForEach( IteratorFunction, arg, self.Clients ) -- -- return self - --end - + -- end --- -- @param #SET_GROUP self @@ -1735,7 +1693,7 @@ do -- SET_GROUP if self.Filter.Active ~= nil then local MGroupActive = false self:F( { Active = self.Filter.Active } ) - if self.Filter.Active == false or ( self.Filter.Active == true and MGroup:IsActive() == true ) then + if self.Filter.Active == false or (self.Filter.Active == true and MGroup:IsActive() == true) then MGroupActive = true end MGroupInclude = MGroupInclude and MGroupActive @@ -1778,7 +1736,7 @@ do -- SET_GROUP local MGroupPrefix = false for GroupPrefixId, GroupPrefix in pairs( self.Filter.GroupPrefixes ) do self:T3( { "Prefix:", string.find( MGroup:GetName(), GroupPrefix, 1 ), GroupPrefix } ) - if string.find( MGroup:GetName(), GroupPrefix:gsub ("-", "%%-"), 1 ) then + if string.find( MGroup:GetName(), GroupPrefix:gsub( "-", "%%-" ), 1 ) then MGroupPrefix = true end end @@ -1789,7 +1747,6 @@ do -- SET_GROUP return MGroupInclude end - --- Iterate the SET_GROUP and set for each unit the default cargo bay weight limit. -- Because within a group, the type of carriers can differ, each cargo bay weight limit is set on @{Wrapper.Unit} level. -- @param #SET_GROUP self @@ -1801,7 +1758,7 @@ do -- SET_GROUP local Set = self:GetSet() for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP for UnitName, UnitData in pairs( GroupData:GetUnits() ) do - --local UnitData = UnitData -- Wrapper.Unit#UNIT + -- local UnitData = UnitData -- Wrapper.Unit#UNIT UnitData:SetCargoBayWeightLimit() end end @@ -1809,7 +1766,6 @@ do -- SET_GROUP end - do -- SET_UNIT --- @type SET_UNIT @@ -1884,7 +1840,7 @@ do -- SET_UNIT -- ### 6.1) When a UNIT object crashes or is dead, the SET_UNIT will trigger a **Dead** event. -- -- You can handle the event using the OnBefore and OnAfter event handlers. - -- The event handlers need to have the paramters From, Event, To, GroupObject. + -- The event handlers need to have the parameters From, Event, To, GroupObject. -- The GroupObject is the UNIT object that is dead and within the SET_UNIT, and is passed as a parameter to the event handler. -- See the following example: -- @@ -1899,7 +1855,7 @@ do -- SET_UNIT -- end -- -- While this is a good example, there is a catch. - -- Imageine you want to execute the code above, the the self would need to be from the object declared outside (above) the OnAfterDead method. + -- Imagine you want to execute the code above, the the self would need to be from the object declared outside (above) the OnAfterDead method. -- So, the self would need to contain another object. Fortunately, this can be done, but you must use then the **`.`** notation for the method. -- See the modified example: -- @@ -1948,7 +1904,6 @@ do -- SET_UNIT }, } - --- Get the first unit from the set. -- @function [parent=#SET_UNIT] GetFirst -- @param #SET_UNIT self @@ -1985,14 +1940,13 @@ do -- SET_UNIT return self end - --- Add UNIT(s) to SET_UNIT. -- @param #SET_UNIT self -- @param #string AddUnitNames A single name or an array of UNIT names. -- @return #SET_UNIT self function SET_UNIT:AddUnitsByName( AddUnitNames ) - local AddUnitNamesArray = ( type( AddUnitNames ) == "table" ) and AddUnitNames or { AddUnitNames } + local AddUnitNamesArray = (type( AddUnitNames ) == "table") and AddUnitNames or { AddUnitNames } self:T( AddUnitNamesArray ) for AddUnitID, AddUnitName in pairs( AddUnitNamesArray ) do @@ -2008,7 +1962,7 @@ do -- SET_UNIT -- @return Core.Set#SET_UNIT self function SET_UNIT:RemoveUnitsByName( RemoveUnitNames ) - local RemoveUnitNamesArray = ( type( RemoveUnitNames ) == "table" ) and RemoveUnitNames or { RemoveUnitNames } + local RemoveUnitNamesArray = (type( RemoveUnitNames ) == "table") and RemoveUnitNames or { RemoveUnitNames } for RemoveUnitID, RemoveUnitName in pairs( RemoveUnitNamesArray ) do self:Remove( RemoveUnitName ) @@ -2017,7 +1971,6 @@ do -- SET_UNIT return self end - --- Finds a Unit based on the Unit Name. -- @param #SET_UNIT self -- @param #string UnitName @@ -2028,8 +1981,6 @@ do -- SET_UNIT return UnitFound end - - --- Builds a set of units of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_UNIT self @@ -2047,7 +1998,6 @@ do -- SET_UNIT return self end - --- Builds a set of units out of categories. -- Possible current categories are plane, helicopter, ground, ship. -- @param #SET_UNIT self @@ -2066,7 +2016,6 @@ do -- SET_UNIT return self end - --- Builds a set of units of defined unit types. -- Possible current types are those types known within DCS world. -- @param #SET_UNIT self @@ -2085,7 +2034,6 @@ do -- SET_UNIT return self end - --- Builds a set of units of defined countries. -- Possible current countries are those known within DCS world. -- @param #SET_UNIT self @@ -2104,7 +2052,6 @@ do -- SET_UNIT return self end - --- Builds a set of UNITs that contain a given string in their unit name. -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all units that **contain** the string. -- @param #SET_UNIT self @@ -2126,7 +2073,7 @@ do -- SET_UNIT --- Builds a set of units that are only active. -- Only the units that are active will be included within the set. -- @param #SET_UNIT self - -- @param #boolean Active (optional) Include only active units to the set. + -- @param #boolean Active (Optional) Include only active units to the set. -- Include inactive units if you provide false. -- @return #SET_UNIT self -- @usage @@ -2144,7 +2091,7 @@ do -- SET_UNIT -- UnitSet = SET_UNIT:New():FilterActive( false ):FilterCoalition( "blue" ):FilterOnce() -- function SET_UNIT:FilterActive( Active ) - Active = Active or not ( Active == false ) + Active = Active or not (Active == false) self.Filter.Active = Active return self end @@ -2183,7 +2130,7 @@ do -- SET_UNIT local Set = self:GetSet() local CountU = 0 - for UnitID, UnitData in pairs(Set) do -- For each GROUP in SET_GROUP + for UnitID, UnitData in pairs( Set ) do -- For each GROUP in SET_GROUP if UnitData and UnitData:IsAlive() then CountU = CountU + 1 end @@ -2209,8 +2156,6 @@ do -- SET_UNIT return self end - - --- Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- @param #SET_UNIT self @@ -2239,11 +2184,9 @@ do -- SET_UNIT function SET_UNIT:FindInDatabase( Event ) self:F2( { Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName], Event } ) - return Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName] end - do -- Is Zone methods --- Check if minimal one element of the SET_UNIT is in the Zone. @@ -2256,7 +2199,7 @@ do -- SET_UNIT local function EvaluateZone( ZoneUnit ) - local ZoneUnitName = ZoneUnit:GetName() + local ZoneUnitName = ZoneUnit:GetName() self:F( { ZoneUnitName = ZoneUnitName } ) if self:FindUnit( ZoneUnitName ) then IsPartiallyInZone = true @@ -2272,7 +2215,6 @@ do -- SET_UNIT return IsPartiallyInZone end - --- Check if no element of the SET_UNIT is in the Zone. -- @param #SET_UNIT self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -2283,7 +2225,7 @@ do -- SET_UNIT local function EvaluateZone( ZoneUnit ) - local ZoneUnitName = ZoneUnit:GetName() + local ZoneUnitName = ZoneUnit:GetName() if self:FindUnit( ZoneUnitName ) then IsNotInZone = false return false @@ -2299,8 +2241,7 @@ do -- SET_UNIT end - - --- Iterate the SET_UNIT and call an interator function for each **alive** UNIT, providing the UNIT and optional parameters. + --- Iterate the SET_UNIT and call an iterator function for each **alive** UNIT, providing the UNIT and optional parameters. -- @param #SET_UNIT self -- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter. -- @return #SET_UNIT self @@ -2312,16 +2253,12 @@ do -- SET_UNIT return self end - --- Get the SET of the SET_UNIT **sorted per Threat Level**. -- -- @param #SET_UNIT self -- @param #number FromThreatLevel The TreatLevel to start the evaluation **From** (this must be a value between 0 and 10). -- @param #number ToThreatLevel The TreatLevel to stop the evaluation **To** (this must be a value between 0 and 10). -- @return #SET_UNIT self - -- @usage - -- - -- function SET_UNIT:GetSetPerThreatLevel( FromThreatLevel, ToThreatLevel ) self:F2( arg ) @@ -2338,12 +2275,10 @@ do -- SET_UNIT self:F( { ThreatLevel = ThreatLevel, ThreatLevelSet = ThreatLevelSet[ThreatLevel].Set } ) end - local OrderedPerThreatLevelSet = {} local ThreatLevelIncrement = FromThreatLevel <= ToThreatLevel and 1 or -1 - for ThreatLevel = FromThreatLevel, ToThreatLevel, ThreatLevelIncrement do self:F( { ThreatLevel = ThreatLevel } ) local ThreatLevelItem = ThreatLevelSet[ThreatLevel] @@ -2359,8 +2294,7 @@ do -- SET_UNIT end - - --- Iterate the SET_UNIT **sorted *per Threat Level** and call an interator function for each **alive** UNIT, providing the UNIT and optional parameters. + --- Iterate the SET_UNIT **sorted *per Threat Level** and call an iterator function for each **alive** UNIT, providing the UNIT and optional parameters. -- -- @param #SET_UNIT self -- @param #number FromThreatLevel The TreatLevel to start the evaluation **From** (this must be a value between 0 and 10). @@ -2376,7 +2310,7 @@ do -- SET_UNIT -- end -- ) -- - function SET_UNIT:ForEachUnitPerThreatLevel( FromThreatLevel, ToThreatLevel, IteratorFunction, ... ) --R2.1 Threat Level implementation + function SET_UNIT:ForEachUnitPerThreatLevel( FromThreatLevel, ToThreatLevel, IteratorFunction, ... ) -- R2.1 Threat Level implementation self:F2( arg ) local ThreatLevelSet = {} @@ -2406,8 +2340,6 @@ do -- SET_UNIT return self end - - --- Iterate the SET_UNIT and call an iterator function for each **alive** UNIT presence completely in a @{Zone}, providing the UNIT and optional parameters to the called function. -- @param #SET_UNIT self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -2416,16 +2348,15 @@ do -- SET_UNIT function SET_UNIT:ForEachUnitCompletelyInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - self:ForEach( IteratorFunction, arg, self:GetSet(), - --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Unit#UNIT UnitObject - function( ZoneObject, UnitObject ) - if UnitObject:IsInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) + self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Unit#UNIT UnitObject + function( ZoneObject, UnitObject ) + if UnitObject:IsInZone( ZoneObject ) then + return true + else + return false + end + end, { ZoneObject } ) return self end @@ -2438,16 +2369,15 @@ do -- SET_UNIT function SET_UNIT:ForEachUnitNotInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - self:ForEach( IteratorFunction, arg, self:GetSet(), - --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Unit#UNIT UnitObject - function( ZoneObject, UnitObject ) - if UnitObject:IsNotInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) + self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Unit#UNIT UnitObject + function( ZoneObject, UnitObject ) + if UnitObject:IsNotInZone( ZoneObject ) then + return true + else + return false + end + end, { ZoneObject } ) return self end @@ -2475,13 +2405,12 @@ do -- SET_UNIT end for UnitTypeID, UnitType in pairs( UnitTypes ) do - MT[#MT+1] = UnitType .. " of " .. UnitTypeID + MT[#MT + 1] = UnitType .. " of " .. UnitTypeID end return UnitTypes end - --- Returns a comma separated string of the unit types with a count in the @{Set}. -- @param #SET_UNIT self -- @return #string The unit types string @@ -2492,7 +2421,7 @@ do -- SET_UNIT local UnitTypes = self:GetUnitTypes() for UnitTypeID, UnitType in pairs( UnitTypes ) do - MT[#MT+1] = UnitType .. " of " .. UnitTypeID + MT[#MT + 1] = UnitType .. " of " .. UnitTypeID end return table.concat( MT, ", " ) @@ -2522,7 +2451,7 @@ do -- SET_UNIT return UnitThreatLevels end - --- Calculate the maxium A2G threat level of the SET_UNIT. + --- Calculate the maximum A2G threat level of the SET_UNIT. -- @param #SET_UNIT self -- @return #number The maximum threatlevel function SET_UNIT:CalculateThreatLevelA2G() @@ -2565,27 +2494,27 @@ do -- SET_UNIT local Unit = UnitData -- Wrapper.Unit#UNIT local Coordinate = Unit:GetCoordinate() - x1 = ( Coordinate.x < x1 ) and Coordinate.x or x1 - x2 = ( Coordinate.x > x2 ) and Coordinate.x or x2 - y1 = ( Coordinate.y < y1 ) and Coordinate.y or y1 - y2 = ( Coordinate.y > y2 ) and Coordinate.y or y2 - z1 = ( Coordinate.y < z1 ) and Coordinate.z or z1 - z2 = ( Coordinate.y > z2 ) and Coordinate.z or z2 + x1 = (Coordinate.x < x1) and Coordinate.x or x1 + x2 = (Coordinate.x > x2) and Coordinate.x or x2 + y1 = (Coordinate.y < y1) and Coordinate.y or y1 + y2 = (Coordinate.y > y2) and Coordinate.y or y2 + z1 = (Coordinate.y < z1) and Coordinate.z or z1 + z2 = (Coordinate.y > z2) and Coordinate.z or z2 local Velocity = Coordinate:GetVelocity() - if Velocity ~= 0 then - MaxVelocity = ( MaxVelocity < Velocity ) and Velocity or MaxVelocity + if Velocity ~= 0 then + MaxVelocity = (MaxVelocity < Velocity) and Velocity or MaxVelocity local Heading = Coordinate:GetHeading() - AvgHeading = AvgHeading and ( AvgHeading + Heading ) or Heading + AvgHeading = AvgHeading and (AvgHeading + Heading) or Heading MovingCount = MovingCount + 1 end end - AvgHeading = AvgHeading and ( AvgHeading / MovingCount ) + AvgHeading = AvgHeading and (AvgHeading / MovingCount) - Coordinate.x = ( x2 - x1 ) / 2 + x1 - Coordinate.y = ( y2 - y1 ) / 2 + y1 - Coordinate.z = ( z2 - z1 ) / 2 + z1 + Coordinate.x = (x2 - x1) / 2 + x1 + Coordinate.y = (y2 - y1) / 2 + y1 + Coordinate.z = (z2 - z1) / 2 + z1 Coordinate:SetHeading( AvgHeading ) Coordinate:SetVelocity( MaxVelocity ) @@ -2609,8 +2538,8 @@ do -- SET_UNIT local Coordinate = Unit:GetCoordinate() local Velocity = Coordinate:GetVelocity() - if Velocity ~= 0 then - MaxVelocity = ( MaxVelocity < Velocity ) and Velocity or MaxVelocity + if Velocity ~= 0 then + MaxVelocity = (MaxVelocity < Velocity) and Velocity or MaxVelocity end end @@ -2633,12 +2562,12 @@ do -- SET_UNIT local Coordinate = Unit:GetCoordinate() local Velocity = Coordinate:GetVelocity() - if Velocity ~= 0 then + if Velocity ~= 0 then local Heading = Coordinate:GetHeading() if HeadingSet == nil then HeadingSet = Heading else - local HeadingDiff = ( HeadingSet - Heading + 180 + 360 ) % 360 - 180 + local HeadingDiff = (HeadingSet - Heading + 180 + 360) % 360 - 180 HeadingDiff = math.abs( HeadingDiff ) if HeadingDiff > 5 then HeadingSet = nil @@ -2652,8 +2581,6 @@ do -- SET_UNIT end - - --- Returns if the @{Set} has targets having a radar (of a given type). -- @param #SET_UNIT self -- @param DCS#Unit.RadarType RadarType @@ -2662,7 +2589,7 @@ do -- SET_UNIT self:F2( RadarType ) local RadarCount = 0 - for UnitID, UnitData in pairs( self:GetSet()) do + for UnitID, UnitData in pairs( self:GetSet() ) do local UnitSensorTest = UnitData -- Wrapper.Unit#UNIT local HasSensors if RadarType then @@ -2670,7 +2597,7 @@ do -- SET_UNIT else HasSensors = UnitSensorTest:HasSensors( Unit.SensorType.RADAR ) end - self:T3(HasSensors) + self:T3( HasSensors ) if HasSensors then RadarCount = RadarCount + 1 end @@ -2686,14 +2613,14 @@ do -- SET_UNIT self:F2() local SEADCount = 0 - for UnitID, UnitData in pairs( self:GetSet()) do + for UnitID, UnitData in pairs( self:GetSet() ) do local UnitSEAD = UnitData -- Wrapper.Unit#UNIT if UnitSEAD:IsAlive() then local UnitSEADAttributes = UnitSEAD:GetDesc().attributes local HasSEAD = UnitSEAD:HasSEAD() - self:T3(HasSEAD) + self:T3( HasSEAD ) if HasSEAD then SEADCount = SEADCount + 1 end @@ -2710,7 +2637,7 @@ do -- SET_UNIT self:F2() local GroundUnitCount = 0 - for UnitID, UnitData in pairs( self:GetSet()) do + for UnitID, UnitData in pairs( self:GetSet() ) do local UnitTest = UnitData -- Wrapper.Unit#UNIT if UnitTest:IsGround() then GroundUnitCount = GroundUnitCount + 1 @@ -2744,7 +2671,7 @@ do -- SET_UNIT self:F2() local FriendlyUnitCount = 0 - for UnitID, UnitData in pairs( self:GetSet()) do + for UnitID, UnitData in pairs( self:GetSet() ) do local UnitTest = UnitData -- Wrapper.Unit#UNIT if UnitTest:IsFriendly( FriendlyCoalition ) then FriendlyUnitCount = FriendlyUnitCount + 1 @@ -2754,33 +2681,30 @@ do -- SET_UNIT return FriendlyUnitCount end - - - ----- Iterate the SET_UNIT and call an interator function for each **alive** player, providing the Unit of the player and optional parameters. + ----- Iterate the SET_UNIT and call an iterator function for each **alive** player, providing the Unit of the player and optional parameters. ---- @param #SET_UNIT self ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_UNIT. The function needs to accept a UNIT parameter. ---- @return #SET_UNIT self - --function SET_UNIT:ForEachPlayer( IteratorFunction, ... ) + -- function SET_UNIT:ForEachPlayer( IteratorFunction, ... ) -- self:F2( arg ) -- -- self:ForEach( IteratorFunction, arg, self.PlayersAlive ) -- -- return self - --end + -- end -- -- - ----- Iterate the SET_UNIT and call an interator function for each client, providing the Client to the function and optional parameters. + ----- Iterate the SET_UNIT and call an iterator function for each client, providing the Client to the function and optional parameters. ---- @param #SET_UNIT self ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_UNIT. The function needs to accept a CLIENT parameter. ---- @return #SET_UNIT self - --function SET_UNIT:ForEachClient( IteratorFunction, ... ) + -- function SET_UNIT:ForEachClient( IteratorFunction, ... ) -- self:F2( arg ) -- -- self:ForEach( IteratorFunction, arg, self.Clients ) -- -- return self - --end - + -- end --- -- @param #SET_UNIT self @@ -2797,7 +2721,7 @@ do -- SET_UNIT if self.Filter.Active ~= nil then local MUnitActive = false - if self.Filter.Active == false or ( self.Filter.Active == true and MUnit:IsActive() == true ) then + if self.Filter.Active == false or (self.Filter.Active == true and MUnit:IsActive() == true) then MUnitActive = true end MUnitInclude = MUnitInclude and MUnitActive @@ -2886,10 +2810,9 @@ do -- SET_UNIT return MUnitInclude end - --- Retrieve the type names of the @{Wrapper.Unit}s in the SET, delimited by an optional delimiter. -- @param #SET_UNIT self - -- @param #string Delimiter (optional) The delimiter, which is default a comma. + -- @param #string Delimiter (Optional) The delimiter, which is default a comma. -- @return #string The types of the @{Wrapper.Unit}s delimited. function SET_UNIT:GetTypeNames( Delimiter ) @@ -2920,16 +2843,13 @@ do -- SET_UNIT function SET_UNIT:SetCargoBayWeightLimit() local Set = self:GetSet() for UnitID, UnitData in pairs( Set ) do -- For each UNIT in SET_UNIT - --local UnitData = UnitData -- Wrapper.Unit#UNIT + -- local UnitData = UnitData -- Wrapper.Unit#UNIT UnitData:SetCargoBayWeightLimit() end end - - end - do -- SET_STATIC --- @type SET_STATIC @@ -3023,7 +2943,6 @@ do -- SET_STATIC }, } - --- Get the first unit from the set. -- @function [parent=#SET_STATIC] GetFirst -- @param #SET_STATIC self @@ -3055,14 +2974,13 @@ do -- SET_STATIC return self end - --- Add STATIC(s) to SET_STATIC. -- @param #SET_STATIC self -- @param #string AddStaticNames A single name or an array of STATIC names. -- @return #SET_STATIC self function SET_STATIC:AddStaticsByName( AddStaticNames ) - local AddStaticNamesArray = ( type( AddStaticNames ) == "table" ) and AddStaticNames or { AddStaticNames } + local AddStaticNamesArray = (type( AddStaticNames ) == "table") and AddStaticNames or { AddStaticNames } self:T( AddStaticNamesArray ) for AddStaticID, AddStaticName in pairs( AddStaticNamesArray ) do @@ -3078,7 +2996,7 @@ do -- SET_STATIC -- @return self function SET_STATIC:RemoveStaticsByName( RemoveStaticNames ) - local RemoveStaticNamesArray = ( type( RemoveStaticNames ) == "table" ) and RemoveStaticNames or { RemoveStaticNames } + local RemoveStaticNamesArray = (type( RemoveStaticNames ) == "table") and RemoveStaticNames or { RemoveStaticNames } for RemoveStaticID, RemoveStaticName in pairs( RemoveStaticNamesArray ) do self:Remove( RemoveStaticName ) @@ -3087,7 +3005,6 @@ do -- SET_STATIC return self end - --- Finds a Static based on the Static Name. -- @param #SET_STATIC self -- @param #string StaticName @@ -3098,8 +3015,6 @@ do -- SET_STATIC return StaticFound end - - --- Builds a set of units of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_STATIC self @@ -3118,7 +3033,6 @@ do -- SET_STATIC return self end - --- Builds a set of units out of categories. -- Possible current categories are plane, helicopter, ground, ship. -- @param #SET_STATIC self @@ -3137,7 +3051,6 @@ do -- SET_STATIC return self end - --- Builds a set of units of defined unit types. -- Possible current types are those types known within DCS world. -- @param #SET_STATIC self @@ -3156,7 +3069,6 @@ do -- SET_STATIC return self end - --- Builds a set of units of defined countries. -- Possible current countries are those known within DCS world. -- @param #SET_STATIC self @@ -3175,7 +3087,6 @@ do -- SET_STATIC return self end - --- Builds a set of STATICs that contain the given string in their name. -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all statics that **contain** the string. -- @param #SET_STATIC self @@ -3194,7 +3105,6 @@ do -- SET_STATIC return self end - --- Starts the filtering. -- @param #SET_STATIC self -- @return #SET_STATIC self @@ -3218,7 +3128,7 @@ do -- SET_STATIC local Set = self:GetSet() local CountU = 0 - for UnitID, UnitData in pairs(Set) do + for UnitID, UnitData in pairs( Set ) do if UnitData and UnitData:IsAlive() then CountU = CountU + 1 end @@ -3256,11 +3166,9 @@ do -- SET_STATIC function SET_STATIC:FindInDatabase( Event ) self:F2( { Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName], Event } ) - return Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName] end - do -- Is Zone methods --- Check if minimal one element of the SET_STATIC is in the Zone. @@ -3273,7 +3181,7 @@ do -- SET_STATIC local function EvaluateZone( ZoneStatic ) - local ZoneStaticName = ZoneStatic:GetName() + local ZoneStaticName = ZoneStatic:GetName() if self:FindStatic( ZoneStaticName ) then IsPartiallyInZone = true return false @@ -3285,7 +3193,6 @@ do -- SET_STATIC return IsPartiallyInZone end - --- Check if no element of the SET_STATIC is in the Zone. -- @param #SET_STATIC self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -3296,7 +3203,7 @@ do -- SET_STATIC local function EvaluateZone( ZoneStatic ) - local ZoneStaticName = ZoneStatic:GetName() + local ZoneStaticName = ZoneStatic:GetName() if self:FindStatic( ZoneStaticName ) then IsNotInZone = false return false @@ -3310,7 +3217,6 @@ do -- SET_STATIC return IsNotInZone end - --- Check if minimal one element of the SET_STATIC is in the Zone. -- @param #SET_STATIC self -- @param #function IteratorFunction The function that will be called when there is an alive STATIC in the SET_STATIC. The function needs to accept a STATIC parameter. @@ -3323,11 +3229,9 @@ do -- SET_STATIC return self end - end - - --- Iterate the SET_STATIC and call an interator function for each **alive** STATIC, providing the STATIC and optional parameters. + --- Iterate the SET_STATIC and call an iterator function for each **alive** STATIC, providing the STATIC and optional parameters. -- @param #SET_STATIC self -- @param #function IteratorFunction The function that will be called when there is an alive STATIC in the SET_STATIC. The function needs to accept a STATIC parameter. -- @return #SET_STATIC self @@ -3339,7 +3243,6 @@ do -- SET_STATIC return self end - --- Iterate the SET_STATIC and call an iterator function for each **alive** STATIC presence completely in a @{Zone}, providing the STATIC and optional parameters to the called function. -- @param #SET_STATIC self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -3348,16 +3251,15 @@ do -- SET_STATIC function SET_STATIC:ForEachStaticCompletelyInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - self:ForEach( IteratorFunction, arg, self:GetSet(), - --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Static#STATIC StaticObject - function( ZoneObject, StaticObject ) - if StaticObject:IsInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) + self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Static#STATIC StaticObject + function( ZoneObject, StaticObject ) + if StaticObject:IsInZone( ZoneObject ) then + return true + else + return false + end + end, { ZoneObject } ) return self end @@ -3370,16 +3272,15 @@ do -- SET_STATIC function SET_STATIC:ForEachStaticNotInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - self:ForEach( IteratorFunction, arg, self:GetSet(), - --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Static#STATIC StaticObject - function( ZoneObject, StaticObject ) - if StaticObject:IsNotInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) + self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Static#STATIC StaticObject + function( ZoneObject, StaticObject ) + if StaticObject:IsNotInZone( ZoneObject ) then + return true + else + return false + end + end, { ZoneObject } ) return self end @@ -3407,13 +3308,12 @@ do -- SET_STATIC end for StaticTypeID, StaticType in pairs( StaticTypes ) do - MT[#MT+1] = StaticType .. " of " .. StaticTypeID + MT[#MT + 1] = StaticType .. " of " .. StaticTypeID end return StaticTypes end - --- Returns a comma separated string of the unit types with a count in the @{Set}. -- @param #SET_STATIC self -- @return #string The unit types string @@ -3424,7 +3324,7 @@ do -- SET_STATIC local StaticTypes = self:GetStaticTypes() for StaticTypeID, StaticType in pairs( StaticTypes ) do - MT[#MT+1] = StaticType .. " of " .. StaticTypeID + MT[#MT + 1] = StaticType .. " of " .. StaticTypeID end return table.concat( MT, ", " ) @@ -3452,27 +3352,27 @@ do -- SET_STATIC local Static = StaticData -- Wrapper.Static#STATIC local Coordinate = Static:GetCoordinate() - x1 = ( Coordinate.x < x1 ) and Coordinate.x or x1 - x2 = ( Coordinate.x > x2 ) and Coordinate.x or x2 - y1 = ( Coordinate.y < y1 ) and Coordinate.y or y1 - y2 = ( Coordinate.y > y2 ) and Coordinate.y or y2 - z1 = ( Coordinate.y < z1 ) and Coordinate.z or z1 - z2 = ( Coordinate.y > z2 ) and Coordinate.z or z2 + x1 = (Coordinate.x < x1) and Coordinate.x or x1 + x2 = (Coordinate.x > x2) and Coordinate.x or x2 + y1 = (Coordinate.y < y1) and Coordinate.y or y1 + y2 = (Coordinate.y > y2) and Coordinate.y or y2 + z1 = (Coordinate.y < z1) and Coordinate.z or z1 + z2 = (Coordinate.y > z2) and Coordinate.z or z2 local Velocity = Coordinate:GetVelocity() - if Velocity ~= 0 then - MaxVelocity = ( MaxVelocity < Velocity ) and Velocity or MaxVelocity + if Velocity ~= 0 then + MaxVelocity = (MaxVelocity < Velocity) and Velocity or MaxVelocity local Heading = Coordinate:GetHeading() - AvgHeading = AvgHeading and ( AvgHeading + Heading ) or Heading + AvgHeading = AvgHeading and (AvgHeading + Heading) or Heading MovingCount = MovingCount + 1 end end - AvgHeading = AvgHeading and ( AvgHeading / MovingCount ) + AvgHeading = AvgHeading and (AvgHeading / MovingCount) - Coordinate.x = ( x2 - x1 ) / 2 + x1 - Coordinate.y = ( y2 - y1 ) / 2 + y1 - Coordinate.z = ( z2 - z1 ) / 2 + z1 + Coordinate.x = (x2 - x1) / 2 + x1 + Coordinate.y = (y2 - y1) / 2 + y1 + Coordinate.z = (z2 - z1) / 2 + z1 Coordinate:SetHeading( AvgHeading ) Coordinate:SetVelocity( MaxVelocity ) @@ -3504,12 +3404,12 @@ do -- SET_STATIC local Coordinate = Static:GetCoordinate() local Velocity = Coordinate:GetVelocity() - if Velocity ~= 0 then + if Velocity ~= 0 then local Heading = Coordinate:GetHeading() if HeadingSet == nil then HeadingSet = Heading else - local HeadingDiff = ( HeadingSet - Heading + 180 + 360 ) % 360 - 180 + local HeadingDiff = (HeadingSet - Heading + 180 + 360) % 360 - 180 HeadingDiff = math.abs( HeadingDiff ) if HeadingDiff > 5 then HeadingSet = nil @@ -3528,19 +3428,19 @@ do -- SET_STATIC -- @return #number The maximum threatlevel function SET_STATIC:CalculateThreatLevelA2G() - local MaxThreatLevelA2G = 0 - local MaxThreatText = "" - for StaticName, StaticData in pairs( self:GetSet() ) do - local ThreatStatic = StaticData -- Wrapper.Static#STATIC - local ThreatLevelA2G, ThreatText = ThreatStatic:GetThreatLevel() - if ThreatLevelA2G > MaxThreatLevelA2G then - MaxThreatLevelA2G = ThreatLevelA2G - MaxThreatText = ThreatText + local MaxThreatLevelA2G = 0 + local MaxThreatText = "" + for StaticName, StaticData in pairs( self:GetSet() ) do + local ThreatStatic = StaticData -- Wrapper.Static#STATIC + local ThreatLevelA2G, ThreatText = ThreatStatic:GetThreatLevel() + if ThreatLevelA2G > MaxThreatLevelA2G then + MaxThreatLevelA2G = ThreatLevelA2G + MaxThreatText = ThreatText + end end - end - self:F( { MaxThreatLevelA2G = MaxThreatLevelA2G, MaxThreatText = MaxThreatText } ) - return MaxThreatLevelA2G, MaxThreatText + self:F( { MaxThreatLevelA2G = MaxThreatLevelA2G, MaxThreatText = MaxThreatText } ) + return MaxThreatLevelA2G, MaxThreatText end @@ -3611,10 +3511,9 @@ do -- SET_STATIC return MStaticInclude end - --- Retrieve the type names of the @{Static}s in the SET, delimited by an optional delimiter. -- @param #SET_STATIC self - -- @param #string Delimiter (optional) The delimiter, which is default a comma. + -- @param #string Delimiter (Optional) The delimiter, which is default a comma. -- @return #string The types of the @{Static}s delimited. function SET_STATIC:GetTypeNames( Delimiter ) @@ -3638,15 +3537,11 @@ do -- SET_STATIC end - do -- SET_CLIENT - --- @type SET_CLIENT -- @extends Core.Set#SET_BASE - - --- Mission designers can use the @{Core.Set#SET_CLIENT} class to build sets of units belonging to certain: -- -- * Coalitions @@ -3745,7 +3640,7 @@ do -- SET_CLIENT -- @return self function SET_CLIENT:AddClientsByName( AddClientNames ) - local AddClientNamesArray = ( type( AddClientNames ) == "table" ) and AddClientNames or { AddClientNames } + local AddClientNamesArray = (type( AddClientNames ) == "table") and AddClientNames or { AddClientNames } for AddClientID, AddClientName in pairs( AddClientNamesArray ) do self:Add( AddClientName, CLIENT:FindByName( AddClientName ) ) @@ -3760,7 +3655,7 @@ do -- SET_CLIENT -- @return self function SET_CLIENT:RemoveClientsByName( RemoveClientNames ) - local RemoveClientNamesArray = ( type( RemoveClientNames ) == "table" ) and RemoveClientNames or { RemoveClientNames } + local RemoveClientNamesArray = (type( RemoveClientNames ) == "table") and RemoveClientNames or { RemoveClientNames } for RemoveClientID, RemoveClientName in pairs( RemoveClientNamesArray ) do self:Remove( RemoveClientName.ClientName ) @@ -3769,7 +3664,6 @@ do -- SET_CLIENT return self end - --- Finds a Client based on the Client Name. -- @param #SET_CLIENT self -- @param #string ClientName @@ -3780,8 +3674,6 @@ do -- SET_CLIENT return ClientFound end - - --- Builds a set of clients of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_CLIENT self @@ -3800,7 +3692,6 @@ do -- SET_CLIENT return self end - --- Builds a set of clients out of categories. -- Possible current categories are plane, helicopter, ground, ship. -- @param #SET_CLIENT self @@ -3819,7 +3710,6 @@ do -- SET_CLIENT return self end - --- Builds a set of clients of defined client types. -- Possible current types are those types known within DCS world. -- @param #SET_CLIENT self @@ -3838,7 +3728,6 @@ do -- SET_CLIENT return self end - --- Builds a set of clients of defined countries. -- Possible current countries are those known within DCS world. -- @param #SET_CLIENT self @@ -3857,7 +3746,6 @@ do -- SET_CLIENT return self end - --- Builds a set of CLIENTs that contain the given string in their unit/pilot name. -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all clients that **contain** the string. -- @param #SET_CLIENT self @@ -3879,7 +3767,7 @@ do -- SET_CLIENT --- Builds a set of clients that are only active. -- Only the clients that are active will be included within the set. -- @param #SET_CLIENT self - -- @param #boolean Active (optional) Include only active clients to the set. + -- @param #boolean Active (Optional) Include only active clients to the set. -- Include inactive clients if you provide false. -- @return #SET_CLIENT self -- @usage @@ -3897,13 +3785,11 @@ do -- SET_CLIENT -- ClientSet = SET_CLIENT:New():FilterActive( false ):FilterCoalition( "blue" ):FilterOnce() -- function SET_CLIENT:FilterActive( Active ) - Active = Active or not ( Active == false ) + Active = Active or not (Active == false) self.Filter.Active = Active return self end - - --- Starts the filtering. -- @param #SET_CLIENT self -- @return #SET_CLIENT self @@ -3943,7 +3829,7 @@ do -- SET_CLIENT return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - --- Iterate the SET_CLIENT and call an interator function for each **alive** CLIENT, providing the CLIENT and optional parameters. + --- Iterate the SET_CLIENT and call an iterator function for each **alive** CLIENT, providing the CLIENT and optional parameters. -- @param #SET_CLIENT self -- @param #function IteratorFunction The function that will be called when there is an alive CLIENT in the SET_CLIENT. The function needs to accept a CLIENT parameter. -- @return #SET_CLIENT self @@ -3963,16 +3849,15 @@ do -- SET_CLIENT function SET_CLIENT:ForEachClientInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - self:ForEach( IteratorFunction, arg, self:GetSet(), - --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Client#CLIENT ClientObject - function( ZoneObject, ClientObject ) - if ClientObject:IsInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) + self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Client#CLIENT ClientObject + function( ZoneObject, ClientObject ) + if ClientObject:IsInZone( ZoneObject ) then + return true + else + return false + end + end, { ZoneObject } ) return self end @@ -3985,20 +3870,19 @@ do -- SET_CLIENT function SET_CLIENT:ForEachClientNotInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - self:ForEach( IteratorFunction, arg, self:GetSet(), - --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Client#CLIENT ClientObject - function( ZoneObject, ClientObject ) - if ClientObject:IsNotInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) + self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Client#CLIENT ClientObject + function( ZoneObject, ClientObject ) + if ClientObject:IsNotInZone( ZoneObject ) then + return true + else + return false + end + end, { ZoneObject } ) return self end - + --- Iterate the SET_CLIENT and count alive units. -- @param #SET_CLIENT self -- @return #number count @@ -4007,7 +3891,7 @@ do -- SET_CLIENT local Set = self:GetSet() local CountU = 0 - for UnitID, UnitData in pairs(Set) do -- For each GROUP in SET_GROUP + for UnitID, UnitData in pairs( Set ) do -- For each GROUP in SET_GROUP if UnitData and UnitData:IsAlive() then CountU = CountU + 1 end @@ -4016,7 +3900,7 @@ do -- SET_CLIENT return CountU end - + --- -- @param #SET_CLIENT self -- @param Wrapper.Client#CLIENT MClient @@ -4031,7 +3915,7 @@ do -- SET_CLIENT if self.Filter.Active ~= nil then local MClientActive = false - if self.Filter.Active == false or ( self.Filter.Active == true and MClient:IsActive() == true ) then + if self.Filter.Active == false or (self.Filter.Active == true and MClient:IsActive() == true) then MClientActive = true end MClientInclude = MClientInclude and MClientActive @@ -4078,7 +3962,7 @@ do -- SET_CLIENT if self.Filter.Countries then local MClientCountry = false for CountryID, CountryName in pairs( self.Filter.Countries ) do - local ClientCountryID = _DATABASE:GetCountryFromClientTemplate(MClientName) + 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 @@ -4107,14 +3991,11 @@ do -- SET_CLIENT end - do -- SET_PLAYER --- @type SET_PLAYER -- @extends Core.Set#SET_BASE - - --- Mission designers can use the @{Core.Set#SET_PLAYER} class to build sets of units belonging to alive players: -- -- ## SET_PLAYER constructor @@ -4178,7 +4059,6 @@ do -- SET_PLAYER }, } - --- Creates a new SET_PLAYER object, building a set of clients belonging to a coalitions, categories, countries, types or with defined prefix names. -- @param #SET_PLAYER self -- @return #SET_PLAYER @@ -4198,7 +4078,7 @@ do -- SET_PLAYER -- @return self function SET_PLAYER:AddClientsByName( AddClientNames ) - local AddClientNamesArray = ( type( AddClientNames ) == "table" ) and AddClientNames or { AddClientNames } + local AddClientNamesArray = (type( AddClientNames ) == "table") and AddClientNames or { AddClientNames } for AddClientID, AddClientName in pairs( AddClientNamesArray ) do self:Add( AddClientName, CLIENT:FindByName( AddClientName ) ) @@ -4213,7 +4093,7 @@ do -- SET_PLAYER -- @return self function SET_PLAYER:RemoveClientsByName( RemoveClientNames ) - local RemoveClientNamesArray = ( type( RemoveClientNames ) == "table" ) and RemoveClientNames or { RemoveClientNames } + local RemoveClientNamesArray = (type( RemoveClientNames ) == "table") and RemoveClientNames or { RemoveClientNames } for RemoveClientID, RemoveClientName in pairs( RemoveClientNamesArray ) do self:Remove( RemoveClientName.ClientName ) @@ -4222,7 +4102,6 @@ do -- SET_PLAYER return self end - --- Finds a Client based on the Player Name. -- @param #SET_PLAYER self -- @param #string PlayerName @@ -4233,8 +4112,6 @@ do -- SET_PLAYER return ClientFound end - - --- Builds a set of clients of coalitions joined by specific players. -- Possible current coalitions are red, blue and neutral. -- @param #SET_PLAYER self @@ -4253,7 +4130,6 @@ do -- SET_PLAYER return self end - --- Builds a set of clients out of categories joined by players. -- Possible current categories are plane, helicopter, ground, ship. -- @param #SET_PLAYER self @@ -4272,7 +4148,6 @@ do -- SET_PLAYER return self end - --- Builds a set of clients of defined client types joined by players. -- Possible current types are those types known within DCS world. -- @param #SET_PLAYER self @@ -4291,7 +4166,6 @@ do -- SET_PLAYER return self end - --- Builds a set of clients of defined countries. -- Possible current countries are those known within DCS world. -- @param #SET_PLAYER self @@ -4310,7 +4184,6 @@ do -- SET_PLAYER return self end - --- Builds a set of PLAYERs that contain the given string in their unit/pilot name. -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all player clients that **contain** the string. -- @param #SET_PLAYER self @@ -4329,9 +4202,6 @@ do -- SET_PLAYER return self end - - - --- Starts the filtering. -- @param #SET_PLAYER self -- @return #SET_PLAYER self @@ -4371,7 +4241,7 @@ do -- SET_PLAYER return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - --- Iterate the SET_PLAYER and call an interator function for each **alive** CLIENT, providing the CLIENT and optional parameters. + --- Iterate the SET_PLAYER and call an iterator function for each **alive** CLIENT, providing the CLIENT and optional parameters. -- @param #SET_PLAYER self -- @param #function IteratorFunction The function that will be called when there is an alive CLIENT in the SET_PLAYER. The function needs to accept a CLIENT parameter. -- @return #SET_PLAYER self @@ -4391,16 +4261,15 @@ do -- SET_PLAYER function SET_PLAYER:ForEachPlayerInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - self:ForEach( IteratorFunction, arg, self:GetSet(), - --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Client#CLIENT ClientObject - function( ZoneObject, ClientObject ) - if ClientObject:IsInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) + self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Client#CLIENT ClientObject + function( ZoneObject, ClientObject ) + if ClientObject:IsInZone( ZoneObject ) then + return true + else + return false + end + end, { ZoneObject } ) return self end @@ -4413,16 +4282,15 @@ do -- SET_PLAYER function SET_PLAYER:ForEachPlayerNotInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - self:ForEach( IteratorFunction, arg, self:GetSet(), - --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Client#CLIENT ClientObject - function( ZoneObject, ClientObject ) - if ClientObject:IsNotInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) + self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Client#CLIENT ClientObject + function( ZoneObject, ClientObject ) + if ClientObject:IsNotInZone( ZoneObject ) then + return true + else + return false + end + end, { ZoneObject } ) return self end @@ -4480,7 +4348,7 @@ do -- SET_PLAYER if self.Filter.Countries then local MClientCountry = false for CountryID, CountryName in pairs( self.Filter.Countries ) do - local ClientCountryID = _DATABASE:GetCountryFromClientTemplate(MClientName) + 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 @@ -4509,7 +4377,6 @@ do -- SET_PLAYER end - do -- SET_AIRBASE --- @type SET_AIRBASE @@ -4571,7 +4438,6 @@ do -- SET_AIRBASE }, } - --- Creates a new SET_AIRBASE object, building a set of airbases belonging to a coalitions and categories. -- @param #SET_AIRBASE self -- @return #SET_AIRBASE self @@ -4602,7 +4468,7 @@ do -- SET_AIRBASE -- @return self function SET_AIRBASE:AddAirbasesByName( AddAirbaseNames ) - local AddAirbaseNamesArray = ( type( AddAirbaseNames ) == "table" ) and AddAirbaseNames or { AddAirbaseNames } + local AddAirbaseNamesArray = (type( AddAirbaseNames ) == "table") and AddAirbaseNames or { AddAirbaseNames } for AddAirbaseID, AddAirbaseName in pairs( AddAirbaseNamesArray ) do self:Add( AddAirbaseName, AIRBASE:FindByName( AddAirbaseName ) ) @@ -4617,7 +4483,7 @@ do -- SET_AIRBASE -- @return self function SET_AIRBASE:RemoveAirbasesByName( RemoveAirbaseNames ) - local RemoveAirbaseNamesArray = ( type( RemoveAirbaseNames ) == "table" ) and RemoveAirbaseNames or { RemoveAirbaseNames } + local RemoveAirbaseNamesArray = (type( RemoveAirbaseNames ) == "table") and RemoveAirbaseNames or { RemoveAirbaseNames } for RemoveAirbaseID, RemoveAirbaseName in pairs( RemoveAirbaseNamesArray ) do self:Remove( RemoveAirbaseName ) @@ -4626,7 +4492,6 @@ do -- SET_AIRBASE return self end - --- Finds a Airbase based on the Airbase Name. -- @param #SET_AIRBASE self -- @param #string AirbaseName @@ -4637,7 +4502,6 @@ do -- SET_AIRBASE return AirbaseFound end - --- Finds an Airbase in range of a coordinate. -- @param #SET_AIRBASE self -- @param Core.Point#COORDINATE Coordinate @@ -4652,7 +4516,7 @@ do -- SET_AIRBASE local AirbaseCoordinate = AirbaseObject:GetCoordinate() local Distance = Coordinate:Get2DDistance( AirbaseCoordinate ) - self:F({Distance=Distance}) + self:F( { Distance = Distance } ) if Distance <= Range then AirbaseFound = AirbaseObject @@ -4664,7 +4528,6 @@ do -- SET_AIRBASE return AirbaseFound end - --- Finds a random Airbase in the set. -- @param #SET_AIRBASE self -- @return Wrapper.Airbase#AIRBASE The found Airbase. @@ -4676,8 +4539,6 @@ do -- SET_AIRBASE return RandomAirbase end - - --- Builds a set of airbases of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_AIRBASE self @@ -4696,7 +4557,6 @@ do -- SET_AIRBASE return self end - --- Builds a set of airbases out of categories. -- Possible current categories are plane, helicopter, ground, ship. -- @param #SET_AIRBASE self @@ -4723,8 +4583,8 @@ do -- SET_AIRBASE if _DATABASE then -- We use the BaseCaptured event, which is generated by DCS when a base got captured. - self:HandleEvent(EVENTS.BaseCaptured) - self:HandleEvent(EVENTS.Dead) + self:HandleEvent( EVENTS.BaseCaptured ) + self:HandleEvent( EVENTS.Dead ) -- We initialize the first set. for ObjectName, Object in pairs( self.Database ) do @@ -4742,7 +4602,7 @@ do -- SET_AIRBASE --- Base capturing event. -- @param #SET_AIRBASE self -- @param Core.Event#EVENT EventData - function SET_AIRBASE:OnEventBaseCaptured(EventData) + function SET_AIRBASE:OnEventBaseCaptured( EventData ) -- When a base got captured, we reevaluate the set. for ObjectName, Object in pairs( self.Database ) do @@ -4760,17 +4620,16 @@ do -- SET_AIRBASE --- Dead event. -- @param #SET_AIRBASE self -- @param Core.Event#EVENT EventData - function SET_AIRBASE:OnEventDead(EventData) + function SET_AIRBASE:OnEventDead( EventData ) - local airbaseName, airbase=self:FindInDatabase(EventData) + local airbaseName, airbase = self:FindInDatabase( EventData ) if airbase and (airbase:IsShip() or airbase:IsHelipad()) then - self:RemoveAirbasesByName(airbaseName) + self:RemoveAirbasesByName( airbaseName ) end end - --- Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- @param #SET_AIRBASE self @@ -4793,7 +4652,7 @@ do -- SET_AIRBASE return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - --- Iterate the SET_AIRBASE and call an interator function for each AIRBASE, providing the AIRBASE and optional parameters. + --- Iterate the SET_AIRBASE and call an iterator function for each AIRBASE, providing the AIRBASE and optional parameters. -- @param #SET_AIRBASE self -- @param #function IteratorFunction The function that will be called when there is an alive AIRBASE in the SET_AIRBASE. The function needs to accept a AIRBASE parameter. -- @return #SET_AIRBASE self @@ -4816,8 +4675,6 @@ do -- SET_AIRBASE return NearestAirbase end - - --- -- @param #SET_AIRBASE self -- @param Wrapper.Airbase#AIRBASE MAirbase @@ -4863,7 +4720,6 @@ do -- SET_AIRBASE end - do -- SET_CARGO --- @type SET_CARGO @@ -4928,40 +4784,37 @@ do -- SET_CARGO }, } - --- Creates a new SET_CARGO object, building a set of cargos belonging to a coalitions and categories. -- @param #SET_CARGO self -- @return #SET_CARGO -- @usage -- -- Define a new SET_CARGO Object. The DatabaseSet will contain a reference to all Cargos. -- DatabaseSet = SET_CARGO:New() - function SET_CARGO:New() --R2.1 + function SET_CARGO:New() -- R2.1 -- Inherits from BASE local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.CARGOS ) ) -- #SET_CARGO return self end - --- (R2.1) Add CARGO to SET_CARGO. -- @param Core.Set#SET_CARGO self -- @param Cargo.Cargo#CARGO Cargo A single cargo. -- @return Core.Set#SET_CARGO self - function SET_CARGO:AddCargo( Cargo ) --R2.4 + function SET_CARGO:AddCargo( Cargo ) -- R2.4 self:Add( Cargo:GetName(), Cargo ) return self end - --- (R2.1) Add CARGOs to SET_CARGO. -- @param Core.Set#SET_CARGO self -- @param #string AddCargoNames A single name or an array of CARGO names. -- @return Core.Set#SET_CARGO self - function SET_CARGO:AddCargosByName( AddCargoNames ) --R2.1 + function SET_CARGO:AddCargosByName( AddCargoNames ) -- R2.1 - local AddCargoNamesArray = ( type( AddCargoNames ) == "table" ) and AddCargoNames or { AddCargoNames } + local AddCargoNamesArray = (type( AddCargoNames ) == "table") and AddCargoNames or { AddCargoNames } for AddCargoID, AddCargoName in pairs( AddCargoNamesArray ) do self:Add( AddCargoName, CARGO:FindByName( AddCargoName ) ) @@ -4974,9 +4827,9 @@ do -- SET_CARGO -- @param Core.Set#SET_CARGO self -- @param Wrapper.Cargo#CARGO RemoveCargoNames A single name or an array of CARGO names. -- @return Core.Set#SET_CARGO self - function SET_CARGO:RemoveCargosByName( RemoveCargoNames ) --R2.1 + function SET_CARGO:RemoveCargosByName( RemoveCargoNames ) -- R2.1 - local RemoveCargoNamesArray = ( type( RemoveCargoNames ) == "table" ) and RemoveCargoNames or { RemoveCargoNames } + local RemoveCargoNamesArray = (type( RemoveCargoNames ) == "table") and RemoveCargoNames or { RemoveCargoNames } for RemoveCargoID, RemoveCargoName in pairs( RemoveCargoNamesArray ) do self:Remove( RemoveCargoName.CargoName ) @@ -4985,25 +4838,22 @@ do -- SET_CARGO return self end - --- (R2.1) Finds a Cargo based on the Cargo Name. -- @param #SET_CARGO self -- @param #string CargoName -- @return Wrapper.Cargo#CARGO The found Cargo. - function SET_CARGO:FindCargo( CargoName ) --R2.1 + function SET_CARGO:FindCargo( CargoName ) -- R2.1 local CargoFound = self.Set[CargoName] return CargoFound end - - --- (R2.1) Builds a set of cargos of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_CARGO self -- @param #string Coalitions Can take the following values: "red", "blue", "neutral". -- @return #SET_CARGO self - function SET_CARGO:FilterCoalitions( Coalitions ) --R2.1 + function SET_CARGO:FilterCoalitions( Coalitions ) -- R2.1 if not self.Filter.Coalitions then self.Filter.Coalitions = {} end @@ -5021,7 +4871,7 @@ do -- SET_CARGO -- @param #SET_CARGO self -- @param #string Types Can take those type strings known within DCS world. -- @return #SET_CARGO self - function SET_CARGO:FilterTypes( Types ) --R2.1 + function SET_CARGO:FilterTypes( Types ) -- R2.1 if not self.Filter.Types then self.Filter.Types = {} end @@ -5034,13 +4884,12 @@ do -- SET_CARGO return self end - --- (R2.1) Builds a set of cargos of defined countries. -- Possible current countries are those known within DCS world. -- @param #SET_CARGO self -- @param #string Countries Can take those country strings known within DCS world. -- @return #SET_CARGO self - function SET_CARGO:FilterCountries( Countries ) --R2.1 + function SET_CARGO:FilterCountries( Countries ) -- R2.1 if not self.Filter.Countries then self.Filter.Countries = {} end @@ -5053,13 +4902,12 @@ do -- SET_CARGO return self end - --- Builds a set of CARGOs that contain a given string in their name. -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all cargos that **contain** the string. -- @param #SET_CARGO self -- @param #string Prefixes The string pattern(s) that need to be in the cargo name. Can also be passed as a `#table` of strings. -- @return #SET_CARGO self - function SET_CARGO:FilterPrefixes( Prefixes ) --R2.1 + function SET_CARGO:FilterPrefixes( Prefixes ) -- R2.1 if not self.Filter.CargoPrefixes then self.Filter.CargoPrefixes = {} end @@ -5072,12 +4920,10 @@ do -- SET_CARGO return self end - - --- (R2.1) Starts the filtering. -- @param #SET_CARGO self -- @return #SET_CARGO self - function SET_CARGO:FilterStart() --R2.1 + function SET_CARGO:FilterStart() -- R2.1 if _DATABASE then self:_FilterStart() @@ -5099,14 +4945,13 @@ do -- SET_CARGO return self end - --- (R2.1) Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- @param #SET_CARGO self -- @param Core.Event#EVENTDATA Event -- @return #string The name of the CARGO -- @return #table The CARGO - function SET_CARGO:AddInDatabase( Event ) --R2.1 + function SET_CARGO:AddInDatabase( Event ) -- R2.1 self:F3( { Event } ) return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] @@ -5118,17 +4963,17 @@ do -- SET_CARGO -- @param Core.Event#EVENTDATA Event -- @return #string The name of the CARGO -- @return #table The CARGO - function SET_CARGO:FindInDatabase( Event ) --R2.1 + function SET_CARGO:FindInDatabase( Event ) -- R2.1 self:F3( { Event } ) return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - --- (R2.1) Iterate the SET_CARGO and call an interator function for each CARGO, providing the CARGO and optional parameters. + --- (R2.1) Iterate the SET_CARGO and call an iterator function for each CARGO, providing the CARGO and optional parameters. -- @param #SET_CARGO self -- @param #function IteratorFunction The function that will be called when there is an alive CARGO in the SET_CARGO. The function needs to accept a CARGO parameter. -- @return #SET_CARGO self - function SET_CARGO:ForEachCargo( IteratorFunction, ... ) --R2.1 + function SET_CARGO:ForEachCargo( IteratorFunction, ... ) -- R2.1 self:F2( arg ) self:ForEach( IteratorFunction, arg, self:GetSet() ) @@ -5140,7 +4985,7 @@ do -- SET_CARGO -- @param #SET_CARGO self -- @param Core.Point#POINT_VEC2 PointVec2 A @{Core.Point#POINT_VEC2} object from where to evaluate the closest @{Cargo.Cargo#CARGO}. -- @return Wrapper.Cargo#CARGO The closest @{Cargo.Cargo#CARGO}. - function SET_CARGO:FindNearestCargoFromPointVec2( PointVec2 ) --R2.1 + function SET_CARGO:FindNearestCargoFromPointVec2( PointVec2 ) -- R2.1 self:F2( PointVec2 ) local NearestCargo = self:FindNearestObjectFromPointVec2( PointVec2 ) @@ -5175,7 +5020,6 @@ do -- SET_CARGO return FirstCargo end - --- Iterate the SET_CARGO while identifying the first @{Cargo.Cargo#CARGO} that is UnLoaded. -- @param #SET_CARGO self -- @return Cargo.Cargo#CARGO The first @{Cargo.Cargo#CARGO}. @@ -5184,7 +5028,6 @@ do -- SET_CARGO return FirstCargo end - --- Iterate the SET_CARGO while identifying the first @{Cargo.Cargo#CARGO} that is UnLoaded and not Deployed. -- @param #SET_CARGO self -- @return Cargo.Cargo#CARGO The first @{Cargo.Cargo#CARGO}. @@ -5193,7 +5036,6 @@ do -- SET_CARGO return FirstCargo end - --- Iterate the SET_CARGO while identifying the first @{Cargo.Cargo#CARGO} that is Loaded. -- @param #SET_CARGO self -- @return Cargo.Cargo#CARGO The first @{Cargo.Cargo#CARGO}. @@ -5202,7 +5044,6 @@ do -- SET_CARGO return FirstCargo end - --- Iterate the SET_CARGO while identifying the first @{Cargo.Cargo#CARGO} that is Deployed. -- @param #SET_CARGO self -- @return Cargo.Cargo#CARGO The first @{Cargo.Cargo#CARGO}. @@ -5211,14 +5052,11 @@ do -- SET_CARGO return FirstCargo end - - - --- (R2.1) -- @param #SET_CARGO self -- @param AI.AI_Cargo#AI_CARGO MCargo -- @return #SET_CARGO self - function SET_CARGO:IsIncludeObject( MCargo ) --R2.1 + function SET_CARGO:IsIncludeObject( MCargo ) -- R2.1 self:F2( MCargo ) local MCargoInclude = true @@ -5271,13 +5109,13 @@ do -- SET_CARGO --- (R2.1) Handles the OnEventNewCargo event for the Set. -- @param #SET_CARGO self -- @param Core.Event#EVENTDATA EventData - function SET_CARGO:OnEventNewCargo( EventData ) --R2.1 + function SET_CARGO:OnEventNewCargo( EventData ) -- R2.1 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 ) + self:Add( EventData.Cargo.Name, EventData.Cargo ) end end end @@ -5285,20 +5123,20 @@ do -- SET_CARGO --- (R2.1) Handles the OnDead or OnCrash event for alive units set. -- @param #SET_CARGO self -- @param Core.Event#EVENTDATA EventData - function SET_CARGO:OnEventDeleteCargo( EventData ) --R2.1 + function SET_CARGO:OnEventDeleteCargo( EventData ) -- R2.1 self:F3( { EventData } ) if EventData.Cargo then local Cargo = _DATABASE:FindCargo( EventData.Cargo.Name ) if Cargo and Cargo.Name then - -- When cargo was deleted, it may probably be because of an S_EVENT_DEAD. - -- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call. - -- And this is a problem because it will remove all entries from the SET_CARGOs. - -- To prevent this from happening, the Cargo object has a flag NoDestroy. - -- When true, the SET_CARGO won't Remove the Cargo object from the set. - -- This flag is switched off after the event handlers have been called in the EVENT class. - self:F( { CargoNoDestroy=Cargo.NoDestroy } ) + -- When cargo was deleted, it may probably be because of an S_EVENT_DEAD. + -- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call. + -- And this is a problem because it will remove all entries from the SET_CARGOs. + -- To prevent this from happening, the Cargo object has a flag NoDestroy. + -- When true, the SET_CARGO won't Remove the Cargo object from the set. + -- This flag is switched off after the event handlers have been called in the EVENT class. + self:F( { CargoNoDestroy = Cargo.NoDestroy } ) if Cargo.NoDestroy then else self:Remove( Cargo.Name ) @@ -5309,7 +5147,6 @@ do -- SET_CARGO end - do -- SET_ZONE --- @type SET_ZONE @@ -5349,17 +5186,15 @@ do -- SET_ZONE -- -- === -- @field #SET_ZONE SET_ZONE - SET_ZONE = { - ClassName = "SET_ZONE", + SET_ZONE = { + lassName = "SET_ZONE", Zones = {}, Filter = { Prefixes = nil, }, - FilterMeta = { - }, + FilterMeta = {}, } - --- Creates a new SET_ZONE object, building a set of zones. -- @param #SET_ZONE self -- @return #SET_ZONE self @@ -5379,7 +5214,7 @@ do -- SET_ZONE -- @return self function SET_ZONE:AddZonesByName( AddZoneNames ) - local AddZoneNamesArray = ( type( AddZoneNames ) == "table" ) and AddZoneNames or { AddZoneNames } + local AddZoneNamesArray = (type( AddZoneNames ) == "table") and AddZoneNames or { AddZoneNames } for AddAirbaseID, AddZoneName in pairs( AddZoneNamesArray ) do self:Add( AddZoneName, ZONE:FindByName( AddZoneName ) ) @@ -5399,14 +5234,13 @@ do -- SET_ZONE return self end - --- Remove ZONEs from SET_ZONE. -- @param Core.Set#SET_ZONE self -- @param Core.Zone#ZONE_BASE RemoveZoneNames A single name or an array of ZONE_BASE names. -- @return self function SET_ZONE:RemoveZonesByName( RemoveZoneNames ) - local RemoveZoneNamesArray = ( type( RemoveZoneNames ) == "table" ) and RemoveZoneNames or { RemoveZoneNames } + local RemoveZoneNamesArray = (type( RemoveZoneNames ) == "table") and RemoveZoneNames or { RemoveZoneNames } for RemoveZoneID, RemoveZoneName in pairs( RemoveZoneNamesArray ) do self:Remove( RemoveZoneName ) @@ -5415,7 +5249,6 @@ do -- SET_ZONE return self end - --- Finds a Zone based on the Zone Name. -- @param #SET_ZONE self -- @param #string ZoneName @@ -5426,14 +5259,13 @@ do -- SET_ZONE return ZoneFound end - --- Get a random zone from the set. -- @param #SET_ZONE self -- @param #number margin Number of tries to find a zone -- @return Core.Zone#ZONE_BASE The random Zone. -- @return #nil if no zone in the collection. - function SET_ZONE:GetRandomZone(margin) - + function SET_ZONE:GetRandomZone( margin ) + local margin = margin or 100 if self:Count() ~= 0 then @@ -5456,7 +5288,6 @@ do -- SET_ZONE return nil end - --- Set a zone probability. -- @param #SET_ZONE self -- @param #string ZoneName The name of the zone. @@ -5465,9 +5296,6 @@ do -- SET_ZONE Zone:SetZoneProbability( ZoneProbability ) end - - - --- Builds a set of ZONEs that contain the given string in their name. -- **ATTENTION!** Bad naming convention as this **does not** filter only **prefixes** but all zones that **contain** the string. -- @param #SET_ZONE self @@ -5486,7 +5314,6 @@ do -- SET_ZONE return self end - --- Starts the filtering. -- @param #SET_ZONE self -- @return #SET_ZONE self @@ -5545,7 +5372,7 @@ do -- SET_ZONE return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - --- Iterate the SET_ZONE and call an interator function for each ZONE, providing the ZONE and optional parameters. + --- Iterate the SET_ZONE and call an iterator function for each ZONE, providing the ZONE and optional parameters. -- @param #SET_ZONE self -- @param #function IteratorFunction The function that will be called when there is an alive ZONE in the SET_ZONE. The function needs to accept a AIRBASE parameter. -- @return #SET_ZONE self @@ -5557,7 +5384,6 @@ do -- SET_ZONE return self end - --- -- @param #SET_ZONE self -- @param Core.Zone#ZONE_BASE MZone @@ -5590,13 +5416,13 @@ do -- SET_ZONE --- Handles the OnEventNewZone event for the Set. -- @param #SET_ZONE self -- @param Core.Event#EVENTDATA EventData - function SET_ZONE:OnEventNewZone( EventData ) --R2.1 + function SET_ZONE:OnEventNewZone( EventData ) -- R2.1 self:F( { "New Zone", EventData } ) if EventData.Zone then if EventData.Zone and self:IsIncludeObject( EventData.Zone ) then - self:Add( EventData.Zone.ZoneName , EventData.Zone ) + self:Add( EventData.Zone.ZoneName, EventData.Zone ) end end end @@ -5604,20 +5430,20 @@ do -- SET_ZONE --- Handles the OnDead or OnCrash event for alive units set. -- @param #SET_ZONE self -- @param Core.Event#EVENTDATA EventData - function SET_ZONE:OnEventDeleteZone( EventData ) --R2.1 + function SET_ZONE:OnEventDeleteZone( EventData ) -- R2.1 self:F3( { EventData } ) if EventData.Zone then local Zone = _DATABASE:FindZone( EventData.Zone.ZoneName ) if Zone and Zone.ZoneName then - -- When cargo was deleted, it may probably be because of an S_EVENT_DEAD. - -- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call. - -- And this is a problem because it will remove all entries from the SET_ZONEs. - -- To prevent this from happening, the Zone object has a flag NoDestroy. - -- When true, the SET_ZONE won't Remove the Zone object from the set. - -- This flag is switched off after the event handlers have been called in the EVENT class. - self:F( { ZoneNoDestroy=Zone.NoDestroy } ) + -- When cargo was deleted, it may probably be because of an S_EVENT_DEAD. + -- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call. + -- And this is a problem because it will remove all entries from the SET_ZONEs. + -- To prevent this from happening, the Zone object has a flag NoDestroy. + -- When true, the SET_ZONE won't Remove the Zone object from the set. + -- This flag is switched off after the event handlers have been called in the EVENT class. + self:F( { ZoneNoDestroy = Zone.NoDestroy } ) if Zone.NoDestroy then else self:Remove( Zone.ZoneName ) @@ -5692,11 +5518,9 @@ do -- SET_ZONE_GOAL Filter = { Prefixes = nil, }, - FilterMeta = { - }, + FilterMeta = {}, } - --- Creates a new SET_ZONE_GOAL object, building a set of zones. -- @param #SET_ZONE_GOAL self -- @return #SET_ZONE_GOAL self @@ -5721,14 +5545,13 @@ do -- SET_ZONE_GOAL return self end - --- Remove ZONEs from SET_ZONE_GOAL. -- @param Core.Set#SET_ZONE_GOAL self -- @param Core.Zone#ZONE_BASE RemoveZoneNames A single name or an array of ZONE_BASE names. -- @return self function SET_ZONE_GOAL:RemoveZonesByName( RemoveZoneNames ) - local RemoveZoneNamesArray = ( type( RemoveZoneNames ) == "table" ) and RemoveZoneNames or { RemoveZoneNames } + local RemoveZoneNamesArray = (type( RemoveZoneNames ) == "table") and RemoveZoneNames or { RemoveZoneNames } for RemoveZoneID, RemoveZoneName in pairs( RemoveZoneNamesArray ) do self:Remove( RemoveZoneName ) @@ -5737,7 +5560,6 @@ do -- SET_ZONE_GOAL return self end - --- Finds a Zone based on the Zone Name. -- @param #SET_ZONE_GOAL self -- @param #string ZoneName @@ -5748,7 +5570,6 @@ do -- SET_ZONE_GOAL return ZoneFound end - --- Get a random zone from the set. -- @param #SET_ZONE_GOAL self -- @return Core.Zone#ZONE_BASE The random Zone. @@ -5774,7 +5595,6 @@ do -- SET_ZONE_GOAL return nil end - --- Set a zone probability. -- @param #SET_ZONE_GOAL self -- @param #string ZoneName The name of the zone. @@ -5783,9 +5603,6 @@ do -- SET_ZONE_GOAL Zone:SetZoneProbability( ZoneProbability ) end - - - --- Builds a set of ZONE_GOALs that contain the given string in their name. -- **ATTENTION!** Bad naming convention as this **does not** filter only **prefixes** but all zones that **contain** the string. -- @param #SET_ZONE_GOAL self @@ -5804,7 +5621,6 @@ do -- SET_ZONE_GOAL return self end - --- Starts the filtering. -- @param #SET_ZONE_GOAL self -- @return #SET_ZONE_GOAL self @@ -5863,7 +5679,7 @@ do -- SET_ZONE_GOAL return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - --- Iterate the SET_ZONE_GOAL and call an interator function for each ZONE, providing the ZONE and optional parameters. + --- Iterate the SET_ZONE_GOAL and call an iterator function for each ZONE, providing the ZONE and optional parameters. -- @param #SET_ZONE_GOAL self -- @param #function IteratorFunction The function that will be called when there is an alive ZONE in the SET_ZONE_GOAL. The function needs to accept a AIRBASE parameter. -- @return #SET_ZONE_GOAL self @@ -5875,7 +5691,6 @@ do -- SET_ZONE_GOAL return self end - --- -- @param #SET_ZONE_GOAL self -- @param Core.Zone#ZONE_BASE MZone @@ -5916,7 +5731,7 @@ do -- SET_ZONE_GOAL if EventData.ZoneGoal then if EventData.ZoneGoal and self:IsIncludeObject( EventData.ZoneGoal ) then self:I( { "Adding Zone Capture Coalition", EventData.ZoneGoal.ZoneName, EventData.ZoneGoal } ) - self:Add( EventData.ZoneGoal.ZoneName , EventData.ZoneGoal ) + self:Add( EventData.ZoneGoal.ZoneName, EventData.ZoneGoal ) end end end @@ -5924,20 +5739,20 @@ do -- SET_ZONE_GOAL --- Handles the OnDead or OnCrash event for alive units set. -- @param #SET_ZONE_GOAL self -- @param Core.Event#EVENTDATA EventData - function SET_ZONE_GOAL:OnEventDeleteZoneGoal( EventData ) --R2.1 + function SET_ZONE_GOAL:OnEventDeleteZoneGoal( EventData ) -- R2.1 self:F3( { EventData } ) if EventData.ZoneGoal then local Zone = _DATABASE:FindZone( EventData.ZoneGoal.ZoneName ) if Zone and Zone.ZoneName then - -- When cargo was deleted, it may probably be because of an S_EVENT_DEAD. - -- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call. - -- And this is a problem because it will remove all entries from the SET_ZONE_GOALs. - -- To prevent this from happening, the Zone object has a flag NoDestroy. - -- When true, the SET_ZONE_GOAL won't Remove the Zone object from the set. - -- This flag is switched off after the event handlers have been called in the EVENT class. - self:F( { ZoneNoDestroy=Zone.NoDestroy } ) + -- When cargo was deleted, it may probably be because of an S_EVENT_DEAD. + -- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call. + -- And this is a problem because it will remove all entries from the SET_ZONE_GOALs. + -- To prevent this from happening, the Zone object has a flag NoDestroy. + -- When true, the SET_ZONE_GOAL won't Remove the Zone object from the set. + -- This flag is switched off after the event handlers have been called in the EVENT class. + self:F( { ZoneNoDestroy = Zone.NoDestroy } ) if Zone.NoDestroy then else self:Remove( Zone.ZoneName ) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index a225ce5c5..535d7fd03 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -45,10 +45,10 @@ -- === -- -- ### Author: **funkyfranky** +-- -- @module Ops.Atis -- @image OPS_ATIS.png - --- ATIS class. -- @type ATIS -- @field #string ClassName Name of the class. @@ -126,7 +126,7 @@ -- ## Subtitles -- -- Currently, DCS allows for displaying subtitles of radio transmissions only from airborne units, *i.e.* airplanes and helicopters. Therefore, if you want to have subtitles, it is necessary to place an --- additonal aircraft on the ATIS airport and set it to uncontrolled. This unit can then function as a radio relay to transmit messages with subtitles. These subtitles will only be displayed, if the +-- additional aircraft on the ATIS airport and set it to uncontrolled. This unit can then function as a radio relay to transmit messages with subtitles. These subtitles will only be displayed, if the -- player has tuned in the correct ATIS frequency. -- -- Radio transmissions via an airborne unit can be set via the @{#ATIS.SetRadioRelayUnitName}(*unitname*) function, where the parameter *unitname* is the name of the unit passed as string, *e.g.* @@ -142,7 +142,7 @@ -- -- ## Active Runway -- --- By default, the currently active runway is determined automatically by analysing the wind direction. Therefore, you should obviously set the wind speed to be greater zero in your mission. +-- By default, the currently active runway is determined automatically by analyzing the wind direction. Therefore, you should obviously set the wind speed to be greater zero in your mission. -- -- Note however, there are a few special cases, where automatic detection does not yield the correct or desired result. -- For example, there are airports with more than one runway facing in the same direction (usually denoted left and right). In this case, there is obviously no *unique* result depending on the wind vector. @@ -170,7 +170,7 @@ -- -- ## Nav Aids -- --- Frequencies or channels of navigation aids can be specified by the user and are then provided as additional information. Unfortunately, it is **not possible** to aquire this information via the DCS API +-- Frequencies or channels of navigation aids can be specified by the user and are then provided as additional information. Unfortunately, it is **not possible** to acquire this information via the DCS API -- we have access to. -- -- As they say, all road lead to Rome but (for me) the easiest way to obtain the available nav aids data of an airport, is to start a mission and click on an airport symbol. @@ -256,14 +256,14 @@ -- # Marks on the F10 Map -- -- You can place marks on the F10 map via the @{#ATIS.SetMapMarks}() function. These will contain info about the ATIS frequency, the currently active runway and some basic info about the weather (wind, pressure and temperature). --- +-- -- # Text-To-Speech --- --- You can enable text-to-speech ATIS information with the @{#ATIS.SetSRS}() function. This uses [SRS](http://dcssimpleradio.com/) (Version >= 1.9.6.0) for broadcasing. +-- +-- You can enable text-to-speech ATIS information with the @{#ATIS.SetSRS}() function. This uses [SRS](http://dcssimpleradio.com/) (Version >= 1.9.6.0) for broadcasting. -- Advantages are that **no sound files** or radio relay units are necessary. Also the issue that FC3 aircraft hear all transmissions will be circumvented. --- +-- -- The @{#ATIS.SetSRS}() requires you to specify the path to the SRS install directory or more specifically the path to the DCS-SR-ExternalAudio.exe file. --- +-- -- Unfortunately, it is not possible to determine the duration of the complete transmission. So once the transmission is finished, there might be some radio silence before -- the next iteration begins. You can fine tune the time interval between transmissions with the @{#ATIS.SetQueueUpdateTime}() function. The default interval is 90 seconds. -- @@ -297,9 +297,9 @@ -- atisAbuDhabi:SetTowerFrequencies({250.5, 119.2}) -- atisAbuDhabi:SetVOR(114.25) -- atisAbuDhabi:Start() --- +-- -- ## SRS --- +-- -- atis=ATIS:New("Batumi", 305, radio.modulation.AM) -- atis:SetSRS("D:\\DCS\\_SRS\\", "male", "en-US") -- atis:Start() @@ -390,14 +390,14 @@ ATIS.Alphabet = { -- @field #number TheChannel -10° (West). -- @field #number Syria +5° (East). -- @field #number MarianaIslands +2° (East). -ATIS.RunwayM2T={ - Caucasus=0, - Nevada=12, - Normandy=-10, - PersianGulf=2, - TheChannel=-10, - Syria=5, - MarianaIslands=2, +ATIS.RunwayM2T = { + Caucasus = 0, + Nevada = 12, + Normandy = -10, + PersianGulf = 2, + TheChannel = -10, + Syria = 5, + MarianaIslands = 2, } --- Whether ICAO phraseology is used for ATIS broadcasts. @@ -409,14 +409,14 @@ ATIS.RunwayM2T={ -- @field #boolean TheChannel true. -- @field #boolean Syria true. -- @field #boolean MarianaIslands true. -ATIS.ICAOPhraseology={ - Caucasus=true, - Nevada=false, - Normandy=true, - PersianGulf=true, - TheChannel=true, - Syria=true, - MarianaIslands=true, +ATIS.ICAOPhraseology = { + Caucasus = true, + Nevada = false, + Normandy = true, + PersianGulf = true, + TheChannel = true, + Syria = true, + MarianaIslands = true } --- Nav point data. @@ -505,91 +505,90 @@ ATIS.ICAOPhraseology={ -- @field #ATIS.Soundfile TACANChannel -- @field #ATIS.Soundfile VORFrequency ATIS.Sound = { - ActiveRunway={filename="ActiveRunway.ogg", duration=0.99}, - AdviceOnInitial={filename="AdviceOnInitial.ogg", duration=3.00}, - Airport={filename="Airport.ogg", duration=0.66}, - Altimeter={filename="Altimeter.ogg", duration=0.68}, - At={filename="At.ogg", duration=0.41}, - CloudBase={filename="CloudBase.ogg", duration=0.82}, - CloudCeiling={filename="CloudCeiling.ogg", duration=0.61}, - CloudsBroken={filename="CloudsBroken.ogg", duration=1.07}, - CloudsFew={filename="CloudsFew.ogg", duration=0.99}, - CloudsNo={filename="CloudsNo.ogg", duration=1.01}, - CloudsNotAvailable={filename="CloudsNotAvailable.ogg", duration=2.35}, - CloudsOvercast={filename="CloudsOvercast.ogg", duration=0.83}, - CloudsScattered={filename="CloudsScattered.ogg", duration=1.18}, - Decimal={filename="Decimal.ogg", duration=0.54}, - DegreesCelsius={filename="DegreesCelsius.ogg", duration=1.27}, - DegreesFahrenheit={filename="DegreesFahrenheit.ogg", duration=1.23}, - DewPoint={filename="DewPoint.ogg", duration=0.65}, - Dust={filename="Dust.ogg", duration=0.54}, - Elevation={filename="Elevation.ogg", duration=0.78}, - EndOfInformation={filename="EndOfInformation.ogg", duration=1.15}, - Feet={filename="Feet.ogg", duration=0.45}, - Fog={filename="Fog.ogg", duration=0.47}, - Gusting={filename="Gusting.ogg", duration=0.55}, - HectoPascal={filename="HectoPascal.ogg", duration=1.15}, - Hundred={filename="Hundred.ogg", duration=0.47}, - InchesOfMercury={filename="InchesOfMercury.ogg", duration=1.16}, - Information={filename="Information.ogg", duration=0.85}, - Kilometers={filename="Kilometers.ogg", duration=0.78}, - Knots={filename="Knots.ogg", duration=0.59}, - Left={filename="Left.ogg", duration=0.54}, - MegaHertz={filename="MegaHertz.ogg", duration=0.87}, - Meters={filename="Meters.ogg", duration=0.59}, - MetersPerSecond={filename="MetersPerSecond.ogg", duration=1.14}, - Miles={filename="Miles.ogg", duration=0.60}, - MillimetersOfMercury={filename="MillimetersOfMercury.ogg", duration=1.53}, - Minus={filename="Minus.ogg", duration=0.64}, - N0={filename="N-0.ogg", duration=0.55}, - N1={filename="N-1.ogg", duration=0.41}, - N2={filename="N-2.ogg", duration=0.37}, - N3={filename="N-3.ogg", duration=0.41}, - N4={filename="N-4.ogg", duration=0.37}, - N5={filename="N-5.ogg", duration=0.43}, - N6={filename="N-6.ogg", duration=0.55}, - N7={filename="N-7.ogg", duration=0.43}, - N8={filename="N-8.ogg", duration=0.38}, - N9={filename="N-9.ogg", duration=0.55}, - NauticalMiles={filename="NauticalMiles.ogg", duration=1.04}, - None={filename="None.ogg", duration=0.43}, - QFE={filename="QFE.ogg", duration=0.63}, - QNH={filename="QNH.ogg", duration=0.71}, - Rain={filename="Rain.ogg", duration=0.41}, - Right={filename="Right.ogg", duration=0.44}, - Snow={filename="Snow.ogg", duration=0.48}, - SnowStorm={filename="SnowStorm.ogg", duration=0.82}, - StatuteMiles={filename="StatuteMiles.ogg", duration=1.15}, - SunriseAt={filename="SunriseAt.ogg", duration=0.92}, - SunsetAt={filename="SunsetAt.ogg", duration=0.95}, - Temperature={filename="Temperature.ogg", duration=0.64}, - Thousand={filename="Thousand.ogg", duration=0.55}, - ThunderStorm={filename="ThunderStorm.ogg", duration=0.81}, - TimeLocal={filename="TimeLocal.ogg", duration=0.90}, - TimeZulu={filename="TimeZulu.ogg", duration=0.86}, - TowerFrequency={filename="TowerFrequency.ogg", duration=1.19}, - Visibilty={filename="Visibility.ogg", duration=0.79}, - WeatherPhenomena={filename="WeatherPhenomena.ogg", duration=1.07}, - WindFrom={filename="WindFrom.ogg", duration=0.60}, - ILSFrequency={filename="ILSFrequency.ogg", duration=1.30}, - InnerNDBFrequency={filename="InnerNDBFrequency.ogg", duration=1.56}, - OuterNDBFrequency={filename="OuterNDBFrequency.ogg", duration=1.59}, - RunwayLength={filename="RunwayLength.ogg", duration=0.91}, - VORFrequency={filename="VORFrequency.ogg", duration=1.38}, - TACANChannel={filename="TACANChannel.ogg", duration=0.88}, - PRMGChannel={filename="PRMGChannel.ogg", duration=1.18}, - RSBNChannel={filename="RSBNChannel.ogg", duration=1.14}, - Zulu={filename="Zulu.ogg", duration=0.62}, + ActiveRunway = { filename = "ActiveRunway.ogg", duration = 0.99 }, + AdviceOnInitial = { filename = "AdviceOnInitial.ogg", duration = 3.00 }, + Airport = { filename = "Airport.ogg", duration = 0.66 }, + Altimeter = { filename = "Altimeter.ogg", duration = 0.68 }, + At = { filename = "At.ogg", duration = 0.41 }, + CloudBase = { filename = "CloudBase.ogg", duration = 0.82 }, + CloudCeiling = { filename = "CloudCeiling.ogg", duration = 0.61 }, + CloudsBroken = { filename = "CloudsBroken.ogg", duration = 1.07 }, + CloudsFew = { filename = "CloudsFew.ogg", duration = 0.99 }, + CloudsNo = { filename = "CloudsNo.ogg", duration = 1.01 }, + CloudsNotAvailable = { filename = "CloudsNotAvailable.ogg", duration = 2.35 }, + CloudsOvercast = { filename = "CloudsOvercast.ogg", duration = 0.83 }, + CloudsScattered = { filename = "CloudsScattered.ogg", duration = 1.18 }, + Decimal = { filename = "Decimal.ogg", duration = 0.54 }, + DegreesCelsius = { filename = "DegreesCelsius.ogg", duration = 1.27 }, + DegreesFahrenheit = { filename = "DegreesFahrenheit.ogg", duration = 1.23 }, + DewPoint = { filename = "DewPoint.ogg", duration = 0.65 }, + Dust = { filename = "Dust.ogg", duration = 0.54 }, + Elevation = { filename = "Elevation.ogg", duration = 0.78 }, + EndOfInformation = { filename = "EndOfInformation.ogg", duration = 1.15 }, + Feet = { filename = "Feet.ogg", duration = 0.45 }, + Fog = { filename = "Fog.ogg", duration = 0.47 }, + Gusting = { filename = "Gusting.ogg", duration = 0.55 }, + HectoPascal = { filename = "HectoPascal.ogg", duration = 1.15 }, + Hundred = { filename = "Hundred.ogg", duration = 0.47 }, + InchesOfMercury = { filename = "InchesOfMercury.ogg", duration = 1.16 }, + Information = { filename = "Information.ogg", duration = 0.85 }, + Kilometers = { filename = "Kilometers.ogg", duration = 0.78 }, + Knots = { filename = "Knots.ogg", duration = 0.59 }, + Left = { filename = "Left.ogg", duration = 0.54 }, + MegaHertz = { filename = "MegaHertz.ogg", duration = 0.87 }, + Meters = { filename = "Meters.ogg", duration = 0.59 }, + MetersPerSecond = { filename = "MetersPerSecond.ogg", duration = 1.14 }, + Miles = { filename = "Miles.ogg", duration = 0.60 }, + MillimetersOfMercury = { filename = "MillimetersOfMercury.ogg", duration = 1.53 }, + Minus = { filename = "Minus.ogg", duration = 0.64 }, + N0 = { filename = "N-0.ogg", duration = 0.55 }, + N1 = { filename = "N-1.ogg", duration = 0.41 }, + N2 = { filename = "N-2.ogg", duration = 0.37 }, + N3 = { filename = "N-3.ogg", duration = 0.41 }, + N4 = { filename = "N-4.ogg", duration = 0.37 }, + N5 = { filename = "N-5.ogg", duration = 0.43 }, + N6 = { filename = "N-6.ogg", duration = 0.55 }, + N7 = { filename = "N-7.ogg", duration = 0.43 }, + N8 = { filename = "N-8.ogg", duration = 0.38 }, + N9 = { filename = "N-9.ogg", duration = 0.55 }, + NauticalMiles = { filename = "NauticalMiles.ogg", duration = 1.04 }, + None = { filename = "None.ogg", duration = 0.43 }, + QFE = { filename = "QFE.ogg", duration = 0.63 }, + QNH = { filename = "QNH.ogg", duration = 0.71 }, + Rain = { filename = "Rain.ogg", duration = 0.41 }, + Right = { filename = "Right.ogg", duration = 0.44 }, + Snow = { filename = "Snow.ogg", duration = 0.48 }, + SnowStorm = { filename = "SnowStorm.ogg", duration = 0.82 }, + StatuteMiles = { filename = "StatuteMiles.ogg", duration = 1.15 }, + SunriseAt = { filename = "SunriseAt.ogg", duration = 0.92 }, + SunsetAt = { filename = "SunsetAt.ogg", duration = 0.95 }, + Temperature = { filename = "Temperature.ogg", duration = 0.64 }, + Thousand = { filename = "Thousand.ogg", duration = 0.55 }, + ThunderStorm = { filename = "ThunderStorm.ogg", duration = 0.81 }, + TimeLocal = { filename = "TimeLocal.ogg", duration = 0.90 }, + TimeZulu = { filename = "TimeZulu.ogg", duration = 0.86 }, + TowerFrequency = { filename = "TowerFrequency.ogg", duration = 1.19 }, + Visibilty = { filename = "Visibility.ogg", duration = 0.79 }, + WeatherPhenomena = { filename = "WeatherPhenomena.ogg", duration = 1.07 }, + WindFrom = { filename = "WindFrom.ogg", duration = 0.60 }, + ILSFrequency = { filename = "ILSFrequency.ogg", duration = 1.30 }, + InnerNDBFrequency = { filename = "InnerNDBFrequency.ogg", duration = 1.56 }, + OuterNDBFrequency = { filename = "OuterNDBFrequency.ogg", duration = 1.59 }, + RunwayLength = { filename = "RunwayLength.ogg", duration = 0.91 }, + VORFrequency = { filename = "VORFrequency.ogg", duration = 1.38 }, + TACANChannel = { filename = "TACANChannel.ogg", duration = 0.88 }, + PRMGChannel = { filename = "PRMGChannel.ogg", duration = 1.18 }, + RSBNChannel = { filename = "RSBNChannel.ogg", duration = 1.14 }, + Zulu = { filename = "Zulu.ogg", duration = 0.62 }, } - --- ATIS table containing all defined ATISes. -- @field #table _ATIS -_ATIS={} +_ATIS = {} --- ATIS class version. -- @field #string version -ATIS.version="0.9.6" +ATIS.version = "0.9.6" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -618,31 +617,31 @@ ATIS.version="0.9.6" -- @param #number frequency Radio frequency in MHz. Default 143.00 MHz. -- @param #number modulation Radio modulation: 0=AM, 1=FM. Default 0=AM. See `radio.modulation.AM` and `radio.modulation.FM` enumerators -- @return #ATIS self -function ATIS:New(airbasename, frequency, modulation) +function ATIS:New( airbasename, frequency, modulation ) -- Inherit everything from FSM class. - local self=BASE:Inherit(self, FSM:New()) -- #ATIS + local self = BASE:Inherit( self, FSM:New() ) -- #ATIS - self.airbasename=airbasename - self.airbase=AIRBASE:FindByName(airbasename) + self.airbasename = airbasename + self.airbase = AIRBASE:FindByName( airbasename ) - if self.airbase==nil then - self:E("ERROR: Airbase %s for ATIS could not be found!", tostring(airbasename)) + if self.airbase == nil then + self:E( "ERROR: Airbase %s for ATIS could not be found!", tostring( airbasename ) ) return nil end -- Default freq and modulation. - self.frequency=frequency or 143.00 - self.modulation=modulation or 0 + self.frequency = frequency or 143.00 + self.modulation = modulation or 0 -- Get map. - self.theatre=env.mission.theatre + self.theatre = env.mission.theatre -- Set some string id for output to DCS.log file. - self.lid=string.format("ATIS %s | ", self.airbasename) + self.lid = string.format( "ATIS %s | ", self.airbasename ) -- This is just to hinder the garbage collector deallocating the ATIS object. - _ATIS[#_ATIS+1]=self + _ATIS[#_ATIS + 1] = self -- Defaults: self:SetSoundfilesPath() @@ -650,13 +649,13 @@ function ATIS:New(airbasename, frequency, modulation) self:SetMagneticDeclination() self:SetRunwayCorrectionMagnetic2True() self:SetRadioPower() - self:SetAltimeterQNH(true) - self:SetMapMarks(false) + self:SetAltimeterQNH( true ) + self:SetMapMarks( false ) self:SetRelativeHumidity() self:SetQueueUpdateTime() -- Start State. - self:SetStartState("Stopped") + self:SetStartState( "Stopped" ) -- Add FSM transitions. -- From State --> Event --> To State @@ -680,7 +679,6 @@ function ATIS:New(airbasename, frequency, modulation) -- @param #ATIS self -- @param #number delay Delay in seconds. - --- Triggers the FSM event "Stop". Stops the ATIS. -- @function [parent=#ATIS] Stop -- @param #ATIS self @@ -690,7 +688,6 @@ function ATIS:New(airbasename, frequency, modulation) -- @param #ATIS self -- @param #number delay Delay in seconds. - --- Triggers the FSM event "Status". -- @function [parent=#ATIS] Status -- @param #ATIS self @@ -700,7 +697,6 @@ function ATIS:New(airbasename, frequency, modulation) -- @param #ATIS self -- @param #number delay Delay in seconds. - --- Triggers the FSM event "Broadcast". -- @function [parent=#ATIS] Broadcast -- @param #ATIS self @@ -710,7 +706,6 @@ function ATIS:New(airbasename, frequency, modulation) -- @param #ATIS self -- @param #number delay Delay in seconds. - --- Triggers the FSM event "CheckQueue". -- @function [parent=#ATIS] CheckQueue -- @param #ATIS self @@ -720,7 +715,6 @@ function ATIS:New(airbasename, frequency, modulation) -- @param #ATIS self -- @param #number delay Delay in seconds. - --- Triggers the FSM event "Report". -- @function [parent=#ATIS] Report -- @param #ATIS self @@ -740,13 +734,12 @@ function ATIS:New(airbasename, frequency, modulation) -- @param #string To To state. -- @param #string Text Report text. - -- Debug trace. if false then - self.Debug=true - BASE:TraceOnOff(true) - BASE:TraceClass(self.ClassName) - BASE:TraceLevel(1) + self.Debug = true + BASE:TraceOnOff( true ) + BASE:TraceClass( self.ClassName ) + BASE:TraceLevel( 1 ) end return self @@ -760,9 +753,9 @@ end -- @param #ATIS self -- @param #string path Path for sound files. Default "ATIS Soundfiles/". Mind the slash "/" at the end! -- @return #ATIS self -function ATIS:SetSoundfilesPath(path) - self.soundpath=tostring(path or "ATIS Soundfiles/") - self:I(self.lid..string.format("Setting sound files path to %s", self.soundpath)) +function ATIS:SetSoundfilesPath( path ) + self.soundpath = tostring( path or "ATIS Soundfiles/" ) + self:I( self.lid .. string.format( "Setting sound files path to %s", self.soundpath ) ) return self end @@ -771,9 +764,9 @@ end -- @param #ATIS self -- @param #string unitname Name of the unit. -- @return #ATIS self -function ATIS:SetRadioRelayUnitName(unitname) - self.relayunitname=unitname - self:I(self.lid..string.format("Setting radio relay unit to %s", self.relayunitname)) +function ATIS:SetRadioRelayUnitName( unitname ) + self.relayunitname = unitname + self:I( self.lid .. string.format( "Setting radio relay unit to %s", self.relayunitname ) ) return self end @@ -781,13 +774,13 @@ end -- @param #ATIS self -- @param #table freqs Table of frequencies in MHz. A single frequency can be given as a plain number (*i.e.* must not be table). -- @return #ATIS self -function ATIS:SetTowerFrequencies(freqs) - if type(freqs)=="table" then +function ATIS:SetTowerFrequencies( freqs ) + if type( freqs ) == "table" then -- nothing to do else - freqs={freqs} + freqs = { freqs } end - self.towerfrequency=freqs + self.towerfrequency = freqs return self end @@ -796,8 +789,8 @@ end -- @param #ATIS self -- @param #string runway Active runway, *e.g.* "31L". -- @return #ATIS self -function ATIS:SetActiveRunway(runway) - self.activerunway=tostring(runway) +function ATIS:SetActiveRunway( runway ) + self.activerunway = tostring( runway ) return self end @@ -805,7 +798,7 @@ end -- @param #ATIS self -- @return #ATIS self function ATIS:SetRunwayLength() - self.rwylength=true + self.rwylength = true return self end @@ -813,7 +806,7 @@ end -- @param #ATIS self -- @return #ATIS self function ATIS:SetElevation() - self.elevation=true + self.elevation = true return self end @@ -821,8 +814,8 @@ end -- @param #ATIS self -- @param #number power Radio power in Watts. Default 100 W. -- @return #ATIS self -function ATIS:SetRadioPower(power) - self.power=power or 100 +function ATIS:SetRadioPower( power ) + self.power = power or 100 return self end @@ -830,11 +823,11 @@ end -- @param #ATIS self -- @param #boolean switch If *true* or *nil*, marks are placed on F10 map. If *false* this feature is set to off (default). -- @return #ATIS self -function ATIS:SetMapMarks(switch) - if switch==nil or switch==true then - self.usemarker=true +function ATIS:SetMapMarks( switch ) + if switch == nil or switch == true then + self.usemarker = true else - self.usemarker=false + self.usemarker = false end return self end @@ -843,46 +836,46 @@ end -- @param #ATIS self -- @param #table headings Magnetic headings. Inverse (-180°) headings are added automatically. You only need to specify one heading per runway direction. "L"eft and "R" right can also be appended. -- @return #ATIS self -function ATIS:SetRunwayHeadingsMagnetic(headings) +function ATIS:SetRunwayHeadingsMagnetic( headings ) -- First make sure, we have a table. - if type(headings)=="table" then + if type( headings ) == "table" then -- nothing to do else - headings={headings} + headings = { headings } end - for _,heading in pairs(headings) do + for _, heading in pairs( headings ) do - if type(heading)=="number" then - heading=string.format("%02d", heading) + if type( heading ) == "number" then + heading = string.format( "%02d", heading ) end -- Add runway heading to table. - self:I(self.lid..string.format("Adding user specified magnetic runway heading %s", heading)) - table.insert(self.runwaymag, heading) + self:I( self.lid .. string.format( "Adding user specified magnetic runway heading %s", heading ) ) + table.insert( self.runwaymag, heading ) - local h=self:GetRunwayWithoutLR(heading) + local h = self:GetRunwayWithoutLR( heading ) - local head2=tonumber(h)-18 - if head2<0 then - head2=head2+36 + local head2 = tonumber( h ) - 18 + if head2 < 0 then + head2 = head2 + 36 end -- Convert to string. - head2=string.format("%02d", head2) + head2 = string.format( "%02d", head2 ) -- Append "L" or "R" if necessary. - local left=self:GetRunwayLR(heading) - if left==true then - head2=head2.."L" - elseif left==false then - head2=head2.."R" + local left = self:GetRunwayLR( heading ) + if left == true then + head2 = head2 .. "L" + elseif left == false then + head2 = head2 .. "R" end -- Add inverse runway heading to table. - self:I(self.lid..string.format("Adding user specified magnetic runway heading %s (inverse)", head2)) - table.insert(self.runwaymag, head2) + self:I( self.lid .. string.format( "Adding user specified magnetic runway heading %s (inverse)", head2 ) ) + table.insert( self.runwaymag, head2 ) end return self @@ -892,8 +885,8 @@ end -- @param #ATIS self -- @param #number duration Duration in seconds. Default 10 seconds. -- @return #ATIS self -function ATIS:SetSubtitleDuration(duration) - self.subduration=tonumber(duration or 10) +function ATIS:SetSubtitleDuration( duration ) + self.subduration = tonumber( duration or 10 ) return self end @@ -901,7 +894,7 @@ end -- @param #ATIS self -- @return #ATIS self function ATIS:SetMetricUnits() - self.metric=true + self.metric = true return self end @@ -909,7 +902,7 @@ end -- @param #ATIS self -- @return #ATIS self function ATIS:SetImperialUnits() - self.metric=false + self.metric = false return self end @@ -918,7 +911,7 @@ end -- @param #ATIS self -- @return #ATIS self function ATIS:SetPressureMillimetersMercury() - self.PmmHg=true + self.PmmHg = true return self end @@ -926,7 +919,7 @@ end -- @param #ATIS self -- @return #ATIS self function ATIS:SetTemperatureFahrenheit() - self.TDegF=true + self.TDegF = true return self end @@ -935,8 +928,8 @@ end -- @param #ATIS self -- @param #number Humidity Relative Humidity, i.e. a number between 0 and 100 %. Default is 50 %. -- @return #ATIS self -function ATIS:SetRelativeHumidity(Humidity) - self.relHumidity=Humidity or 50 +function ATIS:SetRelativeHumidity( Humidity ) + self.relHumidity = Humidity or 50 return self end @@ -944,12 +937,12 @@ end -- @param #ATIS self -- @param #boolean switch If true or nil, report altimeter QHN. If false, report QFF. -- @return #ATIS self -function ATIS:SetAltimeterQNH(switch) +function ATIS:SetAltimeterQNH( switch ) - if switch==true or switch==nil then - self.altimeterQNH=true + if switch == true or switch == nil then + self.altimeterQNH = true else - self.altimeterQNH=false + self.altimeterQNH = false end return self @@ -959,7 +952,7 @@ end -- @param #ATIS self -- @return #ATIS self function ATIS:ReportQNHOnly() - self.qnhonly=true + self.qnhonly = true return self end @@ -988,8 +981,8 @@ end -- @param #ATIS self -- @param #number magvar Magnetic variation in degrees. Positive for easterly and negative for westerly variation. Default is magnatic declinaton of the used map, c.f. @{Utilities.UTils#UTILS.GetMagneticDeclination}. -- @return #ATIS self -function ATIS:SetMagneticDeclination(magvar) - self.magvar=magvar or UTILS.GetMagneticDeclination() +function ATIS:SetMagneticDeclination( magvar ) + self.magvar = magvar or UTILS.GetMagneticDeclination() return self end @@ -997,8 +990,8 @@ end -- @param #ATIS self -- @param #number correction Correction of magnetic to true heading for runways in degrees. -- @return #ATIS self -function ATIS:SetRunwayCorrectionMagnetic2True(correction) - self.runwaym2t=correction or ATIS.RunwayM2T[UTILS.GetDCSMap()] +function ATIS:SetRunwayCorrectionMagnetic2True( correction ) + self.runwaym2t = correction or ATIS.RunwayM2T[UTILS.GetDCSMap()] return self end @@ -1006,7 +999,7 @@ end -- @param #ATIS self -- @return #ATIS self function ATIS:SetReportWindTrue() - self.windtrue=true + self.windtrue = true return self end @@ -1022,8 +1015,8 @@ end -- @param #ATIS self -- @param #number delta Time difference in hours. -- @return #ATIS self -function ATIS:SetZuluTimeDifference(delta) - self.zuludiff=delta +function ATIS:SetZuluTimeDifference( delta ) + self.zuludiff = delta return self end @@ -1031,7 +1024,7 @@ end -- @param #ATIS self -- @return #ATIS self function ATIS:ReportZuluTimeOnly() - self.zulutimeonly=true + self.zulutimeonly = true return self end @@ -1040,11 +1033,11 @@ end -- @param #number frequency ILS frequency in MHz. -- @param #string runway (Optional) Runway for which the given ILS frequency applies. Default all (*nil*). -- @return #ATIS self -function ATIS:AddILS(frequency, runway) - local ils={} --#ATIS.NavPoint - ils.frequency=tonumber(frequency) - ils.runway=runway and tostring(runway) or nil - table.insert(self.ils, ils) +function ATIS:AddILS( frequency, runway ) + local ils = {} -- #ATIS.NavPoint + ils.frequency = tonumber( frequency ) + ils.runway = runway and tostring( runway ) or nil + table.insert( self.ils, ils ) return self end @@ -1052,8 +1045,8 @@ end -- @param #ATIS self -- @param #number frequency VOR frequency. -- @return #ATIS self -function ATIS:SetVOR(frequency) - self.vor=frequency +function ATIS:SetVOR( frequency ) + self.vor = frequency return self end @@ -1062,11 +1055,11 @@ end -- @param #number frequency NDB frequency in MHz. -- @param #string runway (Optional) Runway for which the given NDB frequency applies. Default all (*nil*). -- @return #ATIS self -function ATIS:AddNDBouter(frequency, runway) - local ndb={} --#ATIS.NavPoint - ndb.frequency=tonumber(frequency) - ndb.runway=runway and tostring(runway) or nil - table.insert(self.ndbouter, ndb) +function ATIS:AddNDBouter( frequency, runway ) + local ndb = {} -- #ATIS.NavPoint + ndb.frequency = tonumber( frequency ) + ndb.runway = runway and tostring( runway ) or nil + table.insert( self.ndbouter, ndb ) return self end @@ -1075,11 +1068,11 @@ end -- @param #number frequency NDB frequency in MHz. -- @param #string runway (Optional) Runway for which the given NDB frequency applies. Default all (*nil*). -- @return #ATIS self -function ATIS:AddNDBinner(frequency, runway) - local ndb={} --#ATIS.NavPoint - ndb.frequency=tonumber(frequency) - ndb.runway=runway and tostring(runway) or nil - table.insert(self.ndbinner, ndb) +function ATIS:AddNDBinner( frequency, runway ) + local ndb = {} -- #ATIS.NavPoint + ndb.frequency = tonumber( frequency ) + ndb.runway = runway and tostring( runway ) or nil + table.insert( self.ndbinner, ndb ) return self end @@ -1087,8 +1080,8 @@ end -- @param #ATIS self -- @param #number channel TACAN channel. -- @return #ATIS self -function ATIS:SetTACAN(channel) - self.tacan=channel +function ATIS:SetTACAN( channel ) + self.tacan = channel return self end @@ -1096,8 +1089,8 @@ end -- @param #ATIS self -- @param #number channel RSBN channel. -- @return #ATIS self -function ATIS:SetRSBN(channel) - self.rsbn=channel +function ATIS:SetRSBN( channel ) + self.rsbn = channel return self end @@ -1106,24 +1099,23 @@ end -- @param #number channel PRMG channel. -- @param #string runway (Optional) Runway for which the given PRMG channel applies. Default all (*nil*). -- @return #ATIS self -function ATIS:AddPRMG(channel, runway) - local ndb={} --#ATIS.NavPoint - ndb.frequency=tonumber(channel) - ndb.runway=runway and tostring(runway) or nil - table.insert(self.prmg, ndb) +function ATIS:AddPRMG( channel, runway ) + local ndb = {} -- #ATIS.NavPoint + ndb.frequency = tonumber( channel ) + ndb.runway = runway and tostring( runway ) or nil + table.insert( self.prmg, ndb ) return self end - --- Place marks with runway data on the F10 map. -- @param #ATIS self -- @param #boolean markall If true, mark all runways of the map. By default only the current ATIS runways are marked. -function ATIS:MarkRunways(markall) - local airbases=AIRBASE.GetAllAirbases() - for _,_airbase in pairs(airbases) do - local airbase=_airbase --Wrapper.Airbase#AIRBASE - if (not markall and airbase:GetName()==self.airbasename) or markall==true then - airbase:GetRunwayData(self.runwaym2t, true) +function ATIS:MarkRunways( markall ) + local airbases = AIRBASE.GetAllAirbases() + for _, _airbase in pairs( airbases ) do + local airbase = _airbase -- Wrapper.Airbase#AIRBASE + if (not markall and airbase:GetName() == self.airbasename) or markall == true then + airbase:GetRunwayData( self.runwaym2t, true ) end end end @@ -1136,16 +1128,16 @@ end -- @param #string Voice Specific voice. Overrides `Gender` and `Culture`. -- @param #number Port SRS port. Default 5002. -- @return #ATIS self -function ATIS:SetSRS(PathToSRS, Gender, Culture, Voice, Port) - self.useSRS=true - self.msrs=MSRS:New(PathToSRS, self.frequency, self.modulation) - self.msrs:SetGender(Gender) - self.msrs:SetCulture(Culture) - self.msrs:SetVoice(Voice) - self.msrs:SetPort(Port) - self.msrs:SetCoalition(self:GetCoalition()) - if self.dTQueueCheck<=10 then - self:SetQueueUpdateTime(90) +function ATIS:SetSRS( PathToSRS, Gender, Culture, Voice, Port ) + self.useSRS = true + self.msrs = MSRS:New( PathToSRS, self.frequency, self.modulation ) + self.msrs:SetGender( Gender ) + self.msrs:SetCulture( Culture ) + self.msrs:SetVoice( Voice ) + self.msrs:SetPort( Port ) + self.msrs:SetCoalition( self:GetCoalition() ) + if self.dTQueueCheck <= 10 then + self:SetQueueUpdateTime( 90 ) end return self end @@ -1154,15 +1146,15 @@ end -- @param #ATIS self -- @param #number TimeInterval Interval in seconds. Default 5 sec. -- @return #ATIS self -function ATIS:SetQueueUpdateTime(TimeInterval) - self.dTQueueCheck=TimeInterval or 5 +function ATIS:SetQueueUpdateTime( TimeInterval ) + self.dTQueueCheck = TimeInterval or 5 end --- Get the coalition of the associated airbase. -- @param #ATIS self -- @return #number Coalition of the associcated airbase. function ATIS:GetCoalition() - local coal=self.airbase and self.airbase:GetCoalition() or nil + local coal = self.airbase and self.airbase:GetCoalition() or nil return coal end @@ -1175,51 +1167,51 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function ATIS:onafterStart(From, Event, To) +function ATIS:onafterStart( From, Event, To ) -- Check that this is an airdrome. - if self.airbase:GetAirbaseCategory()~=Airbase.Category.AIRDROME then - self:E(self.lid..string.format("ERROR: Cannot start ATIS for airbase %s! Only AIRDROMES are supported but NOT FARPS or SHIPS.", self.airbasename)) + if self.airbase:GetAirbaseCategory() ~= Airbase.Category.AIRDROME then + self:E( self.lid .. string.format( "ERROR: Cannot start ATIS for airbase %s! Only AIRDROMES are supported but NOT FARPS or SHIPS.", self.airbasename ) ) return end -- Info. - self:I(self.lid..string.format("Starting ATIS v%s for airbase %s on %.3f MHz Modulation=%d", ATIS.version, self.airbasename, self.frequency, self.modulation)) + self:I( self.lid .. string.format( "Starting ATIS v%s for airbase %s on %.3f MHz Modulation=%d", ATIS.version, self.airbasename, self.frequency, self.modulation ) ) -- Start radio queue. - self.radioqueue=RADIOQUEUE:New(self.frequency, self.modulation, string.format("ATIS %s", self.airbasename)) + self.radioqueue = RADIOQUEUE:New( self.frequency, self.modulation, string.format( "ATIS %s", self.airbasename ) ) -- Send coordinate is airbase coord. - self.radioqueue:SetSenderCoordinate(self.airbase:GetCoordinate()) + self.radioqueue:SetSenderCoordinate( self.airbase:GetCoordinate() ) -- Set relay unit if we have one. - self.radioqueue:SetSenderUnitName(self.relayunitname) + self.radioqueue:SetSenderUnitName( self.relayunitname ) -- Set radio power. - self.radioqueue:SetRadioPower(self.power) + self.radioqueue:SetRadioPower( self.power ) -- Init numbers. - self.radioqueue:SetDigit(0, ATIS.Sound.N0.filename, ATIS.Sound.N0.duration, self.soundpath) - self.radioqueue:SetDigit(1, ATIS.Sound.N1.filename, ATIS.Sound.N1.duration, self.soundpath) - self.radioqueue:SetDigit(2, ATIS.Sound.N2.filename, ATIS.Sound.N2.duration, self.soundpath) - self.radioqueue:SetDigit(3, ATIS.Sound.N3.filename, ATIS.Sound.N3.duration, self.soundpath) - self.radioqueue:SetDigit(4, ATIS.Sound.N4.filename, ATIS.Sound.N4.duration, self.soundpath) - self.radioqueue:SetDigit(5, ATIS.Sound.N5.filename, ATIS.Sound.N5.duration, self.soundpath) - self.radioqueue:SetDigit(6, ATIS.Sound.N6.filename, ATIS.Sound.N6.duration, self.soundpath) - self.radioqueue:SetDigit(7, ATIS.Sound.N7.filename, ATIS.Sound.N7.duration, self.soundpath) - self.radioqueue:SetDigit(8, ATIS.Sound.N8.filename, ATIS.Sound.N8.duration, self.soundpath) - self.radioqueue:SetDigit(9, ATIS.Sound.N9.filename, ATIS.Sound.N9.duration, self.soundpath) + self.radioqueue:SetDigit( 0, ATIS.Sound.N0.filename, ATIS.Sound.N0.duration, self.soundpath ) + self.radioqueue:SetDigit( 1, ATIS.Sound.N1.filename, ATIS.Sound.N1.duration, self.soundpath ) + self.radioqueue:SetDigit( 2, ATIS.Sound.N2.filename, ATIS.Sound.N2.duration, self.soundpath ) + self.radioqueue:SetDigit( 3, ATIS.Sound.N3.filename, ATIS.Sound.N3.duration, self.soundpath ) + self.radioqueue:SetDigit( 4, ATIS.Sound.N4.filename, ATIS.Sound.N4.duration, self.soundpath ) + self.radioqueue:SetDigit( 5, ATIS.Sound.N5.filename, ATIS.Sound.N5.duration, self.soundpath ) + self.radioqueue:SetDigit( 6, ATIS.Sound.N6.filename, ATIS.Sound.N6.duration, self.soundpath ) + self.radioqueue:SetDigit( 7, ATIS.Sound.N7.filename, ATIS.Sound.N7.duration, self.soundpath ) + self.radioqueue:SetDigit( 8, ATIS.Sound.N8.filename, ATIS.Sound.N8.duration, self.soundpath ) + self.radioqueue:SetDigit( 9, ATIS.Sound.N9.filename, ATIS.Sound.N9.duration, self.soundpath ) -- Start radio queue. - self.radioqueue:Start(1, 0.1) - + self.radioqueue:Start( 1, 0.1 ) + -- Handle airbase capture -- Handle events. - self:HandleEvent(EVENTS.BaseCaptured) + self:HandleEvent( EVENTS.BaseCaptured ) -- Init status updates. - self:__Status(-2) - self:__CheckQueue(-3) + self:__Status( -2 ) + self:__CheckQueue( -3 ) end --- Update status. @@ -1227,30 +1219,29 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function ATIS:onafterStatus(From, Event, To) +function ATIS:onafterStatus( From, Event, To ) -- Get FSM state. - local fsmstate=self:GetState() + local fsmstate = self:GetState() - local relayunitstatus="N/A" + local relayunitstatus = "N/A" if self.relayunitname then - local ru=UNIT:FindByName(self.relayunitname) + local ru = UNIT:FindByName( self.relayunitname ) if ru then - relayunitstatus=tostring(ru:IsAlive()) + relayunitstatus = tostring( ru:IsAlive() ) end end - -- Info text. - local text=string.format("State %s: Freq=%.3f MHz %s", fsmstate, self.frequency, UTILS.GetModulationName(self.modulation)) + -- Info text. + local text = string.format( "State %s: Freq=%.3f MHz %s", fsmstate, self.frequency, UTILS.GetModulationName( self.modulation ) ) if self.useSRS then - text=text..string.format(", SRS path=%s (%s), gender=%s, culture=%s, voice=%s", - tostring(self.msrs.path), tostring(self.msrs.port), tostring(self.msrs.gender), tostring(self.msrs.culture), tostring(self.msrs.voice)) + text = text .. string.format( ", SRS path=%s (%s), gender=%s, culture=%s, voice=%s", tostring( self.msrs.path ), tostring( self.msrs.port ), tostring( self.msrs.gender ), tostring( self.msrs.culture ), tostring( self.msrs.voice ) ) else - text=text..string.format(", Relay unit=%s (alive=%s)", tostring(self.relayunitname), relayunitstatus) + text = text .. string.format( ", Relay unit=%s (alive=%s)", tostring( self.relayunitname ), relayunitstatus ) end - self:I(self.lid..text) + self:I( self.lid .. text ) - self:__Status(-60) + self:__Status( -60 ) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1262,27 +1253,25 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function ATIS:onafterCheckQueue(From, Event, To) +function ATIS:onafterCheckQueue( From, Event, To ) if self.useSRS then - + self:Broadcast() - + else - - if #self.radioqueue.queue==0 then - self:T(self.lid..string.format("Radio queue empty. Repeating message.")) + + if #self.radioqueue.queue == 0 then + self:T( self.lid .. string.format( "Radio queue empty. Repeating message." ) ) self:Broadcast() else - self:T2(self.lid..string.format("Radio queue %d transmissions queued.", #self.radioqueue.queue)) + self:T2( self.lid .. string.format( "Radio queue %d transmissions queued.", #self.radioqueue.queue ) ) end - - - + end -- Check back in 5 seconds. - self:__CheckQueue(-math.abs(self.dTQueueCheck)) + self:__CheckQueue( -math.abs( self.dTQueueCheck ) ) end --- Broadcast ATIS radio message. @@ -1290,69 +1279,67 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function ATIS:onafterBroadcast(From, Event, To) +function ATIS:onafterBroadcast( From, Event, To ) -- Get current coordinate. - local coord=self.airbase:GetCoordinate() + local coord = self.airbase:GetCoordinate() -- Get elevation. - local height=coord:GetLandHeight() + local height = coord:GetLandHeight() ---------------- --- Pressure --- ---------------- -- Pressure in hPa. - local qfe=coord:GetPressure(height) - local qnh=coord:GetPressure(0) + local qfe = coord:GetPressure( height ) + local qnh = coord:GetPressure( 0 ) if self.altimeterQNH then -- Some constants. - local L=-0.0065 --[K/m] - local R= 8.31446 --[J/mol/K] - local g= 9.80665 --[m/s^2] - local M= 0.0289644 --[kg/mol] - local T0=coord:GetTemperature(0)+273.15 --[K] Temp at sea level. - local TS=288.15 -- Standard Temperature assumed by Altimeter is 15°C - local q=qnh*100 + local L = -0.0065 -- [K/m] + local R = 8.31446 -- [J/mol/K] + local g = 9.80665 -- [m/s^2] + local M = 0.0289644 -- [kg/mol] + local T0 = coord:GetTemperature( 0 ) + 273.15 -- [K] Temp at sea level. + local TS = 288.15 -- Standard Temperature assumed by Altimeter is 15°C + local q = qnh * 100 -- Calculate Pressure. - local P=q*(1+L*height/T0)^(-g*M/(R*L)) -- Pressure at sea level - local Q=P/(1+L*height/TS)^(-g*M/(R*L)) -- Altimeter QNH - local A=(T0/L)*((P/q)^(((-R*L)/(g*M)))-1) -- Altitude check - + local P = q * (1 + L * height / T0) ^ (-g * M / (R * L)) -- Pressure at sea level + local Q = P / (1 + L * height / TS) ^ (-g * M / (R * L)) -- Altimeter QNH + local A = (T0 / L) * ((P / q) ^ (((-R * L) / (g * M))) - 1) -- Altitude check -- Debug aoutput - self:T2(self.lid..string.format("height=%.1f, A=%.1f, T0=%.1f, QFE=%.1f, QNH=%.1f, P=%.1f, Q=%.1f hPa = %.2f", height, A, T0-273.15, qfe, qnh, P/100, Q/100, UTILS.hPa2inHg(Q/100))) + self:T2( self.lid .. string.format( "height=%.1f, A=%.1f, T0=%.1f, QFE=%.1f, QNH=%.1f, P=%.1f, Q=%.1f hPa = %.2f", height, A, T0 - 273.15, qfe, qnh, P / 100, Q / 100, UTILS.hPa2inHg( Q / 100 ) ) ) -- Set QNH value in hPa. - qnh=Q/100 + qnh = Q / 100 end - -- Convert to inHg. if self.PmmHg then - qfe=UTILS.hPa2mmHg(qfe) - qnh=UTILS.hPa2mmHg(qnh) + qfe = UTILS.hPa2mmHg( qfe ) + qnh = UTILS.hPa2mmHg( qnh ) else if not self.metric then - qfe=UTILS.hPa2inHg(qfe) - qnh=UTILS.hPa2inHg(qnh) + qfe = UTILS.hPa2inHg( qfe ) + qnh = UTILS.hPa2inHg( qnh ) end end - local QFE=UTILS.Split(string.format("%.2f", qfe), ".") - local QNH=UTILS.Split(string.format("%.2f", qnh), ".") + local QFE = UTILS.Split( string.format( "%.2f", qfe ), "." ) + local QNH = UTILS.Split( string.format( "%.2f", qnh ), "." ) if self.PmmHg then - QFE=UTILS.Split(string.format("%.1f", qfe), ".") - QNH=UTILS.Split(string.format("%.1f", qnh), ".") + QFE = UTILS.Split( string.format( "%.1f", qfe ), "." ) + QNH = UTILS.Split( string.format( "%.1f", qnh ), "." ) else if self.metric then - QFE=UTILS.Split(string.format("%.1f", qfe), ".") - QNH=UTILS.Split(string.format("%.1f", qnh), ".") + QFE = UTILS.Split( string.format( "%.1f", qfe ), "." ) + QNH = UTILS.Split( string.format( "%.1f", qnh ), "." ) end end @@ -1361,126 +1348,124 @@ function ATIS:onafterBroadcast(From, Event, To) ------------ -- Get wind direction and speed in m/s. - local windFrom, windSpeed=coord:GetWind(height+10) + local windFrom, windSpeed = coord:GetWind( height + 10 ) -- Wind in magnetic or true. - local magvar=self.magvar + local magvar = self.magvar if self.windtrue then - magvar=0 + magvar = 0 end - windFrom=windFrom-magvar - + windFrom = windFrom - magvar + -- Correct negative values. - if windFrom<0 then - windFrom=windFrom+360 + if windFrom < 0 then + windFrom = windFrom + 360 end - local WINDFROM=string.format("%03d", windFrom) - local WINDSPEED=string.format("%d", UTILS.MpsToKnots(windSpeed)) - + local WINDFROM = string.format( "%03d", windFrom ) + local WINDSPEED = string.format( "%d", UTILS.MpsToKnots( windSpeed ) ) + -- Report North as 0. - if WINDFROM=="000" then - WINDFROM="360" + if WINDFROM == "000" then + WINDFROM = "360" end if self.metric then - WINDSPEED=string.format("%d", windSpeed) + WINDSPEED = string.format( "%d", windSpeed ) end -------------- --- Runway --- -------------- - local runway, rwyLeft=self:GetActiveRunway() + local runway, rwyLeft = self:GetActiveRunway() ------------ --- Time --- ------------ - local time=timer.getAbsTime() + local time = timer.getAbsTime() -- Conversion to Zulu time. if self.zuludiff then -- User specified. - time=time-self.zuludiff*60*60 + time = time - self.zuludiff * 60 * 60 else - time=time-UTILS.GMTToLocalTimeDifference()*60*60 + time = time - UTILS.GMTToLocalTimeDifference() * 60 * 60 end if time < 0 then - time = 24*60*60 + time --avoid negative time around midnight - end - - local clock=UTILS.SecondsToClock(time) - local zulu=UTILS.Split(clock, ":") - local ZULU=string.format("%s%s", zulu[1], zulu[2]) - if self.useSRS then - ZULU=string.format("%s hours", zulu[1]) + time = 24 * 60 * 60 + time -- avoid negative time around midnight end + local clock = UTILS.SecondsToClock( time ) + local zulu = UTILS.Split( clock, ":" ) + local ZULU = string.format( "%s%s", zulu[1], zulu[2] ) + if self.useSRS then + ZULU = string.format( "%s hours", zulu[1] ) + end -- NATO time stamp. 0=Alfa, 1=Bravo, 2=Charlie, etc. - local NATO=ATIS.Alphabet[tonumber(zulu[1])+1] + local NATO = ATIS.Alphabet[tonumber( zulu[1] ) + 1] -- Debug. - self:T3(string.format("clock=%s", tostring(clock))) - self:T3(string.format("zulu1=%s", tostring(zulu[1]))) - self:T3(string.format("zulu2=%s", tostring(zulu[2]))) - self:T3(string.format("ZULU =%s", tostring(ZULU))) - self:T3(string.format("NATO =%s", tostring(NATO))) + self:T3( string.format( "clock=%s", tostring( clock ) ) ) + self:T3( string.format( "zulu1=%s", tostring( zulu[1] ) ) ) + self:T3( string.format( "zulu2=%s", tostring( zulu[2] ) ) ) + self:T3( string.format( "ZULU =%s", tostring( ZULU ) ) ) + self:T3( string.format( "NATO =%s", tostring( NATO ) ) ) -------------------------- --- Sunrise and Sunset --- -------------------------- - local sunrise=coord:GetSunrise() - sunrise=UTILS.Split(sunrise, ":") - local SUNRISE=string.format("%s%s", sunrise[1], sunrise[2]) + local sunrise = coord:GetSunrise() + sunrise = UTILS.Split( sunrise, ":" ) + local SUNRISE = string.format( "%s%s", sunrise[1], sunrise[2] ) if self.useSRS then - SUNRISE=string.format("%s %s hours", sunrise[1], sunrise[2]) - end + SUNRISE = string.format( "%s %s hours", sunrise[1], sunrise[2] ) + end - local sunset=coord:GetSunset() - sunset=UTILS.Split(sunset, ":") - local SUNSET=string.format("%s%s", sunset[1], sunset[2]) + local sunset = coord:GetSunset() + sunset = UTILS.Split( sunset, ":" ) + local SUNSET = string.format( "%s%s", sunset[1], sunset[2] ) if self.useSRS then - SUNSET=string.format("%s %s hours", sunset[1], sunset[2]) - end - + SUNSET = string.format( "%s %s hours", sunset[1], sunset[2] ) + end --------------------------------- --- Temperature and Dew Point --- --------------------------------- -- Temperature in °C. - local temperature=coord:GetTemperature(height+5) - + local temperature = coord:GetTemperature( height + 5 ) + -- Dew point in °C. - local dewpoint=temperature-(100-self.relHumidity)/5 + local dewpoint = temperature - (100 - self.relHumidity) / 5 -- Convert to °F. if self.TDegF then - temperature=UTILS.CelsiusToFahrenheit(temperature) - dewpoint=UTILS.CelsiusToFahrenheit(dewpoint) + temperature = UTILS.CelsiusToFahrenheit( temperature ) + dewpoint = UTILS.CelsiusToFahrenheit( dewpoint ) end - local TEMPERATURE=string.format("%d", math.abs(temperature)) - local DEWPOINT=string.format("%d", math.abs(dewpoint)) + local TEMPERATURE = string.format( "%d", math.abs( temperature ) ) + local DEWPOINT = string.format( "%d", math.abs( dewpoint ) ) --------------- --- Weather --- --------------- -- Get mission weather info. Most of this is static. - local clouds, visibility, turbulence, fog, dust, static=self:GetMissionWeather() + local clouds, visibility, turbulence, fog, dust, static = self:GetMissionWeather() -- Check that fog is actually "thick" enough to reach the airport. If an airport is in the mountains, fog might not affect it as it is measured from sea level. - if fog and fog.thicknessUTILS.FeetToMeters(1500) then - dust=nil + if dust and height + 25 > UTILS.FeetToMeters( 1500 ) then + dust = nil end ------------------ @@ -1488,214 +1473,214 @@ function ATIS:onafterBroadcast(From, Event, To) ------------------ -- Get min visibility. - local visibilitymin=visibility + local visibilitymin = visibility if fog then - if fog.visibility 10 then - reportedviz=10 + reportedviz = 10 end - VISIBILITY=string.format("%d", reportedviz) + VISIBILITY = string.format( "%d", reportedviz ) else -- max reported visibility 10 NM - local reportedviz=UTILS.Round(UTILS.MetersToSM(visibilitymin)) + local reportedviz = UTILS.Round( UTILS.MetersToSM( visibilitymin ) ) if reportedviz > 10 then - reportedviz=10 + reportedviz = 10 end - VISIBILITY=string.format("%d", reportedviz) + VISIBILITY = string.format( "%d", reportedviz ) end -------------- --- Clouds --- -------------- - local cloudbase=clouds.base - local cloudceil=clouds.base+clouds.thickness - local clouddens=clouds.density + local cloudbase = clouds.base + local cloudceil = clouds.base + clouds.thickness + local clouddens = clouds.density -- Cloud preset (DCS 2.7) - local cloudspreset=clouds.preset or "Nothing" - - -- Precepitation: 0=None, 1=Rain, 2=Thunderstorm, 3=Snow, 4=Snowstorm. - local precepitation=0 + local cloudspreset = clouds.preset or "Nothing" - if cloudspreset:find("Preset10") then + -- Precepitation: 0=None, 1=Rain, 2=Thunderstorm, 3=Snow, 4=Snowstorm. + local precepitation = 0 + + if cloudspreset:find( "Preset10" ) then -- Scattered 5 - clouddens=4 - elseif cloudspreset:find("Preset11") then + clouddens = 4 + elseif cloudspreset:find( "Preset11" ) then -- Scattered 6 - clouddens=4 - elseif cloudspreset:find("Preset12") then + clouddens = 4 + elseif cloudspreset:find( "Preset12" ) then -- Scattered 7 - clouddens=4 - elseif cloudspreset:find("Preset13") then + clouddens = 4 + elseif cloudspreset:find( "Preset13" ) then -- Broken 1 - clouddens=7 - elseif cloudspreset:find("Preset14") then + clouddens = 7 + elseif cloudspreset:find( "Preset14" ) then -- Broken 2 - clouddens=7 - elseif cloudspreset:find("Preset15") then + clouddens = 7 + elseif cloudspreset:find( "Preset15" ) then -- Broken 3 - clouddens=7 - elseif cloudspreset:find("Preset16") then + clouddens = 7 + elseif cloudspreset:find( "Preset16" ) then -- Broken 4 - clouddens=7 - elseif cloudspreset:find("Preset17") then + clouddens = 7 + elseif cloudspreset:find( "Preset17" ) then -- Broken 5 - clouddens=7 - elseif cloudspreset:find("Preset18") then + clouddens = 7 + elseif cloudspreset:find( "Preset18" ) then -- Broken 6 - clouddens=7 - elseif cloudspreset:find("Preset19") then + clouddens = 7 + elseif cloudspreset:find( "Preset19" ) then -- Broken 7 - clouddens=7 - elseif cloudspreset:find("Preset20") then + clouddens = 7 + elseif cloudspreset:find( "Preset20" ) then -- Broken 8 - clouddens=7 - elseif cloudspreset:find("Preset21") then + clouddens = 7 + elseif cloudspreset:find( "Preset21" ) then -- Overcast 1 - clouddens=9 - elseif cloudspreset:find("Preset22") then + clouddens = 9 + elseif cloudspreset:find( "Preset22" ) then -- Overcast 2 - clouddens=9 - elseif cloudspreset:find("Preset23") then + clouddens = 9 + elseif cloudspreset:find( "Preset23" ) then -- Overcast 3 - clouddens=9 - elseif cloudspreset:find("Preset24") then + clouddens = 9 + elseif cloudspreset:find( "Preset24" ) then -- Overcast 4 - clouddens=9 - elseif cloudspreset:find("Preset25") then + clouddens = 9 + elseif cloudspreset:find( "Preset25" ) then -- Overcast 5 - clouddens=9 - elseif cloudspreset:find("Preset26") then + clouddens = 9 + elseif cloudspreset:find( "Preset26" ) then -- Overcast 6 - clouddens=9 - elseif cloudspreset:find("Preset27") then + clouddens = 9 + elseif cloudspreset:find( "Preset27" ) then -- Overcast 7 - clouddens=9 - elseif cloudspreset:find("Preset1") then + clouddens = 9 + elseif cloudspreset:find( "Preset1" ) then -- Light Scattered 1 - clouddens=1 - elseif cloudspreset:find("Preset2") then + clouddens = 1 + elseif cloudspreset:find( "Preset2" ) then -- Light Scattered 2 - clouddens=1 - elseif cloudspreset:find("Preset3") then + clouddens = 1 + elseif cloudspreset:find( "Preset3" ) then -- High Scattered 1 - clouddens=4 - elseif cloudspreset:find("Preset4") then + clouddens = 4 + elseif cloudspreset:find( "Preset4" ) then -- High Scattered 2 - clouddens=4 - elseif cloudspreset:find("Preset5") then + clouddens = 4 + elseif cloudspreset:find( "Preset5" ) then -- Scattered 1 - clouddens=4 - elseif cloudspreset:find("Preset6") then + clouddens = 4 + elseif cloudspreset:find( "Preset6" ) then -- Scattered 2 - clouddens=4 - elseif cloudspreset:find("Preset7") then + clouddens = 4 + elseif cloudspreset:find( "Preset7" ) then -- Scattered 3 - clouddens=4 - elseif cloudspreset:find("Preset8") then + clouddens = 4 + elseif cloudspreset:find( "Preset8" ) then -- High Scattered 3 - clouddens=4 - elseif cloudspreset:find("Preset9") then + clouddens = 4 + elseif cloudspreset:find( "Preset9" ) then -- Scattered 4 - clouddens=4 - elseif cloudspreset:find("RainyPreset") then - -- Overcast + Rain - clouddens=9 - if temperature>5 then - precepitation=1 -- rain - else - precepitation=3 -- snow - end - elseif cloudspreset:find("RainyPreset1") then - -- Overcast + Rain - clouddens=9 - if temperature>5 then - precepitation=1 -- rain - else - precepitation=3 -- snow - end - elseif cloudspreset:find("RainyPreset2") then - -- Overcast + Rain - clouddens=9 - if temperature>5 then - precepitation=1 -- rain - else - precepitation=3 -- snow - end - elseif cloudspreset:find("RainyPreset3") then + clouddens = 4 + elseif cloudspreset:find( "RainyPreset" ) then -- Overcast + Rain - clouddens=9 - if temperature>5 then - precepitation=1 -- rain + clouddens = 9 + if temperature > 5 then + precepitation = 1 -- rain else - precepitation=3 -- snow + precepitation = 3 -- snow + end + elseif cloudspreset:find( "RainyPreset1" ) then + -- Overcast + Rain + clouddens = 9 + if temperature > 5 then + precepitation = 1 -- rain + else + precepitation = 3 -- snow + end + elseif cloudspreset:find( "RainyPreset2" ) then + -- Overcast + Rain + clouddens = 9 + if temperature > 5 then + precepitation = 1 -- rain + else + precepitation = 3 -- snow + end + elseif cloudspreset:find( "RainyPreset3" ) then + -- Overcast + Rain + clouddens = 9 + if temperature > 5 then + precepitation = 1 -- rain + else + precepitation = 3 -- snow end end - - local CLOUDBASE=string.format("%d", UTILS.MetersToFeet(cloudbase)) - local CLOUDCEIL=string.format("%d", UTILS.MetersToFeet(cloudceil)) + + 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) + CLOUDBASE = string.format( "%d", cloudbase ) + CLOUDCEIL = string.format( "%d", cloudceil ) end -- Cloud base/ceiling in thousands and hundrets of ft/meters. - local CLOUDBASE1000, CLOUDBASE0100=self:_GetThousandsAndHundreds(UTILS.MetersToFeet(cloudbase)) - local CLOUDCEIL1000, CLOUDCEIL0100=self:_GetThousandsAndHundreds(UTILS.MetersToFeet(cloudceil)) + 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) + CLOUDBASE1000, CLOUDBASE0100 = self:_GetThousandsAndHundreds( cloudbase ) + CLOUDCEIL1000, CLOUDCEIL0100 = self:_GetThousandsAndHundreds( cloudceil ) end -- No cloud info for dynamic weather. - local CloudCover={} --#ATIS.Soundfile - CloudCover=ATIS.Sound.CloudsNotAvailable - local CLOUDSsub="Cloud coverage information not available" + local CloudCover = {} -- #ATIS.Soundfile + CloudCover = ATIS.Sound.CloudsNotAvailable + local CLOUDSsub = "Cloud coverage information not available" -- Only valid for static weather. if static then - if clouddens>=9 then + if clouddens >= 9 then -- Overcast 9,10 - CloudCover=ATIS.Sound.CloudsOvercast - CLOUDSsub="Overcast" - elseif clouddens>=7 then + CloudCover = ATIS.Sound.CloudsOvercast + CLOUDSsub = "Overcast" + elseif clouddens >= 7 then -- Broken 7,8 - CloudCover=ATIS.Sound.CloudsBroken - CLOUDSsub="Broken clouds" - elseif clouddens>=4 then + CloudCover = ATIS.Sound.CloudsBroken + CLOUDSsub = "Broken clouds" + elseif clouddens >= 4 then -- Scattered 4,5,6 - CloudCover=ATIS.Sound.CloudsScattered - CLOUDSsub="Scattered clouds" - elseif clouddens>=1 then + CloudCover = ATIS.Sound.CloudsScattered + CLOUDSsub = "Scattered clouds" + elseif clouddens >= 1 then -- Few 1,2,3 - CloudCover=ATIS.Sound.CloudsFew - CLOUDSsub="Few clouds" + CloudCover = ATIS.Sound.CloudsFew + CLOUDSsub = "Few clouds" else -- No clouds - CLOUDBASE=nil - CLOUDCEIL=nil - CloudCover=ATIS.Sound.CloudsNo - CLOUDSsub="No clouds" + CLOUDBASE = nil + CLOUDCEIL = nil + CloudCover = ATIS.Sound.CloudsNo + CLOUDSsub = "No clouds" end end @@ -1704,558 +1689,558 @@ function ATIS:onafterBroadcast(From, Event, To) -------------------- -- Subtitle - local subtitle="" + local subtitle = "" - --Airbase name - 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" + -- Airbase name + 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 if not self.useSRS then - self.radioqueue:NewTransmission(string.format("%s/%s.ogg", self.theatre, self.airbasename), 3.0, self.soundpath, nil, nil, subtitle, self.subduration) + self.radioqueue:NewTransmission( string.format( "%s/%s.ogg", self.theatre, self.airbasename ), 3.0, self.soundpath, nil, nil, subtitle, self.subduration ) end - local alltext=subtitle + local alltext = subtitle -- Information tag - subtitle=string.format("Information %s", NATO) - local _INFORMATION=subtitle + subtitle = string.format( "Information %s", NATO ) + local _INFORMATION = subtitle if not self.useSRS then - self:Transmission(ATIS.Sound.Information, 0.5, subtitle) - self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg", NATO), 0.75, self.soundpath) + self:Transmission( ATIS.Sound.Information, 0.5, subtitle ) + self.radioqueue:NewTransmission( string.format( "NATO Alphabet/%s.ogg", NATO ), 0.75, self.soundpath ) end - alltext=alltext..";\n"..subtitle + alltext = alltext .. ";\n" .. subtitle -- Zulu Time - subtitle=string.format("%s Zulu", ZULU) + subtitle = string.format( "%s Zulu", ZULU ) if not self.useSRS then - self.radioqueue:Number2Transmission(ZULU, nil, 0.5) - self:Transmission(ATIS.Sound.Zulu, 0.2, subtitle) + self.radioqueue:Number2Transmission( ZULU, nil, 0.5 ) + self:Transmission( ATIS.Sound.Zulu, 0.2, subtitle ) end - alltext=alltext..";\n"..subtitle - + alltext = alltext .. ";\n" .. subtitle + if not self.zulutimeonly then -- Sunrise Time - subtitle=string.format("Sunrise at %s local time", SUNRISE) + subtitle = string.format( "Sunrise at %s local time", SUNRISE ) if not self.useSRS then - self:Transmission(ATIS.Sound.SunriseAt, 0.5, subtitle) - self.radioqueue:Number2Transmission(SUNRISE, nil, 0.2) - self:Transmission(ATIS.Sound.TimeLocal, 0.2) + self:Transmission( ATIS.Sound.SunriseAt, 0.5, subtitle ) + self.radioqueue:Number2Transmission( SUNRISE, nil, 0.2 ) + self:Transmission( ATIS.Sound.TimeLocal, 0.2 ) end - alltext=alltext..";\n"..subtitle - + alltext = alltext .. ";\n" .. subtitle + -- Sunset Time - subtitle=string.format("Sunset at %s local time", SUNSET) + subtitle = string.format( "Sunset at %s local time", SUNSET ) if not self.useSRS then - self:Transmission(ATIS.Sound.SunsetAt, 0.5, subtitle) - self.radioqueue:Number2Transmission(SUNSET, nil, 0.5) - self:Transmission(ATIS.Sound.TimeLocal, 0.2) + self:Transmission( ATIS.Sound.SunsetAt, 0.5, subtitle ) + self.radioqueue:Number2Transmission( SUNSET, nil, 0.5 ) + self:Transmission( ATIS.Sound.TimeLocal, 0.2 ) end - alltext=alltext..";\n"..subtitle + alltext = alltext .. ";\n" .. subtitle end - + -- Wind if self.metric then - subtitle=string.format("Wind from %s at %s m/s", WINDFROM, WINDSPEED) + subtitle = string.format( "Wind from %s at %s m/s", WINDFROM, WINDSPEED ) else - subtitle=string.format("Wind from %s at %s knots", WINDFROM, WINDSPEED) + subtitle = string.format( "Wind from %s at %s knots", WINDFROM, WINDSPEED ) end - if turbulence>0 then - subtitle=subtitle..", gusting" + if turbulence > 0 then + subtitle = subtitle .. ", gusting" end - local _WIND=subtitle + local _WIND = subtitle if not self.useSRS then - self:Transmission(ATIS.Sound.WindFrom, 1.0, subtitle) - self.radioqueue:Number2Transmission(WINDFROM) - self:Transmission(ATIS.Sound.At, 0.2) - self.radioqueue:Number2Transmission(WINDSPEED) + 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) + self:Transmission( ATIS.Sound.MetersPerSecond, 0.2 ) else - self:Transmission(ATIS.Sound.Knots, 0.2) + self:Transmission( ATIS.Sound.Knots, 0.2 ) end - if turbulence>0 then - self:Transmission(ATIS.Sound.Gusting, 0.2) + if turbulence > 0 then + self:Transmission( ATIS.Sound.Gusting, 0.2 ) end end - alltext=alltext..";\n"..subtitle + alltext = alltext .. ";\n" .. subtitle -- Visibility if self.metric then - subtitle=string.format("Visibility %s km", VISIBILITY) + subtitle = string.format( "Visibility %s km", VISIBILITY ) else - subtitle=string.format("Visibility %s SM", VISIBILITY) + subtitle = string.format( "Visibility %s SM", VISIBILITY ) end if not self.useSRS then - self:Transmission(ATIS.Sound.Visibilty, 1.0, subtitle) - self.radioqueue:Number2Transmission(VISIBILITY) + self:Transmission( ATIS.Sound.Visibilty, 1.0, subtitle ) + self.radioqueue:Number2Transmission( VISIBILITY ) if self.metric then - self:Transmission(ATIS.Sound.Kilometers, 0.2) + self:Transmission( ATIS.Sound.Kilometers, 0.2 ) else - self:Transmission(ATIS.Sound.StatuteMiles, 0.2) + self:Transmission( ATIS.Sound.StatuteMiles, 0.2 ) end end - alltext=alltext..";\n"..subtitle - + alltext = alltext .. ";\n" .. subtitle + -- Weather phenomena - local wp=false - local wpsub="" - if precepitation==1 then - wp=true - wpsub=wpsub.." rain" - elseif precepitation==2 then + local wp = false + local wpsub = "" + if precepitation == 1 then + wp = true + wpsub = wpsub .. " rain" + elseif precepitation == 2 then if wp then - wpsub=wpsub.."," + 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 + 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.."," + wpsub = wpsub .. "," end - wpsub=wpsub.." fog" - wp=true + wpsub = wpsub .. " fog" + wp = true end if dust then if wp then - wpsub=wpsub.."," + wpsub = wpsub .. "," end - wpsub=wpsub.." dust" - wp=true + wpsub = wpsub .. " dust" + wp = true end -- Actual output if wp then - subtitle=string.format("Weather phenomena:%s", wpsub) + subtitle = string.format( "Weather phenomena:%s", wpsub ) if not self.useSRS then - self:Transmission(ATIS.Sound.WeatherPhenomena, 1.0, subtitle) - if precepitation==1 then - self:Transmission(ATIS.Sound.Rain, 0.5) - elseif precepitation==2 then - self:Transmission(ATIS.Sound.ThunderStorm, 0.5) - elseif precepitation==3 then - self:Transmission(ATIS.Sound.Snow, 0.5) - elseif precepitation==4 then - self:Transmission(ATIS.Sound.SnowStorm, 0.5) + 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) + self:Transmission( ATIS.Sound.Fog, 0.5 ) end if dust then - self:Transmission(ATIS.Sound.Dust, 0.5) + self:Transmission( ATIS.Sound.Dust, 0.5 ) end end - alltext=alltext..";\n"..subtitle + alltext = alltext .. ";\n" .. subtitle end -- Cloud base if not self.useSRS then - self:Transmission(CloudCover, 1.0, CLOUDSsub) + self:Transmission( CloudCover, 1.0, CLOUDSsub ) end if CLOUDBASE and static then -- Base - local cbase=tostring(tonumber(CLOUDBASE1000)*1000+tonumber(CLOUDBASE0100)*100) - local cceil=tostring(tonumber(CLOUDCEIL1000)*1000+tonumber(CLOUDCEIL0100)*100) + local cbase = tostring( tonumber( CLOUDBASE1000 ) * 1000 + tonumber( CLOUDBASE0100 ) * 100 ) + local cceil = tostring( tonumber( CLOUDCEIL1000 ) * 1000 + tonumber( CLOUDCEIL0100 ) * 100 ) if self.metric then - --subtitle=string.format("Cloud base %s, ceiling %s meters", CLOUDBASE, CLOUDCEIL) - subtitle=string.format("Cloud base %s, ceiling %s meters", cbase, cceil) + -- subtitle=string.format("Cloud base %s, ceiling %s meters", CLOUDBASE, CLOUDCEIL) + subtitle = string.format( "Cloud base %s, ceiling %s meters", cbase, cceil ) else - --subtitle=string.format("Cloud base %s, ceiling %s feet", CLOUDBASE, CLOUDCEIL) - subtitle=string.format("Cloud base %s, ceiling %s feet", cbase, cceil) + -- subtitle=string.format("Cloud base %s, ceiling %s feet", CLOUDBASE, CLOUDCEIL) + subtitle = string.format( "Cloud base %s, ceiling %s feet", cbase, cceil ) end if not self.useSRS then - self:Transmission(ATIS.Sound.CloudBase, 1.0, subtitle) - if tonumber(CLOUDBASE1000)>0 then - self.radioqueue:Number2Transmission(CLOUDBASE1000) - self:Transmission(ATIS.Sound.Thousand, 0.1) + 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) + if tonumber( CLOUDBASE0100 ) > 0 then + self.radioqueue:Number2Transmission( CLOUDBASE0100 ) + self:Transmission( ATIS.Sound.Hundred, 0.1 ) end -- Ceiling - self:Transmission(ATIS.Sound.CloudCeiling, 0.5) - if tonumber(CLOUDCEIL1000)>0 then - self.radioqueue:Number2Transmission(CLOUDCEIL1000) - self:Transmission(ATIS.Sound.Thousand, 0.1) + 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) + 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) + self:Transmission( ATIS.Sound.Meters, 0.1 ) else - self:Transmission(ATIS.Sound.Feet, 0.1) + self:Transmission( ATIS.Sound.Feet, 0.1 ) end end end - alltext=alltext..";\n"..subtitle - + alltext = alltext .. ";\n" .. subtitle + -- Temperature if self.TDegF then - if temperature<0 then - subtitle=string.format("Temperature -%s °F", TEMPERATURE) + if temperature < 0 then + subtitle = string.format( "Temperature -%s °F", TEMPERATURE ) else - subtitle=string.format("Temperature %s °F", TEMPERATURE) + subtitle = string.format( "Temperature %s °F", TEMPERATURE ) end else - if temperature<0 then - subtitle=string.format("Temperature -%s °C", TEMPERATURE) + if temperature < 0 then + subtitle = string.format( "Temperature -%s °C", TEMPERATURE ) else - subtitle=string.format("Temperature %s °C", TEMPERATURE) + subtitle = string.format( "Temperature %s °C", TEMPERATURE ) end end - local _TEMPERATURE=subtitle + local _TEMPERATURE = subtitle if not self.useSRS then - self:Transmission(ATIS.Sound.Temperature, 1.0, subtitle) - if temperature<0 then - self:Transmission(ATIS.Sound.Minus, 0.2) + self:Transmission( ATIS.Sound.Temperature, 1.0, subtitle ) + if temperature < 0 then + self:Transmission( ATIS.Sound.Minus, 0.2 ) end - self.radioqueue:Number2Transmission(TEMPERATURE) + self.radioqueue:Number2Transmission( TEMPERATURE ) if self.TDegF then - self:Transmission(ATIS.Sound.DegreesFahrenheit, 0.2) + self:Transmission( ATIS.Sound.DegreesFahrenheit, 0.2 ) else - self:Transmission(ATIS.Sound.DegreesCelsius, 0.2) + self:Transmission( ATIS.Sound.DegreesCelsius, 0.2 ) end end - alltext=alltext..";\n"..subtitle - + alltext = alltext .. ";\n" .. subtitle + -- Dew point if self.TDegF then - if dewpoint<0 then - subtitle=string.format("Dew point -%s °F", DEWPOINT) + if dewpoint < 0 then + subtitle = string.format( "Dew point -%s °F", DEWPOINT ) else - subtitle=string.format("Dew point %s °F", DEWPOINT) + subtitle = string.format( "Dew point %s °F", DEWPOINT ) end else - if dewpoint<0 then - subtitle=string.format("Dew point -%s °C", DEWPOINT) + if dewpoint < 0 then + subtitle = string.format( "Dew point -%s °C", DEWPOINT ) else - subtitle=string.format("Dew point %s °C", DEWPOINT) + subtitle = string.format( "Dew point %s °C", DEWPOINT ) end end - local _DEWPOINT=subtitle + local _DEWPOINT = subtitle if not self.useSRS then - self:Transmission(ATIS.Sound.DewPoint, 1.0, subtitle) - if dewpoint<0 then - self:Transmission(ATIS.Sound.Minus, 0.2) + self:Transmission( ATIS.Sound.DewPoint, 1.0, subtitle ) + if dewpoint < 0 then + self:Transmission( ATIS.Sound.Minus, 0.2 ) end - self.radioqueue:Number2Transmission(DEWPOINT) + self.radioqueue:Number2Transmission( DEWPOINT ) if self.TDegF then - self:Transmission(ATIS.Sound.DegreesFahrenheit, 0.2) + self:Transmission( ATIS.Sound.DegreesFahrenheit, 0.2 ) else - self:Transmission(ATIS.Sound.DegreesCelsius, 0.2) + self:Transmission( ATIS.Sound.DegreesCelsius, 0.2 ) end end - alltext=alltext..";\n"..subtitle + alltext = alltext .. ";\n" .. subtitle -- Altimeter QNH/QFE. if self.PmmHg then if self.qnhonly then - subtitle=string.format("Altimeter %s.%s mmHg", QNH[1], QNH[2]) + 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]) + 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]) + 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]) + 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]) + 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]) + 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 + local _ALTIMETER = subtitle if not self.useSRS then - self:Transmission(ATIS.Sound.Altimeter, 1.0, subtitle) + self:Transmission( ATIS.Sound.Altimeter, 1.0, subtitle ) if not self.qnhonly then - self:Transmission(ATIS.Sound.QNH, 0.5) + self:Transmission( ATIS.Sound.QNH, 0.5 ) end - self.radioqueue:Number2Transmission(QNH[1]) + self.radioqueue:Number2Transmission( QNH[1] ) if ATIS.ICAOPhraseology[UTILS.GetDCSMap()] then - self:Transmission(ATIS.Sound.Decimal, 0.2) + self:Transmission( ATIS.Sound.Decimal, 0.2 ) end - self.radioqueue:Number2Transmission(QNH[2]) - + 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 - + 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) + self:Transmission( ATIS.Sound.MillimetersOfMercury, 0.1 ) else if self.metric then - self:Transmission(ATIS.Sound.HectoPascal, 0.1) + self:Transmission( ATIS.Sound.HectoPascal, 0.1 ) else - self:Transmission(ATIS.Sound.InchesOfMercury, 0.1) - end + self:Transmission( ATIS.Sound.InchesOfMercury, 0.1 ) + end end end - alltext=alltext..";\n"..subtitle + alltext = alltext .. ";\n" .. subtitle -- Active runway. - local subtitle=string.format("Active runway %s", runway) - if rwyLeft==true then - subtitle=subtitle.." Left" - elseif rwyLeft==false then - subtitle=subtitle.." Right" + 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 + local _RUNACT = subtitle if not self.useSRS then - 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) + 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 end - alltext=alltext..";\n"..subtitle + alltext = alltext .. ";\n" .. subtitle -- Runway length. if self.rwylength then - local runact=self.airbase:GetActiveRunway(self.runwaym2t) - local length=runact.length + local runact = self.airbase:GetActiveRunway( self.runwaym2t ) + local length = runact.length if not self.metric then - length=UTILS.MetersToFeet(length) + length = UTILS.MetersToFeet( length ) end -- Length in thousands and hundrets of ft/meters. - local L1000, L0100=self:_GetThousandsAndHundreds(length) + local L1000, L0100 = self:_GetThousandsAndHundreds( length ) -- Subtitle. - local subtitle=string.format("Runway length %d", length) + local subtitle = string.format( "Runway length %d", length ) if self.metric then - subtitle=subtitle.." meters" + subtitle = subtitle .. " meters" else - subtitle=subtitle.." feet" + subtitle = subtitle .. " feet" end -- Transmit. if not self.useSRS then - self:Transmission(ATIS.Sound.RunwayLength, 1.0, subtitle) - if tonumber(L1000)>0 then - self.radioqueue:Number2Transmission(L1000) - self:Transmission(ATIS.Sound.Thousand, 0.1) + 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) + 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) + self:Transmission( ATIS.Sound.Meters, 0.1 ) else - self:Transmission(ATIS.Sound.Feet, 0.1) + self:Transmission( ATIS.Sound.Feet, 0.1 ) end end - alltext=alltext..";\n"..subtitle + alltext = alltext .. ";\n" .. subtitle end -- Airfield elevation if self.elevation then - local elevation=self.airbase:GetHeight() + local elevation = self.airbase:GetHeight() if not self.metric then - elevation=UTILS.MetersToFeet(elevation) + elevation = UTILS.MetersToFeet( elevation ) end -- Length in thousands and hundrets of ft/meters. - local L1000, L0100=self:_GetThousandsAndHundreds(elevation) + local L1000, L0100 = self:_GetThousandsAndHundreds( elevation ) -- Subtitle. - local subtitle=string.format("Elevation %d", elevation) + local subtitle = string.format( "Elevation %d", elevation ) if self.metric then - subtitle=subtitle.." meters" + subtitle = subtitle .. " meters" else - subtitle=subtitle.." feet" + subtitle = subtitle .. " feet" end -- Transmit. if not self.useSRS then - self:Transmission(ATIS.Sound.Elevation, 1.0, subtitle) - if tonumber(L1000)>0 then - self.radioqueue:Number2Transmission(L1000) - self:Transmission(ATIS.Sound.Thousand, 0.1) + 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) + 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) + self:Transmission( ATIS.Sound.Meters, 0.1 ) else - self:Transmission(ATIS.Sound.Feet, 0.1) + self:Transmission( ATIS.Sound.Feet, 0.1 ) end end - alltext=alltext..";\n"..subtitle + alltext = alltext .. ";\n" .. subtitle end -- Tower frequency. 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..", " + 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) + subtitle = string.format( "Tower frequency %s", freqs ) if not self.useSRS then - self:Transmission(ATIS.Sound.TowerFrequency, 1.0, subtitle) - for _,freq in pairs(self.towerfrequency) do - local f=string.format("%.3f", freq) - f=UTILS.Split(f, ".") - self.radioqueue:Number2Transmission(f[1], nil, 0.5) - if tonumber(f[2])>0 then - self:Transmission(ATIS.Sound.Decimal, 0.2) - self.radioqueue:Number2Transmission(f[2]) + 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) + self:Transmission( ATIS.Sound.MegaHertz, 0.2 ) end end - alltext=alltext..";\n"..subtitle + alltext = alltext .. ";\n" .. subtitle end -- ILS - local ils=self:GetNavPoint(self.ils, runway, rwyLeft) + local ils = self:GetNavPoint( self.ils, runway, rwyLeft ) if ils then - subtitle=string.format("ILS frequency %.2f MHz", ils.frequency) - if not self.useSRS then - self:Transmission(ATIS.Sound.ILSFrequency, 1.0, subtitle) - local f=string.format("%.2f", ils.frequency) - f=UTILS.Split(f, ".") - self.radioqueue:Number2Transmission(f[1], nil, 0.5) - if tonumber(f[2])>0 then - self:Transmission(ATIS.Sound.Decimal, 0.2) - self.radioqueue:Number2Transmission(f[2]) + subtitle = string.format( "ILS frequency %.2f MHz", ils.frequency ) + if not self.useSRS then + self:Transmission( ATIS.Sound.ILSFrequency, 1.0, subtitle ) + local f = string.format( "%.2f", ils.frequency ) + f = UTILS.Split( f, "." ) + self.radioqueue:Number2Transmission( f[1], nil, 0.5 ) + if tonumber( f[2] ) > 0 then + self:Transmission( ATIS.Sound.Decimal, 0.2 ) + self.radioqueue:Number2Transmission( f[2] ) end - self:Transmission(ATIS.Sound.MegaHertz, 0.2) - end - alltext=alltext..";\n"..subtitle + self:Transmission( ATIS.Sound.MegaHertz, 0.2 ) + end + alltext = alltext .. ";\n" .. subtitle end -- Outer NDB - local ndb=self:GetNavPoint(self.ndbouter, runway, rwyLeft) + local ndb = self:GetNavPoint( self.ndbouter, runway, rwyLeft ) if ndb then - subtitle=string.format("Outer NDB frequency %.2f MHz", ndb.frequency) + subtitle = string.format( "Outer NDB frequency %.2f MHz", ndb.frequency ) if not self.useSRS then - self:Transmission(ATIS.Sound.OuterNDBFrequency, 1.0, subtitle) - local f=string.format("%.2f", ndb.frequency) - f=UTILS.Split(f, ".") - self.radioqueue:Number2Transmission(f[1], nil, 0.5) - if tonumber(f[2])>0 then - self:Transmission(ATIS.Sound.Decimal, 0.2) - self.radioqueue:Number2Transmission(f[2]) + 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) + self:Transmission( ATIS.Sound.MegaHertz, 0.2 ) end - alltext=alltext..";\n"..subtitle + alltext = alltext .. ";\n" .. subtitle end -- Inner NDB - local ndb=self:GetNavPoint(self.ndbinner, runway, rwyLeft) + local ndb = self:GetNavPoint( self.ndbinner, runway, rwyLeft ) if ndb then - subtitle=string.format("Inner NDB frequency %.2f MHz", ndb.frequency) + subtitle = string.format( "Inner NDB frequency %.2f MHz", ndb.frequency ) if not self.useSRS then - self:Transmission(ATIS.Sound.InnerNDBFrequency, 1.0, subtitle) - local f=string.format("%.2f", ndb.frequency) - f=UTILS.Split(f, ".") - self.radioqueue:Number2Transmission(f[1], nil, 0.5) - if tonumber(f[2])>0 then - self:Transmission(ATIS.Sound.Decimal, 0.2) - self.radioqueue:Number2Transmission(f[2]) + self:Transmission( ATIS.Sound.InnerNDBFrequency, 1.0, subtitle ) + local f = string.format( "%.2f", ndb.frequency ) + f = UTILS.Split( f, "." ) + self.radioqueue:Number2Transmission( f[1], nil, 0.5 ) + if tonumber( f[2] ) > 0 then + self:Transmission( ATIS.Sound.Decimal, 0.2 ) + self.radioqueue:Number2Transmission( f[2] ) end - self:Transmission(ATIS.Sound.MegaHertz, 0.2) - end - alltext=alltext..";\n"..subtitle + self:Transmission( ATIS.Sound.MegaHertz, 0.2 ) + end + alltext = alltext .. ";\n" .. subtitle end -- VOR if self.vor then - subtitle=string.format("VOR frequency %.2f MHz", self.vor) + subtitle = string.format( "VOR frequency %.2f MHz", self.vor ) if self.useSRS then - subtitle=string.format("V O R frequency %.2f MHz", self.vor) + subtitle = string.format( "V O R frequency %.2f MHz", self.vor ) end if not self.useSRS then - self:Transmission(ATIS.Sound.VORFrequency, 1.0, subtitle) - local f=string.format("%.2f", self.vor) - f=UTILS.Split(f, ".") - self.radioqueue:Number2Transmission(f[1], nil, 0.5) - if tonumber(f[2])>0 then - self:Transmission(ATIS.Sound.Decimal, 0.2) - self.radioqueue:Number2Transmission(f[2]) + 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) + self:Transmission( ATIS.Sound.MegaHertz, 0.2 ) end - alltext=alltext..";\n"..subtitle + alltext = alltext .. ";\n" .. subtitle end -- TACAN if self.tacan then - subtitle=string.format("TACAN channel %dX", self.tacan) + subtitle = string.format( "TACAN channel %dX", self.tacan ) if not self.useSRS then - self:Transmission(ATIS.Sound.TACANChannel, 1.0, subtitle) - self.radioqueue:Number2Transmission(tostring(self.tacan), nil, 0.2) - self.radioqueue:NewTransmission("NATO Alphabet/Xray.ogg", 0.75, self.soundpath, nil, 0.2) + self:Transmission( ATIS.Sound.TACANChannel, 1.0, subtitle ) + self.radioqueue:Number2Transmission( tostring( self.tacan ), nil, 0.2 ) + self.radioqueue:NewTransmission( "NATO Alphabet/Xray.ogg", 0.75, self.soundpath, nil, 0.2 ) end - alltext=alltext..";\n"..subtitle + alltext = alltext .. ";\n" .. subtitle end -- RSBN if self.rsbn then - subtitle=string.format("RSBN channel %d", self.rsbn) + subtitle = string.format( "RSBN channel %d", self.rsbn ) if not self.useSRS then - self:Transmission(ATIS.Sound.RSBNChannel, 1.0, subtitle) - self.radioqueue:Number2Transmission(tostring(self.rsbn), nil, 0.2) + self:Transmission( ATIS.Sound.RSBNChannel, 1.0, subtitle ) + self.radioqueue:Number2Transmission( tostring( self.rsbn ), nil, 0.2 ) end - alltext=alltext..";\n"..subtitle + alltext = alltext .. ";\n" .. subtitle end -- PRMG - local ndb=self:GetNavPoint(self.prmg, runway, rwyLeft) + local ndb = self:GetNavPoint( self.prmg, runway, rwyLeft ) if ndb then - subtitle=string.format("PRMG channel %d", ndb.frequency) + subtitle = string.format( "PRMG channel %d", ndb.frequency ) if not self.useSRS then - self:Transmission(ATIS.Sound.PRMGChannel, 1.0, subtitle) - self.radioqueue:Number2Transmission(tostring(ndb.frequency), nil, 0.5) - end - alltext=alltext..";\n"..subtitle + self:Transmission( ATIS.Sound.PRMGChannel, 1.0, subtitle ) + self.radioqueue:Number2Transmission( tostring( ndb.frequency ), nil, 0.5 ) + end + alltext = alltext .. ";\n" .. subtitle end - + -- Advice on initial... - subtitle=string.format("Advise on initial contact, you have information %s", NATO) + subtitle = string.format( "Advise on initial contact, you have information %s", NATO ) if not self.useSRS then - self:Transmission(ATIS.Sound.AdviceOnInitial, 0.5, subtitle) - self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg", NATO), 0.75, self.soundpath) - end - alltext=alltext..";\n"..subtitle - + self:Transmission( ATIS.Sound.AdviceOnInitial, 0.5, subtitle ) + self.radioqueue:NewTransmission( string.format( "NATO Alphabet/%s.ogg", NATO ), 0.75, self.soundpath ) + end + alltext = alltext .. ";\n" .. subtitle + -- Report ATIS text. - self:Report(alltext) + self:Report( alltext ) -- Update F10 marker. if self.usemarker then - self:UpdateMarker(_INFORMATION, _RUNACT, _WIND, _ALTIMETER, _TEMPERATURE) + self:UpdateMarker( _INFORMATION, _RUNACT, _WIND, _ALTIMETER, _TEMPERATURE ) end end @@ -2266,34 +2251,34 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param #string Text Report text. -function ATIS:onafterReport(From, Event, To, Text) - self:T(self.lid..string.format("Report:\n%s", Text)) - +function ATIS:onafterReport( From, Event, To, Text ) + self:T( self.lid .. string.format( "Report:\n%s", Text ) ) + if self.useSRS and self.msrs then - + -- Remove line breaks - local text=string.gsub(Text, "[\r\n]", "") - + local text = string.gsub( Text, "[\r\n]", "" ) + -- Replace other stuff. - local text=string.gsub(text, "SM", "statute miles") - local text=string.gsub(text, "°C", "degrees Celsius") - local text=string.gsub(text, "°F", "degrees Fahrenheit") - local text=string.gsub(text, "inHg", "inches of Mercury") - local text=string.gsub(text, "mmHg", "millimeters of Mercury") - local text=string.gsub(text, "hPa", "hectopascals") - local text=string.gsub(text, "m/s", "meters per second") - + local text = string.gsub( text, "SM", "statute miles" ) + local text = string.gsub( text, "°C", "degrees Celsius" ) + local text = string.gsub( text, "°F", "degrees Fahrenheit" ) + local text = string.gsub( text, "inHg", "inches of Mercury" ) + local text = string.gsub( text, "mmHg", "millimeters of Mercury" ) + local text = string.gsub( text, "hPa", "hectopascals" ) + local text = string.gsub( text, "m/s", "meters per second" ) + -- Replace ";" by "." - local text=string.gsub(text, ";", " . ") - - --Debug output. - self:T("SRS TTS: "..text) - + local text = string.gsub( text, ";", " . " ) + + -- Debug output. + self:T( "SRS TTS: " .. text ) + -- Play text-to-speech report. - self.msrs:PlayText(text) - + self.msrs:PlayText( text ) + end - + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -2303,25 +2288,25 @@ end --- Base captured -- @param #ATIS self -- @param Core.Event#EVENTDATA EventData Event data. -function ATIS:OnEventBaseCaptured(EventData) - +function ATIS:OnEventBaseCaptured( EventData ) + if EventData and EventData.Place then -- Place is the airbase that was captured. - local airbase=EventData.Place --Wrapper.Airbase#AIRBASE + local airbase = EventData.Place -- Wrapper.Airbase#AIRBASE -- Check that this airbase belongs or did belong to this warehouse. - if EventData.PlaceName==self.airbasename then + if EventData.PlaceName == self.airbasename then -- New coalition of airbase after it was captured. - local NewCoalitionAirbase=airbase:GetCoalition() - - if self.useSRS and self.msrs and self.msrs.coalition~=NewCoalitionAirbase then - self.msrs:SetCoalition(NewCoalitionAirbase) + local NewCoalitionAirbase = airbase:GetCoalition() + + if self.useSRS and self.msrs and self.msrs.coalition ~= NewCoalitionAirbase then + self.msrs:SetCoalition( NewCoalitionAirbase ) end - + end - + end end @@ -2338,21 +2323,21 @@ end -- @param #string altimeter Altimeter text. -- @param #string temperature Temperature text. -- @return #number Marker ID. -function ATIS:UpdateMarker(information, runact, wind, altimeter, temperature) +function ATIS:UpdateMarker( information, runact, wind, altimeter, temperature ) if self.markerid then - self.airbase:GetCoordinate():RemoveMark(self.markerid) + 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)) + 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 ) ) -- More info is not displayed on the marker! -- Place new mark - self.markerid=self.airbase:GetCoordinate():MarkToAll(text, true) + self.markerid = self.airbase:GetCoordinate():MarkToAll( text, true ) return self.markerid end @@ -2363,32 +2348,32 @@ end -- @return #boolean Use Left=true, Right=false, or nil. function ATIS:GetActiveRunway() - local coord=self.airbase:GetCoordinate() - local height=coord:GetLandHeight() + local coord = self.airbase:GetCoordinate() + local height = coord:GetLandHeight() -- Get wind direction and speed in m/s. - local windFrom, windSpeed=coord:GetWind(height+10) + local windFrom, windSpeed = coord:GetWind( height + 10 ) -- Get active runway data based on wind direction. - local runact=self.airbase:GetActiveRunway(self.runwaym2t) + local runact = self.airbase:GetActiveRunway( self.runwaym2t ) -- Active runway "31". - local runway=self:GetMagneticRunway(windFrom) or runact.idx + local runway = self:GetMagneticRunway( windFrom ) or runact.idx -- Left or right in case there are two runways with the same heading. - local rwyLeft=nil + local rwyLeft = nil -- Check if user explicitly specified a runway. if self.activerunway then -- Get explicit runway heading if specified. - local runwayno=self:GetRunwayWithoutLR(self.activerunway) - if runwayno~="" then - runway=runwayno + local runwayno = self:GetRunwayWithoutLR( self.activerunway ) + if runwayno ~= "" then + runway = runwayno end -- Was "L"eft or "R"ight given? - rwyLeft=self:GetRunwayLR(self.activerunway) + rwyLeft = self:GetRunwayLR( self.activerunway ) end return runway, rwyLeft @@ -2398,18 +2383,18 @@ end -- @param #ATIS self -- @param #number windfrom Wind direction (from) in degrees. -- @return #string Runway magnetic heading divided by ten (and rounded). Eg, "13" for 130°. -function ATIS:GetMagneticRunway(windfrom) +function ATIS:GetMagneticRunway( windfrom ) - local diffmin=nil - local runway=nil - for _,heading in pairs(self.runwaymag) do + local diffmin = nil + local runway = nil + for _, heading in pairs( self.runwaymag ) do - local hdg=self:GetRunwayWithoutLR(heading) + local hdg = self:GetRunwayWithoutLR( heading ) - local diff=UTILS.HdgDiff(windfrom, tonumber(hdg)*10) - if diffmin==nil or diff data is valid for all runways. return nav else - local navy=tonumber(self:GetRunwayWithoutLR(nav.runway))*10 - local rwyy=tonumber(self:GetRunwayWithoutLR(runway))*10 + local navy = tonumber( self:GetRunwayWithoutLR( nav.runway ) ) * 10 + local rwyy = tonumber( self:GetRunwayWithoutLR( runway ) ) * 10 - local navL=self:GetRunwayLR(nav.runway) - local hdgD=UTILS.HdgDiff(navy,rwyy) + local navL = self:GetRunwayLR( nav.runway ) + local hdgD = UTILS.HdgDiff( navy, rwyy ) - if hdgD<=15 then --We allow an error of +-15° here. - if navL==nil or (navL==true and left==true) or (navL==false and left==false) then + if hdgD <= 15 then -- We allow an error of +-15° here. + if navL == nil or (navL == true and left == true) or (navL == false and left == false) then return nav end end @@ -2455,9 +2440,9 @@ end -- @param #ATIS self -- @param #string runway Runway heading, *e.g.* "31L". -- @return #string Runway heading without left or right, *e.g.* "31". -function ATIS:GetRunwayWithoutLR(runway) - local rwywo=runway:gsub("%D+", "") - --self:I(string.format("FF runway=%s ==> rwywo=%s", runway, rwywo)) +function ATIS:GetRunwayWithoutLR( runway ) + local rwywo = runway:gsub( "%D+", "" ) + -- self:I(string.format("FF runway=%s ==> rwywo=%s", runway, rwywo)) return rwywo end @@ -2465,11 +2450,11 @@ end -- @param #ATIS self -- @param #string runway Runway heading, *e.g.* "31L". -- @return #boolean If *true*, left runway is active. If *false*, right runway. If *nil*, neither applies. -function ATIS:GetRunwayLR(runway) +function ATIS:GetRunwayLR( runway ) -- Get left/right if specified. - local rwyL=runway:lower():find("l") - local rwyR=runway:lower():find("r") + local rwyL = runway:lower():find( "l" ) + local rwyR = runway:lower():find( "r" ) if rwyL then return true @@ -2487,19 +2472,19 @@ end -- @param #number interval Interval in seconds after the last transmission finished. -- @param #string subtitle Subtitle of the transmission. -- @param #string path Path to sound file. Default self.soundpath. -function ATIS:Transmission(sound, interval, subtitle, path) - self.radioqueue:NewTransmission(sound.filename, sound.duration, path or self.soundpath, nil, interval, subtitle, self.subduration) +function ATIS:Transmission( sound, interval, subtitle, path ) + self.radioqueue:NewTransmission( sound.filename, sound.duration, path or self.soundpath, nil, interval, subtitle, self.subduration ) end --- Play all audio files. -- @param #ATIS self function ATIS:SoundCheck() - for _,_sound in pairs(ATIS.Sound) do - local sound=_sound --#ATIS.Soundfile - local subtitle=string.format("Playing sound file %s, duration %.2f sec", sound.filename, sound.duration) - self:Transmission(sound, nil, subtitle) - MESSAGE:New(subtitle, 5, "ATIS"):ToAll() + for _, _sound in pairs( ATIS.Sound ) do + local sound = _sound -- #ATIS.Soundfile + local subtitle = string.format( "Playing sound file %s, duration %.2f sec", sound.filename, sound.duration ) + self:Transmission( sound, nil, subtitle ) + MESSAGE:New( subtitle, 5, "ATIS" ):ToAll() end end @@ -2515,7 +2500,7 @@ end function ATIS:GetMissionWeather() -- Weather data from mission file. - local weather=env.mission.weather + local weather = env.mission.weather -- Clouds --[[ @@ -2527,25 +2512,25 @@ function ATIS:GetMissionWeather() ["iprecptns"] = 1, }, -- end of ["clouds"] ]] - local clouds=weather.clouds + local clouds = weather.clouds -- 0=static, 1=dynamic - local static=weather.atmosphere_type==0 + local static = weather.atmosphere_type == 0 -- Visibilty distance in meters. - local visibility=weather.visibility.distance + local visibility = weather.visibility.distance -- Ground turbulence. - local turbulence=weather.groundTurbulence + local turbulence = weather.groundTurbulence -- Dust --[[ ["enable_dust"] = false, ["dust_density"] = 0, ]] - local dust=nil - if weather.enable_dust==true then - dust=weather.dust_density + local dust = nil + if weather.enable_dust == true then + dust = weather.dust_density end -- Fog @@ -2557,35 +2542,34 @@ function ATIS:GetMissionWeather() ["visibility"] = 25, }, -- end of ["fog"] ]] - local fog=nil - if weather.enable_fog==true then - fog=weather.fog + local fog = nil + if weather.enable_fog == true then + fog = weather.fog end - self:T("FF weather:") - self:T({clouds=clouds}) - self:T({visibility=visibility}) - self:T({turbulence=turbulence}) - self:T({fog=fog}) - self:T({dust=dust}) - self:T({static=static}) + self:T( "FF weather:" ) + self:T( { clouds = clouds } ) + self:T( { visibility = visibility } ) + self:T( { turbulence = turbulence } ) + self:T( { fog = fog } ) + self:T( { dust = dust } ) + self:T( { static = static } ) return clouds, visibility, turbulence, fog, dust, static end - --- Get thousands of a number. -- @param #ATIS self -- @param #number n Number, *e.g.* 4359. -- @return #string Thousands of n, *e.g.* "4" for 4359. -- @return #string Hundreds of n, *e.g.* "4" for 4359 because its rounded. -function ATIS:_GetThousandsAndHundreds(n) +function ATIS:_GetThousandsAndHundreds( n ) - local N=UTILS.Round(n/1000, 1) + local N = UTILS.Round( n / 1000, 1 ) - local S=UTILS.Split(string.format("%.1f", N), ".") + local S = UTILS.Split( string.format( "%.1f", N ), "." ) - local t=S[1] - local h=S[2] + local t = S[1] + local h = S[2] return t, h end diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 515dd4ca7..eee1ea3db 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -303,7 +303,6 @@ -- The AV-8B Harrier pattern is very similar, the only differences are as there is no angled deck there is no wake check. from the ninety you wil fly a straight approach offset 26 ft to port (left) of the tram line. -- The aim is to arrive abeam the landing spot in a stable hover at 120 ft with forward speed matched to the boat. From there the LSO will call "cleared to land". You then level cross to the tram line at the designated landing spot at land vertcally. When you stabalise over the landing spot LSO will call Stabalise to indicate you are centered at the correct spot. -- --- -- ## CASE III -- -- ![Banner Image](..\Presentations\AIRBOSS\Airboss_Case3.png) @@ -958,8 +957,6 @@ -- -- Again, changing the file name, subtitle, subtitle duration is not required if you name the file exactly like the original one, which is this case would be "LSO-RogerBall.ogg". -- --- --- -- ## The Radio Dilemma -- -- DCS offers two (actually three) ways to send radio messages. Each one has its advantages and disadvantages and it is important to understand the differences. @@ -1304,19 +1301,19 @@ AIRBOSS.AircraftCarrier={ -- @field #string JCARLOS Juan Carlos I (L61) [V/STOL Carrier] -- @field #string HMAS Canberra (L02) [V/STOL Carrier] -- @field #string KUZNETSOV Admiral Kuznetsov (CV 1143.5) -AIRBOSS.CarrierType={ - ROOSEVELT="CVN_71", - LINCOLN="CVN_72", - WASHINGTON="CVN_73", - TRUMAN="CVN_75", - STENNIS="Stennis", - FORRESTAL="Forrestal", - VINSON="VINSON", - TARAWA="LHA_Tarawa", - AMERICA="USS America LHA-6", - JCARLOS="L61", - CANBERRA="L02", - KUZNETSOV="KUZNECOW", +AIRBOSS.CarrierType = { + ROOSEVELT = "CVN_71", + LINCOLN = "CVN_72", + WASHINGTON = "CVN_73", + TRUMAN = "CVN_75", + STENNIS = "Stennis", + FORRESTAL = "Forrestal", + VINSON = "VINSON", + TARAWA = "LHA_Tarawa", + AMERICA = "USS America LHA-6", + JCARLOS = "L61", + CANBERRA = "L02", + KUZNETSOV = "KUZNECOW", } --- Carrier specific parameters. @@ -1396,36 +1393,36 @@ AIRBOSS.CarrierType={ -- @field #string BOLTER "Bolter Pattern". -- @field #string EMERGENCY "Emergency Landing". -- @field #string DEBRIEF "Debrief". -AIRBOSS.PatternStep={ - UNDEFINED="Undefined", - REFUELING="Refueling", - SPINNING="Spinning", - COMMENCING="Commencing", - HOLDING="Holding", - WAITING="Waiting for free Marshal stack", - PLATFORM="Platform", - ARCIN="Arc Turn In", - ARCOUT="Arc Turn Out", - DIRTYUP="Dirty Up", - BULLSEYE="Bullseye", - INITIAL="Initial", - BREAKENTRY="Break Entry", - EARLYBREAK="Early Break", - LATEBREAK="Late Break", - ABEAM="Abeam", - NINETY="Ninety", - WAKE="Wake", - FINAL="Turn Final", - GROOVE_XX="Groove X", - GROOVE_IM="Groove In the Middle", - GROOVE_IC="Groove In Close", - GROOVE_AR="Groove At the Ramp", - GROOVE_IW="Groove In the Wires", - GROOVE_AL="Groove Abeam Landing Spot", - GROOVE_LC="Groove Level Cross", - BOLTER="Bolter Pattern", - EMERGENCY="Emergency Landing", - DEBRIEF="Debrief", +AIRBOSS.PatternStep = { + UNDEFINED = "Undefined", + REFUELING = "Refueling", + SPINNING = "Spinning", + COMMENCING = "Commencing", + HOLDING = "Holding", + WAITING = "Waiting for free Marshal stack", + PLATFORM = "Platform", + ARCIN = "Arc Turn In", + ARCOUT = "Arc Turn Out", + DIRTYUP = "Dirty Up", + BULLSEYE = "Bullseye", + INITIAL = "Initial", + BREAKENTRY = "Break Entry", + EARLYBREAK = "Early Break", + LATEBREAK = "Late Break", + ABEAM = "Abeam", + NINETY = "Ninety", + WAKE = "Wake", + FINAL = "Turn Final", + GROOVE_XX = "Groove X", + GROOVE_IM = "Groove In the Middle", + GROOVE_IC = "Groove In Close", + GROOVE_AR = "Groove At the Ramp", + GROOVE_IW = "Groove In the Wires", + GROOVE_AL = "Groove Abeam Landing Spot", + GROOVE_LC = "Groove Level Cross", + BOLTER = "Bolter Pattern", + EMERGENCY = "Emergency Landing", + DEBRIEF = "Debrief", } --- Groove position. @@ -1438,15 +1435,15 @@ AIRBOSS.PatternStep={ -- @field #string AL "AL": Abeam landing position (V/STOL). -- @field #string LC "LC": Level crossing (V/STOL). -- @field #string IW "IW": In the wires. -AIRBOSS.GroovePos={ - X0="X0", - XX="XX", - IM="IM", - IC="IC", - AR="AR", - AL="AL", - LC="LC", - IW="IW", +AIRBOSS.GroovePos = { + X0 = "X0", + XX = "XX", + IM = "IM", + IC = "IC", + AR = "AR", + AL = "AL", + LC = "LC", + IW = "IW", } --- Radio. @@ -1581,10 +1578,10 @@ AIRBOSS.GroovePos={ -- @field #string EASY Flight Student. Shows tips and hints in important phases of the approach. -- @field #string NORMAL Naval aviator. Moderate number of hints but not really zip lip. -- @field #string HARD TOPGUN graduate. For people who know what they are doing. Nearly *ziplip*. -AIRBOSS.Difficulty={ - EASY="Flight Student", - NORMAL="Naval Aviator", - HARD="TOPGUN Graduate", +AIRBOSS.Difficulty = { + EASY = "Flight Student", + NORMAL = "Naval Aviator", + HARD = "TOPGUN Graduate", } --- Recovery window parameters. diff --git a/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua b/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua index d3b67cbff..12f0446a7 100644 --- a/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua +++ b/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua @@ -82,7 +82,7 @@ do -- TASK_A2A_DISPATCHER -- to pick up enemy aircraft as they approach so that CAP and GCI flights can be tasked to intercept them. -- -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia7.JPG) - -- + -- -- Additionally in a hot war situation where the border is no longer respected the placement of radars has a big effect on how fast the war escalates. -- For example if they are a long way forward and can detect enemy planes on the ground and taking off -- they will start to vector CAP and GCI flights to attack them straight away which will immediately draw a response from the other coalition. @@ -182,7 +182,7 @@ do -- TASK_A2A_DISPATCHER Mission = nil, Detection = nil, Tasks = {}, - SweepZones = {} + SweepZones = {}, } --- TASK_A2A_DISPATCHER constructor. @@ -292,7 +292,7 @@ do -- TASK_A2A_DISPATCHER self:F( { DetectedItem.ItemID } ) local DetectedSet = DetectedItem.Set - local DetectedZone = DetectedItem.Zone + local DetectedZone = DetectedItem.Zone -- TODO: This seems unused, remove? if DetectedItem.IsDetected == false then @@ -316,7 +316,7 @@ do -- TASK_A2A_DISPATCHER self:F( { DetectedItem.ItemID } ) local DetectedSet = DetectedItem.Set - local DetectedZone = DetectedItem.Zone + local DetectedZone = DetectedItem.Zone -- TODO: This seems unused, remove? local PlayersCount, PlayersReport = self:GetPlayerFriendliesNearBy( DetectedItem ) From 2138a332921e9e00655a509781c015ea7f0d23c0 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 10 Dec 2021 12:02:50 +0100 Subject: [PATCH 028/200] CSAR fixed KM message --- Moose Development/Moose/Ops/CSAR.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 3e602cd43..efb04badc 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -22,7 +22,7 @@ -- @module Ops.CSAR -- @image OPS_CSAR.jpg --- Date: Oct 2021 +-- Date: Dec 2021 ------------------------------------------------------------------------- --- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM @@ -247,7 +247,7 @@ CSAR.AircraftType["Bell-47"] = 2 --- CSAR class version. -- @field #string version -CSAR.version="0.1.12r4" +CSAR.version="0.1.12r5" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -1668,7 +1668,7 @@ function CSAR:_Reqsmoke( _unitName ) if _SETTINGS:IsImperial() then _distance = string.format("%.1fnm",UTILS.MetersToNM(_closest.distance)) else - _distance = string.format("%.1fkm",_closest.distance) + _distance = string.format("%.1fkm",_closest.distance/1000) end local _msg = string.format("%s - Popping smoke at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance) self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true) From ef4dc48ea174a6e9d542fa8a3be46805e7412a67 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 11 Dec 2021 14:14:44 +0100 Subject: [PATCH 029/200] CTLD - added option for smoke/flare at position --- Moose Development/Moose/Ops/CTLD.lua | 46 ++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 6abfd8477..532256489 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -22,7 +22,7 @@ -- @module Ops.CTLD -- @image OPS_CTLD.jpg --- Date: Oct 2021 +-- Date: Dec 2021 do ------------------------------------------------------ @@ -669,7 +669,9 @@ do -- my_ctld.cratecountry = country.id.GERMANY -- ID of crates. Will default to country.id.RUSSIA for RED coalition setups. -- my_ctld.allowcratepickupagain = true -- allow re-pickup crates that were dropped. -- my_ctld.enableslingload = false -- allow cargos to be slingloaded - might not work for all cargo types --- my_ctld.pilotmustopendoors = false -- -- force opening of doors +-- my_ctld.pilotmustopendoors = false -- force opening of doors +-- my_ctld.SmokeColor = SMOKECOLOR.Red -- color to use when dropping smoke from heli +-- my_ctld.FlareColor = FLARECOLOR.Red -- color to use when flaring from heli -- -- ## 2.1 User functions -- @@ -812,7 +814,7 @@ do -- -- ## 4.1 Manage Crates -- --- Use this entry to get, load, list nearby, drop, build and repair crates. Also @see options. +-- Use this entry to get, load, list nearby, drop, build and repair crates. Also see options. -- -- ## 4.2 Manage Troops -- @@ -823,7 +825,7 @@ do -- -- Lists what you have loaded. Shows load capabilities for number of crates and number of seats for troops. -- --- ## 4.4 Smoke & Flare zones nearby +-- ## 4.4 Smoke & Flare zones nearby or drop smoke or flare from Heli -- -- Does what it says. -- @@ -920,7 +922,7 @@ CTLD = { -- DONE: Added support for Hercules -- TODO: Possibly - either/or loading crates and troops -- DONE: Make inject respect existing cargo types --- TODO: Drop beacons or flares/smoke +-- DONE: Drop beacons or flares/smoke -- DONE: Add statics as cargo -- DONE: List cargo in stock -- DONE: Limit of troops, crates buildable? @@ -988,7 +990,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="0.2.4" +CTLD.version="1.0.0" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1154,6 +1156,10 @@ function CTLD:New(Coalition, Prefixes, Alias) -- slingload self.enableslingload = false + -- Smokes and Flares + self.SmokeColor = SMOKECOLOR.Red + self.FlareColor = FLARECOLOR.Red + for i=1,100 do math.random() end @@ -2911,8 +2917,11 @@ function CTLD:_RefreshF10Menus() local listmenu = MENU_GROUP_COMMAND:New(_group,"List boarded cargo",topmenu, self._ListCargo, self, _group, _unit) local invtry = MENU_GROUP_COMMAND:New(_group,"Inventory",topmenu, self._ListInventory, self, _group, _unit) local rbcns = MENU_GROUP_COMMAND:New(_group,"List active zone beacons",topmenu, self._ListRadioBeacons, self, _group, _unit) - local smokemenu = MENU_GROUP_COMMAND:New(_group,"Smoke zones nearby",topmenu, self.SmokeZoneNearBy, self, _unit, false) - local smokemenu = MENU_GROUP_COMMAND:New(_group,"Flare zones nearby",topmenu, self.SmokeZoneNearBy, self, _unit, true):Refresh() + local smoketopmenu = MENU_GROUP:New(_group,"Smokes & Flares",topmenu) + local smokemenu = MENU_GROUP_COMMAND:New(_group,"Smoke zones nearby",smoketopmenu, self.SmokeZoneNearBy, self, _unit, false) + local smokeself = MENU_GROUP_COMMAND:New(_group,"Drop smoke now",smoketopmenu, self.SmokePositionNow, self, _unit, false) + local flaremenu = MENU_GROUP_COMMAND:New(_group,"Flare zones nearby",smoketopmenu, self.SmokeZoneNearBy, self, _unit, true) + local flareself = MENU_GROUP_COMMAND:New(_group,"Fire flare now",smoketopmenu, self.SmokePositionNow, self, _unit, true):Refresh() -- sub menus -- sub menu troops management if cantroops then @@ -3373,7 +3382,26 @@ function CTLD:IsUnitInZone(Unit,Zonetype) end end ---- User function - Start smoke in a zone close to the Unit. +--- User function - Drop a smoke or flare at current location. +-- @param #CTLD self +-- @param Wrapper.Unit#UNIT Unit The Unit. +-- @param #boolean Flare If true, flare instead. +function CTLD:SmokePositionNow(Unit, Flare) + self:T(self.lid .. " SmokePositionNow") + local SmokeColor = self.SmokeColor or SMOKECOLOR.Red + local FlareColor = self.FlareColor or FLARECOLOR.Red + -- table of #CTLD.CargoZone table + local unitcoord = Unit:GetCoordinate() -- Core.Point#COORDINATE + local Group = Unit:GetGroup() + if Flare then + unitcoord:Flare(FlareColor, 90) + else + unitcoord:Smoke(SmokeColor) + end + return self +end + +--- User function - Start smoke/flare in a zone close to the Unit. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit The Unit. -- @param #boolean Flare If true, flare instead. From 848e2f1294b5b04617f4618451509b3586a5a5b8 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 11 Dec 2021 14:24:20 +0100 Subject: [PATCH 030/200] SET - Added Zone Filter for STATIC --- Moose Development/Moose/Core/Set.lua | 1921 ++++++++++++++++++++------ 1 file changed, 1464 insertions(+), 457 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 88c1e8760..d29ea28b9 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -20,9 +20,9 @@ -- Various types of SET_ classes are available: -- -- * @{#SET_GROUP}: Defines a collection of @{Wrapper.Group}s filtered by filter criteria. --- * @{#SET_UNIT}: Defines a collection of @{Wrapper.Unit}s filtered by filter criteria. +-- * @{#SET_UNIT}: Defines a colleciton of @{Wrapper.Unit}s filtered by filter criteria. -- * @{#SET_STATIC}: Defines a collection of @{Wrapper.Static}s filtered by filter criteria. --- * @{#SET_CLIENT}: Defines a collection of @{Client}s filtered by filter criteria. +-- * @{#SET_CLIENT}: Defines a collection of @{Client}s filterd by filter criteria. -- * @{#SET_AIRBASE}: Defines a collection of @{Wrapper.Airbase}s filtered by filter criteria. -- * @{#SET_CARGO}: Defines a collection of @{Cargo.Cargo}s filtered by filter criteria. -- * @{#SET_ZONE}: Defines a collection of @{Core.Zone}s filtered by filter criteria. @@ -44,18 +44,20 @@ -- @module Core.Set -- @image Core_Sets.JPG + do -- SET_BASE --- @type SET_BASE -- @field #table Filter Table of filters. -- @field #table Set Table of objects. - -- @field #table Index Table of indices. + -- @field #table Index Table of indicies. -- @field #table List Unused table. -- @field Core.Scheduler#SCHEDULER CallScheduler -- @extends Core.Base#BASE + --- The @{Core.Set#SET_BASE} class defines the core functions that define a collection of objects. - -- A SET provides iterators to iterate the SET, but will **temporarily** yield the ForEach iterator loop at defined **"intervals"** to the mail simulator loop. + -- A SET provides iterators to iterate the SET, but will **temporarily** yield the ForEach interator loop at defined **"intervals"** to the mail simulator loop. -- In this way, large loops can be done while not blocking the simulator main processing loop. -- The default **"yield interval"** is after 10 objects processed. -- The default **"time interval"** is after 0.001 seconds. @@ -66,7 +68,7 @@ do -- SET_BASE -- -- ## Define the SET iterator **"yield interval"** and the **"time interval"** -- - -- Modify the iterator intervals with the @{Core.Set#SET_BASE.SetIteratorIntervals} method. + -- Modify the iterator intervals with the @{Core.Set#SET_BASE.SetInteratorIntervals} method. -- You can set the **"yield interval"**, and the **"time interval"**. (See above). -- -- @field #SET_BASE SET_BASE @@ -77,11 +79,12 @@ do -- SET_BASE List = {}, Index = {}, Database = nil, - CallScheduler = nil, - TimeInterval = nil, - YieldInterval = nil, + CallScheduler=nil, + TimeInterval=nil, + YieldInterval=nil, } + --- Creates a new SET_BASE object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. -- @param #SET_BASE self -- @return #SET_BASE @@ -106,7 +109,8 @@ do -- SET_BASE -- @param #string ObjectName The name of the object. -- @param Object The object. - self:AddTransition( "*", "Added", "*" ) + + self:AddTransition( "*", "Added", "*" ) --- Removed Handler OnAfter for SET_BASE -- @function [parent=#SET_BASE] OnAfterRemoved @@ -117,7 +121,7 @@ do -- SET_BASE -- @param #string ObjectName The name of the object. -- @param Object The object. - self:AddTransition( "*", "Removed", "*" ) + self:AddTransition( "*", "Removed", "*" ) self.YieldInterval = 10 self.TimeInterval = 0.001 @@ -144,6 +148,8 @@ do -- SET_BASE return self end + + --- Finds an @{Core.Base#BASE} object based on the object Name. -- @param #SET_BASE self -- @param #string ObjectName @@ -154,6 +160,7 @@ do -- SET_BASE return ObjectFound end + --- Gets the Set. -- @param #SET_BASE self -- @return #SET_BASE self @@ -166,7 +173,7 @@ do -- SET_BASE --- Gets a list of the Names of the Objects in the Set. -- @param #SET_BASE self -- @return #SET_BASE self - function SET_BASE:GetSetNames() -- R2.3 + function SET_BASE:GetSetNames() -- R2.3 self:F2() local Names = {} @@ -178,10 +185,11 @@ do -- SET_BASE return Names end + --- Gets a list of the Objects in the Set. -- @param #SET_BASE self -- @return #SET_BASE self - function SET_BASE:GetSetObjects() -- R2.3 + function SET_BASE:GetSetObjects() -- R2.3 self:F2() local Objects = {} @@ -193,18 +201,17 @@ do -- SET_BASE return Objects end + --- Removes a @{Core.Base#BASE} object from the @{Core.Set#SET_BASE} and derived classes, based on the Object Name. -- @param #SET_BASE self -- @param #string ObjectName - -- @param NoTriggerEvent (Optional) When `true`, the :Remove() method will not trigger a **Removed** event. + -- @param NoTriggerEvent (optional) When `true`, the :Remove() method will not trigger a **Removed** event. function SET_BASE:Remove( ObjectName, NoTriggerEvent ) self:F2( { ObjectName = ObjectName } ) - + local TriggerEvent = true - if NoTriggerEvent == false then - TriggerEvent = false - end - + if NoTriggerEvent == false then TriggerEvent = false end + local Object = self.Set[ObjectName] if Object then @@ -222,13 +229,16 @@ do -- SET_BASE end end + --- Adds a @{Core.Base#BASE} object in the @{Core.Set#SET_BASE}, using a given ObjectName as the index. -- @param #SET_BASE self -- @param #string ObjectName The name of the object. -- @param Core.Base#BASE Object The object itself. -- @return Core.Base#BASE The added BASE Object. function SET_BASE:Add( ObjectName, Object ) - self:F2( { ObjectName = ObjectName, Object = Object } ) + + -- Debug info. + self:T( { ObjectName = ObjectName, Object = Object } ) -- Ensure that the existing element is removed from the Set before a new one is inserted to the Set if self.Set[ObjectName] then @@ -258,20 +268,47 @@ do -- SET_BASE end + --- Sort the set by name. + -- @param #SET_BASE self + -- @return Core.Base#BASE The added BASE Object. + function SET_BASE:SortByName() + + local function sort(a, b) + return a= Limit then - break - end - -- if Count % self.YieldInterval == 0 then - -- coroutine.yield( false ) - -- end + Count = Count + 1 + if Count >= Limit then + break + end + -- if Count % self.YieldInterval == 0 then + -- coroutine.yield( false ) + -- end end return true end - -- local co = coroutine.create( CoRoutine ) + -- local co = coroutine.create( CoRoutine ) local co = CoRoutine local function Schedule() - -- local status, res = coroutine.resume( co ) + -- local status, res = coroutine.resume( co ) local status, res = co() self:T3( { status, res } ) @@ -731,48 +780,50 @@ do -- SET_BASE return false end - -- self.CallScheduler:Schedule( self, Schedule, {}, self.TimeInterval, self.TimeInterval, 0 ) + --self.CallScheduler:Schedule( self, Schedule, {}, self.TimeInterval, self.TimeInterval, 0 ) Schedule() return self end - ----- Iterate the SET_BASE and call an iterator function for each **alive** unit, providing the Unit and optional parameters. + + ----- Iterate the SET_BASE and call an interator function for each **alive** unit, providing the Unit and optional parameters. ---- @param #SET_BASE self ---- @param #function IteratorFunction The function that will be called when there is an alive unit in the SET_BASE. The function needs to accept a UNIT parameter. ---- @return #SET_BASE self - -- function SET_BASE:ForEachDCSUnitAlive( IteratorFunction, ... ) + --function SET_BASE:ForEachDCSUnitAlive( IteratorFunction, ... ) -- self:F3( arg ) -- -- self:ForEach( IteratorFunction, arg, self.DCSUnitsAlive ) -- -- return self - -- end + --end -- - ----- Iterate the SET_BASE and call an iterator function for each **alive** player, providing the Unit of the player and optional parameters. + ----- Iterate the SET_BASE and call an interator function for each **alive** player, providing the Unit of the player and optional parameters. ---- @param #SET_BASE self ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_BASE. The function needs to accept a UNIT parameter. ---- @return #SET_BASE self - -- function SET_BASE:ForEachPlayer( IteratorFunction, ... ) + --function SET_BASE:ForEachPlayer( IteratorFunction, ... ) -- self:F3( arg ) -- -- self:ForEach( IteratorFunction, arg, self.PlayersAlive ) -- -- return self - -- end + --end -- -- - ----- Iterate the SET_BASE and call an iterator function for each client, providing the Client to the function and optional parameters. + ----- Iterate the SET_BASE and call an interator function for each client, providing the Client to the function and optional parameters. ---- @param #SET_BASE self ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_BASE. The function needs to accept a CLIENT parameter. ---- @return #SET_BASE self - -- function SET_BASE:ForEachClient( IteratorFunction, ... ) + --function SET_BASE:ForEachClient( IteratorFunction, ... ) -- self:F3( arg ) -- -- self:ForEach( IteratorFunction, arg, self.Clients ) -- -- return self - -- end + --end + --- Decides whether to include the Object. -- @param #SET_BASE self @@ -788,7 +839,7 @@ do -- SET_BASE -- @param #SET_BASE self -- @param #table Object -- @return #SET_BASE self - function SET_BASE:IsInSet( ObjectName ) + function SET_BASE:IsInSet(ObjectName) self:F3( Object ) return true @@ -810,7 +861,7 @@ do -- SET_BASE --- Flushes the current SET_BASE contents in the log ... (for debugging reasons). -- @param #SET_BASE self - -- @param Core.Base#BASE MasterObject (Optional) The master object as a reference. + -- @param Core.Base#BASE MasterObject (optional) The master object as a reference. -- @return #string A string with the names of the objects. function SET_BASE:Flush( MasterObject ) self:F3() @@ -826,6 +877,7 @@ do -- SET_BASE end + do -- SET_GROUP --- @type SET_GROUP @@ -867,17 +919,13 @@ do -- SET_GROUP -- * @{#SET_GROUP.FilterCategoryGround}: Builds the SET_GROUP from ground vehicles or infantry. -- * @{#SET_GROUP.FilterCategoryShip}: Builds the SET_GROUP from ships. -- * @{#SET_GROUP.FilterCategoryStructure}: Builds the SET_GROUP from structures. - -- + -- * @{#SET_GROUP.FilterZones}: Builds the SET_GROUP with the groups within a @{Core.Zone#ZONE}. -- -- Once the filter criteria have been set for the SET_GROUP, you can start filtering using: -- -- * @{#SET_GROUP.FilterStart}: Starts the filtering of the groups within the SET_GROUP and add or remove GROUP objects **dynamically**. -- * @{#SET_GROUP.FilterOnce}: Filters of the groups **once**. -- - -- Planned filter criteria within development are (so these are not yet available): - -- - -- * @{#SET_GROUP.FilterZones}: Builds the SET_GROUP with the groups within a @{Core.Zone#ZONE}. - -- -- ## SET_GROUP iterators -- -- Once the filters have been defined and the SET_GROUP has been built, you can iterate the SET_GROUP with the available iterator methods. @@ -897,7 +945,7 @@ do -- SET_GROUP -- ### When a GROUP object crashes or is dead, the SET_GROUP will trigger a **Dead** event. -- -- You can handle the event using the OnBefore and OnAfter event handlers. - -- The event handlers need to have the parameters From, Event, To, GroupObject. + -- The event handlers need to have the paramters From, Event, To, GroupObject. -- The GroupObject is the GROUP object that is dead and within the SET_GROUP, and is passed as a parameter to the event handler. -- See the following example: -- @@ -912,7 +960,7 @@ do -- SET_GROUP -- end -- -- While this is a good example, there is a catch. - -- Imagine you want to execute the code above, the the self would need to be from the object declared outside (above) the OnAfterDead method. + -- Imageine you want to execute the code above, the the self would need to be from the object declared outside (above) the OnAfterDead method. -- So, the self would need to contain another object. Fortunately, this can be done, but you must use then the **`.`** notation for the method. -- See the modified example: -- @@ -944,6 +992,7 @@ do -- SET_GROUP Categories = nil, Countries = nil, GroupPrefixes = nil, + Zones = nil, }, FilterMeta = { Coalitions = { @@ -961,6 +1010,7 @@ do -- SET_GROUP }, } + --- Creates a new SET_GROUP object, building a set of groups belonging to a coalitions, categories, countries, types or with defined prefix names. -- @param #SET_GROUP self -- @return #SET_GROUP @@ -987,7 +1037,7 @@ do -- SET_GROUP -- Clean the Set before returning with only the alive Groups. for GroupName, GroupObject in pairs( self.Set ) do - local GroupObject = GroupObject -- Wrapper.Group#GROUP + local GroupObject=GroupObject --Wrapper.Group#GROUP if GroupObject then if GroupObject:IsAlive() then AliveSet:Add( GroupName, GroupObject ) @@ -1054,7 +1104,7 @@ do -- SET_GROUP -- @return Core.Set#SET_GROUP self function SET_GROUP:AddGroupsByName( AddGroupNames ) - local AddGroupNamesArray = (type( AddGroupNames ) == "table") and AddGroupNames or { AddGroupNames } + local AddGroupNamesArray = ( type( AddGroupNames ) == "table" ) and AddGroupNames or { AddGroupNames } for AddGroupID, AddGroupName in pairs( AddGroupNamesArray ) do self:Add( AddGroupName, GROUP:FindByName( AddGroupName ) ) @@ -1069,7 +1119,7 @@ do -- SET_GROUP -- @return Core.Set#SET_GROUP self function SET_GROUP:RemoveGroupsByName( RemoveGroupNames ) - local RemoveGroupNamesArray = (type( RemoveGroupNames ) == "table") and RemoveGroupNames or { RemoveGroupNames } + local RemoveGroupNamesArray = ( type( RemoveGroupNames ) == "table" ) and RemoveGroupNames or { RemoveGroupNames } for RemoveGroupID, RemoveGroupName in pairs( RemoveGroupNamesArray ) do self:Remove( RemoveGroupName ) @@ -1078,6 +1128,9 @@ do -- SET_GROUP return self end + + + --- Finds a Group based on the Group Name. -- @param #SET_GROUP self -- @param #string GroupName @@ -1095,7 +1148,7 @@ do -- SET_GROUP function SET_GROUP:FindNearestGroupFromPointVec2( PointVec2 ) self:F2( PointVec2 ) - local NearestGroup = nil -- Wrapper.Group#GROUP + local NearestGroup = nil --Wrapper.Group#GROUP local ClosestDistance = nil for ObjectID, ObjectData in pairs( self.Set ) do @@ -1114,6 +1167,31 @@ do -- SET_GROUP return NearestGroup end + + --- Builds a set of groups in zones. + -- @param #SET_GROUP self + -- @param #table Zones Table of Core.Zone#ZONE Zone objects, or a Core.Set#SET_ZONE + -- @return #SET_GROUP self + function SET_GROUP:FilterZones( Zones ) + if not self.Filter.Zones then + self.Filter.Zones = {} + end + local zones = {} + if Zones.ClassName and Zones.ClassName == "SET_ZONE" then + zones = Zones.Set + elseif type( Zones ) ~= "table" or (type( Zones ) == "table" and Zones.ClassName ) then + self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!") + return self + else + zones = Zones + end + for _,Zone in pairs( zones ) do + local zonename = Zone:GetName() + self.Filter.Zones[zonename] = Zone + end + return self + end + --- Builds a set of groups of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_GROUP self @@ -1132,6 +1210,7 @@ do -- SET_GROUP return self end + --- Builds a set of groups out of categories. -- Possible current categories are plane, helicopter, ground, ship. -- @param #SET_GROUP self @@ -1190,6 +1269,8 @@ do -- SET_GROUP return self end + + --- Builds a set of groups of defined countries. -- Possible current countries are those known within DCS world. -- @param #SET_GROUP self @@ -1208,6 +1289,7 @@ do -- SET_GROUP return self end + --- Builds a set of groups that contain the given string in their group name. -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all groups that **contain** the string. -- @param #SET_GROUP self @@ -1229,7 +1311,7 @@ do -- SET_GROUP --- Builds a set of groups that are only active. -- Only the groups that are active will be included within the set. -- @param #SET_GROUP self - -- @param #boolean Active (Optional) Include only active groups to the set. + -- @param #boolean Active (optional) Include only active groups to the set. -- Include inactive groups if you provide false. -- @return #SET_GROUP self -- @usage @@ -1247,11 +1329,12 @@ do -- SET_GROUP -- GroupSet = SET_GROUP:New():FilterActive( false ):FilterCoalition( "blue" ):FilterOnce() -- function SET_GROUP:FilterActive( Active ) - Active = Active or not (Active == false) + Active = Active or not ( Active == false ) self.Filter.Active = Active return self end + --- Starts the filtering. -- @param #SET_GROUP self -- @return #SET_GROUP self @@ -1265,6 +1348,8 @@ do -- SET_GROUP self:HandleEvent( EVENTS.RemoveUnit, self._EventOnDeadOrCrash ) end + + return self end @@ -1364,6 +1449,22 @@ do -- SET_GROUP return self end + --- Activate late activated groups. + -- @param #SET_GROUP self + -- @param #number Delay Delay in seconds. + -- @return #SET_GROUP self + function SET_GROUP:Activate(Delay) + local Set = self:GetSet() + for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP + local group=GroupData --Wrapper.Group#GROUP + if group and group:IsAlive()==false then + group:Activate(Delay) + end + end + return self + end + + --- Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence completely in a @{Zone}, providing the GROUP and optional parameters to the called function. -- @param #SET_GROUP self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -1372,15 +1473,16 @@ do -- SET_GROUP function SET_GROUP:ForEachGroupCompletelyInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Group#GROUP GroupObject - function( ZoneObject, GroupObject ) - if GroupObject:IsCompletelyInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) + self:ForEach( IteratorFunction, arg, self:GetSet(), + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Group#GROUP GroupObject + function( ZoneObject, GroupObject ) + if GroupObject:IsCompletelyInZone( ZoneObject ) then + return true + else + return false + end + end, { ZoneObject } ) return self end @@ -1393,15 +1495,16 @@ do -- SET_GROUP function SET_GROUP:ForEachGroupPartlyInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Group#GROUP GroupObject - function( ZoneObject, GroupObject ) - if GroupObject:IsPartlyInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) + self:ForEach( IteratorFunction, arg, self:GetSet(), + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Group#GROUP GroupObject + function( ZoneObject, GroupObject ) + if GroupObject:IsPartlyInZone( ZoneObject ) then + return true + else + return false + end + end, { ZoneObject } ) return self end @@ -1414,15 +1517,16 @@ do -- SET_GROUP function SET_GROUP:ForEachGroupNotInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Group#GROUP GroupObject - function( ZoneObject, GroupObject ) - if GroupObject:IsNotInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) + self:ForEach( IteratorFunction, arg, self:GetSet(), + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Group#GROUP GroupObject + function( ZoneObject, GroupObject ) + if GroupObject:IsNotInZone( ZoneObject ) then + return true + else + return false + end + end, { ZoneObject } ) return self end @@ -1430,7 +1534,7 @@ do -- SET_GROUP --- Iterate the SET_GROUP and return true if all the @{Wrapper.Group#GROUP} are completely in the @{Core.Zone#ZONE} -- @param #SET_GROUP self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. - -- @return #boolean true if all the @{Wrapper.Group#GROUP} are completely in the @{Core.Zone#ZONE}, false otherwise + -- @return #boolean true if all the @{Wrapper.Group#GROUP} are completly in the @{Core.Zone#ZONE}, false otherwise -- @usage -- local MyZone = ZONE:New("Zone1") -- local MySetGroup = SET_GROUP:New() @@ -1441,11 +1545,11 @@ do -- SET_GROUP -- else -- MESSAGE:New("Some or all SET's GROUP are outside zone !", 10):ToAll() -- end - function SET_GROUP:AllCompletelyInZone( Zone ) - self:F2( Zone ) + function SET_GROUP:AllCompletelyInZone(Zone) + self:F2(Zone) local Set = self:GetSet() - for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP - if not GroupData:IsCompletelyInZone( Zone ) then + for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP + if not GroupData:IsCompletelyInZone(Zone) then return false end end @@ -1460,23 +1564,25 @@ do -- SET_GROUP function SET_GROUP:ForEachGroupAnyInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Group#GROUP GroupObject - function( ZoneObject, GroupObject ) - if GroupObject:IsAnyInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) + self:ForEach( IteratorFunction, arg, self:GetSet(), + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Group#GROUP GroupObject + function( ZoneObject, GroupObject ) + if GroupObject:IsAnyInZone( ZoneObject ) then + return true + else + return false + end + end, { ZoneObject } ) return self end + --- Iterate the SET_GROUP and return true if at least one of the @{Wrapper.Group#GROUP} is completely inside the @{Core.Zone#ZONE} -- @param #SET_GROUP self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. - -- @return #boolean true if at least one of the @{Wrapper.Group#GROUP} is completely inside the @{Core.Zone#ZONE}, false otherwise. + -- @return #boolean true if at least one of the @{Wrapper.Group#GROUP} is completly inside the @{Core.Zone#ZONE}, false otherwise. -- @usage -- local MyZone = ZONE:New("Zone1") -- local MySetGroup = SET_GROUP:New() @@ -1487,11 +1593,11 @@ do -- SET_GROUP -- else -- MESSAGE:New("No GROUP is completely in zone !", 10):ToAll() -- end - function SET_GROUP:AnyCompletelyInZone( Zone ) - self:F2( Zone ) + function SET_GROUP:AnyCompletelyInZone(Zone) + self:F2(Zone) local Set = self:GetSet() - for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP - if GroupData:IsCompletelyInZone( Zone ) then + for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP + if GroupData:IsCompletelyInZone(Zone) then return true end end @@ -1501,7 +1607,7 @@ do -- SET_GROUP --- Iterate the SET_GROUP and return true if at least one @{#UNIT} of one @{GROUP} of the @{SET_GROUP} is in @{ZONE} -- @param #SET_GROUP self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. - -- @return #boolean true if at least one of the @{Wrapper.Group#GROUP} is partly or completely inside the @{Core.Zone#ZONE}, false otherwise. + -- @return #boolean true if at least one of the @{Wrapper.Group#GROUP} is partly or completly inside the @{Core.Zone#ZONE}, false otherwise. -- @usage -- local MyZone = ZONE:New("Zone1") -- local MySetGroup = SET_GROUP:New() @@ -1512,11 +1618,11 @@ do -- SET_GROUP -- else -- MESSAGE:New("No UNIT of any GROUP is in zone !", 10):ToAll() -- end - function SET_GROUP:AnyInZone( Zone ) - self:F2( Zone ) + function SET_GROUP:AnyInZone(Zone) + self:F2(Zone) local Set = self:GetSet() - for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP - if GroupData:IsPartlyInZone( Zone ) or GroupData:IsCompletelyInZone( Zone ) then + for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP + if GroupData:IsPartlyInZone(Zone) or GroupData:IsCompletelyInZone(Zone) then return true end end @@ -1527,7 +1633,7 @@ do -- SET_GROUP -- Will return false if a @{GROUP} is fully in the @{ZONE} -- @param #SET_GROUP self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. - -- @return #boolean true if at least one of the @{Wrapper.Group#GROUP} is partly or completely inside the @{Core.Zone#ZONE}, false otherwise. + -- @return #boolean true if at least one of the @{Wrapper.Group#GROUP} is partly or completly inside the @{Core.Zone#ZONE}, false otherwise. -- @usage -- local MyZone = ZONE:New("Zone1") -- local MySetGroup = SET_GROUP:New() @@ -1538,14 +1644,14 @@ do -- SET_GROUP -- else -- MESSAGE:New("No GROUP are in zone, or one (or more) GROUP is completely in it !", 10):ToAll() -- end - function SET_GROUP:AnyPartlyInZone( Zone ) - self:F2( Zone ) + function SET_GROUP:AnyPartlyInZone(Zone) + self:F2(Zone) local IsPartlyInZone = false local Set = self:GetSet() - for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP - if GroupData:IsCompletelyInZone( Zone ) then + for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP + if GroupData:IsCompletelyInZone(Zone) then return false - elseif GroupData:IsPartlyInZone( Zone ) then + elseif GroupData:IsPartlyInZone(Zone) then IsPartlyInZone = true -- at least one GROUP is partly in zone end end @@ -1573,11 +1679,11 @@ do -- SET_GROUP -- else -- MESSAGE:New("No UNIT of any GROUP is in zone !", 10):ToAll() -- end - function SET_GROUP:NoneInZone( Zone ) - self:F2( Zone ) + function SET_GROUP:NoneInZone(Zone) + self:F2(Zone) local Set = self:GetSet() - for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP - if not GroupData:IsNotInZone( Zone ) then -- If the GROUP is in Zone in any way + for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP + if not GroupData:IsNotInZone(Zone) then -- If the GROUP is in Zone in any way return false end end @@ -1596,12 +1702,12 @@ do -- SET_GROUP -- MySetGroup:AddGroupsByName({"Group1", "Group2"}) -- -- MESSAGE:New("There are " .. MySetGroup:CountInZone(MyZone) .. " GROUPs in the Zone !", 10):ToAll() - function SET_GROUP:CountInZone( Zone ) - self:F2( Zone ) + function SET_GROUP:CountInZone(Zone) + self:F2(Zone) local Count = 0 local Set = self:GetSet() - for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP - if GroupData:IsCompletelyInZone( Zone ) then + for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP + if GroupData:IsCompletelyInZone(Zone) then Count = Count + 1 end end @@ -1618,12 +1724,12 @@ do -- SET_GROUP -- MySetGroup:AddGroupsByName({"Group1", "Group2"}) -- -- MESSAGE:New("There are " .. MySetGroup:CountUnitInZone(MyZone) .. " UNITs in the Zone !", 10):ToAll() - function SET_GROUP:CountUnitInZone( Zone ) - self:F2( Zone ) + function SET_GROUP:CountUnitInZone(Zone) + self:F2(Zone) local Count = 0 local Set = self:GetSet() - for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP - Count = Count + GroupData:CountInZone( Zone ) + for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP + Count = Count + GroupData:CountInZone(Zone) end return Count end @@ -1638,49 +1744,50 @@ do -- SET_GROUP local Set = self:GetSet() - for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP + for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP if GroupData and GroupData:IsAlive() then CountG = CountG + 1 - -- Count Units. - for _, _unit in pairs( GroupData:GetUnits() ) do - local unit = _unit -- Wrapper.Unit#UNIT + --Count Units. + for _,_unit in pairs(GroupData:GetUnits()) do + local unit=_unit --Wrapper.Unit#UNIT if unit and unit:IsAlive() then - CountU = CountU + 1 + CountU=CountU+1 end end end end - return CountG, CountU + return CountG,CountU end - ----- Iterate the SET_GROUP and call an iterator function for each **alive** player, providing the Group of the player and optional parameters. + ----- Iterate the SET_GROUP and call an interator function for each **alive** player, providing the Group of the player and optional parameters. ---- @param #SET_GROUP self ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_GROUP. The function needs to accept a GROUP parameter. ---- @return #SET_GROUP self - -- function SET_GROUP:ForEachPlayer( IteratorFunction, ... ) + --function SET_GROUP:ForEachPlayer( IteratorFunction, ... ) -- self:F2( arg ) -- -- self:ForEach( IteratorFunction, arg, self.PlayersAlive ) -- -- return self - -- end + --end -- -- - ----- Iterate the SET_GROUP and call an iterator function for each client, providing the Client to the function and optional parameters. + ----- Iterate the SET_GROUP and call an interator function for each client, providing the Client to the function and optional parameters. ---- @param #SET_GROUP self ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_GROUP. The function needs to accept a CLIENT parameter. ---- @return #SET_GROUP self - -- function SET_GROUP:ForEachClient( IteratorFunction, ... ) + --function SET_GROUP:ForEachClient( IteratorFunction, ... ) -- self:F2( arg ) -- -- self:ForEach( IteratorFunction, arg, self.Clients ) -- -- return self - -- end + --end + --- -- @param #SET_GROUP self @@ -1693,7 +1800,7 @@ do -- SET_GROUP if self.Filter.Active ~= nil then local MGroupActive = false self:F( { Active = self.Filter.Active } ) - if self.Filter.Active == false or (self.Filter.Active == true and MGroup:IsActive() == true) then + if self.Filter.Active == false or ( self.Filter.Active == true and MGroup:IsActive() == true ) then MGroupActive = true end MGroupInclude = MGroupInclude and MGroupActive @@ -1736,17 +1843,29 @@ do -- SET_GROUP local MGroupPrefix = false for GroupPrefixId, GroupPrefix in pairs( self.Filter.GroupPrefixes ) do self:T3( { "Prefix:", string.find( MGroup:GetName(), GroupPrefix, 1 ), GroupPrefix } ) - if string.find( MGroup:GetName(), GroupPrefix:gsub( "-", "%%-" ), 1 ) then + if string.find( MGroup:GetName(), GroupPrefix:gsub ("-", "%%-"), 1 ) then MGroupPrefix = true end end MGroupInclude = MGroupInclude and MGroupPrefix end - + + if self.Filter.Zones then + local MGroupZone = false + for ZoneName, Zone in pairs( self.Filter.Zones ) do + self:T3( "Zone:", ZoneName ) + if MGroup:IsInZone(Zone) then + MGroupZone = true + end + end + MGroupInclude = MGroupInclude and MGroupZone + end + self:T2( MGroupInclude ) return MGroupInclude end + --- Iterate the SET_GROUP and set for each unit the default cargo bay weight limit. -- Because within a group, the type of carriers can differ, each cargo bay weight limit is set on @{Wrapper.Unit} level. -- @param #SET_GROUP self @@ -1758,7 +1877,7 @@ do -- SET_GROUP local Set = self:GetSet() for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP for UnitName, UnitData in pairs( GroupData:GetUnits() ) do - -- local UnitData = UnitData -- Wrapper.Unit#UNIT + --local UnitData = UnitData -- Wrapper.Unit#UNIT UnitData:SetCargoBayWeightLimit() end end @@ -1766,6 +1885,7 @@ do -- SET_GROUP end + do -- SET_UNIT --- @type SET_UNIT @@ -1801,16 +1921,13 @@ do -- SET_UNIT -- * @{#SET_UNIT.FilterCountries}: Builds the SET_UNIT with the units belonging to the country(ies). -- * @{#SET_UNIT.FilterPrefixes}: Builds the SET_UNIT with the units sharing the same string(s) in their name. **ATTENTION!** Bad naming convention as this *does not* only filter *prefixes*. -- * @{#SET_UNIT.FilterActive}: Builds the SET_UNIT with the units that are only active. Units that are inactive (late activation) won't be included in the set! - -- + -- * @{#SET_UNIT.FilterZones}: Builds the SET_UNIT with the units within a @{Core.Zone#ZONE}. + -- -- Once the filter criteria have been set for the SET_UNIT, you can start filtering using: -- -- * @{#SET_UNIT.FilterStart}: Starts the filtering of the units **dynamically**. -- * @{#SET_UNIT.FilterOnce}: Filters of the units **once**. -- - -- Planned filter criteria within development are (so these are not yet available): - -- - -- * @{#SET_UNIT.FilterZones}: Builds the SET_UNIT with the units within a @{Core.Zone#ZONE}. - -- -- ## 4) SET_UNIT iterators -- -- Once the filters have been defined and the SET_UNIT has been built, you can iterate the SET_UNIT with the available iterator methods. @@ -1840,7 +1957,7 @@ do -- SET_UNIT -- ### 6.1) When a UNIT object crashes or is dead, the SET_UNIT will trigger a **Dead** event. -- -- You can handle the event using the OnBefore and OnAfter event handlers. - -- The event handlers need to have the parameters From, Event, To, GroupObject. + -- The event handlers need to have the paramters From, Event, To, GroupObject. -- The GroupObject is the UNIT object that is dead and within the SET_UNIT, and is passed as a parameter to the event handler. -- See the following example: -- @@ -1855,7 +1972,7 @@ do -- SET_UNIT -- end -- -- While this is a good example, there is a catch. - -- Imagine you want to execute the code above, the the self would need to be from the object declared outside (above) the OnAfterDead method. + -- Imageine you want to execute the code above, the the self would need to be from the object declared outside (above) the OnAfterDead method. -- So, the self would need to contain another object. Fortunately, this can be done, but you must use then the **`.`** notation for the method. -- See the modified example: -- @@ -1887,6 +2004,7 @@ do -- SET_UNIT Types = nil, Countries = nil, UnitPrefixes = nil, + Zones = nil, }, FilterMeta = { Coalitions = { @@ -1904,6 +2022,7 @@ do -- SET_UNIT }, } + --- Get the first unit from the set. -- @function [parent=#SET_UNIT] GetFirst -- @param #SET_UNIT self @@ -1940,13 +2059,14 @@ do -- SET_UNIT return self end + --- Add UNIT(s) to SET_UNIT. -- @param #SET_UNIT self -- @param #string AddUnitNames A single name or an array of UNIT names. -- @return #SET_UNIT self function SET_UNIT:AddUnitsByName( AddUnitNames ) - local AddUnitNamesArray = (type( AddUnitNames ) == "table") and AddUnitNames or { AddUnitNames } + local AddUnitNamesArray = ( type( AddUnitNames ) == "table" ) and AddUnitNames or { AddUnitNames } self:T( AddUnitNamesArray ) for AddUnitID, AddUnitName in pairs( AddUnitNamesArray ) do @@ -1962,7 +2082,7 @@ do -- SET_UNIT -- @return Core.Set#SET_UNIT self function SET_UNIT:RemoveUnitsByName( RemoveUnitNames ) - local RemoveUnitNamesArray = (type( RemoveUnitNames ) == "table") and RemoveUnitNames or { RemoveUnitNames } + local RemoveUnitNamesArray = ( type( RemoveUnitNames ) == "table" ) and RemoveUnitNames or { RemoveUnitNames } for RemoveUnitID, RemoveUnitName in pairs( RemoveUnitNamesArray ) do self:Remove( RemoveUnitName ) @@ -1971,6 +2091,7 @@ do -- SET_UNIT return self end + --- Finds a Unit based on the Unit Name. -- @param #SET_UNIT self -- @param #string UnitName @@ -1981,6 +2102,8 @@ do -- SET_UNIT return UnitFound end + + --- Builds a set of units of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_UNIT self @@ -1998,6 +2121,7 @@ do -- SET_UNIT return self end + --- Builds a set of units out of categories. -- Possible current categories are plane, helicopter, ground, ship. -- @param #SET_UNIT self @@ -2016,6 +2140,7 @@ do -- SET_UNIT return self end + --- Builds a set of units of defined unit types. -- Possible current types are those types known within DCS world. -- @param #SET_UNIT self @@ -2034,6 +2159,7 @@ do -- SET_UNIT return self end + --- Builds a set of units of defined countries. -- Possible current countries are those known within DCS world. -- @param #SET_UNIT self @@ -2052,6 +2178,7 @@ do -- SET_UNIT return self end + --- Builds a set of UNITs that contain a given string in their unit name. -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all units that **contain** the string. -- @param #SET_UNIT self @@ -2069,11 +2196,35 @@ do -- SET_UNIT end return self end - + + --- Builds a set of units in zones. + -- @param #SET_UNIT self + -- @param #table Zones Table of Core.Zone#ZONE Zone objects, or a Core.Set#SET_ZONE + -- @return #SET_UNIT self + function SET_UNIT:FilterZones( Zones ) + if not self.Filter.Zones then + self.Filter.Zones = {} + end + local zones = {} + if Zones.ClassName and Zones.ClassName == "SET_ZONE" then + zones = Zones.Set + elseif type( Zones ) ~= "table" or (type( Zones ) == "table" and Zones.ClassName ) then + self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!") + return self + else + zones = Zones + end + for _,Zone in pairs( zones ) do + local zonename = Zone:GetName() + self.Filter.Zones[zonename] = Zone + end + return self + end + --- Builds a set of units that are only active. -- Only the units that are active will be included within the set. -- @param #SET_UNIT self - -- @param #boolean Active (Optional) Include only active units to the set. + -- @param #boolean Active (optional) Include only active units to the set. -- Include inactive units if you provide false. -- @return #SET_UNIT self -- @usage @@ -2091,7 +2242,7 @@ do -- SET_UNIT -- UnitSet = SET_UNIT:New():FilterActive( false ):FilterCoalition( "blue" ):FilterOnce() -- function SET_UNIT:FilterActive( Active ) - Active = Active or not (Active == false) + Active = Active or not ( Active == false ) self.Filter.Active = Active return self end @@ -2130,7 +2281,7 @@ do -- SET_UNIT local Set = self:GetSet() local CountU = 0 - for UnitID, UnitData in pairs( Set ) do -- For each GROUP in SET_GROUP + for UnitID, UnitData in pairs(Set) do -- For each GROUP in SET_GROUP if UnitData and UnitData:IsAlive() then CountU = CountU + 1 end @@ -2156,6 +2307,8 @@ do -- SET_UNIT return self end + + --- Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- @param #SET_UNIT self @@ -2184,9 +2337,11 @@ do -- SET_UNIT function SET_UNIT:FindInDatabase( Event ) self:F2( { Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName], Event } ) + return Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName] end + do -- Is Zone methods --- Check if minimal one element of the SET_UNIT is in the Zone. @@ -2199,7 +2354,7 @@ do -- SET_UNIT local function EvaluateZone( ZoneUnit ) - local ZoneUnitName = ZoneUnit:GetName() + local ZoneUnitName = ZoneUnit:GetName() self:F( { ZoneUnitName = ZoneUnitName } ) if self:FindUnit( ZoneUnitName ) then IsPartiallyInZone = true @@ -2215,6 +2370,7 @@ do -- SET_UNIT return IsPartiallyInZone end + --- Check if no element of the SET_UNIT is in the Zone. -- @param #SET_UNIT self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -2225,7 +2381,7 @@ do -- SET_UNIT local function EvaluateZone( ZoneUnit ) - local ZoneUnitName = ZoneUnit:GetName() + local ZoneUnitName = ZoneUnit:GetName() if self:FindUnit( ZoneUnitName ) then IsNotInZone = false return false @@ -2241,7 +2397,8 @@ do -- SET_UNIT end - --- Iterate the SET_UNIT and call an iterator function for each **alive** UNIT, providing the UNIT and optional parameters. + + --- Iterate the SET_UNIT and call an interator function for each **alive** UNIT, providing the UNIT and optional parameters. -- @param #SET_UNIT self -- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter. -- @return #SET_UNIT self @@ -2253,12 +2410,16 @@ do -- SET_UNIT return self end + --- Get the SET of the SET_UNIT **sorted per Threat Level**. -- -- @param #SET_UNIT self -- @param #number FromThreatLevel The TreatLevel to start the evaluation **From** (this must be a value between 0 and 10). -- @param #number ToThreatLevel The TreatLevel to stop the evaluation **To** (this must be a value between 0 and 10). -- @return #SET_UNIT self + -- @usage + -- + -- function SET_UNIT:GetSetPerThreatLevel( FromThreatLevel, ToThreatLevel ) self:F2( arg ) @@ -2275,10 +2436,12 @@ do -- SET_UNIT self:F( { ThreatLevel = ThreatLevel, ThreatLevelSet = ThreatLevelSet[ThreatLevel].Set } ) end + local OrderedPerThreatLevelSet = {} local ThreatLevelIncrement = FromThreatLevel <= ToThreatLevel and 1 or -1 + for ThreatLevel = FromThreatLevel, ToThreatLevel, ThreatLevelIncrement do self:F( { ThreatLevel = ThreatLevel } ) local ThreatLevelItem = ThreatLevelSet[ThreatLevel] @@ -2294,7 +2457,8 @@ do -- SET_UNIT end - --- Iterate the SET_UNIT **sorted *per Threat Level** and call an iterator function for each **alive** UNIT, providing the UNIT and optional parameters. + + --- Iterate the SET_UNIT **sorted *per Threat Level** and call an interator function for each **alive** UNIT, providing the UNIT and optional parameters. -- -- @param #SET_UNIT self -- @param #number FromThreatLevel The TreatLevel to start the evaluation **From** (this must be a value between 0 and 10). @@ -2310,7 +2474,7 @@ do -- SET_UNIT -- end -- ) -- - function SET_UNIT:ForEachUnitPerThreatLevel( FromThreatLevel, ToThreatLevel, IteratorFunction, ... ) -- R2.1 Threat Level implementation + function SET_UNIT:ForEachUnitPerThreatLevel( FromThreatLevel, ToThreatLevel, IteratorFunction, ... ) --R2.1 Threat Level implementation self:F2( arg ) local ThreatLevelSet = {} @@ -2340,6 +2504,8 @@ do -- SET_UNIT return self end + + --- Iterate the SET_UNIT and call an iterator function for each **alive** UNIT presence completely in a @{Zone}, providing the UNIT and optional parameters to the called function. -- @param #SET_UNIT self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -2348,15 +2514,16 @@ do -- SET_UNIT function SET_UNIT:ForEachUnitCompletelyInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Unit#UNIT UnitObject - function( ZoneObject, UnitObject ) - if UnitObject:IsInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) + self:ForEach( IteratorFunction, arg, self:GetSet(), + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Unit#UNIT UnitObject + function( ZoneObject, UnitObject ) + if UnitObject:IsInZone( ZoneObject ) then + return true + else + return false + end + end, { ZoneObject } ) return self end @@ -2369,15 +2536,16 @@ do -- SET_UNIT function SET_UNIT:ForEachUnitNotInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Unit#UNIT UnitObject - function( ZoneObject, UnitObject ) - if UnitObject:IsNotInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) + self:ForEach( IteratorFunction, arg, self:GetSet(), + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Unit#UNIT UnitObject + function( ZoneObject, UnitObject ) + if UnitObject:IsNotInZone( ZoneObject ) then + return true + else + return false + end + end, { ZoneObject } ) return self end @@ -2405,12 +2573,13 @@ do -- SET_UNIT end for UnitTypeID, UnitType in pairs( UnitTypes ) do - MT[#MT + 1] = UnitType .. " of " .. UnitTypeID + MT[#MT+1] = UnitType .. " of " .. UnitTypeID end return UnitTypes end + --- Returns a comma separated string of the unit types with a count in the @{Set}. -- @param #SET_UNIT self -- @return #string The unit types string @@ -2421,7 +2590,7 @@ do -- SET_UNIT local UnitTypes = self:GetUnitTypes() for UnitTypeID, UnitType in pairs( UnitTypes ) do - MT[#MT + 1] = UnitType .. " of " .. UnitTypeID + MT[#MT+1] = UnitType .. " of " .. UnitTypeID end return table.concat( MT, ", " ) @@ -2451,7 +2620,7 @@ do -- SET_UNIT return UnitThreatLevels end - --- Calculate the maximum A2G threat level of the SET_UNIT. + --- Calculate the maxium A2G threat level of the SET_UNIT. -- @param #SET_UNIT self -- @return #number The maximum threatlevel function SET_UNIT:CalculateThreatLevelA2G() @@ -2494,27 +2663,27 @@ do -- SET_UNIT local Unit = UnitData -- Wrapper.Unit#UNIT local Coordinate = Unit:GetCoordinate() - x1 = (Coordinate.x < x1) and Coordinate.x or x1 - x2 = (Coordinate.x > x2) and Coordinate.x or x2 - y1 = (Coordinate.y < y1) and Coordinate.y or y1 - y2 = (Coordinate.y > y2) and Coordinate.y or y2 - z1 = (Coordinate.y < z1) and Coordinate.z or z1 - z2 = (Coordinate.y > z2) and Coordinate.z or z2 + x1 = ( Coordinate.x < x1 ) and Coordinate.x or x1 + x2 = ( Coordinate.x > x2 ) and Coordinate.x or x2 + y1 = ( Coordinate.y < y1 ) and Coordinate.y or y1 + y2 = ( Coordinate.y > y2 ) and Coordinate.y or y2 + z1 = ( Coordinate.y < z1 ) and Coordinate.z or z1 + z2 = ( Coordinate.y > z2 ) and Coordinate.z or z2 local Velocity = Coordinate:GetVelocity() - if Velocity ~= 0 then - MaxVelocity = (MaxVelocity < Velocity) and Velocity or MaxVelocity + if Velocity ~= 0 then + MaxVelocity = ( MaxVelocity < Velocity ) and Velocity or MaxVelocity local Heading = Coordinate:GetHeading() - AvgHeading = AvgHeading and (AvgHeading + Heading) or Heading + AvgHeading = AvgHeading and ( AvgHeading + Heading ) or Heading MovingCount = MovingCount + 1 end end - AvgHeading = AvgHeading and (AvgHeading / MovingCount) + AvgHeading = AvgHeading and ( AvgHeading / MovingCount ) - Coordinate.x = (x2 - x1) / 2 + x1 - Coordinate.y = (y2 - y1) / 2 + y1 - Coordinate.z = (z2 - z1) / 2 + z1 + Coordinate.x = ( x2 - x1 ) / 2 + x1 + Coordinate.y = ( y2 - y1 ) / 2 + y1 + Coordinate.z = ( z2 - z1 ) / 2 + z1 Coordinate:SetHeading( AvgHeading ) Coordinate:SetVelocity( MaxVelocity ) @@ -2538,8 +2707,8 @@ do -- SET_UNIT local Coordinate = Unit:GetCoordinate() local Velocity = Coordinate:GetVelocity() - if Velocity ~= 0 then - MaxVelocity = (MaxVelocity < Velocity) and Velocity or MaxVelocity + if Velocity ~= 0 then + MaxVelocity = ( MaxVelocity < Velocity ) and Velocity or MaxVelocity end end @@ -2562,12 +2731,12 @@ do -- SET_UNIT local Coordinate = Unit:GetCoordinate() local Velocity = Coordinate:GetVelocity() - if Velocity ~= 0 then + if Velocity ~= 0 then local Heading = Coordinate:GetHeading() if HeadingSet == nil then HeadingSet = Heading else - local HeadingDiff = (HeadingSet - Heading + 180 + 360) % 360 - 180 + local HeadingDiff = ( HeadingSet - Heading + 180 + 360 ) % 360 - 180 HeadingDiff = math.abs( HeadingDiff ) if HeadingDiff > 5 then HeadingSet = nil @@ -2581,6 +2750,8 @@ do -- SET_UNIT end + + --- Returns if the @{Set} has targets having a radar (of a given type). -- @param #SET_UNIT self -- @param DCS#Unit.RadarType RadarType @@ -2589,7 +2760,7 @@ do -- SET_UNIT self:F2( RadarType ) local RadarCount = 0 - for UnitID, UnitData in pairs( self:GetSet() ) do + for UnitID, UnitData in pairs( self:GetSet()) do local UnitSensorTest = UnitData -- Wrapper.Unit#UNIT local HasSensors if RadarType then @@ -2597,7 +2768,7 @@ do -- SET_UNIT else HasSensors = UnitSensorTest:HasSensors( Unit.SensorType.RADAR ) end - self:T3( HasSensors ) + self:T3(HasSensors) if HasSensors then RadarCount = RadarCount + 1 end @@ -2613,14 +2784,14 @@ do -- SET_UNIT self:F2() local SEADCount = 0 - for UnitID, UnitData in pairs( self:GetSet() ) do + for UnitID, UnitData in pairs( self:GetSet()) do local UnitSEAD = UnitData -- Wrapper.Unit#UNIT if UnitSEAD:IsAlive() then local UnitSEADAttributes = UnitSEAD:GetDesc().attributes local HasSEAD = UnitSEAD:HasSEAD() - self:T3( HasSEAD ) + self:T3(HasSEAD) if HasSEAD then SEADCount = SEADCount + 1 end @@ -2637,7 +2808,7 @@ do -- SET_UNIT self:F2() local GroundUnitCount = 0 - for UnitID, UnitData in pairs( self:GetSet() ) do + for UnitID, UnitData in pairs( self:GetSet()) do local UnitTest = UnitData -- Wrapper.Unit#UNIT if UnitTest:IsGround() then GroundUnitCount = GroundUnitCount + 1 @@ -2671,7 +2842,7 @@ do -- SET_UNIT self:F2() local FriendlyUnitCount = 0 - for UnitID, UnitData in pairs( self:GetSet() ) do + for UnitID, UnitData in pairs( self:GetSet()) do local UnitTest = UnitData -- Wrapper.Unit#UNIT if UnitTest:IsFriendly( FriendlyCoalition ) then FriendlyUnitCount = FriendlyUnitCount + 1 @@ -2681,30 +2852,33 @@ do -- SET_UNIT return FriendlyUnitCount end - ----- Iterate the SET_UNIT and call an iterator function for each **alive** player, providing the Unit of the player and optional parameters. + + + ----- Iterate the SET_UNIT and call an interator function for each **alive** player, providing the Unit of the player and optional parameters. ---- @param #SET_UNIT self ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_UNIT. The function needs to accept a UNIT parameter. ---- @return #SET_UNIT self - -- function SET_UNIT:ForEachPlayer( IteratorFunction, ... ) + --function SET_UNIT:ForEachPlayer( IteratorFunction, ... ) -- self:F2( arg ) -- -- self:ForEach( IteratorFunction, arg, self.PlayersAlive ) -- -- return self - -- end + --end -- -- - ----- Iterate the SET_UNIT and call an iterator function for each client, providing the Client to the function and optional parameters. + ----- Iterate the SET_UNIT and call an interator function for each client, providing the Client to the function and optional parameters. ---- @param #SET_UNIT self ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_UNIT. The function needs to accept a CLIENT parameter. ---- @return #SET_UNIT self - -- function SET_UNIT:ForEachClient( IteratorFunction, ... ) + --function SET_UNIT:ForEachClient( IteratorFunction, ... ) -- self:F2( arg ) -- -- self:ForEach( IteratorFunction, arg, self.Clients ) -- -- return self - -- end + --end + --- -- @param #SET_UNIT self @@ -2721,7 +2895,7 @@ do -- SET_UNIT if self.Filter.Active ~= nil then local MUnitActive = false - if self.Filter.Active == false or (self.Filter.Active == true and MUnit:IsActive() == true) then + if self.Filter.Active == false or ( self.Filter.Active == true and MUnit:IsActive() == true ) then MUnitActive = true end MUnitInclude = MUnitInclude and MUnitActive @@ -2805,14 +2979,26 @@ do -- SET_UNIT MUnitInclude = MUnitInclude and MUnitSEAD end end - + + if self.Filter.Zones then + local MGroupZone = false + for ZoneName, Zone in pairs( self.Filter.Zones ) do + self:T3( "Zone:", ZoneName ) + if MUnit:IsInZone(Zone) then + MGroupZone = true + end + end + MUnitInclude = MUnitInclude and MGroupZone + end + self:T2( MUnitInclude ) return MUnitInclude end + --- Retrieve the type names of the @{Wrapper.Unit}s in the SET, delimited by an optional delimiter. -- @param #SET_UNIT self - -- @param #string Delimiter (Optional) The delimiter, which is default a comma. + -- @param #string Delimiter (optional) The delimiter, which is default a comma. -- @return #string The types of the @{Wrapper.Unit}s delimited. function SET_UNIT:GetTypeNames( Delimiter ) @@ -2843,13 +3029,16 @@ do -- SET_UNIT function SET_UNIT:SetCargoBayWeightLimit() local Set = self:GetSet() for UnitID, UnitData in pairs( Set ) do -- For each UNIT in SET_UNIT - -- local UnitData = UnitData -- Wrapper.Unit#UNIT + --local UnitData = UnitData -- Wrapper.Unit#UNIT UnitData:SetCargoBayWeightLimit() end end + + end + do -- SET_STATIC --- @type SET_STATIC @@ -2884,15 +3073,12 @@ do -- SET_STATIC -- * @{#SET_STATIC.FilterTypes}: Builds the SET_STATIC with the units belonging to the unit type(s). -- * @{#SET_STATIC.FilterCountries}: Builds the SET_STATIC with the units belonging to the country(ies). -- * @{#SET_STATIC.FilterPrefixes}: Builds the SET_STATIC with the units containing the same string(s) in their name. **ATTENTION** bad naming convention as this *does not** only filter *prefixes*. - -- + -- * @{#SET_STATIC.FilterZones}: Builds the SET_STATIC with the units within a @{Core.Zone#ZONE}. + -- -- Once the filter criteria have been set for the SET_STATIC, you can start filtering using: -- -- * @{#SET_STATIC.FilterStart}: Starts the filtering of the units within the SET_STATIC. -- - -- Planned filter criteria within development are (so these are not yet available): - -- - -- * @{#SET_STATIC.FilterZones}: Builds the SET_STATIC with the units within a @{Core.Zone#ZONE}. - -- -- ## SET_STATIC iterators -- -- Once the filters have been defined and the SET_STATIC has been built, you can iterate the SET_STATIC with the available iterator methods. @@ -2900,14 +3086,9 @@ do -- SET_STATIC -- The following iterator methods are currently available within the SET_STATIC: -- -- * @{#SET_STATIC.ForEachStatic}: Calls a function for each alive unit it finds within the SET_STATIC. - -- * @{#SET_GROUP.ForEachGroupCompletelyInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence completely in a @{Zone}, providing the GROUP and optional parameters to the called function. - -- * @{#SET_GROUP.ForEachGroupNotInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence not in a @{Zone}, providing the GROUP and optional parameters to the called function. - -- - -- Planned iterators methods in development are (so these are not yet available): - -- - -- * @{#SET_STATIC.ForEachStaticInZone}: Calls a function for each unit contained within the SET_STATIC. - -- * @{#SET_STATIC.ForEachStaticCompletelyInZone}: Iterate and call an iterator function for each **alive** STATIC presence completely in a @{Zone}, providing the STATIC and optional parameters to the called function. - -- * @{#SET_STATIC.ForEachStaticNotInZone}: Iterate and call an iterator function for each **alive** STATIC presence not in a @{Zone}, providing the STATIC and optional parameters to the called function. + -- * @{#SET_STATIC.ForEachStaticCompletelyInZone}: Iterate the SET_STATIC and call an iterator function for each **alive** STATIC presence completely in a @{Zone}, providing the STATIC and optional parameters to the called function. + -- * @{#SET_STATIC.ForEachStaticInZone}: Iterate the SET_STATIC and call an iterator function for each **alive** STATIC presence completely in a @{Zone}, providing the STATIC and optional parameters to the called function. + -- * @{#SET_STATIC.ForEachStaticNotInZone}: Iterate the SET_STATIC and call an iterator function for each **alive** STATIC presence not in a @{Zone}, providing the STATIC and optional parameters to the called function. -- -- ## SET_STATIC atomic methods -- @@ -2926,6 +3107,7 @@ do -- SET_STATIC Types = nil, Countries = nil, StaticPrefixes = nil, + Zones = nil, }, FilterMeta = { Coalitions = { @@ -2943,6 +3125,7 @@ do -- SET_STATIC }, } + --- Get the first unit from the set. -- @function [parent=#SET_STATIC] GetFirst -- @param #SET_STATIC self @@ -2974,13 +3157,14 @@ do -- SET_STATIC return self end + --- Add STATIC(s) to SET_STATIC. -- @param #SET_STATIC self -- @param #string AddStaticNames A single name or an array of STATIC names. -- @return #SET_STATIC self function SET_STATIC:AddStaticsByName( AddStaticNames ) - local AddStaticNamesArray = (type( AddStaticNames ) == "table") and AddStaticNames or { AddStaticNames } + local AddStaticNamesArray = ( type( AddStaticNames ) == "table" ) and AddStaticNames or { AddStaticNames } self:T( AddStaticNamesArray ) for AddStaticID, AddStaticName in pairs( AddStaticNamesArray ) do @@ -2996,7 +3180,7 @@ do -- SET_STATIC -- @return self function SET_STATIC:RemoveStaticsByName( RemoveStaticNames ) - local RemoveStaticNamesArray = (type( RemoveStaticNames ) == "table") and RemoveStaticNames or { RemoveStaticNames } + local RemoveStaticNamesArray = ( type( RemoveStaticNames ) == "table" ) and RemoveStaticNames or { RemoveStaticNames } for RemoveStaticID, RemoveStaticName in pairs( RemoveStaticNamesArray ) do self:Remove( RemoveStaticName ) @@ -3005,6 +3189,7 @@ do -- SET_STATIC return self end + --- Finds a Static based on the Static Name. -- @param #SET_STATIC self -- @param #string StaticName @@ -3015,6 +3200,8 @@ do -- SET_STATIC return StaticFound end + + --- Builds a set of units of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_STATIC self @@ -3032,6 +3219,31 @@ do -- SET_STATIC end return self end + + + --- Builds a set of statics in zones. + -- @param #SET_STATIC self + -- @param #table Zones Table of Core.Zone#ZONE Zone objects, or a Core.Set#SET_ZONE + -- @return #SET_STATIC self + function SET_STATIC:FilterZones( Zones ) + if not self.Filter.Zones then + self.Filter.Zones = {} + end + local zones = {} + if Zones.ClassName and Zones.ClassName == "SET_ZONE" then + zones = Zones.Set + elseif type( Zones ) ~= "table" or (type( Zones ) == "table" and Zones.ClassName ) then + self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!") + return self + else + zones = Zones + end + for _,Zone in pairs( zones ) do + local zonename = Zone:GetName() + self.Filter.Zones[zonename] = Zone + end + return self + end --- Builds a set of units out of categories. -- Possible current categories are plane, helicopter, ground, ship. @@ -3051,6 +3263,7 @@ do -- SET_STATIC return self end + --- Builds a set of units of defined unit types. -- Possible current types are those types known within DCS world. -- @param #SET_STATIC self @@ -3069,6 +3282,7 @@ do -- SET_STATIC return self end + --- Builds a set of units of defined countries. -- Possible current countries are those known within DCS world. -- @param #SET_STATIC self @@ -3087,6 +3301,7 @@ do -- SET_STATIC return self end + --- Builds a set of STATICs that contain the given string in their name. -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all statics that **contain** the string. -- @param #SET_STATIC self @@ -3105,6 +3320,7 @@ do -- SET_STATIC return self end + --- Starts the filtering. -- @param #SET_STATIC self -- @return #SET_STATIC self @@ -3128,7 +3344,7 @@ do -- SET_STATIC local Set = self:GetSet() local CountU = 0 - for UnitID, UnitData in pairs( Set ) do + for UnitID, UnitData in pairs(Set) do if UnitData and UnitData:IsAlive() then CountU = CountU + 1 end @@ -3166,9 +3382,11 @@ do -- SET_STATIC function SET_STATIC:FindInDatabase( Event ) self:F2( { Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName], Event } ) + return Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName] end + do -- Is Zone methods --- Check if minimal one element of the SET_STATIC is in the Zone. @@ -3181,7 +3399,7 @@ do -- SET_STATIC local function EvaluateZone( ZoneStatic ) - local ZoneStaticName = ZoneStatic:GetName() + local ZoneStaticName = ZoneStatic:GetName() if self:FindStatic( ZoneStaticName ) then IsPartiallyInZone = true return false @@ -3193,6 +3411,7 @@ do -- SET_STATIC return IsPartiallyInZone end + --- Check if no element of the SET_STATIC is in the Zone. -- @param #SET_STATIC self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -3203,7 +3422,7 @@ do -- SET_STATIC local function EvaluateZone( ZoneStatic ) - local ZoneStaticName = ZoneStatic:GetName() + local ZoneStaticName = ZoneStatic:GetName() if self:FindStatic( ZoneStaticName ) then IsNotInZone = false return false @@ -3217,6 +3436,7 @@ do -- SET_STATIC return IsNotInZone end + --- Check if minimal one element of the SET_STATIC is in the Zone. -- @param #SET_STATIC self -- @param #function IteratorFunction The function that will be called when there is an alive STATIC in the SET_STATIC. The function needs to accept a STATIC parameter. @@ -3229,9 +3449,11 @@ do -- SET_STATIC return self end + end - --- Iterate the SET_STATIC and call an iterator function for each **alive** STATIC, providing the STATIC and optional parameters. + + --- Iterate the SET_STATIC and call an interator function for each **alive** STATIC, providing the STATIC and optional parameters. -- @param #SET_STATIC self -- @param #function IteratorFunction The function that will be called when there is an alive STATIC in the SET_STATIC. The function needs to accept a STATIC parameter. -- @return #SET_STATIC self @@ -3243,6 +3465,7 @@ do -- SET_STATIC return self end + --- Iterate the SET_STATIC and call an iterator function for each **alive** STATIC presence completely in a @{Zone}, providing the STATIC and optional parameters to the called function. -- @param #SET_STATIC self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -3251,15 +3474,16 @@ do -- SET_STATIC function SET_STATIC:ForEachStaticCompletelyInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Static#STATIC StaticObject - function( ZoneObject, StaticObject ) - if StaticObject:IsInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) + self:ForEach( IteratorFunction, arg, self:GetSet(), + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Static#STATIC StaticObject + function( ZoneObject, StaticObject ) + if StaticObject:IsInZone( ZoneObject ) then + return true + else + return false + end + end, { ZoneObject } ) return self end @@ -3272,15 +3496,16 @@ do -- SET_STATIC function SET_STATIC:ForEachStaticNotInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Static#STATIC StaticObject - function( ZoneObject, StaticObject ) - if StaticObject:IsNotInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) + self:ForEach( IteratorFunction, arg, self:GetSet(), + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Static#STATIC StaticObject + function( ZoneObject, StaticObject ) + if StaticObject:IsNotInZone( ZoneObject ) then + return true + else + return false + end + end, { ZoneObject } ) return self end @@ -3308,12 +3533,13 @@ do -- SET_STATIC end for StaticTypeID, StaticType in pairs( StaticTypes ) do - MT[#MT + 1] = StaticType .. " of " .. StaticTypeID + MT[#MT+1] = StaticType .. " of " .. StaticTypeID end return StaticTypes end + --- Returns a comma separated string of the unit types with a count in the @{Set}. -- @param #SET_STATIC self -- @return #string The unit types string @@ -3324,7 +3550,7 @@ do -- SET_STATIC local StaticTypes = self:GetStaticTypes() for StaticTypeID, StaticType in pairs( StaticTypes ) do - MT[#MT + 1] = StaticType .. " of " .. StaticTypeID + MT[#MT+1] = StaticType .. " of " .. StaticTypeID end return table.concat( MT, ", " ) @@ -3352,27 +3578,27 @@ do -- SET_STATIC local Static = StaticData -- Wrapper.Static#STATIC local Coordinate = Static:GetCoordinate() - x1 = (Coordinate.x < x1) and Coordinate.x or x1 - x2 = (Coordinate.x > x2) and Coordinate.x or x2 - y1 = (Coordinate.y < y1) and Coordinate.y or y1 - y2 = (Coordinate.y > y2) and Coordinate.y or y2 - z1 = (Coordinate.y < z1) and Coordinate.z or z1 - z2 = (Coordinate.y > z2) and Coordinate.z or z2 + x1 = ( Coordinate.x < x1 ) and Coordinate.x or x1 + x2 = ( Coordinate.x > x2 ) and Coordinate.x or x2 + y1 = ( Coordinate.y < y1 ) and Coordinate.y or y1 + y2 = ( Coordinate.y > y2 ) and Coordinate.y or y2 + z1 = ( Coordinate.y < z1 ) and Coordinate.z or z1 + z2 = ( Coordinate.y > z2 ) and Coordinate.z or z2 local Velocity = Coordinate:GetVelocity() - if Velocity ~= 0 then - MaxVelocity = (MaxVelocity < Velocity) and Velocity or MaxVelocity + if Velocity ~= 0 then + MaxVelocity = ( MaxVelocity < Velocity ) and Velocity or MaxVelocity local Heading = Coordinate:GetHeading() - AvgHeading = AvgHeading and (AvgHeading + Heading) or Heading + AvgHeading = AvgHeading and ( AvgHeading + Heading ) or Heading MovingCount = MovingCount + 1 end end - AvgHeading = AvgHeading and (AvgHeading / MovingCount) + AvgHeading = AvgHeading and ( AvgHeading / MovingCount ) - Coordinate.x = (x2 - x1) / 2 + x1 - Coordinate.y = (y2 - y1) / 2 + y1 - Coordinate.z = (z2 - z1) / 2 + z1 + Coordinate.x = ( x2 - x1 ) / 2 + x1 + Coordinate.y = ( y2 - y1 ) / 2 + y1 + Coordinate.z = ( z2 - z1 ) / 2 + z1 Coordinate:SetHeading( AvgHeading ) Coordinate:SetVelocity( MaxVelocity ) @@ -3404,12 +3630,12 @@ do -- SET_STATIC local Coordinate = Static:GetCoordinate() local Velocity = Coordinate:GetVelocity() - if Velocity ~= 0 then + if Velocity ~= 0 then local Heading = Coordinate:GetHeading() if HeadingSet == nil then HeadingSet = Heading else - local HeadingDiff = (HeadingSet - Heading + 180 + 360) % 360 - 180 + local HeadingDiff = ( HeadingSet - Heading + 180 + 360 ) % 360 - 180 HeadingDiff = math.abs( HeadingDiff ) if HeadingDiff > 5 then HeadingSet = nil @@ -3428,19 +3654,19 @@ do -- SET_STATIC -- @return #number The maximum threatlevel function SET_STATIC:CalculateThreatLevelA2G() - local MaxThreatLevelA2G = 0 - local MaxThreatText = "" - for StaticName, StaticData in pairs( self:GetSet() ) do - local ThreatStatic = StaticData -- Wrapper.Static#STATIC - local ThreatLevelA2G, ThreatText = ThreatStatic:GetThreatLevel() - if ThreatLevelA2G > MaxThreatLevelA2G then - MaxThreatLevelA2G = ThreatLevelA2G - MaxThreatText = ThreatText - end + local MaxThreatLevelA2G = 0 + local MaxThreatText = "" + for StaticName, StaticData in pairs( self:GetSet() ) do + local ThreatStatic = StaticData -- Wrapper.Static#STATIC + local ThreatLevelA2G, ThreatText = ThreatStatic:GetThreatLevel() + if ThreatLevelA2G > MaxThreatLevelA2G then + MaxThreatLevelA2G = ThreatLevelA2G + MaxThreatText = ThreatText end + end - self:F( { MaxThreatLevelA2G = MaxThreatLevelA2G, MaxThreatText = MaxThreatText } ) - return MaxThreatLevelA2G, MaxThreatText + self:F( { MaxThreatLevelA2G = MaxThreatLevelA2G, MaxThreatText = MaxThreatText } ) + return MaxThreatLevelA2G, MaxThreatText end @@ -3506,14 +3732,26 @@ do -- SET_STATIC end MStaticInclude = MStaticInclude and MStaticPrefix end - + + if self.Filter.Zones then + local MStaticZone = false + for ZoneName, Zone in pairs( self.Filter.Zones ) do + self:T3( "Zone:", ZoneName ) + if MStatic and MStatic:IsInZone(Zone) then + MStaticZone = true + end + end + MStaticInclude = MStaticInclude and MStaticZone + end + self:T2( MStaticInclude ) return MStaticInclude end + --- Retrieve the type names of the @{Static}s in the SET, delimited by an optional delimiter. -- @param #SET_STATIC self - -- @param #string Delimiter (Optional) The delimiter, which is default a comma. + -- @param #string Delimiter (optional) The delimiter, which is default a comma. -- @return #string The types of the @{Static}s delimited. function SET_STATIC:GetTypeNames( Delimiter ) @@ -3537,11 +3775,15 @@ do -- SET_STATIC end + do -- SET_CLIENT + --- @type SET_CLIENT -- @extends Core.Set#SET_BASE + + --- Mission designers can use the @{Core.Set#SET_CLIENT} class to build sets of units belonging to certain: -- -- * Coalitions @@ -3572,16 +3814,13 @@ do -- SET_CLIENT -- * @{#SET_CLIENT.FilterCountries}: Builds the SET_CLIENT with the clients belonging to the country(ies). -- * @{#SET_CLIENT.FilterPrefixes}: Builds the SET_CLIENT with the clients containing the same string(s) in their unit/pilot name. **ATTENTION!** Bad naming convention as this *does not* only filter *prefixes*. -- * @{#SET_CLIENT.FilterActive}: Builds the SET_CLIENT with the units that are only active. Units that are inactive (late activation) won't be included in the set! - -- + -- * @{#SET_CLIENT.FilterZones}: Builds the SET_CLIENT with the clients within a @{Core.Zone#ZONE}. + -- -- Once the filter criteria have been set for the SET_CLIENT, you can start filtering using: -- -- * @{#SET_CLIENT.FilterStart}: Starts the filtering of the clients **dynamically**. -- * @{#SET_CLIENT.FilterOnce}: Filters the clients **once**. -- - -- Planned filter criteria within development are (so these are not yet available): - -- - -- * @{#SET_CLIENT.FilterZones}: Builds the SET_CLIENT with the clients within a @{Core.Zone#ZONE}. - -- -- ## 4) SET_CLIENT iterators -- -- Once the filters have been defined and the SET_CLIENT has been built, you can iterate the SET_CLIENT with the available iterator methods. @@ -3601,6 +3840,7 @@ do -- SET_CLIENT Types = nil, Countries = nil, ClientPrefixes = nil, + Zones = nil, }, FilterMeta = { Coalitions = { @@ -3640,7 +3880,7 @@ do -- SET_CLIENT -- @return self function SET_CLIENT:AddClientsByName( AddClientNames ) - local AddClientNamesArray = (type( AddClientNames ) == "table") and AddClientNames or { AddClientNames } + local AddClientNamesArray = ( type( AddClientNames ) == "table" ) and AddClientNames or { AddClientNames } for AddClientID, AddClientName in pairs( AddClientNamesArray ) do self:Add( AddClientName, CLIENT:FindByName( AddClientName ) ) @@ -3655,7 +3895,7 @@ do -- SET_CLIENT -- @return self function SET_CLIENT:RemoveClientsByName( RemoveClientNames ) - local RemoveClientNamesArray = (type( RemoveClientNames ) == "table") and RemoveClientNames or { RemoveClientNames } + local RemoveClientNamesArray = ( type( RemoveClientNames ) == "table" ) and RemoveClientNames or { RemoveClientNames } for RemoveClientID, RemoveClientName in pairs( RemoveClientNamesArray ) do self:Remove( RemoveClientName.ClientName ) @@ -3664,6 +3904,7 @@ do -- SET_CLIENT return self end + --- Finds a Client based on the Client Name. -- @param #SET_CLIENT self -- @param #string ClientName @@ -3674,6 +3915,8 @@ do -- SET_CLIENT return ClientFound end + + --- Builds a set of clients of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_CLIENT self @@ -3692,6 +3935,7 @@ do -- SET_CLIENT return self end + --- Builds a set of clients out of categories. -- Possible current categories are plane, helicopter, ground, ship. -- @param #SET_CLIENT self @@ -3710,6 +3954,7 @@ do -- SET_CLIENT return self end + --- Builds a set of clients of defined client types. -- Possible current types are those types known within DCS world. -- @param #SET_CLIENT self @@ -3728,6 +3973,7 @@ do -- SET_CLIENT return self end + --- Builds a set of clients of defined countries. -- Possible current countries are those known within DCS world. -- @param #SET_CLIENT self @@ -3746,6 +3992,7 @@ do -- SET_CLIENT return self end + --- Builds a set of CLIENTs that contain the given string in their unit/pilot name. -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all clients that **contain** the string. -- @param #SET_CLIENT self @@ -3767,7 +4014,7 @@ do -- SET_CLIENT --- Builds a set of clients that are only active. -- Only the clients that are active will be included within the set. -- @param #SET_CLIENT self - -- @param #boolean Active (Optional) Include only active clients to the set. + -- @param #boolean Active (optional) Include only active clients to the set. -- Include inactive clients if you provide false. -- @return #SET_CLIENT self -- @usage @@ -3785,11 +4032,35 @@ do -- SET_CLIENT -- ClientSet = SET_CLIENT:New():FilterActive( false ):FilterCoalition( "blue" ):FilterOnce() -- function SET_CLIENT:FilterActive( Active ) - Active = Active or not (Active == false) + Active = Active or not ( Active == false ) self.Filter.Active = Active return self end + --- Builds a set of clients in zones. + -- @param #SET_CLIENT self + -- @param #table Zones Table of Core.Zone#ZONE Zone objects, or a Core.Set#SET_ZONE + -- @return #SET_TABLE self + function SET_CLIENT:FilterZones( Zones ) + if not self.Filter.Zones then + self.Filter.Zones = {} + end + local zones = {} + if Zones.ClassName and Zones.ClassName == "SET_ZONE" then + zones = Zones.Set + elseif type( Zones ) ~= "table" or (type( Zones ) == "table" and Zones.ClassName ) then + self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!") + return self + else + zones = Zones + end + for _,Zone in pairs( zones ) do + local zonename = Zone:GetName() + self.Filter.Zones[zonename] = Zone + end + return self + end + --- Starts the filtering. -- @param #SET_CLIENT self -- @return #SET_CLIENT self @@ -3829,7 +4100,7 @@ do -- SET_CLIENT return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - --- Iterate the SET_CLIENT and call an iterator function for each **alive** CLIENT, providing the CLIENT and optional parameters. + --- Iterate the SET_CLIENT and call an interator function for each **alive** CLIENT, providing the CLIENT and optional parameters. -- @param #SET_CLIENT self -- @param #function IteratorFunction The function that will be called when there is an alive CLIENT in the SET_CLIENT. The function needs to accept a CLIENT parameter. -- @return #SET_CLIENT self @@ -3849,15 +4120,16 @@ do -- SET_CLIENT function SET_CLIENT:ForEachClientInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Client#CLIENT ClientObject - function( ZoneObject, ClientObject ) - if ClientObject:IsInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) + self:ForEach( IteratorFunction, arg, self:GetSet(), + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Client#CLIENT ClientObject + function( ZoneObject, ClientObject ) + if ClientObject:IsInZone( ZoneObject ) then + return true + else + return false + end + end, { ZoneObject } ) return self end @@ -3870,19 +4142,20 @@ do -- SET_CLIENT function SET_CLIENT:ForEachClientNotInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Client#CLIENT ClientObject - function( ZoneObject, ClientObject ) - if ClientObject:IsNotInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) + self:ForEach( IteratorFunction, arg, self:GetSet(), + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Client#CLIENT ClientObject + function( ZoneObject, ClientObject ) + if ClientObject:IsNotInZone( ZoneObject ) then + return true + else + return false + end + end, { ZoneObject } ) return self end - + --- Iterate the SET_CLIENT and count alive units. -- @param #SET_CLIENT self -- @return #number count @@ -3891,7 +4164,7 @@ do -- SET_CLIENT local Set = self:GetSet() local CountU = 0 - for UnitID, UnitData in pairs( Set ) do -- For each GROUP in SET_GROUP + for UnitID, UnitData in pairs(Set) do -- For each GROUP in SET_GROUP if UnitData and UnitData:IsAlive() then CountU = CountU + 1 end @@ -3900,7 +4173,7 @@ do -- SET_CLIENT return CountU end - + --- -- @param #SET_CLIENT self -- @param Wrapper.Client#CLIENT MClient @@ -3915,7 +4188,7 @@ do -- SET_CLIENT if self.Filter.Active ~= nil then local MClientActive = false - if self.Filter.Active == false or (self.Filter.Active == true and MClient:IsActive() == true) then + if self.Filter.Active == false or ( self.Filter.Active == true and MClient:IsActive() == true ) then MClientActive = true end MClientInclude = MClientInclude and MClientActive @@ -3962,7 +4235,7 @@ do -- SET_CLIENT if self.Filter.Countries then local MClientCountry = false for CountryID, CountryName in pairs( self.Filter.Countries ) do - local ClientCountryID = _DATABASE:GetCountryFromClientTemplate( MClientName ) + 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 @@ -3985,17 +4258,32 @@ do -- SET_CLIENT end end + if self.Filter.Zones then + local MClientZone = false + for ZoneName, Zone in pairs( self.Filter.Zones ) do + self:T3( "Zone:", ZoneName ) + local unit = MClient:GetClientGroupUnit() + if unit and unit:IsInZone(Zone) then + MClientZone = true + end + end + MClientInclude = MClientInclude and MClientZone + end + self:T2( MClientInclude ) return MClientInclude end end + do -- SET_PLAYER --- @type SET_PLAYER -- @extends Core.Set#SET_BASE + + --- Mission designers can use the @{Core.Set#SET_PLAYER} class to build sets of units belonging to alive players: -- -- ## SET_PLAYER constructor @@ -4042,6 +4330,7 @@ do -- SET_PLAYER Types = nil, Countries = nil, ClientPrefixes = nil, + Zones = nil, }, FilterMeta = { Coalitions = { @@ -4059,6 +4348,7 @@ do -- SET_PLAYER }, } + --- Creates a new SET_PLAYER object, building a set of clients belonging to a coalitions, categories, countries, types or with defined prefix names. -- @param #SET_PLAYER self -- @return #SET_PLAYER @@ -4078,7 +4368,7 @@ do -- SET_PLAYER -- @return self function SET_PLAYER:AddClientsByName( AddClientNames ) - local AddClientNamesArray = (type( AddClientNames ) == "table") and AddClientNames or { AddClientNames } + local AddClientNamesArray = ( type( AddClientNames ) == "table" ) and AddClientNames or { AddClientNames } for AddClientID, AddClientName in pairs( AddClientNamesArray ) do self:Add( AddClientName, CLIENT:FindByName( AddClientName ) ) @@ -4093,7 +4383,7 @@ do -- SET_PLAYER -- @return self function SET_PLAYER:RemoveClientsByName( RemoveClientNames ) - local RemoveClientNamesArray = (type( RemoveClientNames ) == "table") and RemoveClientNames or { RemoveClientNames } + local RemoveClientNamesArray = ( type( RemoveClientNames ) == "table" ) and RemoveClientNames or { RemoveClientNames } for RemoveClientID, RemoveClientName in pairs( RemoveClientNamesArray ) do self:Remove( RemoveClientName.ClientName ) @@ -4102,6 +4392,7 @@ do -- SET_PLAYER return self end + --- Finds a Client based on the Player Name. -- @param #SET_PLAYER self -- @param #string PlayerName @@ -4112,6 +4403,8 @@ do -- SET_PLAYER return ClientFound end + + --- Builds a set of clients of coalitions joined by specific players. -- Possible current coalitions are red, blue and neutral. -- @param #SET_PLAYER self @@ -4129,6 +4422,31 @@ do -- SET_PLAYER end return self end + + --- Builds a set of players in zones. + -- @param #SET_PLAYER self + -- @param #table Zones Table of Core.Zone#ZONE Zone objects, or a Core.Set#SET_ZONE + -- @return #SET_PLAYER self + function SET_PLAYER:FilterZones( Zones ) + if not self.Filter.Zones then + self.Filter.Zones = {} + end + local zones = {} + if Zones.ClassName and Zones.ClassName == "SET_ZONE" then + zones = Zones.Set + elseif type( Zones ) ~= "table" or (type( Zones ) == "table" and Zones.ClassName ) then + self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!") + return self + else + zones = Zones + end + for _,Zone in pairs( zones ) do + local zonename = Zone:GetName() + self.Filter.Zones[zonename] = Zone + end + return self + end + --- Builds a set of clients out of categories joined by players. -- Possible current categories are plane, helicopter, ground, ship. @@ -4148,6 +4466,7 @@ do -- SET_PLAYER return self end + --- Builds a set of clients of defined client types joined by players. -- Possible current types are those types known within DCS world. -- @param #SET_PLAYER self @@ -4166,6 +4485,7 @@ do -- SET_PLAYER return self end + --- Builds a set of clients of defined countries. -- Possible current countries are those known within DCS world. -- @param #SET_PLAYER self @@ -4184,6 +4504,7 @@ do -- SET_PLAYER return self end + --- Builds a set of PLAYERs that contain the given string in their unit/pilot name. -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all player clients that **contain** the string. -- @param #SET_PLAYER self @@ -4202,6 +4523,9 @@ do -- SET_PLAYER return self end + + + --- Starts the filtering. -- @param #SET_PLAYER self -- @return #SET_PLAYER self @@ -4241,7 +4565,7 @@ do -- SET_PLAYER return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - --- Iterate the SET_PLAYER and call an iterator function for each **alive** CLIENT, providing the CLIENT and optional parameters. + --- Iterate the SET_PLAYER and call an interator function for each **alive** CLIENT, providing the CLIENT and optional parameters. -- @param #SET_PLAYER self -- @param #function IteratorFunction The function that will be called when there is an alive CLIENT in the SET_PLAYER. The function needs to accept a CLIENT parameter. -- @return #SET_PLAYER self @@ -4261,15 +4585,16 @@ do -- SET_PLAYER function SET_PLAYER:ForEachPlayerInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Client#CLIENT ClientObject - function( ZoneObject, ClientObject ) - if ClientObject:IsInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) + self:ForEach( IteratorFunction, arg, self:GetSet(), + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Client#CLIENT ClientObject + function( ZoneObject, ClientObject ) + if ClientObject:IsInZone( ZoneObject ) then + return true + else + return false + end + end, { ZoneObject } ) return self end @@ -4282,15 +4607,16 @@ do -- SET_PLAYER function SET_PLAYER:ForEachPlayerNotInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Client#CLIENT ClientObject - function( ZoneObject, ClientObject ) - if ClientObject:IsNotInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) + self:ForEach( IteratorFunction, arg, self:GetSet(), + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Client#CLIENT ClientObject + function( ZoneObject, ClientObject ) + if ClientObject:IsNotInZone( ZoneObject ) then + return true + else + return false + end + end, { ZoneObject } ) return self end @@ -4348,7 +4674,7 @@ do -- SET_PLAYER if self.Filter.Countries then local MClientCountry = false for CountryID, CountryName in pairs( self.Filter.Countries ) do - local ClientCountryID = _DATABASE:GetCountryFromClientTemplate( MClientName ) + 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 @@ -4370,13 +4696,26 @@ do -- SET_PLAYER MClientInclude = MClientInclude and MClientPrefix end end - + + if self.Filter.Zones then + local MClientZone = false + for ZoneName, Zone in pairs( self.Filter.Zones ) do + self:T3( "Zone:", ZoneName ) + local unit = MClient:GetClientGroupUnit() + if unit and unit:IsInZone(Zone) then + MClientZone = true + end + end + MClientInclude = MClientInclude and MClientZone + end + self:T2( MClientInclude ) return MClientInclude end end + do -- SET_AIRBASE --- @type SET_AIRBASE @@ -4438,6 +4777,7 @@ do -- SET_AIRBASE }, } + --- Creates a new SET_AIRBASE object, building a set of airbases belonging to a coalitions and categories. -- @param #SET_AIRBASE self -- @return #SET_AIRBASE self @@ -4468,7 +4808,7 @@ do -- SET_AIRBASE -- @return self function SET_AIRBASE:AddAirbasesByName( AddAirbaseNames ) - local AddAirbaseNamesArray = (type( AddAirbaseNames ) == "table") and AddAirbaseNames or { AddAirbaseNames } + local AddAirbaseNamesArray = ( type( AddAirbaseNames ) == "table" ) and AddAirbaseNames or { AddAirbaseNames } for AddAirbaseID, AddAirbaseName in pairs( AddAirbaseNamesArray ) do self:Add( AddAirbaseName, AIRBASE:FindByName( AddAirbaseName ) ) @@ -4483,7 +4823,7 @@ do -- SET_AIRBASE -- @return self function SET_AIRBASE:RemoveAirbasesByName( RemoveAirbaseNames ) - local RemoveAirbaseNamesArray = (type( RemoveAirbaseNames ) == "table") and RemoveAirbaseNames or { RemoveAirbaseNames } + local RemoveAirbaseNamesArray = ( type( RemoveAirbaseNames ) == "table" ) and RemoveAirbaseNames or { RemoveAirbaseNames } for RemoveAirbaseID, RemoveAirbaseName in pairs( RemoveAirbaseNamesArray ) do self:Remove( RemoveAirbaseName ) @@ -4492,6 +4832,7 @@ do -- SET_AIRBASE return self end + --- Finds a Airbase based on the Airbase Name. -- @param #SET_AIRBASE self -- @param #string AirbaseName @@ -4502,6 +4843,7 @@ do -- SET_AIRBASE return AirbaseFound end + --- Finds an Airbase in range of a coordinate. -- @param #SET_AIRBASE self -- @param Core.Point#COORDINATE Coordinate @@ -4516,7 +4858,7 @@ do -- SET_AIRBASE local AirbaseCoordinate = AirbaseObject:GetCoordinate() local Distance = Coordinate:Get2DDistance( AirbaseCoordinate ) - self:F( { Distance = Distance } ) + self:F({Distance=Distance}) if Distance <= Range then AirbaseFound = AirbaseObject @@ -4528,6 +4870,7 @@ do -- SET_AIRBASE return AirbaseFound end + --- Finds a random Airbase in the set. -- @param #SET_AIRBASE self -- @return Wrapper.Airbase#AIRBASE The found Airbase. @@ -4539,6 +4882,8 @@ do -- SET_AIRBASE return RandomAirbase end + + --- Builds a set of airbases of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_AIRBASE self @@ -4557,6 +4902,7 @@ do -- SET_AIRBASE return self end + --- Builds a set of airbases out of categories. -- Possible current categories are plane, helicopter, ground, ship. -- @param #SET_AIRBASE self @@ -4583,8 +4929,8 @@ do -- SET_AIRBASE if _DATABASE then -- We use the BaseCaptured event, which is generated by DCS when a base got captured. - self:HandleEvent( EVENTS.BaseCaptured ) - self:HandleEvent( EVENTS.Dead ) + self:HandleEvent(EVENTS.BaseCaptured) + self:HandleEvent(EVENTS.Dead) -- We initialize the first set. for ObjectName, Object in pairs( self.Database ) do @@ -4602,7 +4948,7 @@ do -- SET_AIRBASE --- Base capturing event. -- @param #SET_AIRBASE self -- @param Core.Event#EVENT EventData - function SET_AIRBASE:OnEventBaseCaptured( EventData ) + function SET_AIRBASE:OnEventBaseCaptured(EventData) -- When a base got captured, we reevaluate the set. for ObjectName, Object in pairs( self.Database ) do @@ -4620,16 +4966,17 @@ do -- SET_AIRBASE --- Dead event. -- @param #SET_AIRBASE self -- @param Core.Event#EVENT EventData - function SET_AIRBASE:OnEventDead( EventData ) + function SET_AIRBASE:OnEventDead(EventData) - local airbaseName, airbase = self:FindInDatabase( EventData ) + local airbaseName, airbase=self:FindInDatabase(EventData) if airbase and (airbase:IsShip() or airbase:IsHelipad()) then - self:RemoveAirbasesByName( airbaseName ) + self:RemoveAirbasesByName(airbaseName) end end + --- Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- @param #SET_AIRBASE self @@ -4652,7 +4999,7 @@ do -- SET_AIRBASE return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - --- Iterate the SET_AIRBASE and call an iterator function for each AIRBASE, providing the AIRBASE and optional parameters. + --- Iterate the SET_AIRBASE and call an interator function for each AIRBASE, providing the AIRBASE and optional parameters. -- @param #SET_AIRBASE self -- @param #function IteratorFunction The function that will be called when there is an alive AIRBASE in the SET_AIRBASE. The function needs to accept a AIRBASE parameter. -- @return #SET_AIRBASE self @@ -4675,6 +5022,8 @@ do -- SET_AIRBASE return NearestAirbase end + + --- -- @param #SET_AIRBASE self -- @param Wrapper.Airbase#AIRBASE MAirbase @@ -4720,6 +5069,7 @@ do -- SET_AIRBASE end + do -- SET_CARGO --- @type SET_CARGO @@ -4784,37 +5134,40 @@ do -- SET_CARGO }, } + --- Creates a new SET_CARGO object, building a set of cargos belonging to a coalitions and categories. -- @param #SET_CARGO self -- @return #SET_CARGO -- @usage -- -- Define a new SET_CARGO Object. The DatabaseSet will contain a reference to all Cargos. -- DatabaseSet = SET_CARGO:New() - function SET_CARGO:New() -- R2.1 + function SET_CARGO:New() --R2.1 -- Inherits from BASE local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.CARGOS ) ) -- #SET_CARGO return self end + --- (R2.1) Add CARGO to SET_CARGO. -- @param Core.Set#SET_CARGO self -- @param Cargo.Cargo#CARGO Cargo A single cargo. -- @return Core.Set#SET_CARGO self - function SET_CARGO:AddCargo( Cargo ) -- R2.4 + function SET_CARGO:AddCargo( Cargo ) --R2.4 self:Add( Cargo:GetName(), Cargo ) return self end + --- (R2.1) Add CARGOs to SET_CARGO. -- @param Core.Set#SET_CARGO self -- @param #string AddCargoNames A single name or an array of CARGO names. -- @return Core.Set#SET_CARGO self - function SET_CARGO:AddCargosByName( AddCargoNames ) -- R2.1 + function SET_CARGO:AddCargosByName( AddCargoNames ) --R2.1 - local AddCargoNamesArray = (type( AddCargoNames ) == "table") and AddCargoNames or { AddCargoNames } + local AddCargoNamesArray = ( type( AddCargoNames ) == "table" ) and AddCargoNames or { AddCargoNames } for AddCargoID, AddCargoName in pairs( AddCargoNamesArray ) do self:Add( AddCargoName, CARGO:FindByName( AddCargoName ) ) @@ -4827,9 +5180,9 @@ do -- SET_CARGO -- @param Core.Set#SET_CARGO self -- @param Wrapper.Cargo#CARGO RemoveCargoNames A single name or an array of CARGO names. -- @return Core.Set#SET_CARGO self - function SET_CARGO:RemoveCargosByName( RemoveCargoNames ) -- R2.1 + function SET_CARGO:RemoveCargosByName( RemoveCargoNames ) --R2.1 - local RemoveCargoNamesArray = (type( RemoveCargoNames ) == "table") and RemoveCargoNames or { RemoveCargoNames } + local RemoveCargoNamesArray = ( type( RemoveCargoNames ) == "table" ) and RemoveCargoNames or { RemoveCargoNames } for RemoveCargoID, RemoveCargoName in pairs( RemoveCargoNamesArray ) do self:Remove( RemoveCargoName.CargoName ) @@ -4838,22 +5191,25 @@ do -- SET_CARGO return self end + --- (R2.1) Finds a Cargo based on the Cargo Name. -- @param #SET_CARGO self -- @param #string CargoName -- @return Wrapper.Cargo#CARGO The found Cargo. - function SET_CARGO:FindCargo( CargoName ) -- R2.1 + function SET_CARGO:FindCargo( CargoName ) --R2.1 local CargoFound = self.Set[CargoName] return CargoFound end + + --- (R2.1) Builds a set of cargos of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_CARGO self -- @param #string Coalitions Can take the following values: "red", "blue", "neutral". -- @return #SET_CARGO self - function SET_CARGO:FilterCoalitions( Coalitions ) -- R2.1 + function SET_CARGO:FilterCoalitions( Coalitions ) --R2.1 if not self.Filter.Coalitions then self.Filter.Coalitions = {} end @@ -4871,7 +5227,7 @@ do -- SET_CARGO -- @param #SET_CARGO self -- @param #string Types Can take those type strings known within DCS world. -- @return #SET_CARGO self - function SET_CARGO:FilterTypes( Types ) -- R2.1 + function SET_CARGO:FilterTypes( Types ) --R2.1 if not self.Filter.Types then self.Filter.Types = {} end @@ -4884,12 +5240,13 @@ do -- SET_CARGO return self end + --- (R2.1) Builds a set of cargos of defined countries. -- Possible current countries are those known within DCS world. -- @param #SET_CARGO self -- @param #string Countries Can take those country strings known within DCS world. -- @return #SET_CARGO self - function SET_CARGO:FilterCountries( Countries ) -- R2.1 + function SET_CARGO:FilterCountries( Countries ) --R2.1 if not self.Filter.Countries then self.Filter.Countries = {} end @@ -4902,12 +5259,13 @@ do -- SET_CARGO return self end + --- Builds a set of CARGOs that contain a given string in their name. -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all cargos that **contain** the string. -- @param #SET_CARGO self -- @param #string Prefixes The string pattern(s) that need to be in the cargo name. Can also be passed as a `#table` of strings. -- @return #SET_CARGO self - function SET_CARGO:FilterPrefixes( Prefixes ) -- R2.1 + function SET_CARGO:FilterPrefixes( Prefixes ) --R2.1 if not self.Filter.CargoPrefixes then self.Filter.CargoPrefixes = {} end @@ -4920,10 +5278,12 @@ do -- SET_CARGO return self end + + --- (R2.1) Starts the filtering. -- @param #SET_CARGO self -- @return #SET_CARGO self - function SET_CARGO:FilterStart() -- R2.1 + function SET_CARGO:FilterStart() --R2.1 if _DATABASE then self:_FilterStart() @@ -4945,13 +5305,14 @@ do -- SET_CARGO return self end + --- (R2.1) Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- @param #SET_CARGO self -- @param Core.Event#EVENTDATA Event -- @return #string The name of the CARGO -- @return #table The CARGO - function SET_CARGO:AddInDatabase( Event ) -- R2.1 + function SET_CARGO:AddInDatabase( Event ) --R2.1 self:F3( { Event } ) return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] @@ -4963,17 +5324,17 @@ do -- SET_CARGO -- @param Core.Event#EVENTDATA Event -- @return #string The name of the CARGO -- @return #table The CARGO - function SET_CARGO:FindInDatabase( Event ) -- R2.1 + function SET_CARGO:FindInDatabase( Event ) --R2.1 self:F3( { Event } ) return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - --- (R2.1) Iterate the SET_CARGO and call an iterator function for each CARGO, providing the CARGO and optional parameters. + --- (R2.1) Iterate the SET_CARGO and call an interator function for each CARGO, providing the CARGO and optional parameters. -- @param #SET_CARGO self -- @param #function IteratorFunction The function that will be called when there is an alive CARGO in the SET_CARGO. The function needs to accept a CARGO parameter. -- @return #SET_CARGO self - function SET_CARGO:ForEachCargo( IteratorFunction, ... ) -- R2.1 + function SET_CARGO:ForEachCargo( IteratorFunction, ... ) --R2.1 self:F2( arg ) self:ForEach( IteratorFunction, arg, self:GetSet() ) @@ -4985,7 +5346,7 @@ do -- SET_CARGO -- @param #SET_CARGO self -- @param Core.Point#POINT_VEC2 PointVec2 A @{Core.Point#POINT_VEC2} object from where to evaluate the closest @{Cargo.Cargo#CARGO}. -- @return Wrapper.Cargo#CARGO The closest @{Cargo.Cargo#CARGO}. - function SET_CARGO:FindNearestCargoFromPointVec2( PointVec2 ) -- R2.1 + function SET_CARGO:FindNearestCargoFromPointVec2( PointVec2 ) --R2.1 self:F2( PointVec2 ) local NearestCargo = self:FindNearestObjectFromPointVec2( PointVec2 ) @@ -5020,6 +5381,7 @@ do -- SET_CARGO return FirstCargo end + --- Iterate the SET_CARGO while identifying the first @{Cargo.Cargo#CARGO} that is UnLoaded. -- @param #SET_CARGO self -- @return Cargo.Cargo#CARGO The first @{Cargo.Cargo#CARGO}. @@ -5028,6 +5390,7 @@ do -- SET_CARGO return FirstCargo end + --- Iterate the SET_CARGO while identifying the first @{Cargo.Cargo#CARGO} that is UnLoaded and not Deployed. -- @param #SET_CARGO self -- @return Cargo.Cargo#CARGO The first @{Cargo.Cargo#CARGO}. @@ -5036,6 +5399,7 @@ do -- SET_CARGO return FirstCargo end + --- Iterate the SET_CARGO while identifying the first @{Cargo.Cargo#CARGO} that is Loaded. -- @param #SET_CARGO self -- @return Cargo.Cargo#CARGO The first @{Cargo.Cargo#CARGO}. @@ -5044,6 +5408,7 @@ do -- SET_CARGO return FirstCargo end + --- Iterate the SET_CARGO while identifying the first @{Cargo.Cargo#CARGO} that is Deployed. -- @param #SET_CARGO self -- @return Cargo.Cargo#CARGO The first @{Cargo.Cargo#CARGO}. @@ -5052,11 +5417,14 @@ do -- SET_CARGO return FirstCargo end + + + --- (R2.1) -- @param #SET_CARGO self -- @param AI.AI_Cargo#AI_CARGO MCargo -- @return #SET_CARGO self - function SET_CARGO:IsIncludeObject( MCargo ) -- R2.1 + function SET_CARGO:IsIncludeObject( MCargo ) --R2.1 self:F2( MCargo ) local MCargoInclude = true @@ -5109,13 +5477,13 @@ do -- SET_CARGO --- (R2.1) Handles the OnEventNewCargo event for the Set. -- @param #SET_CARGO self -- @param Core.Event#EVENTDATA EventData - function SET_CARGO:OnEventNewCargo( EventData ) -- R2.1 + function SET_CARGO:OnEventNewCargo( EventData ) --R2.1 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 ) + self:Add( EventData.Cargo.Name , EventData.Cargo ) end end end @@ -5123,20 +5491,20 @@ do -- SET_CARGO --- (R2.1) Handles the OnDead or OnCrash event for alive units set. -- @param #SET_CARGO self -- @param Core.Event#EVENTDATA EventData - function SET_CARGO:OnEventDeleteCargo( EventData ) -- R2.1 + function SET_CARGO:OnEventDeleteCargo( EventData ) --R2.1 self:F3( { EventData } ) if EventData.Cargo then local Cargo = _DATABASE:FindCargo( EventData.Cargo.Name ) if Cargo and Cargo.Name then - -- When cargo was deleted, it may probably be because of an S_EVENT_DEAD. - -- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call. - -- And this is a problem because it will remove all entries from the SET_CARGOs. - -- To prevent this from happening, the Cargo object has a flag NoDestroy. - -- When true, the SET_CARGO won't Remove the Cargo object from the set. - -- This flag is switched off after the event handlers have been called in the EVENT class. - self:F( { CargoNoDestroy = Cargo.NoDestroy } ) + -- When cargo was deleted, it may probably be because of an S_EVENT_DEAD. + -- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call. + -- And this is a problem because it will remove all entries from the SET_CARGOs. + -- To prevent this from happening, the Cargo object has a flag NoDestroy. + -- When true, the SET_CARGO won't Remove the Cargo object from the set. + -- This flag is switched off after the event handlers have been called in the EVENT class. + self:F( { CargoNoDestroy=Cargo.NoDestroy } ) if Cargo.NoDestroy then else self:Remove( Cargo.Name ) @@ -5147,6 +5515,7 @@ do -- SET_CARGO end + do -- SET_ZONE --- @type SET_ZONE @@ -5186,15 +5555,17 @@ do -- SET_ZONE -- -- === -- @field #SET_ZONE SET_ZONE - SET_ZONE = { - lassName = "SET_ZONE", + SET_ZONE = { + ClassName = "SET_ZONE", Zones = {}, Filter = { Prefixes = nil, }, - FilterMeta = {}, + FilterMeta = { + }, } + --- Creates a new SET_ZONE object, building a set of zones. -- @param #SET_ZONE self -- @return #SET_ZONE self @@ -5214,7 +5585,7 @@ do -- SET_ZONE -- @return self function SET_ZONE:AddZonesByName( AddZoneNames ) - local AddZoneNamesArray = (type( AddZoneNames ) == "table") and AddZoneNames or { AddZoneNames } + local AddZoneNamesArray = ( type( AddZoneNames ) == "table" ) and AddZoneNames or { AddZoneNames } for AddAirbaseID, AddZoneName in pairs( AddZoneNamesArray ) do self:Add( AddZoneName, ZONE:FindByName( AddZoneName ) ) @@ -5234,13 +5605,14 @@ do -- SET_ZONE return self end + --- Remove ZONEs from SET_ZONE. -- @param Core.Set#SET_ZONE self -- @param Core.Zone#ZONE_BASE RemoveZoneNames A single name or an array of ZONE_BASE names. -- @return self function SET_ZONE:RemoveZonesByName( RemoveZoneNames ) - local RemoveZoneNamesArray = (type( RemoveZoneNames ) == "table") and RemoveZoneNames or { RemoveZoneNames } + local RemoveZoneNamesArray = ( type( RemoveZoneNames ) == "table" ) and RemoveZoneNames or { RemoveZoneNames } for RemoveZoneID, RemoveZoneName in pairs( RemoveZoneNamesArray ) do self:Remove( RemoveZoneName ) @@ -5249,6 +5621,7 @@ do -- SET_ZONE return self end + --- Finds a Zone based on the Zone Name. -- @param #SET_ZONE self -- @param #string ZoneName @@ -5259,13 +5632,14 @@ do -- SET_ZONE return ZoneFound end + --- Get a random zone from the set. -- @param #SET_ZONE self -- @param #number margin Number of tries to find a zone -- @return Core.Zone#ZONE_BASE The random Zone. -- @return #nil if no zone in the collection. - function SET_ZONE:GetRandomZone( margin ) - + function SET_ZONE:GetRandomZone(margin) + local margin = margin or 100 if self:Count() ~= 0 then @@ -5288,6 +5662,7 @@ do -- SET_ZONE return nil end + --- Set a zone probability. -- @param #SET_ZONE self -- @param #string ZoneName The name of the zone. @@ -5296,6 +5671,9 @@ do -- SET_ZONE Zone:SetZoneProbability( ZoneProbability ) end + + + --- Builds a set of ZONEs that contain the given string in their name. -- **ATTENTION!** Bad naming convention as this **does not** filter only **prefixes** but all zones that **contain** the string. -- @param #SET_ZONE self @@ -5314,6 +5692,7 @@ do -- SET_ZONE return self end + --- Starts the filtering. -- @param #SET_ZONE self -- @return #SET_ZONE self @@ -5372,7 +5751,7 @@ do -- SET_ZONE return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - --- Iterate the SET_ZONE and call an iterator function for each ZONE, providing the ZONE and optional parameters. + --- Iterate the SET_ZONE and call an interator function for each ZONE, providing the ZONE and optional parameters. -- @param #SET_ZONE self -- @param #function IteratorFunction The function that will be called when there is an alive ZONE in the SET_ZONE. The function needs to accept a AIRBASE parameter. -- @return #SET_ZONE self @@ -5384,6 +5763,7 @@ do -- SET_ZONE return self end + --- -- @param #SET_ZONE self -- @param Core.Zone#ZONE_BASE MZone @@ -5416,13 +5796,13 @@ do -- SET_ZONE --- Handles the OnEventNewZone event for the Set. -- @param #SET_ZONE self -- @param Core.Event#EVENTDATA EventData - function SET_ZONE:OnEventNewZone( EventData ) -- R2.1 + function SET_ZONE:OnEventNewZone( EventData ) --R2.1 self:F( { "New Zone", EventData } ) if EventData.Zone then if EventData.Zone and self:IsIncludeObject( EventData.Zone ) then - self:Add( EventData.Zone.ZoneName, EventData.Zone ) + self:Add( EventData.Zone.ZoneName , EventData.Zone ) end end end @@ -5430,20 +5810,20 @@ do -- SET_ZONE --- Handles the OnDead or OnCrash event for alive units set. -- @param #SET_ZONE self -- @param Core.Event#EVENTDATA EventData - function SET_ZONE:OnEventDeleteZone( EventData ) -- R2.1 + function SET_ZONE:OnEventDeleteZone( EventData ) --R2.1 self:F3( { EventData } ) if EventData.Zone then local Zone = _DATABASE:FindZone( EventData.Zone.ZoneName ) if Zone and Zone.ZoneName then - -- When cargo was deleted, it may probably be because of an S_EVENT_DEAD. - -- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call. - -- And this is a problem because it will remove all entries from the SET_ZONEs. - -- To prevent this from happening, the Zone object has a flag NoDestroy. - -- When true, the SET_ZONE won't Remove the Zone object from the set. - -- This flag is switched off after the event handlers have been called in the EVENT class. - self:F( { ZoneNoDestroy = Zone.NoDestroy } ) + -- When cargo was deleted, it may probably be because of an S_EVENT_DEAD. + -- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call. + -- And this is a problem because it will remove all entries from the SET_ZONEs. + -- To prevent this from happening, the Zone object has a flag NoDestroy. + -- When true, the SET_ZONE won't Remove the Zone object from the set. + -- This flag is switched off after the event handlers have been called in the EVENT class. + self:F( { ZoneNoDestroy=Zone.NoDestroy } ) if Zone.NoDestroy then else self:Remove( Zone.ZoneName ) @@ -5518,9 +5898,11 @@ do -- SET_ZONE_GOAL Filter = { Prefixes = nil, }, - FilterMeta = {}, + FilterMeta = { + }, } + --- Creates a new SET_ZONE_GOAL object, building a set of zones. -- @param #SET_ZONE_GOAL self -- @return #SET_ZONE_GOAL self @@ -5545,13 +5927,14 @@ do -- SET_ZONE_GOAL return self end + --- Remove ZONEs from SET_ZONE_GOAL. -- @param Core.Set#SET_ZONE_GOAL self -- @param Core.Zone#ZONE_BASE RemoveZoneNames A single name or an array of ZONE_BASE names. -- @return self function SET_ZONE_GOAL:RemoveZonesByName( RemoveZoneNames ) - local RemoveZoneNamesArray = (type( RemoveZoneNames ) == "table") and RemoveZoneNames or { RemoveZoneNames } + local RemoveZoneNamesArray = ( type( RemoveZoneNames ) == "table" ) and RemoveZoneNames or { RemoveZoneNames } for RemoveZoneID, RemoveZoneName in pairs( RemoveZoneNamesArray ) do self:Remove( RemoveZoneName ) @@ -5560,6 +5943,7 @@ do -- SET_ZONE_GOAL return self end + --- Finds a Zone based on the Zone Name. -- @param #SET_ZONE_GOAL self -- @param #string ZoneName @@ -5570,6 +5954,7 @@ do -- SET_ZONE_GOAL return ZoneFound end + --- Get a random zone from the set. -- @param #SET_ZONE_GOAL self -- @return Core.Zone#ZONE_BASE The random Zone. @@ -5595,6 +5980,7 @@ do -- SET_ZONE_GOAL return nil end + --- Set a zone probability. -- @param #SET_ZONE_GOAL self -- @param #string ZoneName The name of the zone. @@ -5603,6 +5989,9 @@ do -- SET_ZONE_GOAL Zone:SetZoneProbability( ZoneProbability ) end + + + --- Builds a set of ZONE_GOALs that contain the given string in their name. -- **ATTENTION!** Bad naming convention as this **does not** filter only **prefixes** but all zones that **contain** the string. -- @param #SET_ZONE_GOAL self @@ -5621,6 +6010,7 @@ do -- SET_ZONE_GOAL return self end + --- Starts the filtering. -- @param #SET_ZONE_GOAL self -- @return #SET_ZONE_GOAL self @@ -5679,7 +6069,7 @@ do -- SET_ZONE_GOAL return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - --- Iterate the SET_ZONE_GOAL and call an iterator function for each ZONE, providing the ZONE and optional parameters. + --- Iterate the SET_ZONE_GOAL and call an interator function for each ZONE, providing the ZONE and optional parameters. -- @param #SET_ZONE_GOAL self -- @param #function IteratorFunction The function that will be called when there is an alive ZONE in the SET_ZONE_GOAL. The function needs to accept a AIRBASE parameter. -- @return #SET_ZONE_GOAL self @@ -5691,6 +6081,7 @@ do -- SET_ZONE_GOAL return self end + --- -- @param #SET_ZONE_GOAL self -- @param Core.Zone#ZONE_BASE MZone @@ -5725,13 +6116,14 @@ do -- SET_ZONE_GOAL -- @param Core.Event#EVENTDATA EventData function SET_ZONE_GOAL:OnEventNewZoneGoal( EventData ) - self:I( { "New Zone Capture Coalition", EventData } ) - self:I( { "Zone Capture Coalition", EventData.ZoneGoal } ) + -- Debug info. + self:T( { "New Zone Capture Coalition", EventData } ) + self:T( { "Zone Capture Coalition", EventData.ZoneGoal } ) if EventData.ZoneGoal then if EventData.ZoneGoal and self:IsIncludeObject( EventData.ZoneGoal ) then - self:I( { "Adding Zone Capture Coalition", EventData.ZoneGoal.ZoneName, EventData.ZoneGoal } ) - self:Add( EventData.ZoneGoal.ZoneName, EventData.ZoneGoal ) + self:T( { "Adding Zone Capture Coalition", EventData.ZoneGoal.ZoneName, EventData.ZoneGoal } ) + self:Add( EventData.ZoneGoal.ZoneName , EventData.ZoneGoal ) end end end @@ -5739,20 +6131,20 @@ do -- SET_ZONE_GOAL --- Handles the OnDead or OnCrash event for alive units set. -- @param #SET_ZONE_GOAL self -- @param Core.Event#EVENTDATA EventData - function SET_ZONE_GOAL:OnEventDeleteZoneGoal( EventData ) -- R2.1 + function SET_ZONE_GOAL:OnEventDeleteZoneGoal( EventData ) --R2.1 self:F3( { EventData } ) if EventData.ZoneGoal then local Zone = _DATABASE:FindZone( EventData.ZoneGoal.ZoneName ) if Zone and Zone.ZoneName then - -- When cargo was deleted, it may probably be because of an S_EVENT_DEAD. - -- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call. - -- And this is a problem because it will remove all entries from the SET_ZONE_GOALs. - -- To prevent this from happening, the Zone object has a flag NoDestroy. - -- When true, the SET_ZONE_GOAL won't Remove the Zone object from the set. - -- This flag is switched off after the event handlers have been called in the EVENT class. - self:F( { ZoneNoDestroy = Zone.NoDestroy } ) + -- When cargo was deleted, it may probably be because of an S_EVENT_DEAD. + -- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call. + -- And this is a problem because it will remove all entries from the SET_ZONE_GOALs. + -- To prevent this from happening, the Zone object has a flag NoDestroy. + -- When true, the SET_ZONE_GOAL won't Remove the Zone object from the set. + -- This flag is switched off after the event handlers have been called in the EVENT class. + self:F( { ZoneNoDestroy=Zone.NoDestroy } ) if Zone.NoDestroy then else self:Remove( Zone.ZoneName ) @@ -5781,3 +6173,618 @@ do -- SET_ZONE_GOAL end end + + + +do -- SET_OPSGROUP + + --- @type SET_OPSGROUP + -- @extends Core.Set#SET_BASE + + --- Mission designers can use the @{Core.Set#SET_OPSGROUP} class to build sets of OPS groups belonging to certain: + -- + -- * Coalitions + -- * Categories + -- * Countries + -- * Contain a certain string pattern + -- + -- ## SET_OPSGROUP constructor + -- + -- Create a new SET_OPSGROUP object with the @{#SET_OPSGROUP.New} method: + -- + -- * @{#SET_OPSGROUP.New}: Creates a new SET_OPSGROUP object. + -- + -- ## Add or Remove GROUP(s) from SET_OPSGROUP + -- + -- GROUPS can be added and removed using the @{Core.Set#SET_OPSGROUP.AddGroupsByName} and @{Core.Set#SET_OPSGROUP.RemoveGroupsByName} respectively. + -- These methods take a single GROUP name or an array of GROUP names to be added or removed from SET_OPSGROUP. + -- + -- ## SET_OPSGROUP filter criteria + -- + -- You can set filter criteria to define the set of groups within the SET_OPSGROUP. + -- Filter criteria are defined by: + -- + -- * @{#SET_OPSGROUP.FilterCoalitions}: Builds the SET_OPSGROUP with the groups belonging to the coalition(s). + -- * @{#SET_OPSGROUP.FilterCategories}: Builds the SET_OPSGROUP with the groups belonging to the category(ies). + -- * @{#SET_OPSGROUP.FilterCountries}: Builds the SET_OPSGROUP with the groups belonging to the country(ies). + -- * @{#SET_OPSGROUP.FilterPrefixes}: Builds the SET_OPSGROUP with the groups *containing* the given string in the group name. **Attention!** Bad naming convention, as this not really filtering *prefixes*. + -- * @{#SET_OPSGROUP.FilterActive}: Builds the SET_OPSGROUP with the groups that are only active. Groups that are inactive (late activation) won't be included in the set! + -- + -- For the Category Filter, extra methods have been added: + -- + -- * @{#SET_OPSGROUP.FilterCategoryAirplane}: Builds the SET_OPSGROUP from airplanes. + -- * @{#SET_OPSGROUP.FilterCategoryHelicopter}: Builds the SET_OPSGROUP from helicopters. + -- * @{#SET_OPSGROUP.FilterCategoryGround}: Builds the SET_OPSGROUP from ground vehicles or infantry. + -- * @{#SET_OPSGROUP.FilterCategoryShip}: Builds the SET_OPSGROUP from ships. + -- + -- + -- Once the filter criteria have been set for the SET_OPSGROUP, you can start filtering using: + -- + -- * @{#SET_OPSGROUP.FilterStart}: Starts the filtering of the groups within the SET_OPSGROUP and add or remove GROUP objects **dynamically**. + -- * @{#SET_OPSGROUP.FilterOnce}: Filters of the groups **once**. + -- + -- + -- ## SET_OPSGROUP iterators + -- + -- Once the filters have been defined and the SET_OPSGROUP has been built, you can iterate the SET_OPSGROUP with the available iterator methods. + -- The iterator methods will walk the SET_OPSGROUP set, and call for each element within the set a function that you provide. + -- The following iterator methods are currently available within the SET_OPSGROUP: + -- + -- * @{#SET_OPSGROUP.ForEachGroup}: Calls a function for each alive group it finds within the SET_OPSGROUP. + -- + -- ## SET_OPSGROUP trigger events on the GROUP objects. + -- + -- The SET is derived from the FSM class, which provides extra capabilities to track the contents of the GROUP objects in the SET_OPSGROUP. + -- + -- ### When a GROUP object crashes or is dead, the SET_OPSGROUP will trigger a **Dead** event. + -- + -- You can handle the event using the OnBefore and OnAfter event handlers. + -- The event handlers need to have the paramters From, Event, To, GroupObject. + -- The GroupObject is the GROUP object that is dead and within the SET_OPSGROUP, and is passed as a parameter to the event handler. + -- See the following example: + -- + -- -- Create the SetCarrier SET_OPSGROUP collection. + -- + -- local SetHelicopter = SET_OPSGROUP:New():FilterPrefixes( "Helicopter" ):FilterStart() + -- + -- -- Put a Dead event handler on SetCarrier, to ensure that when a carrier is destroyed, that all internal parameters are reset. + -- + -- function SetHelicopter:OnAfterDead( From, Event, To, GroupObject ) + -- self:F( { GroupObject = GroupObject:GetName() } ) + -- end + -- + -- + -- === + -- + -- @field #SET_OPSGROUP SET_OPSGROUP + -- + SET_OPSGROUP = { + ClassName = "SET_OPSGROUP", + Filter = { + Coalitions = nil, + Categories = nil, + Countries = nil, + GroupPrefixes = nil, + }, + FilterMeta = { + Coalitions = { + red = coalition.side.RED, + blue = coalition.side.BLUE, + neutral = coalition.side.NEUTRAL, + }, + Categories = { + plane = Group.Category.AIRPLANE, + helicopter = Group.Category.HELICOPTER, + ground = Group.Category.GROUND, + ship = Group.Category.SHIP, + }, + }, -- FilterMeta + } + + + --- Creates a new SET_OPSGROUP object, building a set of groups belonging to a coalitions, categories, countries, types or with defined prefix names. + -- @param #SET_OPSGROUP self + -- @return #SET_OPSGROUP + function SET_OPSGROUP:New() + + -- Inherit SET_BASE. + local self = BASE:Inherit(self, SET_BASE:New(_DATABASE.GROUPS)) -- #SET_OPSGROUP + + -- Include non activated + self:FilterActive( false ) + + return self + end + + --- Gets the Set. + -- @param #SET_OPSGROUP self + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:GetAliveSet() + + local AliveSet = SET_OPSGROUP:New() + + -- Clean the Set before returning with only the alive Groups. + for GroupName, GroupObject in pairs(self.Set) do + local GroupObject=GroupObject --Wrapper.Group#GROUP + + if GroupObject and GroupObject:IsAlive() then + AliveSet:Add(GroupName, GroupObject) + end + end + + return AliveSet.Set or {} + end + + --- Adds a @{Core.Base#BASE} object in the @{Core.Set#SET_BASE}, using a given ObjectName as the index. + -- @param #SET_BASE self + -- @param #string ObjectName The name of the object. + -- @param Core.Base#BASE Object The object itself. + -- @return Core.Base#BASE The added BASE Object. + function SET_OPSGROUP:Add(ObjectName, Object) + self:T( { ObjectName = ObjectName, Object = Object } ) + + -- Ensure that the existing element is removed from the Set before a new one is inserted to the Set + if self.Set[ObjectName] then + self:Remove(ObjectName, true) + end + + local object=nil --Ops.OpsGroup#OPSGROUP + if Object:IsInstanceOf("GROUP") then + + --- + -- GROUP Object + --- + + -- Fist, look up in the DATABASE if an OPSGROUP already exists. + object=_DATABASE:FindOpsGroup(ObjectName) + + if not object then + + if Object:IsShip() then + object=NAVYGROUP:New(Object) + elseif Object:IsGround() then + object=ARMYGROUP:New(Object) + elseif Object:IsAir() then + object=FLIGHTGROUP:New(Object) + else + env.error("ERROR: Unknown category of group object!") + end + end + + elseif Object:IsInstanceOf("OPSGROUP") then + -- We already have an OPSGROUP. + object=Object + else + env.error("ERROR: Object must be a GROUP or OPSGROUP!") + end + + -- Add object to set. + self.Set[ObjectName]=object + + -- Add Object name to Index. + table.insert(self.Index, ObjectName) + + -- Trigger Added event. + self:Added(ObjectName, object) + end + + --- Add a GROUP or OPSGROUP object to the set. + -- **NOTE** that an OPSGROUP is automatically created from the GROUP if it does not exist already. + -- @param Core.Set#SET_OPSGROUP self + -- @param Wrapper.Group#GROUP group The GROUP which should be added to the set. Can also be given as an #OPSGROUP object. + -- @return Core.Set#SET_OPSGROUP self + function SET_OPSGROUP:AddGroup(group) + + local groupname=group:GetName() + + self:Add(groupname, group ) + + return self + end + + --- Add GROUP(s) or OPSGROUP(s) to the set. + -- @param Core.Set#SET_OPSGROUP self + -- @param #string AddGroupNames A single name or an array of GROUP names. + -- @return Core.Set#SET_OPSGROUP self + function SET_OPSGROUP:AddGroupsByName( AddGroupNames ) + + local AddGroupNamesArray = ( type( AddGroupNames ) == "table" ) and AddGroupNames or { AddGroupNames } + + for AddGroupID, AddGroupName in pairs( AddGroupNamesArray ) do + self:Add(AddGroupName, GROUP:FindByName(AddGroupName)) + end + + return self + end + + --- Remove GROUP(s) or OPSGROUP(s) from the set. + -- @param Core.Set#SET_OPSGROUP self + -- @param Wrapper.Group#GROUP RemoveGroupNames A single name or an array of GROUP names. + -- @return Core.Set#SET_OPSGROUP self + function SET_OPSGROUP:RemoveGroupsByName( RemoveGroupNames ) + + local RemoveGroupNamesArray = ( type( RemoveGroupNames ) == "table" ) and RemoveGroupNames or { RemoveGroupNames } + + for RemoveGroupID, RemoveGroupName in pairs( RemoveGroupNamesArray ) do + self:Remove( RemoveGroupName ) + end + + return self + end + + --- Finds an OPSGROUP based on the group name. + -- @param #SET_OPSGROUP self + -- @param #string GroupName Name of the group. + -- @return Ops.OpsGroup#OPSGROUP The found OPSGROUP (FLIGHTGROUP, ARMYGROUP or NAVYGROUP) or `#nil` if the group is not in the set. + function SET_OPSGROUP:FindGroup(GroupName) + local GroupFound = self.Set[GroupName] + return GroupFound + end + + --- Finds a FLIGHTGROUP based on the group name. + -- @param #SET_OPSGROUP self + -- @param #string GroupName Name of the group. + -- @return Ops.FlightGroup#FLIGHTGROUP The found FLIGHTGROUP or `#nil` if the group is not in the set. + function SET_OPSGROUP:FindFlightGroup(GroupName) + local GroupFound = self:FindGroup(GroupName) + return GroupFound + end + + --- Finds a ARMYGROUP based on the group name. + -- @param #SET_OPSGROUP self + -- @param #string GroupName Name of the group. + -- @return Ops.ArmyGroup#ARMYGROUP The found ARMYGROUP or `#nil` if the group is not in the set. + function SET_OPSGROUP:FindArmyGroup(GroupName) + local GroupFound = self:FindGroup(GroupName) + return GroupFound + end + + + --- Finds a NAVYGROUP based on the group name. + -- @param #SET_OPSGROUP self + -- @param #string GroupName Name of the group. + -- @return Ops.NavyGroup#NAVYGROUP The found NAVYGROUP or `#nil` if the group is not in the set. + function SET_OPSGROUP:FindNavyGroup(GroupName) + local GroupFound = self:FindGroup(GroupName) + return GroupFound + end + + --- Builds a set of groups of coalitions. + -- Possible current coalitions are red, blue and neutral. + -- @param #SET_OPSGROUP self + -- @param #string Coalitions Can take the following values: "red", "blue", "neutral" or combinations as a table, for example `{"red", "neutral"}`. + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:FilterCoalitions(Coalitions) + + -- Create an empty set. + if not self.Filter.Coalitions then + self.Filter.Coalitions={} + end + + -- Ensure we got a table. + if type(Coalitions)~="table" then + Coalitions = {Coalitions} + end + + -- Set filter. + for CoalitionID, Coalition in pairs( Coalitions ) do + self.Filter.Coalitions[Coalition] = Coalition + end + + return self + end + + + --- Builds a set of groups out of categories. + -- + -- Possible current categories are: + -- + -- * "plane" for fixed wing groups + -- * "helicopter" for rotary wing groups + -- * "ground" for ground groups + -- * "ship" for naval groups + -- + -- @param #SET_OPSGROUP self + -- @param #string Categories Can take the following values: "plane", "helicopter", "ground", "ship" or combinations as a table, for example `{"plane", "helicopter"}`. + -- @return #SET_OPSGROUP self + function SET_OPSGROUP: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 + + --- Builds a set of groups out of ground category. + -- @param #SET_OPSGROUP self + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:FilterCategoryGround() + self:FilterCategories("ground") + return self + end + + --- Builds a set of groups out of airplane category. + -- @param #SET_OPSGROUP self + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:FilterCategoryAirplane() + self:FilterCategories("plane") + return self + end + + --- Builds a set of groups out of aicraft category (planes and helicopters). + -- @param #SET_OPSGROUP self + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:FilterCategoryAircraft() + self:FilterCategories({"plane", "helicopter"}) + return self + end + + --- Builds a set of groups out of helicopter category. + -- @param #SET_OPSGROUP self + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:FilterCategoryHelicopter() + self:FilterCategories("helicopter") + return self + end + + --- Builds a set of groups out of ship category. + -- @param #SET_OPSGROUP self + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:FilterCategoryShip() + self:FilterCategories("ship") + return self + end + + --- Builds a set of groups of defined countries. + -- @param #SET_OPSGROUP self + -- @param #string Countries Can take those country strings known within DCS world. + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:FilterCountries(Countries) + + -- Create empty table if necessary. + if not self.Filter.Countries then + self.Filter.Countries = {} + end + + -- Ensure input is a table. + if type(Countries)~="table" then + Countries={Countries} + end + + -- Set filter. + for CountryID, Country in pairs( Countries ) do + self.Filter.Countries[Country] = Country + end + + return self + end + + + --- Builds a set of groups that contain the given string in their group name. + -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all groups that **contain** the string. + -- @param #SET_OPSGROUP self + -- @param #string Prefixes The string pattern(s) that needs to be contained in the group name. Can also be passed as a `#table` of strings. + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:FilterPrefixes(Prefixes) + + -- Create emtpy table if necessary. + if not self.Filter.GroupPrefixes then + self.Filter.GroupPrefixes={} + end + + -- Ensure we have a table. + if type(Prefixes)~="table" then + Prefixes={Prefixes} + end + + -- Set group prefixes. + for PrefixID, Prefix in pairs(Prefixes) do + self.Filter.GroupPrefixes[Prefix]=Prefix + end + + return self + end + + --- Builds a set of groups that are only active. + -- Only the groups that are active will be included within the set. + -- @param #SET_OPSGROUP self + -- @param #boolean Active (optional) Include only active groups to the set. + -- Include inactive groups if you provide false. + -- @return #SET_OPSGROUP self + -- @usage + -- + -- -- Include only active groups to the set. + -- GroupSet = SET_OPSGROUP:New():FilterActive():FilterStart() + -- + -- -- Include only active groups to the set of the blue coalition, and filter one time. + -- GroupSet = SET_OPSGROUP:New():FilterActive():FilterCoalition( "blue" ):FilterOnce() + -- + -- -- Include only active groups to the set of the blue coalition, and filter one time. + -- -- Later, reset to include back inactive groups to the set. + -- GroupSet = SET_OPSGROUP:New():FilterActive():FilterCoalition( "blue" ):FilterOnce() + -- ... logic ... + -- GroupSet = SET_OPSGROUP:New():FilterActive( false ):FilterCoalition( "blue" ):FilterOnce() + -- + function SET_OPSGROUP:FilterActive( Active ) + Active = Active or not ( Active == false ) + self.Filter.Active = Active + return self + end + + + --- Starts the filtering. + -- @param #SET_OPSGROUP self + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:FilterStart() + + if _DATABASE then + self:_FilterStart() + self:HandleEvent( EVENTS.Birth, self._EventOnBirth ) + self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) + self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) + self:HandleEvent( EVENTS.RemoveUnit, self._EventOnDeadOrCrash ) + end + + return self + end + + --- Activate late activated groups in the set. + -- @param #SET_OPSGROUP self + -- @param #number Delay Delay in seconds. + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:Activate(Delay) + local Set = self:GetSet() + for GroupID, GroupData in pairs(Set) do + local group=GroupData --Ops.OpsGroup#OPSGROUP + if group and group:IsAlive()==false then + group:Activate(Delay) + end + end + return self + end + + --- Handles the OnDead or OnCrash event for alive groups set. + -- Note: The GROUP object in the SET_OPSGROUP collection will only be removed if the last unit is destroyed of the GROUP. + -- @param #SET_OPSGROUP self + -- @param Core.Event#EVENTDATA Event + function SET_OPSGROUP:_EventOnDeadOrCrash( Event ) + self:F( { Event } ) + + if Event.IniDCSUnit then + local ObjectName, Object = self:FindInDatabase( Event ) + if ObjectName then + if Event.IniDCSGroup:getSize() == 1 then -- Only remove if the last unit of the group was destroyed. + self:Remove( ObjectName ) + end + end + end + end + + --- Handles the Database to check on an event (birth) that the Object was added in the Database. + -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! + -- @param #SET_OPSGROUP self + -- @param Core.Event#EVENTDATA Event + -- @return #string The name of the GROUP + -- @return #table The GROUP + function SET_OPSGROUP:AddInDatabase( Event ) + + if Event.IniObjectCategory==1 then + + if not self.Database[Event.IniDCSGroupName] then + self.Database[Event.IniDCSGroupName] = GROUP:Register( Event.IniDCSGroupName ) + end + + end + + return Event.IniDCSGroupName, self.Database[Event.IniDCSGroupName] + end + + --- Handles the Database to check on any event that Object exists in the Database. + -- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! + -- @param #SET_OPSGROUP self + -- @param Core.Event#EVENTDATA Event Event data table. + -- @return #string The name of the GROUP + -- @return #table The GROUP + function SET_OPSGROUP:FindInDatabase(Event) + return Event.IniDCSGroupName, self.Database[Event.IniDCSGroupName] + end + + --- Iterate the set and call an iterator function for each OPSGROUP object. + -- @param #SET_OPSGROUP self + -- @param #function IteratorFunction The function that will be called for all OPSGROUPs in the set. **NOTE** that the function must have the OPSGROUP as first parameter! + -- @param ... (Optional) arguments passed to the `IteratorFunction`. + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:ForEachGroup( IteratorFunction, ... ) + + self:ForEach(IteratorFunction, arg, self:GetSet()) + + return self + end + + --- Check include object. + -- @param #SET_OPSGROUP self + -- @param Wrapper.Group#GROUP MGroup The group that is checked for inclusion. + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:IsIncludeObject(MGroup) + + -- Assume it is and check later if not. + local MGroupInclude=true + + -- Filter active. + if self.Filter.Active~=nil then + + local MGroupActive = false + + if self.Filter.Active==false or (self.Filter.Active==true and MGroup:IsActive()==true) then + MGroupActive = true + end + + MGroupInclude = MGroupInclude and MGroupActive + end + + -- Filter coalitions. + if self.Filter.Coalitions then + + local MGroupCoalition = false + + for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do + if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName]==MGroup:GetCoalition() then + MGroupCoalition = true + end + end + + MGroupInclude = MGroupInclude and MGroupCoalition + end + + -- Filter categories. + if self.Filter.Categories then + + local MGroupCategory = false + + for CategoryID, CategoryName in pairs( self.Filter.Categories ) do + if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName]==MGroup:GetCategory() then + MGroupCategory = true + end + end + + MGroupInclude = MGroupInclude and MGroupCategory + end + + -- Filter countries. + if self.Filter.Countries then + local MGroupCountry = false + for CountryID, CountryName in pairs( self.Filter.Countries ) do + if country.id[CountryName] == MGroup:GetCountry() then + MGroupCountry = true + end + end + MGroupInclude = MGroupInclude and MGroupCountry + end + + -- Filter "prefixes". + if self.Filter.GroupPrefixes then + + local MGroupPrefix = false + + for GroupPrefixId, GroupPrefix in pairs( self.Filter.GroupPrefixes ) do + if string.find( MGroup:GetName(), GroupPrefix:gsub ("-", "%%-"), 1 ) then --Not sure why "-" is replaced by "%-" ?! + MGroupPrefix = true + end + end + + MGroupInclude = MGroupInclude and MGroupPrefix + end + + return MGroupInclude + end + +end From a3cab7097a6088f583751c5168d1fbf22755c8c9 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 11 Dec 2021 19:41:02 +0100 Subject: [PATCH 031/200] SET - Typos --- Moose Development/Moose/Core/Set.lua | 60 ++++++++++++++-------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index d29ea28b9..679ba9820 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -20,9 +20,9 @@ -- Various types of SET_ classes are available: -- -- * @{#SET_GROUP}: Defines a collection of @{Wrapper.Group}s filtered by filter criteria. --- * @{#SET_UNIT}: Defines a colleciton of @{Wrapper.Unit}s filtered by filter criteria. +-- * @{#SET_UNIT}: Defines a collection of @{Wrapper.Unit}s filtered by filter criteria. -- * @{#SET_STATIC}: Defines a collection of @{Wrapper.Static}s filtered by filter criteria. --- * @{#SET_CLIENT}: Defines a collection of @{Client}s filterd by filter criteria. +-- * @{#SET_CLIENT}: Defines a collection of @{Client}s filtered by filter criteria. -- * @{#SET_AIRBASE}: Defines a collection of @{Wrapper.Airbase}s filtered by filter criteria. -- * @{#SET_CARGO}: Defines a collection of @{Cargo.Cargo}s filtered by filter criteria. -- * @{#SET_ZONE}: Defines a collection of @{Core.Zone}s filtered by filter criteria. @@ -50,14 +50,14 @@ do -- SET_BASE --- @type SET_BASE -- @field #table Filter Table of filters. -- @field #table Set Table of objects. - -- @field #table Index Table of indicies. + -- @field #table Index Table of indices. -- @field #table List Unused table. -- @field Core.Scheduler#SCHEDULER CallScheduler -- @extends Core.Base#BASE --- The @{Core.Set#SET_BASE} class defines the core functions that define a collection of objects. - -- A SET provides iterators to iterate the SET, but will **temporarily** yield the ForEach interator loop at defined **"intervals"** to the mail simulator loop. + -- A SET provides iterators to iterate the SET, but will **temporarily** yield the ForEach iterator loop at defined **"intervals"** to the mail simulator loop. -- In this way, large loops can be done while not blocking the simulator main processing loop. -- The default **"yield interval"** is after 10 objects processed. -- The default **"time interval"** is after 0.001 seconds. @@ -68,7 +68,7 @@ do -- SET_BASE -- -- ## Define the SET iterator **"yield interval"** and the **"time interval"** -- - -- Modify the iterator intervals with the @{Core.Set#SET_BASE.SetInteratorIntervals} method. + -- Modify the iterator intervals with the @{Core.Set#SET_BASE.SetIteratorIntervals} method. -- You can set the **"yield interval"**, and the **"time interval"**. (See above). -- -- @field #SET_BASE SET_BASE @@ -787,7 +787,7 @@ do -- SET_BASE end - ----- Iterate the SET_BASE and call an interator function for each **alive** unit, providing the Unit and optional parameters. + ----- Iterate the SET_BASE and call an iterator function for each **alive** unit, providing the Unit and optional parameters. ---- @param #SET_BASE self ---- @param #function IteratorFunction The function that will be called when there is an alive unit in the SET_BASE. The function needs to accept a UNIT parameter. ---- @return #SET_BASE self @@ -799,7 +799,7 @@ do -- SET_BASE -- return self --end -- - ----- Iterate the SET_BASE and call an interator function for each **alive** player, providing the Unit of the player and optional parameters. + ----- Iterate the SET_BASE and call an iterator function for each **alive** player, providing the Unit of the player and optional parameters. ---- @param #SET_BASE self ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_BASE. The function needs to accept a UNIT parameter. ---- @return #SET_BASE self @@ -812,7 +812,7 @@ do -- SET_BASE --end -- -- - ----- Iterate the SET_BASE and call an interator function for each client, providing the Client to the function and optional parameters. + ----- Iterate the SET_BASE and call an iterator function for each client, providing the Client to the function and optional parameters. ---- @param #SET_BASE self ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_BASE. The function needs to accept a CLIENT parameter. ---- @return #SET_BASE self @@ -945,7 +945,7 @@ do -- SET_GROUP -- ### When a GROUP object crashes or is dead, the SET_GROUP will trigger a **Dead** event. -- -- You can handle the event using the OnBefore and OnAfter event handlers. - -- The event handlers need to have the paramters From, Event, To, GroupObject. + -- The event handlers need to have the parameters From, Event, To, GroupObject. -- The GroupObject is the GROUP object that is dead and within the SET_GROUP, and is passed as a parameter to the event handler. -- See the following example: -- @@ -960,7 +960,7 @@ do -- SET_GROUP -- end -- -- While this is a good example, there is a catch. - -- Imageine you want to execute the code above, the the self would need to be from the object declared outside (above) the OnAfterDead method. + -- Imagine you want to execute the code above, the the self would need to be from the object declared outside (above) the OnAfterDead method. -- So, the self would need to contain another object. Fortunately, this can be done, but you must use then the **`.`** notation for the method. -- See the modified example: -- @@ -1534,7 +1534,7 @@ do -- SET_GROUP --- Iterate the SET_GROUP and return true if all the @{Wrapper.Group#GROUP} are completely in the @{Core.Zone#ZONE} -- @param #SET_GROUP self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. - -- @return #boolean true if all the @{Wrapper.Group#GROUP} are completly in the @{Core.Zone#ZONE}, false otherwise + -- @return #boolean true if all the @{Wrapper.Group#GROUP} are completely in the @{Core.Zone#ZONE}, false otherwise -- @usage -- local MyZone = ZONE:New("Zone1") -- local MySetGroup = SET_GROUP:New() @@ -1582,7 +1582,7 @@ do -- SET_GROUP --- Iterate the SET_GROUP and return true if at least one of the @{Wrapper.Group#GROUP} is completely inside the @{Core.Zone#ZONE} -- @param #SET_GROUP self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. - -- @return #boolean true if at least one of the @{Wrapper.Group#GROUP} is completly inside the @{Core.Zone#ZONE}, false otherwise. + -- @return #boolean true if at least one of the @{Wrapper.Group#GROUP} is completely inside the @{Core.Zone#ZONE}, false otherwise. -- @usage -- local MyZone = ZONE:New("Zone1") -- local MySetGroup = SET_GROUP:New() @@ -1607,7 +1607,7 @@ do -- SET_GROUP --- Iterate the SET_GROUP and return true if at least one @{#UNIT} of one @{GROUP} of the @{SET_GROUP} is in @{ZONE} -- @param #SET_GROUP self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. - -- @return #boolean true if at least one of the @{Wrapper.Group#GROUP} is partly or completly inside the @{Core.Zone#ZONE}, false otherwise. + -- @return #boolean true if at least one of the @{Wrapper.Group#GROUP} is partly or completely inside the @{Core.Zone#ZONE}, false otherwise. -- @usage -- local MyZone = ZONE:New("Zone1") -- local MySetGroup = SET_GROUP:New() @@ -1633,7 +1633,7 @@ do -- SET_GROUP -- Will return false if a @{GROUP} is fully in the @{ZONE} -- @param #SET_GROUP self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. - -- @return #boolean true if at least one of the @{Wrapper.Group#GROUP} is partly or completly inside the @{Core.Zone#ZONE}, false otherwise. + -- @return #boolean true if at least one of the @{Wrapper.Group#GROUP} is partly or completely inside the @{Core.Zone#ZONE}, false otherwise. -- @usage -- local MyZone = ZONE:New("Zone1") -- local MySetGroup = SET_GROUP:New() @@ -1763,7 +1763,7 @@ do -- SET_GROUP return CountG,CountU end - ----- Iterate the SET_GROUP and call an interator function for each **alive** player, providing the Group of the player and optional parameters. + ----- Iterate the SET_GROUP and call an iterator function for each **alive** player, providing the Group of the player and optional parameters. ---- @param #SET_GROUP self ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_GROUP. The function needs to accept a GROUP parameter. ---- @return #SET_GROUP self @@ -1776,7 +1776,7 @@ do -- SET_GROUP --end -- -- - ----- Iterate the SET_GROUP and call an interator function for each client, providing the Client to the function and optional parameters. + ----- Iterate the SET_GROUP and call an iterator function for each client, providing the Client to the function and optional parameters. ---- @param #SET_GROUP self ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_GROUP. The function needs to accept a CLIENT parameter. ---- @return #SET_GROUP self @@ -1957,7 +1957,7 @@ do -- SET_UNIT -- ### 6.1) When a UNIT object crashes or is dead, the SET_UNIT will trigger a **Dead** event. -- -- You can handle the event using the OnBefore and OnAfter event handlers. - -- The event handlers need to have the paramters From, Event, To, GroupObject. + -- The event handlers need to have the parameters From, Event, To, GroupObject. -- The GroupObject is the UNIT object that is dead and within the SET_UNIT, and is passed as a parameter to the event handler. -- See the following example: -- @@ -1972,7 +1972,7 @@ do -- SET_UNIT -- end -- -- While this is a good example, there is a catch. - -- Imageine you want to execute the code above, the the self would need to be from the object declared outside (above) the OnAfterDead method. + -- Imagine you want to execute the code above, the the self would need to be from the object declared outside (above) the OnAfterDead method. -- So, the self would need to contain another object. Fortunately, this can be done, but you must use then the **`.`** notation for the method. -- See the modified example: -- @@ -2398,7 +2398,7 @@ do -- SET_UNIT end - --- Iterate the SET_UNIT and call an interator function for each **alive** UNIT, providing the UNIT and optional parameters. + --- Iterate the SET_UNIT and call an iterator function for each **alive** UNIT, providing the UNIT and optional parameters. -- @param #SET_UNIT self -- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter. -- @return #SET_UNIT self @@ -2458,7 +2458,7 @@ do -- SET_UNIT end - --- Iterate the SET_UNIT **sorted *per Threat Level** and call an interator function for each **alive** UNIT, providing the UNIT and optional parameters. + --- Iterate the SET_UNIT **sorted *per Threat Level** and call an iterator function for each **alive** UNIT, providing the UNIT and optional parameters. -- -- @param #SET_UNIT self -- @param #number FromThreatLevel The TreatLevel to start the evaluation **From** (this must be a value between 0 and 10). @@ -2854,7 +2854,7 @@ do -- SET_UNIT - ----- Iterate the SET_UNIT and call an interator function for each **alive** player, providing the Unit of the player and optional parameters. + ----- Iterate the SET_UNIT and call an iterator function for each **alive** player, providing the Unit of the player and optional parameters. ---- @param #SET_UNIT self ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_UNIT. The function needs to accept a UNIT parameter. ---- @return #SET_UNIT self @@ -2867,7 +2867,7 @@ do -- SET_UNIT --end -- -- - ----- Iterate the SET_UNIT and call an interator function for each client, providing the Client to the function and optional parameters. + ----- Iterate the SET_UNIT and call an iterator function for each client, providing the Client to the function and optional parameters. ---- @param #SET_UNIT self ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_UNIT. The function needs to accept a CLIENT parameter. ---- @return #SET_UNIT self @@ -3453,7 +3453,7 @@ do -- SET_STATIC end - --- Iterate the SET_STATIC and call an interator function for each **alive** STATIC, providing the STATIC and optional parameters. + --- Iterate the SET_STATIC and call an iterator function for each **alive** STATIC, providing the STATIC and optional parameters. -- @param #SET_STATIC self -- @param #function IteratorFunction The function that will be called when there is an alive STATIC in the SET_STATIC. The function needs to accept a STATIC parameter. -- @return #SET_STATIC self @@ -4100,7 +4100,7 @@ do -- SET_CLIENT return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - --- Iterate the SET_CLIENT and call an interator function for each **alive** CLIENT, providing the CLIENT and optional parameters. + --- Iterate the SET_CLIENT and call an iterator function for each **alive** CLIENT, providing the CLIENT and optional parameters. -- @param #SET_CLIENT self -- @param #function IteratorFunction The function that will be called when there is an alive CLIENT in the SET_CLIENT. The function needs to accept a CLIENT parameter. -- @return #SET_CLIENT self @@ -4565,7 +4565,7 @@ do -- SET_PLAYER return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - --- Iterate the SET_PLAYER and call an interator function for each **alive** CLIENT, providing the CLIENT and optional parameters. + --- Iterate the SET_PLAYER and call an iterator function for each **alive** CLIENT, providing the CLIENT and optional parameters. -- @param #SET_PLAYER self -- @param #function IteratorFunction The function that will be called when there is an alive CLIENT in the SET_PLAYER. The function needs to accept a CLIENT parameter. -- @return #SET_PLAYER self @@ -4999,7 +4999,7 @@ do -- SET_AIRBASE return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - --- Iterate the SET_AIRBASE and call an interator function for each AIRBASE, providing the AIRBASE and optional parameters. + --- Iterate the SET_AIRBASE and call an iterator function for each AIRBASE, providing the AIRBASE and optional parameters. -- @param #SET_AIRBASE self -- @param #function IteratorFunction The function that will be called when there is an alive AIRBASE in the SET_AIRBASE. The function needs to accept a AIRBASE parameter. -- @return #SET_AIRBASE self @@ -5330,7 +5330,7 @@ do -- SET_CARGO return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - --- (R2.1) Iterate the SET_CARGO and call an interator function for each CARGO, providing the CARGO and optional parameters. + --- (R2.1) Iterate the SET_CARGO and call an iterator function for each CARGO, providing the CARGO and optional parameters. -- @param #SET_CARGO self -- @param #function IteratorFunction The function that will be called when there is an alive CARGO in the SET_CARGO. The function needs to accept a CARGO parameter. -- @return #SET_CARGO self @@ -5751,7 +5751,7 @@ do -- SET_ZONE return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - --- Iterate the SET_ZONE and call an interator function for each ZONE, providing the ZONE and optional parameters. + --- Iterate the SET_ZONE and call an iterator function for each ZONE, providing the ZONE and optional parameters. -- @param #SET_ZONE self -- @param #function IteratorFunction The function that will be called when there is an alive ZONE in the SET_ZONE. The function needs to accept a AIRBASE parameter. -- @return #SET_ZONE self @@ -6069,7 +6069,7 @@ do -- SET_ZONE_GOAL return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - --- Iterate the SET_ZONE_GOAL and call an interator function for each ZONE, providing the ZONE and optional parameters. + --- Iterate the SET_ZONE_GOAL and call an iterator function for each ZONE, providing the ZONE and optional parameters. -- @param #SET_ZONE_GOAL self -- @param #function IteratorFunction The function that will be called when there is an alive ZONE in the SET_ZONE_GOAL. The function needs to accept a AIRBASE parameter. -- @return #SET_ZONE_GOAL self @@ -6239,7 +6239,7 @@ do -- SET_OPSGROUP -- ### When a GROUP object crashes or is dead, the SET_OPSGROUP will trigger a **Dead** event. -- -- You can handle the event using the OnBefore and OnAfter event handlers. - -- The event handlers need to have the paramters From, Event, To, GroupObject. + -- The event handlers need to have the parameters From, Event, To, GroupObject. -- The GroupObject is the GROUP object that is dead and within the SET_OPSGROUP, and is passed as a parameter to the event handler. -- See the following example: -- From 456fcd38d05d609a02ae1cde1574b78c27dacb80 Mon Sep 17 00:00:00 2001 From: TommyC81 Date: Sun, 12 Dec 2021 16:53:04 +0400 Subject: [PATCH 032/200] Code and documentation tweaks. (#1662) * Update Point.lua General code formatting. * Update Set.lua General code formatting. * Update Positionable.lua Code formatting, and documentation fixes. --- Moose Development/Moose/Core/Point.lua | 134 +- Moose Development/Moose/Core/Set.lua | 1100 +++++++---------- .../Moose/Wrapper/Positionable.lua | 32 +- 3 files changed, 546 insertions(+), 720 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index c0565afc9..9fdd5f659 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -254,7 +254,7 @@ do -- COORDINATE --- Create a new COORDINATE object from Vec2 coordinates. -- @param #COORDINATE self -- @param DCS#Vec2 Vec2 The Vec2 point. - -- @param DCS#Distance LandHeightAdd (optional) The default height if required to be evaluated will be the land height of the x, y coordinate. You can specify an extra height to be added to the land height. + -- @param DCS#Distance LandHeightAdd (Optional) The default height if required to be evaluated will be the land height of the x, y coordinate. You can specify an extra height to be added to the land height. -- @return #COORDINATE function COORDINATE:NewFromVec2( Vec2, LandHeightAdd ) @@ -536,10 +536,10 @@ do -- COORDINATE -- @param #COORDINATE self -- @param DCS#Distance Distance The Distance to be added in meters. -- @param DCS#Angle Angle The Angle in degrees. Defaults to 0 if not specified (nil). - -- @param #boolean Keepalt If true, keep altitude of original coordinate. Default is that the new coordinate is created at the translated land height. + -- @param #boolean KeepAltitude If true, keep altitude of original coordinate. Default is that the new coordinate is created at the translated land height. -- @param #boolean Overwrite If true, overwrite the original COORDINATE with the translated one. Otherwise, create a new COORDINATE. -- @return #COORDINATE The new calculated COORDINATE. - function COORDINATE:Translate( Distance, Angle, Keepalt, Overwrite ) + function COORDINATE:Translate( Distance, Angle, KeepAltitude, Overwrite ) -- Angle in rad. local alpha = math.rad( (Angle or 0) ) @@ -547,7 +547,7 @@ do -- COORDINATE local x = Distance * math.cos( alpha ) + self.x -- New x local z = Distance * math.sin( alpha ) + self.z -- New z - local y = Keepalt and self.y or land.getHeight( { x = x, y = z } ) + local y = KeepAltitude and self.y or land.getHeight( { x = x, y = z } ) if Overwrite then self.x = x @@ -1725,89 +1725,89 @@ do -- COORDINATE --- Big smoke and fire at the coordinate. -- @param #COORDINATE self -- @param Utilities.Utils#BIGSMOKEPRESET preset Smoke preset (0=small smoke and fire, 1=medium smoke and fire, 2=large smoke and fire, 3=huge smoke and fire, 4=small smoke, 5=medium smoke, 6=large smoke, 7=huge smoke). - -- @param #number density (Optional) Smoke density. Number in [0,...,1]. Default 0.5. - function COORDINATE:BigSmokeAndFire( preset, density ) - self:F2( { preset = preset, density = density } ) - density = density or 0.5 - trigger.action.effectSmokeBig( self:GetVec3(), preset, density ) + -- @param #number Density (Optional) Smoke density. Number in [0,...,1]. Default 0.5. + function COORDINATE:BigSmokeAndFire( Preset, Density ) + self:F2( { Preset = Preset, Density = Density } ) + Density = Density or 0.5 + trigger.action.effectSmokeBig( self:GetVec3(), Preset, Density ) end --- Small smoke and fire at the coordinate. -- @param #COORDINATE self - -- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. - function COORDINATE:BigSmokeAndFireSmall( density ) - self:F2( { density = density } ) - density = density or 0.5 - self:BigSmokeAndFire( BIGSMOKEPRESET.SmallSmokeAndFire, density ) + -- @number Density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + function COORDINATE:BigSmokeAndFireSmall( Density ) + self:F2( { Density = Density } ) + Density = Density or 0.5 + self:BigSmokeAndFire( BIGSMOKEPRESET.SmallSmokeAndFire, Density ) end --- Medium smoke and fire at the coordinate. -- @param #COORDINATE self - -- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. - function COORDINATE:BigSmokeAndFireMedium( density ) - self:F2( { density = density } ) - density = density or 0.5 - self:BigSmokeAndFire( BIGSMOKEPRESET.MediumSmokeAndFire, density ) + -- @number Density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + function COORDINATE:BigSmokeAndFireMedium( Density ) + self:F2( { Density = Density } ) + Density = Density or 0.5 + self:BigSmokeAndFire( BIGSMOKEPRESET.MediumSmokeAndFire, Density ) end --- Large smoke and fire at the coordinate. -- @param #COORDINATE self - -- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. - function COORDINATE:BigSmokeAndFireLarge( density ) - self:F2( { density = density } ) - density = density or 0.5 - self:BigSmokeAndFire( BIGSMOKEPRESET.LargeSmokeAndFire, density ) + -- @number Density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + function COORDINATE:BigSmokeAndFireLarge( Density ) + self:F2( { Density = Density } ) + Density = Density or 0.5 + self:BigSmokeAndFire( BIGSMOKEPRESET.LargeSmokeAndFire, Density ) end --- Huge smoke and fire at the coordinate. -- @param #COORDINATE self - -- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. - function COORDINATE:BigSmokeAndFireHuge( density ) - self:F2( { density = density } ) - density = density or 0.5 - self:BigSmokeAndFire( BIGSMOKEPRESET.HugeSmokeAndFire, density ) + -- @number Density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + function COORDINATE:BigSmokeAndFireHuge( Density ) + self:F2( { Density = Density } ) + Density = Density or 0.5 + self:BigSmokeAndFire( BIGSMOKEPRESET.HugeSmokeAndFire, Density ) end --- Small smoke at the coordinate. -- @param #COORDINATE self - -- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. - function COORDINATE:BigSmokeSmall( density ) - self:F2( { density = density } ) - density = density or 0.5 - self:BigSmokeAndFire( BIGSMOKEPRESET.SmallSmoke, density ) + -- @number Density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + function COORDINATE:BigSmokeSmall( Density ) + self:F2( { Density = Density } ) + Density = Density or 0.5 + self:BigSmokeAndFire( BIGSMOKEPRESET.SmallSmoke, Density ) end --- Medium smoke at the coordinate. -- @param #COORDINATE self - -- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. - function COORDINATE:BigSmokeMedium( density ) - self:F2( { density = density } ) - density = density or 0.5 - self:BigSmokeAndFire( BIGSMOKEPRESET.MediumSmoke, density ) + -- @number Density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + function COORDINATE:BigSmokeMedium( Density ) + self:F2( { Density = Density } ) + Density = Density or 0.5 + self:BigSmokeAndFire( BIGSMOKEPRESET.MediumSmoke, Density ) end --- Large smoke at the coordinate. -- @param #COORDINATE self - -- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. - function COORDINATE:BigSmokeLarge( density ) - self:F2( { density = density } ) - density = density or 0.5 - self:BigSmokeAndFire( BIGSMOKEPRESET.LargeSmoke, density ) + -- @number Density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + function COORDINATE:BigSmokeLarge( Density ) + self:F2( { Density = Density } ) + Density = Density or 0.5 + self:BigSmokeAndFire( BIGSMOKEPRESET.LargeSmoke, Density ) end --- Huge smoke at the coordinate. -- @param #COORDINATE self - -- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. - function COORDINATE:BigSmokeHuge( density ) - self:F2( { density = density } ) - density = density or 0.5 - self:BigSmokeAndFire( BIGSMOKEPRESET.HugeSmoke, density ) + -- @number Density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + function COORDINATE:BigSmokeHuge( Density ) + self:F2( { Density = Density } ) + Density = Density or 0.5 + self:BigSmokeAndFire( BIGSMOKEPRESET.HugeSmoke, Density ) end --- Flares the point in a color. -- @param #COORDINATE self -- @param Utilities.Utils#FLARECOLOR FlareColor - -- @param DCS#Azimuth Azimuth (optional) The azimuth of the flare direction. The default azimuth is 0. + -- @param DCS#Azimuth Azimuth (Optional) The azimuth of the flare direction. The default azimuth is 0. function COORDINATE:Flare( FlareColor, Azimuth ) self:F2( { FlareColor } ) trigger.action.signalFlare( self:GetVec3(), FlareColor, Azimuth and Azimuth or 0 ) @@ -1815,7 +1815,7 @@ do -- COORDINATE --- Flare the COORDINATE White. -- @param #COORDINATE self - -- @param DCS#Azimuth Azimuth (optional) The azimuth of the flare direction. The default azimuth is 0. + -- @param DCS#Azimuth Azimuth (Optional) The azimuth of the flare direction. The default azimuth is 0. function COORDINATE:FlareWhite( Azimuth ) self:F2( Azimuth ) self:Flare( FLARECOLOR.White, Azimuth ) @@ -1823,7 +1823,7 @@ do -- COORDINATE --- Flare the COORDINATE Yellow. -- @param #COORDINATE self - -- @param DCS#Azimuth Azimuth (optional) The azimuth of the flare direction. The default azimuth is 0. + -- @param DCS#Azimuth Azimuth (Optional) The azimuth of the flare direction. The default azimuth is 0. function COORDINATE:FlareYellow( Azimuth ) self:F2( Azimuth ) self:Flare( FLARECOLOR.Yellow, Azimuth ) @@ -1831,7 +1831,7 @@ do -- COORDINATE --- Flare the COORDINATE Green. -- @param #COORDINATE self - -- @param DCS#Azimuth Azimuth (optional) The azimuth of the flare direction. The default azimuth is 0. + -- @param DCS#Azimuth Azimuth (Optional) The azimuth of the flare direction. The default azimuth is 0. function COORDINATE:FlareGreen( Azimuth ) self:F2( Azimuth ) self:Flare( FLARECOLOR.Green, Azimuth ) @@ -2527,7 +2527,7 @@ do -- COORDINATE --- Return a BR string from a COORDINATE to the COORDINATE. -- @param #COORDINATE self -- @param #COORDINATE FromCoordinate The coordinate to measure the distance and the bearing from. - -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. + -- @param Core.Settings#SETTINGS Settings (Optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @return #string The BR text. function COORDINATE:ToStringBR( FromCoordinate, Settings ) local DirectionVec3 = FromCoordinate:GetDirectionVec3( self ) @@ -2539,7 +2539,7 @@ do -- COORDINATE --- Return a BRAA string from a COORDINATE to the COORDINATE. -- @param #COORDINATE self -- @param #COORDINATE FromCoordinate The coordinate to measure the distance and the bearing from. - -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. + -- @param Core.Settings#SETTINGS Settings (Optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @return #string The BR text. function COORDINATE:ToStringBRA( FromCoordinate, Settings, Language ) local DirectionVec3 = FromCoordinate:GetDirectionVec3( self ) @@ -2552,7 +2552,7 @@ do -- COORDINATE --- Return a BULLS string out of the BULLS of the coalition to the COORDINATE. -- @param #COORDINATE self -- @param DCS#coalition.side Coalition The coalition. - -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. + -- @param Core.Settings#SETTINGS Settings (Optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @return #string The BR text. function COORDINATE:ToStringBULLS( Coalition, Settings ) local BullsCoordinate = COORDINATE:NewFromVec3( coalition.getMainRefPoint( Coalition ) ) @@ -2600,7 +2600,7 @@ do -- COORDINATE --- Provides a Lat Lon string in Degree Minute Second format. -- @param #COORDINATE self - -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. + -- @param Core.Settings#SETTINGS Settings (Optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @return #string The LL DMS Text function COORDINATE:ToStringLLDMS( Settings ) @@ -2611,7 +2611,7 @@ do -- COORDINATE --- Provides a Lat Lon string in Degree Decimal Minute format. -- @param #COORDINATE self - -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. + -- @param Core.Settings#SETTINGS Settings (Optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @return #string The LL DDM Text function COORDINATE:ToStringLLDDM( Settings ) @@ -2622,7 +2622,7 @@ do -- COORDINATE --- Provides a MGRS string -- @param #COORDINATE self - -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. + -- @param Core.Settings#SETTINGS Settings (Optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @return #string The MGRS Text function COORDINATE:ToStringMGRS( Settings ) -- R2.1 Fixes issue #424. @@ -2639,7 +2639,7 @@ do -- COORDINATE -- @param #COORDINATE ReferenceCoord The refrence coordinate. -- @param #string ReferenceName The refrence name. -- @param Wrapper.Controllable#CONTROLLABLE Controllable - -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. + -- @param Core.Settings#SETTINGS Settings (Optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @return #string The coordinate Text in the configured coordinate system. function COORDINATE:ToStringFromRP( ReferenceCoord, ReferenceName, Controllable, Settings ) @@ -2668,7 +2668,7 @@ do -- COORDINATE --- Provides a coordinate string of the point, based on the A2G coordinate format system. -- @param #COORDINATE self -- @param Wrapper.Controllable#CONTROLLABLE Controllable - -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. + -- @param Core.Settings#SETTINGS Settings (Optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @return #string The coordinate Text in the configured coordinate system. function COORDINATE:ToStringA2G( Controllable, Settings ) @@ -2702,7 +2702,7 @@ do -- COORDINATE --- Provides a coordinate string of the point, based on the A2A coordinate format system. -- @param #COORDINATE self -- @param Wrapper.Controllable#CONTROLLABLE Controllable - -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. + -- @param Core.Settings#SETTINGS Settings (Optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @return #string The coordinate Text in the configured coordinate system. function COORDINATE:ToStringA2A( Controllable, Settings, Language ) -- R2.2 @@ -2741,7 +2741,7 @@ do -- COORDINATE -- * Can be overridden if for a GROUP containing x clients, a menu was selected to override the default. -- @param #COORDINATE self -- @param Wrapper.Controllable#CONTROLLABLE Controllable The controllable to retrieve the settings from, otherwise the default settings will be chosen. - -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. + -- @param Core.Settings#SETTINGS Settings (Optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @param Tasking.Task#TASK Task The task for which coordinates need to be calculated. -- @return #string The coordinate Text in the configured coordinate system. function COORDINATE:ToString( Controllable, Settings, Task ) @@ -2793,7 +2793,7 @@ do -- COORDINATE -- * Can be overridden if for a GROUP containing x clients, a menu was selected to override the default. -- @param #COORDINATE self -- @param Wrapper.Controllable#CONTROLLABLE Controllable - -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. + -- @param Core.Settings#SETTINGS Settings (Optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @return #string The pressure text in the configured measurement system. function COORDINATE:ToStringPressure( Controllable, Settings ) -- R2.3 @@ -2809,7 +2809,7 @@ do -- COORDINATE -- * Can be overridden if for a GROUP containing x clients, a menu was selected to override the default. -- @param #COORDINATE self -- @param Wrapper.Controllable#CONTROLLABLE Controllable - -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. + -- @param Core.Settings#SETTINGS Settings (Optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @return #string The wind text in the configured measurement system. function COORDINATE:ToStringWind( Controllable, Settings ) @@ -2940,7 +2940,7 @@ do -- POINT_VEC3 --- Create a new POINT_VEC3 object from Vec2 coordinates. -- @param #POINT_VEC3 self -- @param DCS#Vec2 Vec2 The Vec2 point. - -- @param DCS#Distance LandHeightAdd (optional) Add a landheight. + -- @param DCS#Distance LandHeightAdd (Optional) Add a landheight. -- @return Core.Point#POINT_VEC3 self function POINT_VEC3:NewFromVec2( Vec2, LandHeightAdd ) @@ -3088,7 +3088,7 @@ do -- POINT_VEC2 -- @param #POINT_VEC2 self -- @param DCS#Distance x The x coordinate of the Vec3 point, pointing to the North. -- @param DCS#Distance y The y coordinate of the Vec3 point, pointing to the Right. - -- @param DCS#Distance LandHeightAdd (optional) The default height if required to be evaluated will be the land height of the x, y coordinate. You can specify an extra height to be added to the land height. + -- @param DCS#Distance LandHeightAdd (Optional) The default height if required to be evaluated will be the land height of the x, y coordinate. You can specify an extra height to be added to the land height. -- @return Core.Point#POINT_VEC2 function POINT_VEC2:New( x, y, LandHeightAdd ) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 679ba9820..35f3c3b35 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -44,7 +44,6 @@ -- @module Core.Set -- @image Core_Sets.JPG - do -- SET_BASE --- @type SET_BASE @@ -55,7 +54,6 @@ do -- SET_BASE -- @field Core.Scheduler#SCHEDULER CallScheduler -- @extends Core.Base#BASE - --- The @{Core.Set#SET_BASE} class defines the core functions that define a collection of objects. -- A SET provides iterators to iterate the SET, but will **temporarily** yield the ForEach iterator loop at defined **"intervals"** to the mail simulator loop. -- In this way, large loops can be done while not blocking the simulator main processing loop. @@ -79,12 +77,11 @@ do -- SET_BASE List = {}, Index = {}, Database = nil, - CallScheduler=nil, - TimeInterval=nil, - YieldInterval=nil, + CallScheduler = nil, + TimeInterval = nil, + YieldInterval = nil, } - --- Creates a new SET_BASE object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. -- @param #SET_BASE self -- @return #SET_BASE @@ -109,8 +106,7 @@ do -- SET_BASE -- @param #string ObjectName The name of the object. -- @param Object The object. - - self:AddTransition( "*", "Added", "*" ) + self:AddTransition( "*", "Added", "*" ) --- Removed Handler OnAfter for SET_BASE -- @function [parent=#SET_BASE] OnAfterRemoved @@ -121,7 +117,7 @@ do -- SET_BASE -- @param #string ObjectName The name of the object. -- @param Object The object. - self:AddTransition( "*", "Removed", "*" ) + self:AddTransition( "*", "Removed", "*" ) self.YieldInterval = 10 self.TimeInterval = 0.001 @@ -148,8 +144,6 @@ do -- SET_BASE return self end - - --- Finds an @{Core.Base#BASE} object based on the object Name. -- @param #SET_BASE self -- @param #string ObjectName @@ -160,7 +154,6 @@ do -- SET_BASE return ObjectFound end - --- Gets the Set. -- @param #SET_BASE self -- @return #SET_BASE self @@ -173,7 +166,7 @@ do -- SET_BASE --- Gets a list of the Names of the Objects in the Set. -- @param #SET_BASE self -- @return #SET_BASE self - function SET_BASE:GetSetNames() -- R2.3 + function SET_BASE:GetSetNames() -- R2.3 self:F2() local Names = {} @@ -185,11 +178,10 @@ do -- SET_BASE return Names end - --- Gets a list of the Objects in the Set. -- @param #SET_BASE self -- @return #SET_BASE self - function SET_BASE:GetSetObjects() -- R2.3 + function SET_BASE:GetSetObjects() -- R2.3 self:F2() local Objects = {} @@ -201,17 +193,18 @@ do -- SET_BASE return Objects end - --- Removes a @{Core.Base#BASE} object from the @{Core.Set#SET_BASE} and derived classes, based on the Object Name. -- @param #SET_BASE self -- @param #string ObjectName - -- @param NoTriggerEvent (optional) When `true`, the :Remove() method will not trigger a **Removed** event. + -- @param NoTriggerEvent (Optional) When `true`, the :Remove() method will not trigger a **Removed** event. function SET_BASE:Remove( ObjectName, NoTriggerEvent ) self:F2( { ObjectName = ObjectName } ) - + local TriggerEvent = true - if NoTriggerEvent == false then TriggerEvent = false end - + if NoTriggerEvent == false then + TriggerEvent = false + end + local Object = self.Set[ObjectName] if Object then @@ -229,14 +222,13 @@ do -- SET_BASE end end - --- Adds a @{Core.Base#BASE} object in the @{Core.Set#SET_BASE}, using a given ObjectName as the index. -- @param #SET_BASE self -- @param #string ObjectName The name of the object. -- @param Core.Base#BASE Object The object itself. -- @return Core.Base#BASE The added BASE Object. function SET_BASE:Add( ObjectName, Object ) - + -- Debug info. self:T( { ObjectName = ObjectName, Object = Object } ) @@ -272,43 +264,42 @@ do -- SET_BASE -- @param #SET_BASE self -- @return Core.Base#BASE The added BASE Object. function SET_BASE:SortByName() - - local function sort(a, b) - return a= Limit then - break - end - -- if Count % self.YieldInterval == 0 then - -- coroutine.yield( false ) - -- end + else + IteratorFunction( Object, unpack( arg ) ) + end + Count = Count + 1 + if Count >= Limit then + break + end + -- if Count % self.YieldInterval == 0 then + -- coroutine.yield( false ) + -- end end return true end - -- local co = coroutine.create( CoRoutine ) + -- local co = coroutine.create( CoRoutine ) local co = CoRoutine local function Schedule() - -- local status, res = coroutine.resume( co ) + -- local status, res = coroutine.resume( co ) local status, res = co() self:T3( { status, res } ) @@ -780,50 +759,48 @@ do -- SET_BASE return false end - --self.CallScheduler:Schedule( self, Schedule, {}, self.TimeInterval, self.TimeInterval, 0 ) + -- self.CallScheduler:Schedule( self, Schedule, {}, self.TimeInterval, self.TimeInterval, 0 ) Schedule() return self end - ----- Iterate the SET_BASE and call an iterator function for each **alive** unit, providing the Unit and optional parameters. ---- @param #SET_BASE self ---- @param #function IteratorFunction The function that will be called when there is an alive unit in the SET_BASE. The function needs to accept a UNIT parameter. ---- @return #SET_BASE self - --function SET_BASE:ForEachDCSUnitAlive( IteratorFunction, ... ) + -- function SET_BASE:ForEachDCSUnitAlive( IteratorFunction, ... ) -- self:F3( arg ) -- -- self:ForEach( IteratorFunction, arg, self.DCSUnitsAlive ) -- -- return self - --end + -- end -- ----- Iterate the SET_BASE and call an iterator function for each **alive** player, providing the Unit of the player and optional parameters. ---- @param #SET_BASE self ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_BASE. The function needs to accept a UNIT parameter. ---- @return #SET_BASE self - --function SET_BASE:ForEachPlayer( IteratorFunction, ... ) + -- function SET_BASE:ForEachPlayer( IteratorFunction, ... ) -- self:F3( arg ) -- -- self:ForEach( IteratorFunction, arg, self.PlayersAlive ) -- -- return self - --end + -- end -- -- ----- Iterate the SET_BASE and call an iterator function for each client, providing the Client to the function and optional parameters. ---- @param #SET_BASE self ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_BASE. The function needs to accept a CLIENT parameter. ---- @return #SET_BASE self - --function SET_BASE:ForEachClient( IteratorFunction, ... ) + -- function SET_BASE:ForEachClient( IteratorFunction, ... ) -- self:F3( arg ) -- -- self:ForEach( IteratorFunction, arg, self.Clients ) -- -- return self - --end - + -- end --- Decides whether to include the Object. -- @param #SET_BASE self @@ -839,7 +816,7 @@ do -- SET_BASE -- @param #SET_BASE self -- @param #table Object -- @return #SET_BASE self - function SET_BASE:IsInSet(ObjectName) + function SET_BASE:IsInSet( ObjectName ) self:F3( Object ) return true @@ -861,7 +838,7 @@ do -- SET_BASE --- Flushes the current SET_BASE contents in the log ... (for debugging reasons). -- @param #SET_BASE self - -- @param Core.Base#BASE MasterObject (optional) The master object as a reference. + -- @param Core.Base#BASE MasterObject (Optional) The master object as a reference. -- @return #string A string with the names of the objects. function SET_BASE:Flush( MasterObject ) self:F3() @@ -877,7 +854,6 @@ do -- SET_BASE end - do -- SET_GROUP --- @type SET_GROUP @@ -1010,7 +986,6 @@ do -- SET_GROUP }, } - --- Creates a new SET_GROUP object, building a set of groups belonging to a coalitions, categories, countries, types or with defined prefix names. -- @param #SET_GROUP self -- @return #SET_GROUP @@ -1037,7 +1012,7 @@ do -- SET_GROUP -- Clean the Set before returning with only the alive Groups. for GroupName, GroupObject in pairs( self.Set ) do - local GroupObject=GroupObject --Wrapper.Group#GROUP + local GroupObject = GroupObject -- Wrapper.Group#GROUP if GroupObject then if GroupObject:IsAlive() then AliveSet:Add( GroupName, GroupObject ) @@ -1104,7 +1079,7 @@ do -- SET_GROUP -- @return Core.Set#SET_GROUP self function SET_GROUP:AddGroupsByName( AddGroupNames ) - local AddGroupNamesArray = ( type( AddGroupNames ) == "table" ) and AddGroupNames or { AddGroupNames } + local AddGroupNamesArray = (type( AddGroupNames ) == "table") and AddGroupNames or { AddGroupNames } for AddGroupID, AddGroupName in pairs( AddGroupNamesArray ) do self:Add( AddGroupName, GROUP:FindByName( AddGroupName ) ) @@ -1119,7 +1094,7 @@ do -- SET_GROUP -- @return Core.Set#SET_GROUP self function SET_GROUP:RemoveGroupsByName( RemoveGroupNames ) - local RemoveGroupNamesArray = ( type( RemoveGroupNames ) == "table" ) and RemoveGroupNames or { RemoveGroupNames } + local RemoveGroupNamesArray = (type( RemoveGroupNames ) == "table") and RemoveGroupNames or { RemoveGroupNames } for RemoveGroupID, RemoveGroupName in pairs( RemoveGroupNamesArray ) do self:Remove( RemoveGroupName ) @@ -1128,9 +1103,6 @@ do -- SET_GROUP return self end - - - --- Finds a Group based on the Group Name. -- @param #SET_GROUP self -- @param #string GroupName @@ -1148,7 +1120,7 @@ do -- SET_GROUP function SET_GROUP:FindNearestGroupFromPointVec2( PointVec2 ) self:F2( PointVec2 ) - local NearestGroup = nil --Wrapper.Group#GROUP + local NearestGroup = nil -- Wrapper.Group#GROUP local ClosestDistance = nil for ObjectID, ObjectData in pairs( self.Set ) do @@ -1167,7 +1139,6 @@ do -- SET_GROUP return NearestGroup end - --- Builds a set of groups in zones. -- @param #SET_GROUP self -- @param #table Zones Table of Core.Zone#ZONE Zone objects, or a Core.Set#SET_ZONE @@ -1179,13 +1150,13 @@ do -- SET_GROUP local zones = {} if Zones.ClassName and Zones.ClassName == "SET_ZONE" then zones = Zones.Set - elseif type( Zones ) ~= "table" or (type( Zones ) == "table" and Zones.ClassName ) then - self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!") - return self + elseif type( Zones ) ~= "table" or (type( Zones ) == "table" and Zones.ClassName) then + self:E( "***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!" ) + return self else zones = Zones end - for _,Zone in pairs( zones ) do + for _, Zone in pairs( zones ) do local zonename = Zone:GetName() self.Filter.Zones[zonename] = Zone end @@ -1210,7 +1181,6 @@ do -- SET_GROUP return self end - --- Builds a set of groups out of categories. -- Possible current categories are plane, helicopter, ground, ship. -- @param #SET_GROUP self @@ -1269,8 +1239,6 @@ do -- SET_GROUP return self end - - --- Builds a set of groups of defined countries. -- Possible current countries are those known within DCS world. -- @param #SET_GROUP self @@ -1289,7 +1257,6 @@ do -- SET_GROUP return self end - --- Builds a set of groups that contain the given string in their group name. -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all groups that **contain** the string. -- @param #SET_GROUP self @@ -1311,7 +1278,7 @@ do -- SET_GROUP --- Builds a set of groups that are only active. -- Only the groups that are active will be included within the set. -- @param #SET_GROUP self - -- @param #boolean Active (optional) Include only active groups to the set. + -- @param #boolean Active (Optional) Include only active groups to the set. -- Include inactive groups if you provide false. -- @return #SET_GROUP self -- @usage @@ -1329,12 +1296,11 @@ do -- SET_GROUP -- GroupSet = SET_GROUP:New():FilterActive( false ):FilterCoalition( "blue" ):FilterOnce() -- function SET_GROUP:FilterActive( Active ) - Active = Active or not ( Active == false ) + Active = Active or not (Active == false) self.Filter.Active = Active return self end - --- Starts the filtering. -- @param #SET_GROUP self -- @return #SET_GROUP self @@ -1348,8 +1314,6 @@ do -- SET_GROUP self:HandleEvent( EVENTS.RemoveUnit, self._EventOnDeadOrCrash ) end - - return self end @@ -1453,18 +1417,17 @@ do -- SET_GROUP -- @param #SET_GROUP self -- @param #number Delay Delay in seconds. -- @return #SET_GROUP self - function SET_GROUP:Activate(Delay) + function SET_GROUP:Activate( Delay ) local Set = self:GetSet() - for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP - local group=GroupData --Wrapper.Group#GROUP - if group and group:IsAlive()==false then - group:Activate(Delay) + for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP + local group = GroupData -- Wrapper.Group#GROUP + if group and group:IsAlive() == false then + group:Activate( Delay ) end end return self end - --- Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence completely in a @{Zone}, providing the GROUP and optional parameters to the called function. -- @param #SET_GROUP self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -1545,11 +1508,11 @@ do -- SET_GROUP -- else -- MESSAGE:New("Some or all SET's GROUP are outside zone !", 10):ToAll() -- end - function SET_GROUP:AllCompletelyInZone(Zone) - self:F2(Zone) + function SET_GROUP:AllCompletelyInZone( Zone ) + self:F2( Zone ) local Set = self:GetSet() - for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP - if not GroupData:IsCompletelyInZone(Zone) then + for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP + if not GroupData:IsCompletelyInZone( Zone ) then return false end end @@ -1578,7 +1541,6 @@ do -- SET_GROUP return self end - --- Iterate the SET_GROUP and return true if at least one of the @{Wrapper.Group#GROUP} is completely inside the @{Core.Zone#ZONE} -- @param #SET_GROUP self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -1593,11 +1555,11 @@ do -- SET_GROUP -- else -- MESSAGE:New("No GROUP is completely in zone !", 10):ToAll() -- end - function SET_GROUP:AnyCompletelyInZone(Zone) - self:F2(Zone) + function SET_GROUP:AnyCompletelyInZone( Zone ) + self:F2( Zone ) local Set = self:GetSet() - for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP - if GroupData:IsCompletelyInZone(Zone) then + for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP + if GroupData:IsCompletelyInZone( Zone ) then return true end end @@ -1618,11 +1580,11 @@ do -- SET_GROUP -- else -- MESSAGE:New("No UNIT of any GROUP is in zone !", 10):ToAll() -- end - function SET_GROUP:AnyInZone(Zone) - self:F2(Zone) + function SET_GROUP:AnyInZone( Zone ) + self:F2( Zone ) local Set = self:GetSet() - for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP - if GroupData:IsPartlyInZone(Zone) or GroupData:IsCompletelyInZone(Zone) then + for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP + if GroupData:IsPartlyInZone( Zone ) or GroupData:IsCompletelyInZone( Zone ) then return true end end @@ -1644,14 +1606,14 @@ do -- SET_GROUP -- else -- MESSAGE:New("No GROUP are in zone, or one (or more) GROUP is completely in it !", 10):ToAll() -- end - function SET_GROUP:AnyPartlyInZone(Zone) - self:F2(Zone) + function SET_GROUP:AnyPartlyInZone( Zone ) + self:F2( Zone ) local IsPartlyInZone = false local Set = self:GetSet() - for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP - if GroupData:IsCompletelyInZone(Zone) then + for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP + if GroupData:IsCompletelyInZone( Zone ) then return false - elseif GroupData:IsPartlyInZone(Zone) then + elseif GroupData:IsPartlyInZone( Zone ) then IsPartlyInZone = true -- at least one GROUP is partly in zone end end @@ -1679,11 +1641,11 @@ do -- SET_GROUP -- else -- MESSAGE:New("No UNIT of any GROUP is in zone !", 10):ToAll() -- end - function SET_GROUP:NoneInZone(Zone) - self:F2(Zone) + function SET_GROUP:NoneInZone( Zone ) + self:F2( Zone ) local Set = self:GetSet() - for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP - if not GroupData:IsNotInZone(Zone) then -- If the GROUP is in Zone in any way + for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP + if not GroupData:IsNotInZone( Zone ) then -- If the GROUP is in Zone in any way return false end end @@ -1702,12 +1664,12 @@ do -- SET_GROUP -- MySetGroup:AddGroupsByName({"Group1", "Group2"}) -- -- MESSAGE:New("There are " .. MySetGroup:CountInZone(MyZone) .. " GROUPs in the Zone !", 10):ToAll() - function SET_GROUP:CountInZone(Zone) - self:F2(Zone) + function SET_GROUP:CountInZone( Zone ) + self:F2( Zone ) local Count = 0 local Set = self:GetSet() - for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP - if GroupData:IsCompletelyInZone(Zone) then + for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP + if GroupData:IsCompletelyInZone( Zone ) then Count = Count + 1 end end @@ -1724,12 +1686,12 @@ do -- SET_GROUP -- MySetGroup:AddGroupsByName({"Group1", "Group2"}) -- -- MESSAGE:New("There are " .. MySetGroup:CountUnitInZone(MyZone) .. " UNITs in the Zone !", 10):ToAll() - function SET_GROUP:CountUnitInZone(Zone) - self:F2(Zone) + function SET_GROUP:CountUnitInZone( Zone ) + self:F2( Zone ) local Count = 0 local Set = self:GetSet() - for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP - Count = Count + GroupData:CountInZone(Zone) + for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP + Count = Count + GroupData:CountInZone( Zone ) end return Count end @@ -1744,50 +1706,49 @@ do -- SET_GROUP local Set = self:GetSet() - for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP + for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP if GroupData and GroupData:IsAlive() then CountG = CountG + 1 - --Count Units. - for _,_unit in pairs(GroupData:GetUnits()) do - local unit=_unit --Wrapper.Unit#UNIT + -- Count Units. + for _, _unit in pairs( GroupData:GetUnits() ) do + local unit = _unit -- Wrapper.Unit#UNIT if unit and unit:IsAlive() then - CountU=CountU+1 + CountU = CountU + 1 end end end end - return CountG,CountU + return CountG, CountU end ----- Iterate the SET_GROUP and call an iterator function for each **alive** player, providing the Group of the player and optional parameters. ---- @param #SET_GROUP self ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_GROUP. The function needs to accept a GROUP parameter. ---- @return #SET_GROUP self - --function SET_GROUP:ForEachPlayer( IteratorFunction, ... ) + -- function SET_GROUP:ForEachPlayer( IteratorFunction, ... ) -- self:F2( arg ) -- -- self:ForEach( IteratorFunction, arg, self.PlayersAlive ) -- -- return self - --end + -- end -- -- ----- Iterate the SET_GROUP and call an iterator function for each client, providing the Client to the function and optional parameters. ---- @param #SET_GROUP self ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_GROUP. The function needs to accept a CLIENT parameter. ---- @return #SET_GROUP self - --function SET_GROUP:ForEachClient( IteratorFunction, ... ) + -- function SET_GROUP:ForEachClient( IteratorFunction, ... ) -- self:F2( arg ) -- -- self:ForEach( IteratorFunction, arg, self.Clients ) -- -- return self - --end - + -- end --- -- @param #SET_GROUP self @@ -1800,7 +1761,7 @@ do -- SET_GROUP if self.Filter.Active ~= nil then local MGroupActive = false self:F( { Active = self.Filter.Active } ) - if self.Filter.Active == false or ( self.Filter.Active == true and MGroup:IsActive() == true ) then + if self.Filter.Active == false or (self.Filter.Active == true and MGroup:IsActive() == true) then MGroupActive = true end MGroupInclude = MGroupInclude and MGroupActive @@ -1843,29 +1804,28 @@ do -- SET_GROUP local MGroupPrefix = false for GroupPrefixId, GroupPrefix in pairs( self.Filter.GroupPrefixes ) do self:T3( { "Prefix:", string.find( MGroup:GetName(), GroupPrefix, 1 ), GroupPrefix } ) - if string.find( MGroup:GetName(), GroupPrefix:gsub ("-", "%%-"), 1 ) then + if string.find( MGroup:GetName(), GroupPrefix:gsub( "-", "%%-" ), 1 ) then MGroupPrefix = true end end MGroupInclude = MGroupInclude and MGroupPrefix end - + if self.Filter.Zones then local MGroupZone = false for ZoneName, Zone in pairs( self.Filter.Zones ) do self:T3( "Zone:", ZoneName ) - if MGroup:IsInZone(Zone) then + if MGroup:IsInZone( Zone ) then MGroupZone = true end end MGroupInclude = MGroupInclude and MGroupZone end - + self:T2( MGroupInclude ) return MGroupInclude end - --- Iterate the SET_GROUP and set for each unit the default cargo bay weight limit. -- Because within a group, the type of carriers can differ, each cargo bay weight limit is set on @{Wrapper.Unit} level. -- @param #SET_GROUP self @@ -1877,7 +1837,7 @@ do -- SET_GROUP local Set = self:GetSet() for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP for UnitName, UnitData in pairs( GroupData:GetUnits() ) do - --local UnitData = UnitData -- Wrapper.Unit#UNIT + -- local UnitData = UnitData -- Wrapper.Unit#UNIT UnitData:SetCargoBayWeightLimit() end end @@ -1885,7 +1845,6 @@ do -- SET_GROUP end - do -- SET_UNIT --- @type SET_UNIT @@ -2059,14 +2018,13 @@ do -- SET_UNIT return self end - --- Add UNIT(s) to SET_UNIT. -- @param #SET_UNIT self -- @param #string AddUnitNames A single name or an array of UNIT names. -- @return #SET_UNIT self function SET_UNIT:AddUnitsByName( AddUnitNames ) - local AddUnitNamesArray = ( type( AddUnitNames ) == "table" ) and AddUnitNames or { AddUnitNames } + local AddUnitNamesArray = (type( AddUnitNames ) == "table") and AddUnitNames or { AddUnitNames } self:T( AddUnitNamesArray ) for AddUnitID, AddUnitName in pairs( AddUnitNamesArray ) do @@ -2082,7 +2040,7 @@ do -- SET_UNIT -- @return Core.Set#SET_UNIT self function SET_UNIT:RemoveUnitsByName( RemoveUnitNames ) - local RemoveUnitNamesArray = ( type( RemoveUnitNames ) == "table" ) and RemoveUnitNames or { RemoveUnitNames } + local RemoveUnitNamesArray = (type( RemoveUnitNames ) == "table") and RemoveUnitNames or { RemoveUnitNames } for RemoveUnitID, RemoveUnitName in pairs( RemoveUnitNamesArray ) do self:Remove( RemoveUnitName ) @@ -2091,7 +2049,6 @@ do -- SET_UNIT return self end - --- Finds a Unit based on the Unit Name. -- @param #SET_UNIT self -- @param #string UnitName @@ -2102,8 +2059,6 @@ do -- SET_UNIT return UnitFound end - - --- Builds a set of units of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_UNIT self @@ -2121,7 +2076,6 @@ do -- SET_UNIT return self end - --- Builds a set of units out of categories. -- Possible current categories are plane, helicopter, ground, ship. -- @param #SET_UNIT self @@ -2140,7 +2094,6 @@ do -- SET_UNIT return self end - --- Builds a set of units of defined unit types. -- Possible current types are those types known within DCS world. -- @param #SET_UNIT self @@ -2159,7 +2112,6 @@ do -- SET_UNIT return self end - --- Builds a set of units of defined countries. -- Possible current countries are those known within DCS world. -- @param #SET_UNIT self @@ -2178,7 +2130,6 @@ do -- SET_UNIT return self end - --- Builds a set of UNITs that contain a given string in their unit name. -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all units that **contain** the string. -- @param #SET_UNIT self @@ -2196,7 +2147,7 @@ do -- SET_UNIT end return self end - + --- Builds a set of units in zones. -- @param #SET_UNIT self -- @param #table Zones Table of Core.Zone#ZONE Zone objects, or a Core.Set#SET_ZONE @@ -2208,23 +2159,23 @@ do -- SET_UNIT local zones = {} if Zones.ClassName and Zones.ClassName == "SET_ZONE" then zones = Zones.Set - elseif type( Zones ) ~= "table" or (type( Zones ) == "table" and Zones.ClassName ) then - self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!") - return self + elseif type( Zones ) ~= "table" or (type( Zones ) == "table" and Zones.ClassName) then + self:E( "***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!" ) + return self else zones = Zones end - for _,Zone in pairs( zones ) do + for _, Zone in pairs( zones ) do local zonename = Zone:GetName() self.Filter.Zones[zonename] = Zone end return self end - + --- Builds a set of units that are only active. -- Only the units that are active will be included within the set. -- @param #SET_UNIT self - -- @param #boolean Active (optional) Include only active units to the set. + -- @param #boolean Active (Optional) Include only active units to the set. -- Include inactive units if you provide false. -- @return #SET_UNIT self -- @usage @@ -2242,7 +2193,7 @@ do -- SET_UNIT -- UnitSet = SET_UNIT:New():FilterActive( false ):FilterCoalition( "blue" ):FilterOnce() -- function SET_UNIT:FilterActive( Active ) - Active = Active or not ( Active == false ) + Active = Active or not (Active == false) self.Filter.Active = Active return self end @@ -2281,7 +2232,7 @@ do -- SET_UNIT local Set = self:GetSet() local CountU = 0 - for UnitID, UnitData in pairs(Set) do -- For each GROUP in SET_GROUP + for UnitID, UnitData in pairs( Set ) do -- For each GROUP in SET_GROUP if UnitData and UnitData:IsAlive() then CountU = CountU + 1 end @@ -2307,8 +2258,6 @@ do -- SET_UNIT return self end - - --- Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- @param #SET_UNIT self @@ -2337,11 +2286,9 @@ do -- SET_UNIT function SET_UNIT:FindInDatabase( Event ) self:F2( { Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName], Event } ) - return Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName] end - do -- Is Zone methods --- Check if minimal one element of the SET_UNIT is in the Zone. @@ -2354,7 +2301,7 @@ do -- SET_UNIT local function EvaluateZone( ZoneUnit ) - local ZoneUnitName = ZoneUnit:GetName() + local ZoneUnitName = ZoneUnit:GetName() self:F( { ZoneUnitName = ZoneUnitName } ) if self:FindUnit( ZoneUnitName ) then IsPartiallyInZone = true @@ -2370,7 +2317,6 @@ do -- SET_UNIT return IsPartiallyInZone end - --- Check if no element of the SET_UNIT is in the Zone. -- @param #SET_UNIT self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -2381,7 +2327,7 @@ do -- SET_UNIT local function EvaluateZone( ZoneUnit ) - local ZoneUnitName = ZoneUnit:GetName() + local ZoneUnitName = ZoneUnit:GetName() if self:FindUnit( ZoneUnitName ) then IsNotInZone = false return false @@ -2397,7 +2343,6 @@ do -- SET_UNIT end - --- Iterate the SET_UNIT and call an iterator function for each **alive** UNIT, providing the UNIT and optional parameters. -- @param #SET_UNIT self -- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter. @@ -2410,16 +2355,12 @@ do -- SET_UNIT return self end - --- Get the SET of the SET_UNIT **sorted per Threat Level**. -- -- @param #SET_UNIT self -- @param #number FromThreatLevel The TreatLevel to start the evaluation **From** (this must be a value between 0 and 10). -- @param #number ToThreatLevel The TreatLevel to stop the evaluation **To** (this must be a value between 0 and 10). -- @return #SET_UNIT self - -- @usage - -- - -- function SET_UNIT:GetSetPerThreatLevel( FromThreatLevel, ToThreatLevel ) self:F2( arg ) @@ -2436,12 +2377,10 @@ do -- SET_UNIT self:F( { ThreatLevel = ThreatLevel, ThreatLevelSet = ThreatLevelSet[ThreatLevel].Set } ) end - local OrderedPerThreatLevelSet = {} local ThreatLevelIncrement = FromThreatLevel <= ToThreatLevel and 1 or -1 - for ThreatLevel = FromThreatLevel, ToThreatLevel, ThreatLevelIncrement do self:F( { ThreatLevel = ThreatLevel } ) local ThreatLevelItem = ThreatLevelSet[ThreatLevel] @@ -2457,7 +2396,6 @@ do -- SET_UNIT end - --- Iterate the SET_UNIT **sorted *per Threat Level** and call an iterator function for each **alive** UNIT, providing the UNIT and optional parameters. -- -- @param #SET_UNIT self @@ -2474,7 +2412,7 @@ do -- SET_UNIT -- end -- ) -- - function SET_UNIT:ForEachUnitPerThreatLevel( FromThreatLevel, ToThreatLevel, IteratorFunction, ... ) --R2.1 Threat Level implementation + function SET_UNIT:ForEachUnitPerThreatLevel( FromThreatLevel, ToThreatLevel, IteratorFunction, ... ) -- R2.1 Threat Level implementation self:F2( arg ) local ThreatLevelSet = {} @@ -2504,8 +2442,6 @@ do -- SET_UNIT return self end - - --- Iterate the SET_UNIT and call an iterator function for each **alive** UNIT presence completely in a @{Zone}, providing the UNIT and optional parameters to the called function. -- @param #SET_UNIT self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -2573,13 +2509,12 @@ do -- SET_UNIT end for UnitTypeID, UnitType in pairs( UnitTypes ) do - MT[#MT+1] = UnitType .. " of " .. UnitTypeID + MT[#MT + 1] = UnitType .. " of " .. UnitTypeID end return UnitTypes end - --- Returns a comma separated string of the unit types with a count in the @{Set}. -- @param #SET_UNIT self -- @return #string The unit types string @@ -2590,7 +2525,7 @@ do -- SET_UNIT local UnitTypes = self:GetUnitTypes() for UnitTypeID, UnitType in pairs( UnitTypes ) do - MT[#MT+1] = UnitType .. " of " .. UnitTypeID + MT[#MT + 1] = UnitType .. " of " .. UnitTypeID end return table.concat( MT, ", " ) @@ -2663,27 +2598,27 @@ do -- SET_UNIT local Unit = UnitData -- Wrapper.Unit#UNIT local Coordinate = Unit:GetCoordinate() - x1 = ( Coordinate.x < x1 ) and Coordinate.x or x1 - x2 = ( Coordinate.x > x2 ) and Coordinate.x or x2 - y1 = ( Coordinate.y < y1 ) and Coordinate.y or y1 - y2 = ( Coordinate.y > y2 ) and Coordinate.y or y2 - z1 = ( Coordinate.y < z1 ) and Coordinate.z or z1 - z2 = ( Coordinate.y > z2 ) and Coordinate.z or z2 + x1 = (Coordinate.x < x1) and Coordinate.x or x1 + x2 = (Coordinate.x > x2) and Coordinate.x or x2 + y1 = (Coordinate.y < y1) and Coordinate.y or y1 + y2 = (Coordinate.y > y2) and Coordinate.y or y2 + z1 = (Coordinate.y < z1) and Coordinate.z or z1 + z2 = (Coordinate.y > z2) and Coordinate.z or z2 local Velocity = Coordinate:GetVelocity() - if Velocity ~= 0 then - MaxVelocity = ( MaxVelocity < Velocity ) and Velocity or MaxVelocity + if Velocity ~= 0 then + MaxVelocity = (MaxVelocity < Velocity) and Velocity or MaxVelocity local Heading = Coordinate:GetHeading() - AvgHeading = AvgHeading and ( AvgHeading + Heading ) or Heading + AvgHeading = AvgHeading and (AvgHeading + Heading) or Heading MovingCount = MovingCount + 1 end end - AvgHeading = AvgHeading and ( AvgHeading / MovingCount ) + AvgHeading = AvgHeading and (AvgHeading / MovingCount) - Coordinate.x = ( x2 - x1 ) / 2 + x1 - Coordinate.y = ( y2 - y1 ) / 2 + y1 - Coordinate.z = ( z2 - z1 ) / 2 + z1 + Coordinate.x = (x2 - x1) / 2 + x1 + Coordinate.y = (y2 - y1) / 2 + y1 + Coordinate.z = (z2 - z1) / 2 + z1 Coordinate:SetHeading( AvgHeading ) Coordinate:SetVelocity( MaxVelocity ) @@ -2707,8 +2642,8 @@ do -- SET_UNIT local Coordinate = Unit:GetCoordinate() local Velocity = Coordinate:GetVelocity() - if Velocity ~= 0 then - MaxVelocity = ( MaxVelocity < Velocity ) and Velocity or MaxVelocity + if Velocity ~= 0 then + MaxVelocity = (MaxVelocity < Velocity) and Velocity or MaxVelocity end end @@ -2731,12 +2666,12 @@ do -- SET_UNIT local Coordinate = Unit:GetCoordinate() local Velocity = Coordinate:GetVelocity() - if Velocity ~= 0 then + if Velocity ~= 0 then local Heading = Coordinate:GetHeading() if HeadingSet == nil then HeadingSet = Heading else - local HeadingDiff = ( HeadingSet - Heading + 180 + 360 ) % 360 - 180 + local HeadingDiff = (HeadingSet - Heading + 180 + 360) % 360 - 180 HeadingDiff = math.abs( HeadingDiff ) if HeadingDiff > 5 then HeadingSet = nil @@ -2750,8 +2685,6 @@ do -- SET_UNIT end - - --- Returns if the @{Set} has targets having a radar (of a given type). -- @param #SET_UNIT self -- @param DCS#Unit.RadarType RadarType @@ -2760,7 +2693,7 @@ do -- SET_UNIT self:F2( RadarType ) local RadarCount = 0 - for UnitID, UnitData in pairs( self:GetSet()) do + for UnitID, UnitData in pairs( self:GetSet() ) do local UnitSensorTest = UnitData -- Wrapper.Unit#UNIT local HasSensors if RadarType then @@ -2768,7 +2701,7 @@ do -- SET_UNIT else HasSensors = UnitSensorTest:HasSensors( Unit.SensorType.RADAR ) end - self:T3(HasSensors) + self:T3( HasSensors ) if HasSensors then RadarCount = RadarCount + 1 end @@ -2784,14 +2717,14 @@ do -- SET_UNIT self:F2() local SEADCount = 0 - for UnitID, UnitData in pairs( self:GetSet()) do + for UnitID, UnitData in pairs( self:GetSet() ) do local UnitSEAD = UnitData -- Wrapper.Unit#UNIT if UnitSEAD:IsAlive() then local UnitSEADAttributes = UnitSEAD:GetDesc().attributes local HasSEAD = UnitSEAD:HasSEAD() - self:T3(HasSEAD) + self:T3( HasSEAD ) if HasSEAD then SEADCount = SEADCount + 1 end @@ -2808,7 +2741,7 @@ do -- SET_UNIT self:F2() local GroundUnitCount = 0 - for UnitID, UnitData in pairs( self:GetSet()) do + for UnitID, UnitData in pairs( self:GetSet() ) do local UnitTest = UnitData -- Wrapper.Unit#UNIT if UnitTest:IsGround() then GroundUnitCount = GroundUnitCount + 1 @@ -2842,7 +2775,7 @@ do -- SET_UNIT self:F2() local FriendlyUnitCount = 0 - for UnitID, UnitData in pairs( self:GetSet()) do + for UnitID, UnitData in pairs( self:GetSet() ) do local UnitTest = UnitData -- Wrapper.Unit#UNIT if UnitTest:IsFriendly( FriendlyCoalition ) then FriendlyUnitCount = FriendlyUnitCount + 1 @@ -2852,33 +2785,30 @@ do -- SET_UNIT return FriendlyUnitCount end - - ----- Iterate the SET_UNIT and call an iterator function for each **alive** player, providing the Unit of the player and optional parameters. ---- @param #SET_UNIT self ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_UNIT. The function needs to accept a UNIT parameter. ---- @return #SET_UNIT self - --function SET_UNIT:ForEachPlayer( IteratorFunction, ... ) + -- function SET_UNIT:ForEachPlayer( IteratorFunction, ... ) -- self:F2( arg ) -- -- self:ForEach( IteratorFunction, arg, self.PlayersAlive ) -- -- return self - --end + -- end -- -- ----- Iterate the SET_UNIT and call an iterator function for each client, providing the Client to the function and optional parameters. ---- @param #SET_UNIT self ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_UNIT. The function needs to accept a CLIENT parameter. ---- @return #SET_UNIT self - --function SET_UNIT:ForEachClient( IteratorFunction, ... ) + -- function SET_UNIT:ForEachClient( IteratorFunction, ... ) -- self:F2( arg ) -- -- self:ForEach( IteratorFunction, arg, self.Clients ) -- -- return self - --end - + -- end --- -- @param #SET_UNIT self @@ -2895,7 +2825,7 @@ do -- SET_UNIT if self.Filter.Active ~= nil then local MUnitActive = false - if self.Filter.Active == false or ( self.Filter.Active == true and MUnit:IsActive() == true ) then + if self.Filter.Active == false or (self.Filter.Active == true and MUnit:IsActive() == true) then MUnitActive = true end MUnitInclude = MUnitInclude and MUnitActive @@ -2979,26 +2909,25 @@ do -- SET_UNIT MUnitInclude = MUnitInclude and MUnitSEAD end end - + if self.Filter.Zones then local MGroupZone = false for ZoneName, Zone in pairs( self.Filter.Zones ) do self:T3( "Zone:", ZoneName ) - if MUnit:IsInZone(Zone) then + if MUnit:IsInZone( Zone ) then MGroupZone = true end end - MUnitInclude = MUnitInclude and MGroupZone + MUnitInclude = MUnitInclude and MGroupZone end - + self:T2( MUnitInclude ) return MUnitInclude end - --- Retrieve the type names of the @{Wrapper.Unit}s in the SET, delimited by an optional delimiter. -- @param #SET_UNIT self - -- @param #string Delimiter (optional) The delimiter, which is default a comma. + -- @param #string Delimiter (Optional) The delimiter, which is default a comma. -- @return #string The types of the @{Wrapper.Unit}s delimited. function SET_UNIT:GetTypeNames( Delimiter ) @@ -3029,16 +2958,13 @@ do -- SET_UNIT function SET_UNIT:SetCargoBayWeightLimit() local Set = self:GetSet() for UnitID, UnitData in pairs( Set ) do -- For each UNIT in SET_UNIT - --local UnitData = UnitData -- Wrapper.Unit#UNIT + -- local UnitData = UnitData -- Wrapper.Unit#UNIT UnitData:SetCargoBayWeightLimit() end end - - end - do -- SET_STATIC --- @type SET_STATIC @@ -3157,14 +3083,13 @@ do -- SET_STATIC return self end - --- Add STATIC(s) to SET_STATIC. -- @param #SET_STATIC self -- @param #string AddStaticNames A single name or an array of STATIC names. -- @return #SET_STATIC self function SET_STATIC:AddStaticsByName( AddStaticNames ) - local AddStaticNamesArray = ( type( AddStaticNames ) == "table" ) and AddStaticNames or { AddStaticNames } + local AddStaticNamesArray = (type( AddStaticNames ) == "table") and AddStaticNames or { AddStaticNames } self:T( AddStaticNamesArray ) for AddStaticID, AddStaticName in pairs( AddStaticNamesArray ) do @@ -3180,7 +3105,7 @@ do -- SET_STATIC -- @return self function SET_STATIC:RemoveStaticsByName( RemoveStaticNames ) - local RemoveStaticNamesArray = ( type( RemoveStaticNames ) == "table" ) and RemoveStaticNames or { RemoveStaticNames } + local RemoveStaticNamesArray = (type( RemoveStaticNames ) == "table") and RemoveStaticNames or { RemoveStaticNames } for RemoveStaticID, RemoveStaticName in pairs( RemoveStaticNamesArray ) do self:Remove( RemoveStaticName ) @@ -3189,7 +3114,6 @@ do -- SET_STATIC return self end - --- Finds a Static based on the Static Name. -- @param #SET_STATIC self -- @param #string StaticName @@ -3200,8 +3124,6 @@ do -- SET_STATIC return StaticFound end - - --- Builds a set of units of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_STATIC self @@ -3219,9 +3141,8 @@ do -- SET_STATIC end return self end - - - --- Builds a set of statics in zones. + + --- Builds a set of statics in zones. -- @param #SET_STATIC self -- @param #table Zones Table of Core.Zone#ZONE Zone objects, or a Core.Set#SET_ZONE -- @return #SET_STATIC self @@ -3232,13 +3153,13 @@ do -- SET_STATIC local zones = {} if Zones.ClassName and Zones.ClassName == "SET_ZONE" then zones = Zones.Set - elseif type( Zones ) ~= "table" or (type( Zones ) == "table" and Zones.ClassName ) then - self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!") - return self + elseif type( Zones ) ~= "table" or (type( Zones ) == "table" and Zones.ClassName) then + self:E( "***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!" ) + return self else zones = Zones end - for _,Zone in pairs( zones ) do + for _, Zone in pairs( zones ) do local zonename = Zone:GetName() self.Filter.Zones[zonename] = Zone end @@ -3263,7 +3184,6 @@ do -- SET_STATIC return self end - --- Builds a set of units of defined unit types. -- Possible current types are those types known within DCS world. -- @param #SET_STATIC self @@ -3282,7 +3202,6 @@ do -- SET_STATIC return self end - --- Builds a set of units of defined countries. -- Possible current countries are those known within DCS world. -- @param #SET_STATIC self @@ -3301,7 +3220,6 @@ do -- SET_STATIC return self end - --- Builds a set of STATICs that contain the given string in their name. -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all statics that **contain** the string. -- @param #SET_STATIC self @@ -3320,7 +3238,6 @@ do -- SET_STATIC return self end - --- Starts the filtering. -- @param #SET_STATIC self -- @return #SET_STATIC self @@ -3344,7 +3261,7 @@ do -- SET_STATIC local Set = self:GetSet() local CountU = 0 - for UnitID, UnitData in pairs(Set) do + for UnitID, UnitData in pairs( Set ) do if UnitData and UnitData:IsAlive() then CountU = CountU + 1 end @@ -3382,11 +3299,9 @@ do -- SET_STATIC function SET_STATIC:FindInDatabase( Event ) self:F2( { Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName], Event } ) - return Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName] end - do -- Is Zone methods --- Check if minimal one element of the SET_STATIC is in the Zone. @@ -3399,7 +3314,7 @@ do -- SET_STATIC local function EvaluateZone( ZoneStatic ) - local ZoneStaticName = ZoneStatic:GetName() + local ZoneStaticName = ZoneStatic:GetName() if self:FindStatic( ZoneStaticName ) then IsPartiallyInZone = true return false @@ -3411,7 +3326,6 @@ do -- SET_STATIC return IsPartiallyInZone end - --- Check if no element of the SET_STATIC is in the Zone. -- @param #SET_STATIC self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -3422,7 +3336,7 @@ do -- SET_STATIC local function EvaluateZone( ZoneStatic ) - local ZoneStaticName = ZoneStatic:GetName() + local ZoneStaticName = ZoneStatic:GetName() if self:FindStatic( ZoneStaticName ) then IsNotInZone = false return false @@ -3436,7 +3350,6 @@ do -- SET_STATIC return IsNotInZone end - --- Check if minimal one element of the SET_STATIC is in the Zone. -- @param #SET_STATIC self -- @param #function IteratorFunction The function that will be called when there is an alive STATIC in the SET_STATIC. The function needs to accept a STATIC parameter. @@ -3449,10 +3362,8 @@ do -- SET_STATIC return self end - end - --- Iterate the SET_STATIC and call an iterator function for each **alive** STATIC, providing the STATIC and optional parameters. -- @param #SET_STATIC self -- @param #function IteratorFunction The function that will be called when there is an alive STATIC in the SET_STATIC. The function needs to accept a STATIC parameter. @@ -3465,7 +3376,6 @@ do -- SET_STATIC return self end - --- Iterate the SET_STATIC and call an iterator function for each **alive** STATIC presence completely in a @{Zone}, providing the STATIC and optional parameters to the called function. -- @param #SET_STATIC self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -3533,13 +3443,12 @@ do -- SET_STATIC end for StaticTypeID, StaticType in pairs( StaticTypes ) do - MT[#MT+1] = StaticType .. " of " .. StaticTypeID + MT[#MT + 1] = StaticType .. " of " .. StaticTypeID end return StaticTypes end - --- Returns a comma separated string of the unit types with a count in the @{Set}. -- @param #SET_STATIC self -- @return #string The unit types string @@ -3550,7 +3459,7 @@ do -- SET_STATIC local StaticTypes = self:GetStaticTypes() for StaticTypeID, StaticType in pairs( StaticTypes ) do - MT[#MT+1] = StaticType .. " of " .. StaticTypeID + MT[#MT + 1] = StaticType .. " of " .. StaticTypeID end return table.concat( MT, ", " ) @@ -3578,27 +3487,27 @@ do -- SET_STATIC local Static = StaticData -- Wrapper.Static#STATIC local Coordinate = Static:GetCoordinate() - x1 = ( Coordinate.x < x1 ) and Coordinate.x or x1 - x2 = ( Coordinate.x > x2 ) and Coordinate.x or x2 - y1 = ( Coordinate.y < y1 ) and Coordinate.y or y1 - y2 = ( Coordinate.y > y2 ) and Coordinate.y or y2 - z1 = ( Coordinate.y < z1 ) and Coordinate.z or z1 - z2 = ( Coordinate.y > z2 ) and Coordinate.z or z2 + x1 = (Coordinate.x < x1) and Coordinate.x or x1 + x2 = (Coordinate.x > x2) and Coordinate.x or x2 + y1 = (Coordinate.y < y1) and Coordinate.y or y1 + y2 = (Coordinate.y > y2) and Coordinate.y or y2 + z1 = (Coordinate.y < z1) and Coordinate.z or z1 + z2 = (Coordinate.y > z2) and Coordinate.z or z2 local Velocity = Coordinate:GetVelocity() - if Velocity ~= 0 then - MaxVelocity = ( MaxVelocity < Velocity ) and Velocity or MaxVelocity + if Velocity ~= 0 then + MaxVelocity = (MaxVelocity < Velocity) and Velocity or MaxVelocity local Heading = Coordinate:GetHeading() - AvgHeading = AvgHeading and ( AvgHeading + Heading ) or Heading + AvgHeading = AvgHeading and (AvgHeading + Heading) or Heading MovingCount = MovingCount + 1 end end - AvgHeading = AvgHeading and ( AvgHeading / MovingCount ) + AvgHeading = AvgHeading and (AvgHeading / MovingCount) - Coordinate.x = ( x2 - x1 ) / 2 + x1 - Coordinate.y = ( y2 - y1 ) / 2 + y1 - Coordinate.z = ( z2 - z1 ) / 2 + z1 + Coordinate.x = (x2 - x1) / 2 + x1 + Coordinate.y = (y2 - y1) / 2 + y1 + Coordinate.z = (z2 - z1) / 2 + z1 Coordinate:SetHeading( AvgHeading ) Coordinate:SetVelocity( MaxVelocity ) @@ -3630,12 +3539,12 @@ do -- SET_STATIC local Coordinate = Static:GetCoordinate() local Velocity = Coordinate:GetVelocity() - if Velocity ~= 0 then + if Velocity ~= 0 then local Heading = Coordinate:GetHeading() if HeadingSet == nil then HeadingSet = Heading else - local HeadingDiff = ( HeadingSet - Heading + 180 + 360 ) % 360 - 180 + local HeadingDiff = (HeadingSet - Heading + 180 + 360) % 360 - 180 HeadingDiff = math.abs( HeadingDiff ) if HeadingDiff > 5 then HeadingSet = nil @@ -3654,19 +3563,19 @@ do -- SET_STATIC -- @return #number The maximum threatlevel function SET_STATIC:CalculateThreatLevelA2G() - local MaxThreatLevelA2G = 0 - local MaxThreatText = "" - for StaticName, StaticData in pairs( self:GetSet() ) do - local ThreatStatic = StaticData -- Wrapper.Static#STATIC - local ThreatLevelA2G, ThreatText = ThreatStatic:GetThreatLevel() - if ThreatLevelA2G > MaxThreatLevelA2G then - MaxThreatLevelA2G = ThreatLevelA2G - MaxThreatText = ThreatText + local MaxThreatLevelA2G = 0 + local MaxThreatText = "" + for StaticName, StaticData in pairs( self:GetSet() ) do + local ThreatStatic = StaticData -- Wrapper.Static#STATIC + local ThreatLevelA2G, ThreatText = ThreatStatic:GetThreatLevel() + if ThreatLevelA2G > MaxThreatLevelA2G then + MaxThreatLevelA2G = ThreatLevelA2G + MaxThreatText = ThreatText + end end - end - self:F( { MaxThreatLevelA2G = MaxThreatLevelA2G, MaxThreatText = MaxThreatText } ) - return MaxThreatLevelA2G, MaxThreatText + self:F( { MaxThreatLevelA2G = MaxThreatLevelA2G, MaxThreatText = MaxThreatText } ) + return MaxThreatLevelA2G, MaxThreatText end @@ -3732,26 +3641,25 @@ do -- SET_STATIC end MStaticInclude = MStaticInclude and MStaticPrefix end - + if self.Filter.Zones then local MStaticZone = false for ZoneName, Zone in pairs( self.Filter.Zones ) do self:T3( "Zone:", ZoneName ) - if MStatic and MStatic:IsInZone(Zone) then + if MStatic and MStatic:IsInZone( Zone ) then MStaticZone = true end end MStaticInclude = MStaticInclude and MStaticZone end - + self:T2( MStaticInclude ) return MStaticInclude end - --- Retrieve the type names of the @{Static}s in the SET, delimited by an optional delimiter. -- @param #SET_STATIC self - -- @param #string Delimiter (optional) The delimiter, which is default a comma. + -- @param #string Delimiter (Optional) The delimiter, which is default a comma. -- @return #string The types of the @{Static}s delimited. function SET_STATIC:GetTypeNames( Delimiter ) @@ -3775,15 +3683,11 @@ do -- SET_STATIC end - do -- SET_CLIENT - --- @type SET_CLIENT -- @extends Core.Set#SET_BASE - - --- Mission designers can use the @{Core.Set#SET_CLIENT} class to build sets of units belonging to certain: -- -- * Coalitions @@ -3880,7 +3784,7 @@ do -- SET_CLIENT -- @return self function SET_CLIENT:AddClientsByName( AddClientNames ) - local AddClientNamesArray = ( type( AddClientNames ) == "table" ) and AddClientNames or { AddClientNames } + local AddClientNamesArray = (type( AddClientNames ) == "table") and AddClientNames or { AddClientNames } for AddClientID, AddClientName in pairs( AddClientNamesArray ) do self:Add( AddClientName, CLIENT:FindByName( AddClientName ) ) @@ -3895,7 +3799,7 @@ do -- SET_CLIENT -- @return self function SET_CLIENT:RemoveClientsByName( RemoveClientNames ) - local RemoveClientNamesArray = ( type( RemoveClientNames ) == "table" ) and RemoveClientNames or { RemoveClientNames } + local RemoveClientNamesArray = (type( RemoveClientNames ) == "table") and RemoveClientNames or { RemoveClientNames } for RemoveClientID, RemoveClientName in pairs( RemoveClientNamesArray ) do self:Remove( RemoveClientName.ClientName ) @@ -3904,7 +3808,6 @@ do -- SET_CLIENT return self end - --- Finds a Client based on the Client Name. -- @param #SET_CLIENT self -- @param #string ClientName @@ -3915,8 +3818,6 @@ do -- SET_CLIENT return ClientFound end - - --- Builds a set of clients of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_CLIENT self @@ -3935,7 +3836,6 @@ do -- SET_CLIENT return self end - --- Builds a set of clients out of categories. -- Possible current categories are plane, helicopter, ground, ship. -- @param #SET_CLIENT self @@ -3954,7 +3854,6 @@ do -- SET_CLIENT return self end - --- Builds a set of clients of defined client types. -- Possible current types are those types known within DCS world. -- @param #SET_CLIENT self @@ -3973,7 +3872,6 @@ do -- SET_CLIENT return self end - --- Builds a set of clients of defined countries. -- Possible current countries are those known within DCS world. -- @param #SET_CLIENT self @@ -3992,7 +3890,6 @@ do -- SET_CLIENT return self end - --- Builds a set of CLIENTs that contain the given string in their unit/pilot name. -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all clients that **contain** the string. -- @param #SET_CLIENT self @@ -4014,7 +3911,7 @@ do -- SET_CLIENT --- Builds a set of clients that are only active. -- Only the clients that are active will be included within the set. -- @param #SET_CLIENT self - -- @param #boolean Active (optional) Include only active clients to the set. + -- @param #boolean Active (Optional) Include only active clients to the set. -- Include inactive clients if you provide false. -- @return #SET_CLIENT self -- @usage @@ -4032,12 +3929,12 @@ do -- SET_CLIENT -- ClientSet = SET_CLIENT:New():FilterActive( false ):FilterCoalition( "blue" ):FilterOnce() -- function SET_CLIENT:FilterActive( Active ) - Active = Active or not ( Active == false ) + Active = Active or not (Active == false) self.Filter.Active = Active return self end - --- Builds a set of clients in zones. + --- Builds a set of clients in zones. -- @param #SET_CLIENT self -- @param #table Zones Table of Core.Zone#ZONE Zone objects, or a Core.Set#SET_ZONE -- @return #SET_TABLE self @@ -4048,13 +3945,13 @@ do -- SET_CLIENT local zones = {} if Zones.ClassName and Zones.ClassName == "SET_ZONE" then zones = Zones.Set - elseif type( Zones ) ~= "table" or (type( Zones ) == "table" and Zones.ClassName ) then - self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!") - return self + elseif type( Zones ) ~= "table" or (type( Zones ) == "table" and Zones.ClassName) then + self:E( "***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!" ) + return self else zones = Zones end - for _,Zone in pairs( zones ) do + for _, Zone in pairs( zones ) do local zonename = Zone:GetName() self.Filter.Zones[zonename] = Zone end @@ -4155,7 +4052,7 @@ do -- SET_CLIENT return self end - + --- Iterate the SET_CLIENT and count alive units. -- @param #SET_CLIENT self -- @return #number count @@ -4164,7 +4061,7 @@ do -- SET_CLIENT local Set = self:GetSet() local CountU = 0 - for UnitID, UnitData in pairs(Set) do -- For each GROUP in SET_GROUP + for UnitID, UnitData in pairs( Set ) do -- For each GROUP in SET_GROUP if UnitData and UnitData:IsAlive() then CountU = CountU + 1 end @@ -4173,7 +4070,7 @@ do -- SET_CLIENT return CountU end - + --- -- @param #SET_CLIENT self -- @param Wrapper.Client#CLIENT MClient @@ -4188,7 +4085,7 @@ do -- SET_CLIENT if self.Filter.Active ~= nil then local MClientActive = false - if self.Filter.Active == false or ( self.Filter.Active == true and MClient:IsActive() == true ) then + if self.Filter.Active == false or (self.Filter.Active == true and MClient:IsActive() == true) then MClientActive = true end MClientInclude = MClientInclude and MClientActive @@ -4235,7 +4132,7 @@ do -- SET_CLIENT if self.Filter.Countries then local MClientCountry = false for CountryID, CountryName in pairs( self.Filter.Countries ) do - local ClientCountryID = _DATABASE:GetCountryFromClientTemplate(MClientName) + 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 @@ -4263,27 +4160,24 @@ do -- SET_CLIENT for ZoneName, Zone in pairs( self.Filter.Zones ) do self:T3( "Zone:", ZoneName ) local unit = MClient:GetClientGroupUnit() - if unit and unit:IsInZone(Zone) then + if unit and unit:IsInZone( Zone ) then MClientZone = true end end MClientInclude = MClientInclude and MClientZone end - + self:T2( MClientInclude ) return MClientInclude end end - do -- SET_PLAYER --- @type SET_PLAYER -- @extends Core.Set#SET_BASE - - --- Mission designers can use the @{Core.Set#SET_PLAYER} class to build sets of units belonging to alive players: -- -- ## SET_PLAYER constructor @@ -4348,7 +4242,6 @@ do -- SET_PLAYER }, } - --- Creates a new SET_PLAYER object, building a set of clients belonging to a coalitions, categories, countries, types or with defined prefix names. -- @param #SET_PLAYER self -- @return #SET_PLAYER @@ -4368,7 +4261,7 @@ do -- SET_PLAYER -- @return self function SET_PLAYER:AddClientsByName( AddClientNames ) - local AddClientNamesArray = ( type( AddClientNames ) == "table" ) and AddClientNames or { AddClientNames } + local AddClientNamesArray = (type( AddClientNames ) == "table") and AddClientNames or { AddClientNames } for AddClientID, AddClientName in pairs( AddClientNamesArray ) do self:Add( AddClientName, CLIENT:FindByName( AddClientName ) ) @@ -4383,7 +4276,7 @@ do -- SET_PLAYER -- @return self function SET_PLAYER:RemoveClientsByName( RemoveClientNames ) - local RemoveClientNamesArray = ( type( RemoveClientNames ) == "table" ) and RemoveClientNames or { RemoveClientNames } + local RemoveClientNamesArray = (type( RemoveClientNames ) == "table") and RemoveClientNames or { RemoveClientNames } for RemoveClientID, RemoveClientName in pairs( RemoveClientNamesArray ) do self:Remove( RemoveClientName.ClientName ) @@ -4392,7 +4285,6 @@ do -- SET_PLAYER return self end - --- Finds a Client based on the Player Name. -- @param #SET_PLAYER self -- @param #string PlayerName @@ -4403,8 +4295,6 @@ do -- SET_PLAYER return ClientFound end - - --- Builds a set of clients of coalitions joined by specific players. -- Possible current coalitions are red, blue and neutral. -- @param #SET_PLAYER self @@ -4422,7 +4312,7 @@ do -- SET_PLAYER end return self end - + --- Builds a set of players in zones. -- @param #SET_PLAYER self -- @param #table Zones Table of Core.Zone#ZONE Zone objects, or a Core.Set#SET_ZONE @@ -4434,19 +4324,18 @@ do -- SET_PLAYER local zones = {} if Zones.ClassName and Zones.ClassName == "SET_ZONE" then zones = Zones.Set - elseif type( Zones ) ~= "table" or (type( Zones ) == "table" and Zones.ClassName ) then - self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!") - return self + elseif type( Zones ) ~= "table" or (type( Zones ) == "table" and Zones.ClassName) then + self:E( "***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!" ) + return self else zones = Zones end - for _,Zone in pairs( zones ) do + for _, Zone in pairs( zones ) do local zonename = Zone:GetName() self.Filter.Zones[zonename] = Zone end return self end - --- Builds a set of clients out of categories joined by players. -- Possible current categories are plane, helicopter, ground, ship. @@ -4466,7 +4355,6 @@ do -- SET_PLAYER return self end - --- Builds a set of clients of defined client types joined by players. -- Possible current types are those types known within DCS world. -- @param #SET_PLAYER self @@ -4485,7 +4373,6 @@ do -- SET_PLAYER return self end - --- Builds a set of clients of defined countries. -- Possible current countries are those known within DCS world. -- @param #SET_PLAYER self @@ -4504,7 +4391,6 @@ do -- SET_PLAYER return self end - --- Builds a set of PLAYERs that contain the given string in their unit/pilot name. -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all player clients that **contain** the string. -- @param #SET_PLAYER self @@ -4523,9 +4409,6 @@ do -- SET_PLAYER return self end - - - --- Starts the filtering. -- @param #SET_PLAYER self -- @return #SET_PLAYER self @@ -4674,7 +4557,7 @@ do -- SET_PLAYER if self.Filter.Countries then local MClientCountry = false for CountryID, CountryName in pairs( self.Filter.Countries ) do - local ClientCountryID = _DATABASE:GetCountryFromClientTemplate(MClientName) + 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 @@ -4696,26 +4579,25 @@ do -- SET_PLAYER MClientInclude = MClientInclude and MClientPrefix end end - + if self.Filter.Zones then local MClientZone = false for ZoneName, Zone in pairs( self.Filter.Zones ) do self:T3( "Zone:", ZoneName ) local unit = MClient:GetClientGroupUnit() - if unit and unit:IsInZone(Zone) then + if unit and unit:IsInZone( Zone ) then MClientZone = true end end MClientInclude = MClientInclude and MClientZone end - + self:T2( MClientInclude ) return MClientInclude end end - do -- SET_AIRBASE --- @type SET_AIRBASE @@ -4777,7 +4659,6 @@ do -- SET_AIRBASE }, } - --- Creates a new SET_AIRBASE object, building a set of airbases belonging to a coalitions and categories. -- @param #SET_AIRBASE self -- @return #SET_AIRBASE self @@ -4808,7 +4689,7 @@ do -- SET_AIRBASE -- @return self function SET_AIRBASE:AddAirbasesByName( AddAirbaseNames ) - local AddAirbaseNamesArray = ( type( AddAirbaseNames ) == "table" ) and AddAirbaseNames or { AddAirbaseNames } + local AddAirbaseNamesArray = (type( AddAirbaseNames ) == "table") and AddAirbaseNames or { AddAirbaseNames } for AddAirbaseID, AddAirbaseName in pairs( AddAirbaseNamesArray ) do self:Add( AddAirbaseName, AIRBASE:FindByName( AddAirbaseName ) ) @@ -4823,7 +4704,7 @@ do -- SET_AIRBASE -- @return self function SET_AIRBASE:RemoveAirbasesByName( RemoveAirbaseNames ) - local RemoveAirbaseNamesArray = ( type( RemoveAirbaseNames ) == "table" ) and RemoveAirbaseNames or { RemoveAirbaseNames } + local RemoveAirbaseNamesArray = (type( RemoveAirbaseNames ) == "table") and RemoveAirbaseNames or { RemoveAirbaseNames } for RemoveAirbaseID, RemoveAirbaseName in pairs( RemoveAirbaseNamesArray ) do self:Remove( RemoveAirbaseName ) @@ -4832,7 +4713,6 @@ do -- SET_AIRBASE return self end - --- Finds a Airbase based on the Airbase Name. -- @param #SET_AIRBASE self -- @param #string AirbaseName @@ -4843,7 +4723,6 @@ do -- SET_AIRBASE return AirbaseFound end - --- Finds an Airbase in range of a coordinate. -- @param #SET_AIRBASE self -- @param Core.Point#COORDINATE Coordinate @@ -4858,7 +4737,7 @@ do -- SET_AIRBASE local AirbaseCoordinate = AirbaseObject:GetCoordinate() local Distance = Coordinate:Get2DDistance( AirbaseCoordinate ) - self:F({Distance=Distance}) + self:F( { Distance = Distance } ) if Distance <= Range then AirbaseFound = AirbaseObject @@ -4870,7 +4749,6 @@ do -- SET_AIRBASE return AirbaseFound end - --- Finds a random Airbase in the set. -- @param #SET_AIRBASE self -- @return Wrapper.Airbase#AIRBASE The found Airbase. @@ -4882,8 +4760,6 @@ do -- SET_AIRBASE return RandomAirbase end - - --- Builds a set of airbases of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_AIRBASE self @@ -4902,7 +4778,6 @@ do -- SET_AIRBASE return self end - --- Builds a set of airbases out of categories. -- Possible current categories are plane, helicopter, ground, ship. -- @param #SET_AIRBASE self @@ -4929,8 +4804,8 @@ do -- SET_AIRBASE if _DATABASE then -- We use the BaseCaptured event, which is generated by DCS when a base got captured. - self:HandleEvent(EVENTS.BaseCaptured) - self:HandleEvent(EVENTS.Dead) + self:HandleEvent( EVENTS.BaseCaptured ) + self:HandleEvent( EVENTS.Dead ) -- We initialize the first set. for ObjectName, Object in pairs( self.Database ) do @@ -4948,7 +4823,7 @@ do -- SET_AIRBASE --- Base capturing event. -- @param #SET_AIRBASE self -- @param Core.Event#EVENT EventData - function SET_AIRBASE:OnEventBaseCaptured(EventData) + function SET_AIRBASE:OnEventBaseCaptured( EventData ) -- When a base got captured, we reevaluate the set. for ObjectName, Object in pairs( self.Database ) do @@ -4966,17 +4841,16 @@ do -- SET_AIRBASE --- Dead event. -- @param #SET_AIRBASE self -- @param Core.Event#EVENT EventData - function SET_AIRBASE:OnEventDead(EventData) + function SET_AIRBASE:OnEventDead( EventData ) - local airbaseName, airbase=self:FindInDatabase(EventData) + local airbaseName, airbase = self:FindInDatabase( EventData ) if airbase and (airbase:IsShip() or airbase:IsHelipad()) then - self:RemoveAirbasesByName(airbaseName) + self:RemoveAirbasesByName( airbaseName ) end end - --- Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- @param #SET_AIRBASE self @@ -5022,8 +4896,6 @@ do -- SET_AIRBASE return NearestAirbase end - - --- -- @param #SET_AIRBASE self -- @param Wrapper.Airbase#AIRBASE MAirbase @@ -5069,7 +4941,6 @@ do -- SET_AIRBASE end - do -- SET_CARGO --- @type SET_CARGO @@ -5134,40 +5005,37 @@ do -- SET_CARGO }, } - --- Creates a new SET_CARGO object, building a set of cargos belonging to a coalitions and categories. -- @param #SET_CARGO self -- @return #SET_CARGO -- @usage -- -- Define a new SET_CARGO Object. The DatabaseSet will contain a reference to all Cargos. -- DatabaseSet = SET_CARGO:New() - function SET_CARGO:New() --R2.1 + function SET_CARGO:New() -- R2.1 -- Inherits from BASE local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.CARGOS ) ) -- #SET_CARGO return self end - --- (R2.1) Add CARGO to SET_CARGO. -- @param Core.Set#SET_CARGO self -- @param Cargo.Cargo#CARGO Cargo A single cargo. -- @return Core.Set#SET_CARGO self - function SET_CARGO:AddCargo( Cargo ) --R2.4 + function SET_CARGO:AddCargo( Cargo ) -- R2.4 self:Add( Cargo:GetName(), Cargo ) return self end - --- (R2.1) Add CARGOs to SET_CARGO. -- @param Core.Set#SET_CARGO self -- @param #string AddCargoNames A single name or an array of CARGO names. -- @return Core.Set#SET_CARGO self - function SET_CARGO:AddCargosByName( AddCargoNames ) --R2.1 + function SET_CARGO:AddCargosByName( AddCargoNames ) -- R2.1 - local AddCargoNamesArray = ( type( AddCargoNames ) == "table" ) and AddCargoNames or { AddCargoNames } + local AddCargoNamesArray = (type( AddCargoNames ) == "table") and AddCargoNames or { AddCargoNames } for AddCargoID, AddCargoName in pairs( AddCargoNamesArray ) do self:Add( AddCargoName, CARGO:FindByName( AddCargoName ) ) @@ -5180,9 +5048,9 @@ do -- SET_CARGO -- @param Core.Set#SET_CARGO self -- @param Wrapper.Cargo#CARGO RemoveCargoNames A single name or an array of CARGO names. -- @return Core.Set#SET_CARGO self - function SET_CARGO:RemoveCargosByName( RemoveCargoNames ) --R2.1 + function SET_CARGO:RemoveCargosByName( RemoveCargoNames ) -- R2.1 - local RemoveCargoNamesArray = ( type( RemoveCargoNames ) == "table" ) and RemoveCargoNames or { RemoveCargoNames } + local RemoveCargoNamesArray = (type( RemoveCargoNames ) == "table") and RemoveCargoNames or { RemoveCargoNames } for RemoveCargoID, RemoveCargoName in pairs( RemoveCargoNamesArray ) do self:Remove( RemoveCargoName.CargoName ) @@ -5191,25 +5059,22 @@ do -- SET_CARGO return self end - --- (R2.1) Finds a Cargo based on the Cargo Name. -- @param #SET_CARGO self -- @param #string CargoName -- @return Wrapper.Cargo#CARGO The found Cargo. - function SET_CARGO:FindCargo( CargoName ) --R2.1 + function SET_CARGO:FindCargo( CargoName ) -- R2.1 local CargoFound = self.Set[CargoName] return CargoFound end - - --- (R2.1) Builds a set of cargos of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_CARGO self -- @param #string Coalitions Can take the following values: "red", "blue", "neutral". -- @return #SET_CARGO self - function SET_CARGO:FilterCoalitions( Coalitions ) --R2.1 + function SET_CARGO:FilterCoalitions( Coalitions ) -- R2.1 if not self.Filter.Coalitions then self.Filter.Coalitions = {} end @@ -5227,7 +5092,7 @@ do -- SET_CARGO -- @param #SET_CARGO self -- @param #string Types Can take those type strings known within DCS world. -- @return #SET_CARGO self - function SET_CARGO:FilterTypes( Types ) --R2.1 + function SET_CARGO:FilterTypes( Types ) -- R2.1 if not self.Filter.Types then self.Filter.Types = {} end @@ -5240,13 +5105,12 @@ do -- SET_CARGO return self end - --- (R2.1) Builds a set of cargos of defined countries. -- Possible current countries are those known within DCS world. -- @param #SET_CARGO self -- @param #string Countries Can take those country strings known within DCS world. -- @return #SET_CARGO self - function SET_CARGO:FilterCountries( Countries ) --R2.1 + function SET_CARGO:FilterCountries( Countries ) -- R2.1 if not self.Filter.Countries then self.Filter.Countries = {} end @@ -5259,13 +5123,12 @@ do -- SET_CARGO return self end - --- Builds a set of CARGOs that contain a given string in their name. -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all cargos that **contain** the string. -- @param #SET_CARGO self -- @param #string Prefixes The string pattern(s) that need to be in the cargo name. Can also be passed as a `#table` of strings. -- @return #SET_CARGO self - function SET_CARGO:FilterPrefixes( Prefixes ) --R2.1 + function SET_CARGO:FilterPrefixes( Prefixes ) -- R2.1 if not self.Filter.CargoPrefixes then self.Filter.CargoPrefixes = {} end @@ -5278,12 +5141,10 @@ do -- SET_CARGO return self end - - --- (R2.1) Starts the filtering. -- @param #SET_CARGO self -- @return #SET_CARGO self - function SET_CARGO:FilterStart() --R2.1 + function SET_CARGO:FilterStart() -- R2.1 if _DATABASE then self:_FilterStart() @@ -5305,14 +5166,13 @@ do -- SET_CARGO return self end - --- (R2.1) Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- @param #SET_CARGO self -- @param Core.Event#EVENTDATA Event -- @return #string The name of the CARGO -- @return #table The CARGO - function SET_CARGO:AddInDatabase( Event ) --R2.1 + function SET_CARGO:AddInDatabase( Event ) -- R2.1 self:F3( { Event } ) return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] @@ -5324,7 +5184,7 @@ do -- SET_CARGO -- @param Core.Event#EVENTDATA Event -- @return #string The name of the CARGO -- @return #table The CARGO - function SET_CARGO:FindInDatabase( Event ) --R2.1 + function SET_CARGO:FindInDatabase( Event ) -- R2.1 self:F3( { Event } ) return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] @@ -5334,7 +5194,7 @@ do -- SET_CARGO -- @param #SET_CARGO self -- @param #function IteratorFunction The function that will be called when there is an alive CARGO in the SET_CARGO. The function needs to accept a CARGO parameter. -- @return #SET_CARGO self - function SET_CARGO:ForEachCargo( IteratorFunction, ... ) --R2.1 + function SET_CARGO:ForEachCargo( IteratorFunction, ... ) -- R2.1 self:F2( arg ) self:ForEach( IteratorFunction, arg, self:GetSet() ) @@ -5346,7 +5206,7 @@ do -- SET_CARGO -- @param #SET_CARGO self -- @param Core.Point#POINT_VEC2 PointVec2 A @{Core.Point#POINT_VEC2} object from where to evaluate the closest @{Cargo.Cargo#CARGO}. -- @return Wrapper.Cargo#CARGO The closest @{Cargo.Cargo#CARGO}. - function SET_CARGO:FindNearestCargoFromPointVec2( PointVec2 ) --R2.1 + function SET_CARGO:FindNearestCargoFromPointVec2( PointVec2 ) -- R2.1 self:F2( PointVec2 ) local NearestCargo = self:FindNearestObjectFromPointVec2( PointVec2 ) @@ -5381,7 +5241,6 @@ do -- SET_CARGO return FirstCargo end - --- Iterate the SET_CARGO while identifying the first @{Cargo.Cargo#CARGO} that is UnLoaded. -- @param #SET_CARGO self -- @return Cargo.Cargo#CARGO The first @{Cargo.Cargo#CARGO}. @@ -5390,7 +5249,6 @@ do -- SET_CARGO return FirstCargo end - --- Iterate the SET_CARGO while identifying the first @{Cargo.Cargo#CARGO} that is UnLoaded and not Deployed. -- @param #SET_CARGO self -- @return Cargo.Cargo#CARGO The first @{Cargo.Cargo#CARGO}. @@ -5399,7 +5257,6 @@ do -- SET_CARGO return FirstCargo end - --- Iterate the SET_CARGO while identifying the first @{Cargo.Cargo#CARGO} that is Loaded. -- @param #SET_CARGO self -- @return Cargo.Cargo#CARGO The first @{Cargo.Cargo#CARGO}. @@ -5408,7 +5265,6 @@ do -- SET_CARGO return FirstCargo end - --- Iterate the SET_CARGO while identifying the first @{Cargo.Cargo#CARGO} that is Deployed. -- @param #SET_CARGO self -- @return Cargo.Cargo#CARGO The first @{Cargo.Cargo#CARGO}. @@ -5417,14 +5273,11 @@ do -- SET_CARGO return FirstCargo end - - - --- (R2.1) -- @param #SET_CARGO self -- @param AI.AI_Cargo#AI_CARGO MCargo -- @return #SET_CARGO self - function SET_CARGO:IsIncludeObject( MCargo ) --R2.1 + function SET_CARGO:IsIncludeObject( MCargo ) -- R2.1 self:F2( MCargo ) local MCargoInclude = true @@ -5477,13 +5330,13 @@ do -- SET_CARGO --- (R2.1) Handles the OnEventNewCargo event for the Set. -- @param #SET_CARGO self -- @param Core.Event#EVENTDATA EventData - function SET_CARGO:OnEventNewCargo( EventData ) --R2.1 + function SET_CARGO:OnEventNewCargo( EventData ) -- R2.1 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 ) + self:Add( EventData.Cargo.Name, EventData.Cargo ) end end end @@ -5491,20 +5344,20 @@ do -- SET_CARGO --- (R2.1) Handles the OnDead or OnCrash event for alive units set. -- @param #SET_CARGO self -- @param Core.Event#EVENTDATA EventData - function SET_CARGO:OnEventDeleteCargo( EventData ) --R2.1 + function SET_CARGO:OnEventDeleteCargo( EventData ) -- R2.1 self:F3( { EventData } ) if EventData.Cargo then local Cargo = _DATABASE:FindCargo( EventData.Cargo.Name ) if Cargo and Cargo.Name then - -- When cargo was deleted, it may probably be because of an S_EVENT_DEAD. - -- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call. - -- And this is a problem because it will remove all entries from the SET_CARGOs. - -- To prevent this from happening, the Cargo object has a flag NoDestroy. - -- When true, the SET_CARGO won't Remove the Cargo object from the set. - -- This flag is switched off after the event handlers have been called in the EVENT class. - self:F( { CargoNoDestroy=Cargo.NoDestroy } ) + -- When cargo was deleted, it may probably be because of an S_EVENT_DEAD. + -- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call. + -- And this is a problem because it will remove all entries from the SET_CARGOs. + -- To prevent this from happening, the Cargo object has a flag NoDestroy. + -- When true, the SET_CARGO won't Remove the Cargo object from the set. + -- This flag is switched off after the event handlers have been called in the EVENT class. + self:F( { CargoNoDestroy = Cargo.NoDestroy } ) if Cargo.NoDestroy then else self:Remove( Cargo.Name ) @@ -5515,7 +5368,6 @@ do -- SET_CARGO end - do -- SET_ZONE --- @type SET_ZONE @@ -5561,11 +5413,9 @@ do -- SET_ZONE Filter = { Prefixes = nil, }, - FilterMeta = { - }, + FilterMeta = {}, } - --- Creates a new SET_ZONE object, building a set of zones. -- @param #SET_ZONE self -- @return #SET_ZONE self @@ -5585,7 +5435,7 @@ do -- SET_ZONE -- @return self function SET_ZONE:AddZonesByName( AddZoneNames ) - local AddZoneNamesArray = ( type( AddZoneNames ) == "table" ) and AddZoneNames or { AddZoneNames } + local AddZoneNamesArray = (type( AddZoneNames ) == "table") and AddZoneNames or { AddZoneNames } for AddAirbaseID, AddZoneName in pairs( AddZoneNamesArray ) do self:Add( AddZoneName, ZONE:FindByName( AddZoneName ) ) @@ -5605,14 +5455,13 @@ do -- SET_ZONE return self end - --- Remove ZONEs from SET_ZONE. -- @param Core.Set#SET_ZONE self -- @param Core.Zone#ZONE_BASE RemoveZoneNames A single name or an array of ZONE_BASE names. -- @return self function SET_ZONE:RemoveZonesByName( RemoveZoneNames ) - local RemoveZoneNamesArray = ( type( RemoveZoneNames ) == "table" ) and RemoveZoneNames or { RemoveZoneNames } + local RemoveZoneNamesArray = (type( RemoveZoneNames ) == "table") and RemoveZoneNames or { RemoveZoneNames } for RemoveZoneID, RemoveZoneName in pairs( RemoveZoneNamesArray ) do self:Remove( RemoveZoneName ) @@ -5621,7 +5470,6 @@ do -- SET_ZONE return self end - --- Finds a Zone based on the Zone Name. -- @param #SET_ZONE self -- @param #string ZoneName @@ -5632,14 +5480,13 @@ do -- SET_ZONE return ZoneFound end - --- Get a random zone from the set. -- @param #SET_ZONE self -- @param #number margin Number of tries to find a zone -- @return Core.Zone#ZONE_BASE The random Zone. -- @return #nil if no zone in the collection. - function SET_ZONE:GetRandomZone(margin) - + function SET_ZONE:GetRandomZone( margin ) + local margin = margin or 100 if self:Count() ~= 0 then @@ -5662,7 +5509,6 @@ do -- SET_ZONE return nil end - --- Set a zone probability. -- @param #SET_ZONE self -- @param #string ZoneName The name of the zone. @@ -5671,9 +5517,6 @@ do -- SET_ZONE Zone:SetZoneProbability( ZoneProbability ) end - - - --- Builds a set of ZONEs that contain the given string in their name. -- **ATTENTION!** Bad naming convention as this **does not** filter only **prefixes** but all zones that **contain** the string. -- @param #SET_ZONE self @@ -5692,7 +5535,6 @@ do -- SET_ZONE return self end - --- Starts the filtering. -- @param #SET_ZONE self -- @return #SET_ZONE self @@ -5763,7 +5605,6 @@ do -- SET_ZONE return self end - --- -- @param #SET_ZONE self -- @param Core.Zone#ZONE_BASE MZone @@ -5796,13 +5637,13 @@ do -- SET_ZONE --- Handles the OnEventNewZone event for the Set. -- @param #SET_ZONE self -- @param Core.Event#EVENTDATA EventData - function SET_ZONE:OnEventNewZone( EventData ) --R2.1 + function SET_ZONE:OnEventNewZone( EventData ) -- R2.1 self:F( { "New Zone", EventData } ) if EventData.Zone then if EventData.Zone and self:IsIncludeObject( EventData.Zone ) then - self:Add( EventData.Zone.ZoneName , EventData.Zone ) + self:Add( EventData.Zone.ZoneName, EventData.Zone ) end end end @@ -5810,20 +5651,20 @@ do -- SET_ZONE --- Handles the OnDead or OnCrash event for alive units set. -- @param #SET_ZONE self -- @param Core.Event#EVENTDATA EventData - function SET_ZONE:OnEventDeleteZone( EventData ) --R2.1 + function SET_ZONE:OnEventDeleteZone( EventData ) -- R2.1 self:F3( { EventData } ) if EventData.Zone then local Zone = _DATABASE:FindZone( EventData.Zone.ZoneName ) if Zone and Zone.ZoneName then - -- When cargo was deleted, it may probably be because of an S_EVENT_DEAD. - -- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call. - -- And this is a problem because it will remove all entries from the SET_ZONEs. - -- To prevent this from happening, the Zone object has a flag NoDestroy. - -- When true, the SET_ZONE won't Remove the Zone object from the set. - -- This flag is switched off after the event handlers have been called in the EVENT class. - self:F( { ZoneNoDestroy=Zone.NoDestroy } ) + -- When cargo was deleted, it may probably be because of an S_EVENT_DEAD. + -- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call. + -- And this is a problem because it will remove all entries from the SET_ZONEs. + -- To prevent this from happening, the Zone object has a flag NoDestroy. + -- When true, the SET_ZONE won't Remove the Zone object from the set. + -- This flag is switched off after the event handlers have been called in the EVENT class. + self:F( { ZoneNoDestroy = Zone.NoDestroy } ) if Zone.NoDestroy then else self:Remove( Zone.ZoneName ) @@ -5898,11 +5739,10 @@ do -- SET_ZONE_GOAL Filter = { Prefixes = nil, }, - FilterMeta = { + FilterMeta = { }, } - --- Creates a new SET_ZONE_GOAL object, building a set of zones. -- @param #SET_ZONE_GOAL self -- @return #SET_ZONE_GOAL self @@ -5927,14 +5767,13 @@ do -- SET_ZONE_GOAL return self end - --- Remove ZONEs from SET_ZONE_GOAL. -- @param Core.Set#SET_ZONE_GOAL self -- @param Core.Zone#ZONE_BASE RemoveZoneNames A single name or an array of ZONE_BASE names. -- @return self function SET_ZONE_GOAL:RemoveZonesByName( RemoveZoneNames ) - local RemoveZoneNamesArray = ( type( RemoveZoneNames ) == "table" ) and RemoveZoneNames or { RemoveZoneNames } + local RemoveZoneNamesArray = (type( RemoveZoneNames ) == "table") and RemoveZoneNames or { RemoveZoneNames } for RemoveZoneID, RemoveZoneName in pairs( RemoveZoneNamesArray ) do self:Remove( RemoveZoneName ) @@ -5943,7 +5782,6 @@ do -- SET_ZONE_GOAL return self end - --- Finds a Zone based on the Zone Name. -- @param #SET_ZONE_GOAL self -- @param #string ZoneName @@ -5954,7 +5792,6 @@ do -- SET_ZONE_GOAL return ZoneFound end - --- Get a random zone from the set. -- @param #SET_ZONE_GOAL self -- @return Core.Zone#ZONE_BASE The random Zone. @@ -5980,7 +5817,6 @@ do -- SET_ZONE_GOAL return nil end - --- Set a zone probability. -- @param #SET_ZONE_GOAL self -- @param #string ZoneName The name of the zone. @@ -5989,9 +5825,6 @@ do -- SET_ZONE_GOAL Zone:SetZoneProbability( ZoneProbability ) end - - - --- Builds a set of ZONE_GOALs that contain the given string in their name. -- **ATTENTION!** Bad naming convention as this **does not** filter only **prefixes** but all zones that **contain** the string. -- @param #SET_ZONE_GOAL self @@ -6010,7 +5843,6 @@ do -- SET_ZONE_GOAL return self end - --- Starts the filtering. -- @param #SET_ZONE_GOAL self -- @return #SET_ZONE_GOAL self @@ -6081,7 +5913,6 @@ do -- SET_ZONE_GOAL return self end - --- -- @param #SET_ZONE_GOAL self -- @param Core.Zone#ZONE_BASE MZone @@ -6123,7 +5954,7 @@ do -- SET_ZONE_GOAL if EventData.ZoneGoal then if EventData.ZoneGoal and self:IsIncludeObject( EventData.ZoneGoal ) then self:T( { "Adding Zone Capture Coalition", EventData.ZoneGoal.ZoneName, EventData.ZoneGoal } ) - self:Add( EventData.ZoneGoal.ZoneName , EventData.ZoneGoal ) + self:Add( EventData.ZoneGoal.ZoneName, EventData.ZoneGoal ) end end end @@ -6131,20 +5962,20 @@ do -- SET_ZONE_GOAL --- Handles the OnDead or OnCrash event for alive units set. -- @param #SET_ZONE_GOAL self -- @param Core.Event#EVENTDATA EventData - function SET_ZONE_GOAL:OnEventDeleteZoneGoal( EventData ) --R2.1 + function SET_ZONE_GOAL:OnEventDeleteZoneGoal( EventData ) -- R2.1 self:F3( { EventData } ) if EventData.ZoneGoal then local Zone = _DATABASE:FindZone( EventData.ZoneGoal.ZoneName ) if Zone and Zone.ZoneName then - -- When cargo was deleted, it may probably be because of an S_EVENT_DEAD. - -- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call. - -- And this is a problem because it will remove all entries from the SET_ZONE_GOALs. - -- To prevent this from happening, the Zone object has a flag NoDestroy. - -- When true, the SET_ZONE_GOAL won't Remove the Zone object from the set. - -- This flag is switched off after the event handlers have been called in the EVENT class. - self:F( { ZoneNoDestroy=Zone.NoDestroy } ) + -- When cargo was deleted, it may probably be because of an S_EVENT_DEAD. + -- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call. + -- And this is a problem because it will remove all entries from the SET_ZONE_GOALs. + -- To prevent this from happening, the Zone object has a flag NoDestroy. + -- When true, the SET_ZONE_GOAL won't Remove the Zone object from the set. + -- This flag is switched off after the event handlers have been called in the EVENT class. + self:F( { ZoneNoDestroy = Zone.NoDestroy } ) if Zone.NoDestroy then else self:Remove( Zone.ZoneName ) @@ -6174,8 +6005,6 @@ do -- SET_ZONE_GOAL end - - do -- SET_OPSGROUP --- @type SET_OPSGROUP @@ -6281,14 +6110,13 @@ do -- SET_OPSGROUP }, -- FilterMeta } - --- Creates a new SET_OPSGROUP object, building a set of groups belonging to a coalitions, categories, countries, types or with defined prefix names. -- @param #SET_OPSGROUP self -- @return #SET_OPSGROUP function SET_OPSGROUP:New() -- Inherit SET_BASE. - local self = BASE:Inherit(self, SET_BASE:New(_DATABASE.GROUPS)) -- #SET_OPSGROUP + local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.GROUPS ) ) -- #SET_OPSGROUP -- Include non activated self:FilterActive( false ) @@ -6304,80 +6132,80 @@ do -- SET_OPSGROUP local AliveSet = SET_OPSGROUP:New() -- Clean the Set before returning with only the alive Groups. - for GroupName, GroupObject in pairs(self.Set) do - local GroupObject=GroupObject --Wrapper.Group#GROUP - - if GroupObject and GroupObject:IsAlive() then - AliveSet:Add(GroupName, GroupObject) + for GroupName, GroupObject in pairs( self.Set ) do + local GroupObject = GroupObject -- Wrapper.Group#GROUP + + if GroupObject and GroupObject:IsAlive() then + AliveSet:Add( GroupName, GroupObject ) end end return AliveSet.Set or {} end - + --- Adds a @{Core.Base#BASE} object in the @{Core.Set#SET_BASE}, using a given ObjectName as the index. -- @param #SET_BASE self -- @param #string ObjectName The name of the object. -- @param Core.Base#BASE Object The object itself. -- @return Core.Base#BASE The added BASE Object. - function SET_OPSGROUP:Add(ObjectName, Object) + function SET_OPSGROUP:Add( ObjectName, Object ) self:T( { ObjectName = ObjectName, Object = Object } ) -- Ensure that the existing element is removed from the Set before a new one is inserted to the Set if self.Set[ObjectName] then - self:Remove(ObjectName, true) + self:Remove( ObjectName, true ) end - - local object=nil --Ops.OpsGroup#OPSGROUP - if Object:IsInstanceOf("GROUP") then - + + local object = nil -- Ops.OpsGroup#OPSGROUP + if Object:IsInstanceOf( "GROUP" ) then + --- -- GROUP Object --- - + -- Fist, look up in the DATABASE if an OPSGROUP already exists. - object=_DATABASE:FindOpsGroup(ObjectName) - + object = _DATABASE:FindOpsGroup( ObjectName ) + if not object then - + if Object:IsShip() then - object=NAVYGROUP:New(Object) + object = NAVYGROUP:New( Object ) elseif Object:IsGround() then - object=ARMYGROUP:New(Object) + object = ARMYGROUP:New( Object ) elseif Object:IsAir() then - object=FLIGHTGROUP:New(Object) + object = FLIGHTGROUP:New( Object ) else - env.error("ERROR: Unknown category of group object!") + env.error( "ERROR: Unknown category of group object!" ) end end - - elseif Object:IsInstanceOf("OPSGROUP") then + + elseif Object:IsInstanceOf( "OPSGROUP" ) then -- We already have an OPSGROUP. - object=Object + object = Object else - env.error("ERROR: Object must be a GROUP or OPSGROUP!") + env.error( "ERROR: Object must be a GROUP or OPSGROUP!" ) end - + -- Add object to set. - self.Set[ObjectName]=object + self.Set[ObjectName] = object -- Add Object name to Index. - table.insert(self.Index, ObjectName) + table.insert( self.Index, ObjectName ) -- Trigger Added event. - self:Added(ObjectName, object) - end + self:Added( ObjectName, object ) + end --- Add a GROUP or OPSGROUP object to the set. -- **NOTE** that an OPSGROUP is automatically created from the GROUP if it does not exist already. -- @param Core.Set#SET_OPSGROUP self -- @param Wrapper.Group#GROUP group The GROUP which should be added to the set. Can also be given as an #OPSGROUP object. -- @return Core.Set#SET_OPSGROUP self - function SET_OPSGROUP:AddGroup(group) - - local groupname=group:GetName() - - self:Add(groupname, group ) + function SET_OPSGROUP:AddGroup( group ) + + local groupname = group:GetName() + + self:Add( groupname, group ) return self end @@ -6388,10 +6216,10 @@ do -- SET_OPSGROUP -- @return Core.Set#SET_OPSGROUP self function SET_OPSGROUP:AddGroupsByName( AddGroupNames ) - local AddGroupNamesArray = ( type( AddGroupNames ) == "table" ) and AddGroupNames or { AddGroupNames } + local AddGroupNamesArray = (type( AddGroupNames ) == "table") and AddGroupNames or { AddGroupNames } for AddGroupID, AddGroupName in pairs( AddGroupNamesArray ) do - self:Add(AddGroupName, GROUP:FindByName(AddGroupName)) + self:Add( AddGroupName, GROUP:FindByName( AddGroupName ) ) end return self @@ -6403,7 +6231,7 @@ do -- SET_OPSGROUP -- @return Core.Set#SET_OPSGROUP self function SET_OPSGROUP:RemoveGroupsByName( RemoveGroupNames ) - local RemoveGroupNamesArray = ( type( RemoveGroupNames ) == "table" ) and RemoveGroupNames or { RemoveGroupNames } + local RemoveGroupNamesArray = (type( RemoveGroupNames ) == "table") and RemoveGroupNames or { RemoveGroupNames } for RemoveGroupID, RemoveGroupName in pairs( RemoveGroupNamesArray ) do self:Remove( RemoveGroupName ) @@ -6416,65 +6244,63 @@ do -- SET_OPSGROUP -- @param #SET_OPSGROUP self -- @param #string GroupName Name of the group. -- @return Ops.OpsGroup#OPSGROUP The found OPSGROUP (FLIGHTGROUP, ARMYGROUP or NAVYGROUP) or `#nil` if the group is not in the set. - function SET_OPSGROUP:FindGroup(GroupName) + function SET_OPSGROUP:FindGroup( GroupName ) local GroupFound = self.Set[GroupName] return GroupFound end - + --- Finds a FLIGHTGROUP based on the group name. -- @param #SET_OPSGROUP self -- @param #string GroupName Name of the group. -- @return Ops.FlightGroup#FLIGHTGROUP The found FLIGHTGROUP or `#nil` if the group is not in the set. - function SET_OPSGROUP:FindFlightGroup(GroupName) - local GroupFound = self:FindGroup(GroupName) + function SET_OPSGROUP:FindFlightGroup( GroupName ) + local GroupFound = self:FindGroup( GroupName ) return GroupFound end - + --- Finds a ARMYGROUP based on the group name. -- @param #SET_OPSGROUP self -- @param #string GroupName Name of the group. -- @return Ops.ArmyGroup#ARMYGROUP The found ARMYGROUP or `#nil` if the group is not in the set. - function SET_OPSGROUP:FindArmyGroup(GroupName) - local GroupFound = self:FindGroup(GroupName) + function SET_OPSGROUP:FindArmyGroup( GroupName ) + local GroupFound = self:FindGroup( GroupName ) return GroupFound - end - + end --- Finds a NAVYGROUP based on the group name. -- @param #SET_OPSGROUP self -- @param #string GroupName Name of the group. -- @return Ops.NavyGroup#NAVYGROUP The found NAVYGROUP or `#nil` if the group is not in the set. - function SET_OPSGROUP:FindNavyGroup(GroupName) - local GroupFound = self:FindGroup(GroupName) + function SET_OPSGROUP:FindNavyGroup( GroupName ) + local GroupFound = self:FindGroup( GroupName ) return GroupFound - end - + end + --- Builds a set of groups of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_OPSGROUP self -- @param #string Coalitions Can take the following values: "red", "blue", "neutral" or combinations as a table, for example `{"red", "neutral"}`. -- @return #SET_OPSGROUP self - function SET_OPSGROUP:FilterCoalitions(Coalitions) - + function SET_OPSGROUP:FilterCoalitions( Coalitions ) + -- Create an empty set. if not self.Filter.Coalitions then - self.Filter.Coalitions={} + self.Filter.Coalitions = {} end - + -- Ensure we got a table. - if type(Coalitions)~="table" then - Coalitions = {Coalitions} + if type( Coalitions ) ~= "table" then + Coalitions = { Coalitions } end - + -- Set filter. for CoalitionID, Coalition in pairs( Coalitions ) do self.Filter.Coalitions[Coalition] = Coalition end - + return self end - --- Builds a set of groups out of categories. -- -- Possible current categories are: @@ -6488,19 +6314,19 @@ do -- SET_OPSGROUP -- @param #string Categories Can take the following values: "plane", "helicopter", "ground", "ship" or combinations as a table, for example `{"plane", "helicopter"}`. -- @return #SET_OPSGROUP self function SET_OPSGROUP:FilterCategories( Categories ) - + if not self.Filter.Categories then - self.Filter.Categories={} + self.Filter.Categories = {} end - - if type(Categories)~="table" then - Categories={Categories} + + if type( Categories ) ~= "table" then + Categories = { Categories } end - + for CategoryID, Category in pairs( Categories ) do self.Filter.Categories[Category] = Category end - + return self end @@ -6508,7 +6334,7 @@ do -- SET_OPSGROUP -- @param #SET_OPSGROUP self -- @return #SET_OPSGROUP self function SET_OPSGROUP:FilterCategoryGround() - self:FilterCategories("ground") + self:FilterCategories( "ground" ) return self end @@ -6516,23 +6342,23 @@ do -- SET_OPSGROUP -- @param #SET_OPSGROUP self -- @return #SET_OPSGROUP self function SET_OPSGROUP:FilterCategoryAirplane() - self:FilterCategories("plane") + self:FilterCategories( "plane" ) return self end - + --- Builds a set of groups out of aicraft category (planes and helicopters). -- @param #SET_OPSGROUP self -- @return #SET_OPSGROUP self function SET_OPSGROUP:FilterCategoryAircraft() - self:FilterCategories({"plane", "helicopter"}) + self:FilterCategories( { "plane", "helicopter" } ) return self - end + end --- Builds a set of groups out of helicopter category. -- @param #SET_OPSGROUP self -- @return #SET_OPSGROUP self function SET_OPSGROUP:FilterCategoryHelicopter() - self:FilterCategories("helicopter") + self:FilterCategories( "helicopter" ) return self end @@ -6540,7 +6366,7 @@ do -- SET_OPSGROUP -- @param #SET_OPSGROUP self -- @return #SET_OPSGROUP self function SET_OPSGROUP:FilterCategoryShip() - self:FilterCategories("ship") + self:FilterCategories( "ship" ) return self end @@ -6548,56 +6374,55 @@ do -- SET_OPSGROUP -- @param #SET_OPSGROUP self -- @param #string Countries Can take those country strings known within DCS world. -- @return #SET_OPSGROUP self - function SET_OPSGROUP:FilterCountries(Countries) - + function SET_OPSGROUP:FilterCountries( Countries ) + -- Create empty table if necessary. if not self.Filter.Countries then self.Filter.Countries = {} end - + -- Ensure input is a table. - if type(Countries)~="table" then - Countries={Countries} + if type( Countries ) ~= "table" then + Countries = { Countries } end - + -- Set filter. for CountryID, Country in pairs( Countries ) do self.Filter.Countries[Country] = Country end - + return self end - --- Builds a set of groups that contain the given string in their group name. -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all groups that **contain** the string. -- @param #SET_OPSGROUP self -- @param #string Prefixes The string pattern(s) that needs to be contained in the group name. Can also be passed as a `#table` of strings. -- @return #SET_OPSGROUP self - function SET_OPSGROUP:FilterPrefixes(Prefixes) - + function SET_OPSGROUP:FilterPrefixes( Prefixes ) + -- Create emtpy table if necessary. if not self.Filter.GroupPrefixes then - self.Filter.GroupPrefixes={} + self.Filter.GroupPrefixes = {} end - + -- Ensure we have a table. - if type(Prefixes)~="table" then - Prefixes={Prefixes} + if type( Prefixes ) ~= "table" then + Prefixes = { Prefixes } end - + -- Set group prefixes. - for PrefixID, Prefix in pairs(Prefixes) do - self.Filter.GroupPrefixes[Prefix]=Prefix + for PrefixID, Prefix in pairs( Prefixes ) do + self.Filter.GroupPrefixes[Prefix] = Prefix end - + return self end --- Builds a set of groups that are only active. -- Only the groups that are active will be included within the set. -- @param #SET_OPSGROUP self - -- @param #boolean Active (optional) Include only active groups to the set. + -- @param #boolean Active (Optional) Include only active groups to the set. -- Include inactive groups if you provide false. -- @return #SET_OPSGROUP self -- @usage @@ -6615,12 +6440,11 @@ do -- SET_OPSGROUP -- GroupSet = SET_OPSGROUP:New():FilterActive( false ):FilterCoalition( "blue" ):FilterOnce() -- function SET_OPSGROUP:FilterActive( Active ) - Active = Active or not ( Active == false ) + Active = Active or not (Active == false) self.Filter.Active = Active return self end - --- Starts the filtering. -- @param #SET_OPSGROUP self -- @return #SET_OPSGROUP self @@ -6636,21 +6460,21 @@ do -- SET_OPSGROUP return self end - + --- Activate late activated groups in the set. -- @param #SET_OPSGROUP self -- @param #number Delay Delay in seconds. -- @return #SET_OPSGROUP self - function SET_OPSGROUP:Activate(Delay) + function SET_OPSGROUP:Activate( Delay ) local Set = self:GetSet() - for GroupID, GroupData in pairs(Set) do - local group=GroupData --Ops.OpsGroup#OPSGROUP - if group and group:IsAlive()==false then - group:Activate(Delay) + for GroupID, GroupData in pairs( Set ) do + local group = GroupData -- Ops.OpsGroup#OPSGROUP + if group and group:IsAlive() == false then + group:Activate( Delay ) end end return self - end + end --- Handles the OnDead or OnCrash event for alive groups set. -- Note: The GROUP object in the SET_OPSGROUP collection will only be removed if the last unit is destroyed of the GROUP. @@ -6676,13 +6500,13 @@ do -- SET_OPSGROUP -- @return #string The name of the GROUP -- @return #table The GROUP function SET_OPSGROUP:AddInDatabase( Event ) - - if Event.IniObjectCategory==1 then - + + if Event.IniObjectCategory == 1 then + if not self.Database[Event.IniDCSGroupName] then self.Database[Event.IniDCSGroupName] = GROUP:Register( Event.IniDCSGroupName ) end - + end return Event.IniDCSGroupName, self.Database[Event.IniDCSGroupName] @@ -6694,7 +6518,7 @@ do -- SET_OPSGROUP -- @param Core.Event#EVENTDATA Event Event data table. -- @return #string The name of the GROUP -- @return #table The GROUP - function SET_OPSGROUP:FindInDatabase(Event) + function SET_OPSGROUP:FindInDatabase( Event ) return Event.IniDCSGroupName, self.Database[Event.IniDCSGroupName] end @@ -6705,7 +6529,7 @@ do -- SET_OPSGROUP -- @return #SET_OPSGROUP self function SET_OPSGROUP:ForEachGroup( IteratorFunction, ... ) - self:ForEach(IteratorFunction, arg, self:GetSet()) + self:ForEach( IteratorFunction, arg, self:GetSet() ) return self end @@ -6714,48 +6538,48 @@ do -- SET_OPSGROUP -- @param #SET_OPSGROUP self -- @param Wrapper.Group#GROUP MGroup The group that is checked for inclusion. -- @return #SET_OPSGROUP self - function SET_OPSGROUP:IsIncludeObject(MGroup) - + function SET_OPSGROUP:IsIncludeObject( MGroup ) + -- Assume it is and check later if not. - local MGroupInclude=true + local MGroupInclude = true -- Filter active. - if self.Filter.Active~=nil then - + if self.Filter.Active ~= nil then + local MGroupActive = false - - if self.Filter.Active==false or (self.Filter.Active==true and MGroup:IsActive()==true) then + + if self.Filter.Active == false or (self.Filter.Active == true and MGroup:IsActive() == true) then MGroupActive = true end - + MGroupInclude = MGroupInclude and MGroupActive end -- Filter coalitions. if self.Filter.Coalitions then - + local MGroupCoalition = false - + for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do - if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName]==MGroup:GetCoalition() then - MGroupCoalition = true + if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == MGroup:GetCoalition() then + MGroupCoalition = true end end - + MGroupInclude = MGroupInclude and MGroupCoalition end -- Filter categories. if self.Filter.Categories then - + local MGroupCategory = false - + for CategoryID, CategoryName in pairs( self.Filter.Categories ) do - if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName]==MGroup:GetCategory() then + if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName] == MGroup:GetCategory() then MGroupCategory = true end end - + MGroupInclude = MGroupInclude and MGroupCategory end @@ -6772,19 +6596,19 @@ do -- SET_OPSGROUP -- Filter "prefixes". if self.Filter.GroupPrefixes then - + local MGroupPrefix = false - + for GroupPrefixId, GroupPrefix in pairs( self.Filter.GroupPrefixes ) do - if string.find( MGroup:GetName(), GroupPrefix:gsub ("-", "%%-"), 1 ) then --Not sure why "-" is replaced by "%-" ?! + if string.find( MGroup:GetName(), GroupPrefix:gsub( "-", "%%-" ), 1 ) then -- Not sure why "-" is replaced by "%-" ?! MGroupPrefix = true end end - + MGroupInclude = MGroupInclude and MGroupPrefix end return MGroupInclude end - + end diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index b2706fa02..dc5955d3d 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -334,11 +334,13 @@ function POSITIONABLE:GetPointVec3() return nil end ---- Returns a COORDINATE object indicating the point in 3D of the POSITIONABLE within the mission. --- If the POSITIONABLE has a COORDINATE OBJECT set, it updates it. If not, it creates a new COORDINATE object. +--- Returns a reference to a COORDINATE object indicating the point in 3D of the POSITIONABLE within the mission. +-- This function works similar to POSITIONABLE.GetCoordinate(), however, this function caches, updates and re-uses the same COORDINATE object stored +-- within the POSITIONABLE. This has higher performance, but comes with all considerations associated with the possible referencing to the same COORDINATE object. +-- This should only be used when performance is critical and there is sufficient awareness of the possible pitfalls. However, in most instances, GetCoordinate() is +-- preferred as it will return a fresh new COORDINATE and thus avoid potentially unexpected issues. -- @param Wrapper.Positionable#POSITIONABLE self --- @return Core.Point#COORDINATE The COORDINATE of the POSITIONABLE. --- TODO: Seems to have been introduced with Airboss. Should it be renamed to better reflect the difference to "GetCoordinate" (it is currently ambiguous)? Or perhaps just be a switch in the the GetCoordinate function; forceCoordinateUpate? +-- @return Core.Point#COORDINATE A reference to the COORDINATE object of the POSITIONABLE. function POSITIONABLE:GetCoord() -- Get DCS object. @@ -366,9 +368,9 @@ function POSITIONABLE:GetCoord() return nil end ---- Returns a COORDINATE object indicating the point in 3D of the POSITIONABLE within the mission. +--- Returns a new COORDINATE object indicating the point in 3D of the POSITIONABLE within the mission. -- @param Wrapper.Positionable#POSITIONABLE self --- @return Core.Point#COORDINATE The COORDINATE of the POSITIONABLE. +-- @return Core.Point#COORDINATE A new COORDINATE object of the POSITIONABLE. function POSITIONABLE:GetCoordinate() -- Get DCS object. @@ -512,15 +514,15 @@ end --- Get the bounding radius of the underlying POSITIONABLE DCS Object. -- @param #POSITIONABLE self --- @param #number mindist (Optional) If bounding box is smaller than this value, mindist is returned. +-- @param #number MinDist (Optional) If bounding box is smaller than this value, MinDist is returned. -- @return DCS#Distance The bounding radius of the POSITIONABLE -- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetBoundingRadius( mindist ) +function POSITIONABLE:GetBoundingRadius( MinDist ) self:F2() local Box = self:GetBoundingBox() - local boxmin = mindist or 0 + local boxmin = MinDist or 0 if Box then local X = Box.max.x - Box.min.x local Z = Box.max.z - Box.min.z @@ -763,13 +765,13 @@ end --- Get relative velocity with respect to another POSITIONABLE. -- @param #POSITIONABLE self --- @param #POSITIONABLE positionable Other POSITIONABLE. +-- @param #POSITIONABLE Positionable Other POSITIONABLE. -- @return #number Relative velocity in m/s. -function POSITIONABLE:GetRelativeVelocity( positionable ) +function POSITIONABLE:GetRelativeVelocity( Positionable ) self:F2( self.PositionableName ) local v1 = self:GetVelocityVec3() - local v2 = positionable:GetVelocityVec3() + local v2 = Positionable:GetVelocityVec3() local vtot = UTILS.VecAdd( v1, v2 ) @@ -1446,7 +1448,7 @@ do -- Cargo -- @return #number CargoBayFreeWeight function POSITIONABLE:GetCargoBayFreeWeight() - -- When there is no cargo bay weight limit set, then calculate this for this positionable! + -- When there is no cargo bay weight limit set, then calculate this for this POSITIONABLE! if not self.__.CargoBayWeightLimit then self:SetCargoBayWeightLimit() end @@ -1475,9 +1477,9 @@ do -- Cargo elseif self.__.CargoBayWeightLimit ~= nil then -- Value already set ==> Do nothing! else - -- If weightlimit is not provided, we will calculate it depending on the type of unit. + -- If WeightLimit is not provided, we will calculate it depending on the type of unit. - -- When an airplane or helicopter, we calculate the weightlimit based on the descriptor. + -- When an airplane or helicopter, we calculate the WeightLimit based on the descriptor. if self:IsAir() then local Desc = self:GetDesc() self:F( { Desc = Desc } ) From 0447ee2d9ed0042c0c5b7b7cd81c09a4bcbc7c8c Mon Sep 17 00:00:00 2001 From: TommyC81 Date: Sun, 12 Dec 2021 22:15:30 +0400 Subject: [PATCH 033/200] Update Airboss.lua (#1664) Code formatting. Typo fixing. --- Moose Development/Moose/Ops/Airboss.lua | 12129 ++++++++++------------ 1 file changed, 5688 insertions(+), 6441 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index eee1ea3db..853295f5a 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -864,7 +864,7 @@ -- * *Points*: Current points for the pass. -- * *Details*: Detailed grading analysis. -- ---## Lineup Error +-- ## Lineup Error -- -- ![Banner Image](..\Presentations\AIRBOSS\Airboss_TrapSheetLUE.png) -- @@ -1361,7 +1361,6 @@ AIRBOSS.CarrierType = { -- @field #number LEFT LUR threshold. Default -3.0 deg. -- @field #number RIGHT LUL threshold. Default 3.0 deg. - --- Pattern steps. -- @type AIRBOSS.PatternStep -- @field #string UNDEFINED "Undefined". @@ -1572,7 +1571,6 @@ AIRBOSS.GroovePos = { -- @field #AIRBOSS.RadioCall CLICK Radio end transmission click sound. -- @field #AIRBOSS.RadioCall NOISE Static noise sound. - --- Difficulty level. -- @type AIRBOSS.Difficulty -- @field #string EASY Flight Student. Shows tips and hints in important phases of the approach. @@ -1718,15 +1716,15 @@ AIRBOSS.Difficulty = { --- Main group level radio menu: F10 Other/Airboss. -- @field #table MenuF10 -AIRBOSS.MenuF10={} +AIRBOSS.MenuF10 = {} --- Airboss mission level F10 root menu. -- @field #table MenuF10Root -AIRBOSS.MenuF10Root=nil +AIRBOSS.MenuF10Root = nil --- Airboss class version. -- @field #string version -AIRBOSS.version="1.2.1" +AIRBOSS.version = "1.2.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -1800,56 +1798,56 @@ AIRBOSS.version="1.2.1" -- @param carriername Name of the aircraft carrier unit as defined in the mission editor. -- @param alias (Optional) Alias for the carrier. This will be used for radio messages and the F10 radius menu. Default is the carrier name as defined in the mission editor. -- @return #AIRBOSS self or nil if carrier unit does not exist. -function AIRBOSS:New(carriername, alias) +function AIRBOSS:New( carriername, alias ) -- Inherit everthing from FSM class. - local self=BASE:Inherit(self, FSM:New()) -- #AIRBOSS + local self = BASE:Inherit( self, FSM:New() ) -- #AIRBOSS -- Debug. - self:F2({carriername=carriername, alias=alias}) + self:F2( { carriername = carriername, alias = alias } ) -- Set carrier unit. - self.carrier=UNIT:FindByName(carriername) + self.carrier = UNIT:FindByName( carriername ) -- Check if carrier unit exists. - if self.carrier==nil then + if self.carrier == nil then -- Error message. - local text=string.format("ERROR: Carrier unit %s could not be found! Make sure this UNIT is defined in the mission editor and check the spelling of the unit name carefully.", carriername) - MESSAGE:New(text, 120):ToAll() - self:E(text) + local text = string.format( "ERROR: Carrier unit %s could not be found! Make sure this UNIT is defined in the mission editor and check the spelling of the unit name carefully.", carriername ) + MESSAGE:New( text, 120 ):ToAll() + self:E( text ) return nil end -- Set some string id for output to DCS.log file. - self.lid=string.format("AIRBOSS %s | ", carriername) + self.lid = string.format( "AIRBOSS %s | ", carriername ) -- Current map. - self.theatre=env.mission.theatre - self:T2(self.lid..string.format("Theatre = %s.", tostring(self.theatre))) + self.theatre = env.mission.theatre + self:T2( self.lid .. string.format( "Theatre = %s.", tostring( self.theatre ) ) ) -- Get carrier type. - self.carriertype=self.carrier:GetTypeName() + self.carriertype = self.carrier:GetTypeName() -- Set alias. - self.alias=alias or carriername + self.alias = alias or carriername -- Set carrier airbase object. - self.airbase=AIRBASE:FindByName(carriername) + self.airbase = AIRBASE:FindByName( carriername ) -- Create carrier beacon. - self.beacon=BEACON:New(self.carrier) + self.beacon = BEACON:New( self.carrier ) -- Set Tower Frequency of carrier. self:_GetTowerFrequency() -- Init player scores table. - self.playerscores={} + self.playerscores = {} -- Initialize ME waypoints. self:_InitWaypoints() -- Current waypoint. - self.currentwp=1 + self.currentwp = 1 -- Patrol route. self:_PatrolRoute() @@ -1868,7 +1866,7 @@ function AIRBOSS:New(carriername, alias) self:SetLSOCallInterval() -- Radio scheduler. - self.radiotimer=SCHEDULER:New() + self.radiotimer = SCHEDULER:New() -- Set magnetic declination. self:SetMagneticDeclination() @@ -1904,10 +1902,10 @@ function AIRBOSS:New(carriername, alias) self:SetEmergencyLandings() -- No despawn after engine shutdown by default. - self:SetDespawnOnEngineShutdown(false) + self:SetDespawnOnEngineShutdown( false ) -- No respawning of AI groups when entering the CCA. - self:SetRespawnAI(false) + self:SetRespawnAI( false ) -- Mission uses static weather by default. self:SetStaticWeather() @@ -1928,7 +1926,7 @@ function AIRBOSS:New(carriername, alias) self:SetInitialMaxAlt() -- Default player skill EASY. - self:SetDefaultPlayerSkill(AIRBOSS.Difficulty.EASY) + self:SetDefaultPlayerSkill( AIRBOSS.Difficulty.EASY ) -- Default glideslope error thresholds. self:SetGlideslopeErrorThresholds() @@ -1943,7 +1941,7 @@ function AIRBOSS:New(carriername, alias) self:SetCarrierControlledZone() -- Carrier patrols its waypoints until the end of time. - self:SetPatrolAdInfinitum(true) + self:SetPatrolAdInfinitum( true ) -- Collision check distance. Default 5 NM. self:SetCollisionDistance() @@ -1956,49 +1954,49 @@ function AIRBOSS:New(carriername, alias) -- Menu options. self:SetMenuMarkZones() self:SetMenuSmokeZones() - self:SetMenuSingleCarrier(false) + self:SetMenuSingleCarrier( false ) -- Welcome players. - self:SetWelcomePlayers(true) + self:SetWelcomePlayers( true ) -- Coordinates - self.landingcoord=COORDINATE:New(0,0,0) --Core.Point#COORDINATE - self.sterncoord=COORDINATE:New(0, 0, 0) --Core.Point#COORDINATE - self.landingspotcoord=COORDINATE:New(0,0,0) --Core.Point#COORDINATE + self.landingcoord = COORDINATE:New( 0, 0, 0 ) -- Core.Point#COORDINATE + self.sterncoord = COORDINATE:New( 0, 0, 0 ) -- Core.Point#COORDINATE + self.landingspotcoord = COORDINATE:New( 0, 0, 0 ) -- Core.Point#COORDINATE -- Init carrier parameters. - if self.carriertype==AIRBOSS.CarrierType.STENNIS then + if self.carriertype == AIRBOSS.CarrierType.STENNIS then self:_InitStennis() - elseif self.carriertype==AIRBOSS.CarrierType.ROOSEVELT then + elseif self.carriertype == AIRBOSS.CarrierType.ROOSEVELT then self:_InitNimitz() - elseif self.carriertype==AIRBOSS.CarrierType.LINCOLN then + elseif self.carriertype == AIRBOSS.CarrierType.LINCOLN then self:_InitNimitz() - elseif self.carriertype==AIRBOSS.CarrierType.WASHINGTON then + elseif self.carriertype == AIRBOSS.CarrierType.WASHINGTON then self:_InitNimitz() - elseif self.carriertype==AIRBOSS.CarrierType.TRUMAN then + elseif self.carriertype == AIRBOSS.CarrierType.TRUMAN then self:_InitNimitz() - elseif self.carriertype==AIRBOSS.CarrierType.FORRESTAL then + elseif self.carriertype == AIRBOSS.CarrierType.FORRESTAL then self:_InitForrestal() - elseif self.carriertype==AIRBOSS.CarrierType.VINSON then + elseif self.carriertype == AIRBOSS.CarrierType.VINSON then -- TODO: Carl Vinson parameters. self:_InitStennis() - elseif self.carriertype==AIRBOSS.CarrierType.TARAWA then + elseif self.carriertype == AIRBOSS.CarrierType.TARAWA then -- Tarawa parameters. self:_InitTarawa() - elseif self.carriertype==AIRBOSS.CarrierType.AMERICA then + elseif self.carriertype == AIRBOSS.CarrierType.AMERICA then -- Use America parameters. self:_InitAmerica() - elseif self.carriertype==AIRBOSS.CarrierType.JCARLOS then + elseif self.carriertype == AIRBOSS.CarrierType.JCARLOS then -- Use Juan Carlos parameters. self:_InitJcarlos() - elseif self.carriertype==AIRBOSS.CarrierType.CANBERRA then + elseif self.carriertype == AIRBOSS.CarrierType.CANBERRA then -- Use Juan Carlos parameters at this stage --TODO Check primary Landing spot. self:_InitJcarlos() - elseif self.carriertype==AIRBOSS.CarrierType.KUZNETSOV then + elseif self.carriertype == AIRBOSS.CarrierType.KUZNETSOV then -- Kusnetsov parameters - maybe... self:_InitStennis() else - self:E(self.lid..string.format("ERROR: Unknown carrier type %s!", tostring(self.carriertype))) + self:E( self.lid .. string.format( "ERROR: Unknown carrier type %s!", tostring( self.carriertype ) ) ) return nil end @@ -2011,48 +2009,48 @@ function AIRBOSS:New(carriername, alias) -- Debug trace. if false then - self.Debug=true - BASE:TraceOnOff(true) - BASE:TraceClass(self.ClassName) - BASE:TraceLevel(3) - --self.dTstatus=0.1 + self.Debug = true + BASE:TraceOnOff( true ) + BASE:TraceClass( self.ClassName ) + BASE:TraceLevel( 3 ) + -- self.dTstatus=0.1 end -- Smoke zones. if false then - local case=3 - self.holdingoffset=30 - self:_GetZoneGroove():SmokeZone(SMOKECOLOR.Red, 5) - self:_GetZoneLineup():SmokeZone(SMOKECOLOR.Green, 5) - self:_GetZoneBullseye(case):SmokeZone(SMOKECOLOR.White, 45) - self:_GetZoneDirtyUp(case):SmokeZone(SMOKECOLOR.Orange, 45) - self:_GetZoneArcIn(case):SmokeZone(SMOKECOLOR.Blue, 45) - self:_GetZoneArcOut(case):SmokeZone(SMOKECOLOR.Blue, 45) - self:_GetZonePlatform(case):SmokeZone(SMOKECOLOR.Blue, 45) - self:_GetZoneCorridor(case):SmokeZone(SMOKECOLOR.Green, 45) - self:_GetZoneHolding(case, 1):SmokeZone(SMOKECOLOR.White, 45) - self:_GetZoneHolding(case, 2):SmokeZone(SMOKECOLOR.White, 45) - self:_GetZoneInitial(case):SmokeZone(SMOKECOLOR.Orange, 45) - self:_GetZoneCommence(case, 1):SmokeZone(SMOKECOLOR.Red, 45) - self:_GetZoneCommence(case, 2):SmokeZone(SMOKECOLOR.Red, 45) - self:_GetZoneAbeamLandingSpot():SmokeZone(SMOKECOLOR.Red, 5) - self:_GetZoneLandingSpot():SmokeZone(SMOKECOLOR.Red, 5) + local case = 3 + self.holdingoffset = 30 + self:_GetZoneGroove():SmokeZone( SMOKECOLOR.Red, 5 ) + self:_GetZoneLineup():SmokeZone( SMOKECOLOR.Green, 5 ) + self:_GetZoneBullseye( case ):SmokeZone( SMOKECOLOR.White, 45 ) + self:_GetZoneDirtyUp( case ):SmokeZone( SMOKECOLOR.Orange, 45 ) + self:_GetZoneArcIn( case ):SmokeZone( SMOKECOLOR.Blue, 45 ) + self:_GetZoneArcOut( case ):SmokeZone( SMOKECOLOR.Blue, 45 ) + self:_GetZonePlatform( case ):SmokeZone( SMOKECOLOR.Blue, 45 ) + self:_GetZoneCorridor( case ):SmokeZone( SMOKECOLOR.Green, 45 ) + self:_GetZoneHolding( case, 1 ):SmokeZone( SMOKECOLOR.White, 45 ) + self:_GetZoneHolding( case, 2 ):SmokeZone( SMOKECOLOR.White, 45 ) + self:_GetZoneInitial( case ):SmokeZone( SMOKECOLOR.Orange, 45 ) + self:_GetZoneCommence( case, 1 ):SmokeZone( SMOKECOLOR.Red, 45 ) + self:_GetZoneCommence( case, 2 ):SmokeZone( SMOKECOLOR.Red, 45 ) + self:_GetZoneAbeamLandingSpot():SmokeZone( SMOKECOLOR.Red, 5 ) + self:_GetZoneLandingSpot():SmokeZone( SMOKECOLOR.Red, 5 ) end -- Carrier parameter debug tests. if false then - -- Stern coordinate. - local FB=self:GetFinalBearing(false) - local hdg=self:GetHeading(false) + -- Stern coordinate. + local FB = self:GetFinalBearing( false ) + local hdg = self:GetHeading( false ) -- Stern pos. - local stern=self:_GetSternCoord() + local stern = self:_GetSternCoord() -- Bow pos. - local bow=stern:Translate(self.carrierparam.totlength, hdg, true) + local bow = stern:Translate( self.carrierparam.totlength, hdg, true ) -- End of rwy. - local rwy=stern:Translate(self.carrierparam.rwylength, FB, true) + local rwy = stern:Translate( self.carrierparam.rwylength, FB, true ) --- Flare points and zones. local function flareme() @@ -2067,31 +2065,30 @@ function AIRBOSS:New(carriername, alias) bow:FlareYellow() -- Runway half width = 10 m. - local r1=stern:Translate(self.carrierparam.rwywidth*0.5, FB+90, true) - local r2=stern:Translate(self.carrierparam.rwywidth*0.5, FB-90, true) - --r1:FlareWhite() - --r2:FlareWhite() + local r1 = stern:Translate( self.carrierparam.rwywidth * 0.5, FB + 90, true ) + local r2 = stern:Translate( self.carrierparam.rwywidth * 0.5, FB - 90, true ) + -- r1:FlareWhite() + -- r2:FlareWhite() -- End of runway. rwy:FlareRed() -- Right 30 meters from stern. - local cR=stern:Translate(self.carrierparam.totwidthstarboard, hdg+90, true) - --cR:FlareYellow() + local cR = stern:Translate( self.carrierparam.totwidthstarboard, hdg + 90, true ) + -- cR:FlareYellow() -- Left 40 meters from stern. - local cL=stern:Translate(self.carrierparam.totwidthport, hdg-90, true) - --cL:FlareYellow() - + local cL = stern:Translate( self.carrierparam.totwidthport, hdg - 90, true ) + -- cL:FlareYellow() -- Carrier specific. - if self.carrier:GetTypeName()~=AIRBOSS.CarrierType.TARAWA or self.carrier:GetTypeName()~=AIRBOSS.CarrierType.AMERICA or self.carrier:GetTypeName()~=AIRBOSS.CarrierType.JCARLOS or self.carrier:GetTypeName()~=AIRBOSS.CarrierType.CANBERRA then + if self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.TARAWA or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.AMERICA or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.JCARLOS or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.CANBERRA then -- Flare wires. - local w1=stern:Translate(self.carrierparam.wire1, FB, true) - local w2=stern:Translate(self.carrierparam.wire2, FB, true) - local w3=stern:Translate(self.carrierparam.wire3, FB, true) - local w4=stern:Translate(self.carrierparam.wire4, FB, true) + local w1 = stern:Translate( self.carrierparam.wire1, FB, true ) + local w2 = stern:Translate( self.carrierparam.wire2, FB, true ) + local w3 = stern:Translate( self.carrierparam.wire3, FB, true ) + local w4 = stern:Translate( self.carrierparam.wire4, FB, true ) w1:FlareWhite() w2:FlareYellow() w3:FlareWhite() @@ -2100,27 +2097,27 @@ function AIRBOSS:New(carriername, alias) else -- Abeam landing spot zone. - local ALSPT=self:_GetZoneAbeamLandingSpot() - ALSPT:FlareZone(FLARECOLOR.Red, 5, nil, UTILS.FeetToMeters(120)) + local ALSPT = self:_GetZoneAbeamLandingSpot() + ALSPT:FlareZone( FLARECOLOR.Red, 5, nil, UTILS.FeetToMeters( 120 ) ) -- Primary landing spot zone. - local LSPT=self:_GetZoneLandingSpot() - LSPT:FlareZone(FLARECOLOR.Green, 5, nil, self.carrierparam.deckheight) + local LSPT = self:_GetZoneLandingSpot() + LSPT:FlareZone( FLARECOLOR.Green, 5, nil, self.carrierparam.deckheight ) -- Landing spot coordinate. - local PLSC=self:_GetLandingSpotCoordinate() + local PLSC = self:_GetLandingSpotCoordinate() PLSC:FlareWhite() end -- Flare carrier and landing runway. - local cbox=self:_GetZoneCarrierBox() - local rbox=self:_GetZoneRunwayBox() - cbox:FlareZone(FLARECOLOR.Green, 5, nil, self.carrierparam.deckheight) - rbox:FlareZone(FLARECOLOR.White, 5, nil, self.carrierparam.deckheight) + local cbox = self:_GetZoneCarrierBox() + local rbox = self:_GetZoneRunwayBox() + cbox:FlareZone( FLARECOLOR.Green, 5, nil, self.carrierparam.deckheight ) + rbox:FlareZone( FLARECOLOR.White, 5, nil, self.carrierparam.deckheight ) end -- Flare points every 3 seconds for 3 minutes. - SCHEDULER:New(nil, flareme, {}, 1, 3, nil, 180) + SCHEDULER:New( nil, flareme, {}, 1, 3, nil, 180 ) end ----------------------- @@ -2128,7 +2125,7 @@ function AIRBOSS:New(carriername, alias) ----------------------- -- Start State. - self:SetStartState("Stopped") + self:SetStartState( "Stopped" ) -- Add FSM transitions. -- From State --> Event --> To State @@ -2164,7 +2161,6 @@ function AIRBOSS:New(carriername, alias) -- @param #string Event Event. -- @param #string To To state. - --- Triggers the FSM event "Idle" that puts the carrier into state "Idle" where no recoveries are carried out. -- @function [parent=#AIRBOSS] Idle -- @param #AIRBOSS self @@ -2174,7 +2170,6 @@ function AIRBOSS:New(carriername, alias) -- @param #AIRBOSS self -- @param #number delay Delay in seconds. - --- Triggers the FSM event "RecoveryStart" that starts the recovery of aircraft. Marshalling aircraft are send to the landing pattern. -- @function [parent=#AIRBOSS] RecoveryStart -- @param #AIRBOSS self @@ -2197,7 +2192,6 @@ function AIRBOSS:New(carriername, alias) -- @param #number Case The recovery case (1, 2 or 3) to start. -- @param #number Offset Holding pattern offset angle in degrees for CASE II/III recoveries. - --- Triggers the FSM event "RecoveryStop" that stops the recovery of aircraft. -- @function [parent=#AIRBOSS] RecoveryStop -- @param #AIRBOSS self @@ -2214,7 +2208,6 @@ function AIRBOSS:New(carriername, alias) -- @param #string Event Event. -- @param #string To To state. - --- Triggers the FSM event "RecoveryPause" that pauses the recovery of aircraft. -- @function [parent=#AIRBOSS] RecoveryPause -- @param #AIRBOSS self @@ -2235,7 +2228,6 @@ function AIRBOSS:New(carriername, alias) -- @param #AIRBOSS self -- @param #number delay Delay in seconds. - --- Triggers the FSM event "RecoveryCase" that switches the aircraft recovery case. -- @function [parent=#AIRBOSS] RecoveryCase -- @param #AIRBOSS self @@ -2249,7 +2241,6 @@ function AIRBOSS:New(carriername, alias) -- @param #number Case The new recovery case (1, 2 or 3). -- @param #number Offset Holding pattern offset angle in degrees for CASE II/III recoveries. - --- Triggers the FSM event "PassingWaypoint". Called when the carrier passes a waypoint. -- @function [parent=#AIRBOSS] PassingWaypoint -- @param #AIRBOSS self @@ -2270,7 +2261,6 @@ function AIRBOSS:New(carriername, alias) -- @param #string To To state. -- @param #number waypoint Number of waypoint. - --- Triggers the FSM event "Save" that saved the player scores to a file. -- @function [parent=#AIRBOSS] Save -- @param #AIRBOSS self @@ -2293,7 +2283,6 @@ function AIRBOSS:New(carriername, alias) -- @param #string path Path where the file is saved. Default is the DCS installation root directory or your "Saved Games\DCS" folder if lfs was desanitized. -- @param #string filename (Optional) File name. Default is AIRBOSS-*ALIAS*_LSOgrades.csv. - --- Triggers the FSM event "Load" that loads the player scores from a file. AIRBOSS FSM must **not** be started at this point. -- @function [parent=#AIRBOSS] Load -- @param #AIRBOSS self @@ -2316,7 +2305,6 @@ function AIRBOSS:New(carriername, alias) -- @param #string path Path where the file is located. Default is the DCS installation root directory or your "Saved Games\DCS" folder if lfs was desanitized. -- @param #string filename (Optional) File name. Default is AIRBOSS-*ALIAS*_LSOgrades.csv. - --- Triggers the FSM event "LSOGrade". Called when the LSO grades a player -- @function [parent=#AIRBOSS] LSOGrade -- @param #AIRBOSS self @@ -2339,7 +2327,6 @@ function AIRBOSS:New(carriername, alias) -- @param #AIRBOSS.PlayerData playerData Player Data. -- @param #AIRBOSS.LSOgrade grade LSO grade. - --- Triggers the FSM event "Marshal". Called when a flight is send to the Marshal stack. -- @function [parent=#AIRBOSS] Marshal -- @param #AIRBOSS self @@ -2359,7 +2346,6 @@ function AIRBOSS:New(carriername, alias) -- @param #string To To state. -- @param #AIRBOSS.FlightGroup flight The flight group data. - --- Triggers the FSM event "Stop" that stops the airboss. Event handlers are stopped. -- @function [parent=#AIRBOSS] Stop -- @param #AIRBOSS self @@ -2378,26 +2364,25 @@ end --- Set welcome messages for players. -- @param #AIRBOSS self --- @param #boolean switch If true, display welcome message to player. +-- @param #boolean Switch If true, display welcome message to player. -- @return #AIRBOSS self -function AIRBOSS:SetWelcomePlayers(switch) +function AIRBOSS:SetWelcomePlayers( Switch ) - self.welcome=switch + self.welcome = Switch return self end - --- Set carrier controlled area (CCA). -- This is a large zone around the carrier, which is constantly updated wrt the carrier position. -- @param #AIRBOSS self --- @param #number radius Radius of zone in nautical miles (NM). Default 50 NM. +-- @param #number Radius Radius of zone in nautical miles (NM). Default 50 NM. -- @return #AIRBOSS self -function AIRBOSS:SetCarrierControlledArea(radius) +function AIRBOSS:SetCarrierControlledArea( Radius ) - radius=UTILS.NMToMeters(radius or 50) + Radius = UTILS.NMToMeters( Radius or 50 ) - self.zoneCCA=ZONE_UNIT:New("Carrier Controlled Area", self.carrier, radius) + self.zoneCCA = ZONE_UNIT:New( "Carrier Controlled Area", self.carrier, Radius ) return self end @@ -2405,37 +2390,37 @@ end --- Set carrier controlled zone (CCZ). -- This is a small zone (usually 5 NM radius) around the carrier, which is constantly updated wrt the carrier position. -- @param #AIRBOSS self --- @param #number radius Radius of zone in nautical miles (NM). Default 5 NM. +-- @param #number Radius Radius of zone in nautical miles (NM). Default 5 NM. -- @return #AIRBOSS self -function AIRBOSS:SetCarrierControlledZone(radius) +function AIRBOSS:SetCarrierControlledZone( Radius ) - radius=UTILS.NMToMeters(radius or 5) + Radius = UTILS.NMToMeters( Radius or 5 ) - self.zoneCCZ=ZONE_UNIT:New("Carrier Controlled Zone", self.carrier, radius) + self.zoneCCZ = ZONE_UNIT:New( "Carrier Controlled Zone", self.carrier, Radius ) return self end --- Set distance up to which water ahead is scanned for collisions. -- @param #AIRBOSS self --- @param #number dist Distance in NM. Default 5 NM. +-- @param #number Distance Distance in NM. Default 5 NM. -- @return #AIRBOSS self -function AIRBOSS:SetCollisionDistance(distance) - self.collisiondist=UTILS.NMToMeters(distance or 5) +function AIRBOSS:SetCollisionDistance( Distance ) + self.collisiondist = UTILS.NMToMeters( Distance or 5 ) return self end --- Set the default recovery case. -- @param #AIRBOSS self --- @param #number case Case of recovery. Either 1, 2 or 3. Default 1. +-- @param #number Case Case of recovery. Either 1, 2 or 3. Default 1. -- @return #AIRBOSS self -function AIRBOSS:SetRecoveryCase(case) +function AIRBOSS:SetRecoveryCase( Case ) -- Set default case or 1. - self.defaultcase=case or 1 + self.defaultcase = Case or 1 -- Current case init. - self.case=self.defaultcase + self.case = self.defaultcase return self end @@ -2444,37 +2429,37 @@ end -- Usually, this is +-15 or +-30 degrees. You should not use and offset angle >= 90 degrees, because this will cause a devision by zero in some of the equations used to calculate the approach corridor. -- So best stick to the defaults up to 30 degrees. -- @param #AIRBOSS self --- @param #number offset Offset angle in degrees. Default 0. +-- @param #number Offset Offset angle in degrees. Default 0. -- @return #AIRBOSS self -function AIRBOSS:SetHoldingOffsetAngle(offset) +function AIRBOSS:SetHoldingOffsetAngle( Offset ) -- Set default angle or 0. - self.defaultoffset=offset or 0 + self.defaultoffset = Offset or 0 -- Current offset init. - self.holdingoffset=self.defaultoffset + self.holdingoffset = self.defaultoffset return self end --- Enable F10 menu to manually start recoveries. -- @param #AIRBOSS self --- @param #number duration Default duration of the recovery in minutes. Default 30 min. --- @param #number windondeck Default wind on deck in knots. Default 25 knots. --- @param #boolean uturn U-turn after recovery window closes on=true or off=false/nil. Default off. --- @param #number offset Relative Marshal radial in degrees for Case II/III recoveries. Default 30°. +-- @param #number Duration Default duration of the recovery in minutes. Default 30 min. +-- @param #number WindOnDeck Default wind on deck in knots. Default 25 knots. +-- @param #boolean Uturn U-turn after recovery window closes on=true or off=false/nil. Default off. +-- @param #number Offset Relative Marshal radial in degrees for Case II/III recoveries. Default 30°. -- @return #AIRBOSS self -function AIRBOSS:SetMenuRecovery(duration, windondeck, uturn, offset) +function AIRBOSS:SetMenuRecovery( Duration, WindOnDeck, Uturn, Offset ) - self.skipperMenu=true - self.skipperTime=duration or 30 - self.skipperSpeed=windondeck or 25 - self.skipperOffset=offset or 30 + self.skipperMenu = true + self.skipperTime = Duration or 30 + self.skipperSpeed = WindOnDeck or 25 + self.skipperOffset = Offset or 30 - if uturn then - self.skipperUturn=true + if Uturn then + self.skipperUturn = true else - self.skipperUturn=false + self.skipperUturn = false end return self @@ -2490,136 +2475,135 @@ end -- @param #number speed Speed in knots during turn into wind leg. -- @param #boolean uturn If true (or nil), carrier wil perform a U-turn and go back to where it came from before resuming its route to the next waypoint. If false, it will go directly to the next waypoint. -- @return #AIRBOSS.Recovery Recovery window. -function AIRBOSS:AddRecoveryWindow(starttime, stoptime, case, holdingoffset, turnintowind, speed, uturn) +function AIRBOSS:AddRecoveryWindow( starttime, stoptime, case, holdingoffset, turnintowind, speed, uturn ) -- Absolute mission time in seconds. - local Tnow=timer.getAbsTime() + local Tnow = timer.getAbsTime() - if starttime and type(starttime)=="number" then - starttime=UTILS.SecondsToClock(Tnow+starttime) + if starttime and type( starttime ) == "number" then + starttime = UTILS.SecondsToClock( Tnow + starttime ) end - if stoptime and type(stoptime)=="number" then - stoptime=UTILS.SecondsToClock(Tnow+stoptime) + if stoptime and type( stoptime ) == "number" then + stoptime = UTILS.SecondsToClock( Tnow + stoptime ) end - -- Input or now. - starttime=starttime or UTILS.SecondsToClock(Tnow) + starttime = starttime or UTILS.SecondsToClock( Tnow ) -- Set start time. - local Tstart=UTILS.ClockToSeconds(starttime) + local Tstart = UTILS.ClockToSeconds( starttime ) -- Set stop time. - local Tstop=stoptime and UTILS.ClockToSeconds(stoptime) or Tstart+90*60 + local Tstop = stoptime and UTILS.ClockToSeconds( stoptime ) or Tstart + 90 * 60 -- Consistancy check for timing. - if Tstart>Tstop then - self:E(string.format("ERROR: Recovery stop time %s lies before recovery start time %s! Recovery window rejected.", UTILS.SecondsToClock(Tstart), UTILS.SecondsToClock(Tstop))) + if Tstart > Tstop then + self:E( string.format( "ERROR: Recovery stop time %s lies before recovery start time %s! Recovery window rejected.", UTILS.SecondsToClock( Tstart ), UTILS.SecondsToClock( Tstop ) ) ) return self end - if Tstop<=Tnow then - self:I(string.format("WARNING: Recovery stop time %s already over. Tnow=%s! Recovery window rejected.", UTILS.SecondsToClock(Tstop), UTILS.SecondsToClock(Tnow))) + 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 or default value. - case=case or self.defaultcase + case = case or self.defaultcase -- Holding offset or default value. - holdingoffset=holdingoffset or self.defaultoffset + holdingoffset = holdingoffset or self.defaultoffset -- Offset zero for case I. - if case==1 then - holdingoffset=0 + if case == 1 then + holdingoffset = 0 end -- Increase counter. - self.windowcount=self.windowcount+1 + self.windowcount = self.windowcount + 1 -- Recovery window. - local recovery={} --#AIRBOSS.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 + local recovery = {} -- #AIRBOSS.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 + if uturn == nil or uturn == true then + recovery.UTURN = true else - recovery.UTURN=false + recovery.UTURN = false end -- Add to table - table.insert(self.recoverytimes, recovery) + table.insert( self.recoverytimes, recovery ) return recovery end --- Define a set of AI groups that are handled by the airboss. -- @param #AIRBOSS self --- @param Core.Set#SET_GROUP setgroup The set of AI groups which are handled by the airboss. +-- @param Core.Set#SET_GROUP SetGroup The set of AI groups which are handled by the airboss. -- @return #AIRBOSS self -function AIRBOSS:SetSquadronAI(setgroup) - self.squadsetAI=setgroup +function AIRBOSS:SetSquadronAI( SetGroup ) + self.squadsetAI = SetGroup return self end --- Define a set of AI groups that excluded from AI handling. Members of this set will be left allone by the airboss and not forced into the Marshal pattern. -- @param #AIRBOSS self --- @param Core.Set#SET_GROUP setgroup The set of AI groups which are excluded. +-- @param Core.Set#SET_GROUP SetGroup The set of AI groups which are excluded. -- @return #AIRBOSS self -function AIRBOSS:SetExcludeAI(setgroup) - self.excludesetAI=setgroup +function AIRBOSS:SetExcludeAI( SetGroup ) + self.excludesetAI = SetGroup return self end --- Add a group to the exclude set. If no set exists, it is created. -- @param #AIRBOSS self --- @param Wrapper.Group#GROUP group The group to be excluded. +-- @param Wrapper.Group#GROUP Group The group to be excluded. -- @return #AIRBOSS self -function AIRBOSS:AddExcludeAI(group) +function AIRBOSS:AddExcludeAI( Group ) - self.excludesetAI=self.excludesetAI or SET_GROUP:New() + self.excludesetAI = self.excludesetAI or SET_GROUP:New() - self.excludesetAI:AddGroup(group) + self.excludesetAI:AddGroup( Group ) return self end --- Close currently running recovery window and stop recovery ops. Recovery window is deleted. -- @param #AIRBOSS self --- @param #number delay (Optional) Delay in seconds before the window is deleted. -function AIRBOSS:CloseCurrentRecoveryWindow(delay) +-- @param #number Delay (Optional) Delay in seconds before the window is deleted. +function AIRBOSS:CloseCurrentRecoveryWindow( Delay ) - if delay and delay>0 then - --SCHEDULER:New(nil, self.CloseCurrentRecoveryWindow, {self}, delay) - self:ScheduleOnce(delay, self.CloseCurrentRecoveryWindow, self) + if Delay and Delay > 0 then + -- SCHEDULER:New(nil, self.CloseCurrentRecoveryWindow, {self}, delay) + 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) + self.recoverywindow.OPEN = false + self.recoverywindow.OVER = true + self:DeleteRecoveryWindow( self.recoverywindow ) end end end --- Delete all recovery windows. -- @param #AIRBOSS self --- @param #number delay (Optional) Delay in seconds before the windows are deleted. +-- @param #number Delay (Optional) Delay in seconds before the windows are deleted. -- @return #AIRBOSS self -function AIRBOSS:DeleteAllRecoveryWindows(delay) +function AIRBOSS:DeleteAllRecoveryWindows( Delay ) -- Loop over all recovery windows. - 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) + 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 @@ -2629,11 +2613,11 @@ end -- @param #AIRBOSS self -- @param #number id The ID of the recovery window. -- @return #AIRBOSS.Recovery Recovery window with the right ID or nil if no such window exists. -function AIRBOSS:GetRecoveryWindowByID(id) +function AIRBOSS:GetRecoveryWindowByID( id ) if id then - for _,_window in pairs(self.recoverytimes) do - local window=_window --#AIRBOSS.Recovery - if window and window.ID==id then + for _, _window in pairs( self.recoverytimes ) do + local window = _window -- #AIRBOSS.Recovery + if window and window.ID == id then return window end end @@ -2643,25 +2627,25 @@ end --- Delete a recovery window. If the window is currently open, it is closed and the recovery stopped. -- @param #AIRBOSS self --- @param #AIRBOSS.Recovery window Recovery window. --- @param #number delay Delay in seconds, before the window is deleted. -function AIRBOSS:DeleteRecoveryWindow(window, delay) +-- @param #AIRBOSS.Recovery Window Recovery window. +-- @param #number Delay Delay in seconds, before the window is deleted. +function AIRBOSS:DeleteRecoveryWindow( Window, Delay ) - if delay and delay>0 then + if Delay and Delay > 0 then -- Delayed call. - --SCHEDULER:New(nil, self.DeleteRecoveryWindow, {self, window}, delay) - self:ScheduleOnce(delay, self.DeleteRecoveryWindow, self, window) + -- SCHEDULER:New(nil, self.DeleteRecoveryWindow, {self, window}, delay) + self:ScheduleOnce( Delay, self.DeleteRecoveryWindow, self, Window ) else - for i,_recovery in pairs(self.recoverytimes) do - local recovery=_recovery --#AIRBOSS.Recovery + for i, _recovery in pairs( self.recoverytimes ) do + local recovery = _recovery -- #AIRBOSS.Recovery - if window and window.ID==recovery.ID then - if window.OPEN then + if Window and Window.ID == recovery.ID then + if Window.OPEN then -- Window is currently open. self:RecoveryStop() else - table.remove(self.recoverytimes, i) + table.remove( self.recoverytimes, i ) end end @@ -2671,10 +2655,10 @@ end --- Set time before carrier turns and recovery window opens. -- @param #AIRBOSS self --- @param #number interval Time interval in seconds. Default 300 sec. +-- @param #number Interval Time interval in seconds. Default 300 sec. -- @return #AIRBOSS self -function AIRBOSS:SetRecoveryTurnTime(interval) - self.dTturn=interval or 300 +function AIRBOSS:SetRecoveryTurnTime( Interval ) + self.dTturn = Interval or 300 return self end @@ -2682,145 +2666,142 @@ end -- @param #AIRBOSS self -- @param #number Dcorr Correction distance in meters. Default 12 m. -- @return #AIRBOSS self -function AIRBOSS:SetMPWireCorrection(Dcorr) - self.mpWireCorrection=Dcorr or 12 +function AIRBOSS:SetMPWireCorrection( Dcorr ) + self.mpWireCorrection = Dcorr or 12 return self end --- Set time interval for updating queues and other stuff. -- @param #AIRBOSS self --- @param #number interval Time interval in seconds. Default 30 sec. +-- @param #number TimeInterval Time interval in seconds. Default 30 sec. -- @return #AIRBOSS self -function AIRBOSS:SetQueueUpdateTime(interval) - self.dTqueue=interval or 30 +function AIRBOSS:SetQueueUpdateTime( TimeInterval ) + self.dTqueue = TimeInterval or 30 return self end --- Set time interval between LSO calls. Optimal time in the groove is ~16 seconds. So the default of 4 seconds gives around 3-4 correction calls in the groove. -- @param #AIRBOSS self --- @param #number interval Time interval in seconds between LSO calls. Default 4 sec. +-- @param #number TimeInterval Time interval in seconds between LSO calls. Default 4 sec. -- @return #AIRBOSS self -function AIRBOSS:SetLSOCallInterval(timeinterval) - self.LSOdT=timeinterval or 4 +function AIRBOSS:SetLSOCallInterval( TimeInterval ) + self.LSOdT = TimeInterval or 4 return self end --- Airboss is a rather nice guy and not strictly following the rules. Fore example, he does allow you into the landing pattern if you are not coming from the Marshal stack. -- @param #AIRBOSS self --- @param #boolean switch If true or nil, Airboss bends the rules a bit. +-- @param #boolean Switch If true or nil, Airboss bends the rules a bit. -- @return #AIRBOSS self -function AIRBOSS:SetAirbossNiceGuy(switch) - if switch==true or switch==nil then - self.airbossnice=true +function AIRBOSS:SetAirbossNiceGuy( Switch ) + if Switch == true or Switch == nil then + self.airbossnice = true else - self.airbossnice=false + self.airbossnice = false end return self end --- Allow emergency landings, i.e. bypassing any pattern and go directly to final approach. -- @param #AIRBOSS self --- @param #boolean switch If true or nil, emergency landings are okay. +-- @param #boolean Switch If true or nil, emergency landings are okay. -- @return #AIRBOSS self -function AIRBOSS:SetEmergencyLandings(switch) - if switch==true or switch==nil then - self.emergency=true +function AIRBOSS:SetEmergencyLandings( Switch ) + if Switch == true or Switch == nil then + self.emergency = true else - self.emergency=false + self.emergency = false end return self end - --- Despawn AI groups after they they shut down their engines. -- @param #AIRBOSS self --- @param #boolean switch If true or nil, AI groups are despawned. +-- @param #boolean Switch If true or nil, AI groups are despawned. -- @return #AIRBOSS self -function AIRBOSS:SetDespawnOnEngineShutdown(switch) - if switch==true or switch==nil then - self.despawnshutdown=true +function AIRBOSS:SetDespawnOnEngineShutdown( Switch ) + if Switch == true or Switch == nil then + self.despawnshutdown = true else - self.despawnshutdown=false + self.despawnshutdown = false end return self end --- Respawn AI groups once they reach the CCA. Clears any attached airbases and allows making them land on the carrier via script. -- @param #AIRBOSS self --- @param #boolean switch If true or nil, AI groups are respawned. +-- @param #boolean Switch If true or nil, AI groups are respawned. -- @return #AIRBOSS self -function AIRBOSS:SetRespawnAI(switch) - if switch==true or switch==nil then - self.respawnAI=true +function AIRBOSS:SetRespawnAI( Switch ) + if Switch == true or Switch == nil then + self.respawnAI = true else - self.respawnAI=false + self.respawnAI = false end return self end --- Give AI aircraft the refueling task if a recovery tanker is present or send them to the nearest divert airfield. -- @param #AIRBOSS self --- @param #number lowfuelthreshold Low fuel threshold in percent. AI will go refueling if their fuel level drops below this value. Default 10 %. +-- @param #number LowFuelThreshold Low fuel threshold in percent. AI will go refueling if their fuel level drops below this value. Default 10 %. -- @return #AIRBOSS self -function AIRBOSS:SetRefuelAI(lowfuelthreshold) - self.lowfuelAI=lowfuelthreshold or 10 +function AIRBOSS:SetRefuelAI( LowFuelThreshold ) + self.lowfuelAI = LowFuelThreshold or 10 return self end --- Set max alitude to register flights in the initial zone. Aircraft above this altitude will not be registerered. -- @param #AIRBOSS self --- @param #number altitude Max alitude in feet. Default 1300 ft. +-- @param #number MaxAltitude Max alitude in feet. Default 1300 ft. -- @return #AIRBOSS self -function AIRBOSS:SetInitialMaxAlt(altitude) - self.initialmaxalt=UTILS.FeetToMeters(altitude or 1300) +function AIRBOSS:SetInitialMaxAlt( MaxAltitude ) + self.initialmaxalt = UTILS.FeetToMeters( MaxAltitude or 1300 ) return self end - ---- Set folder where the airboss sound files are located **within you mission (miz) file**. +--- Set folder path where the airboss sound files are located **within you mission (miz) file**. -- The default path is "l10n/DEFAULT/" but sound files simply copied there will be removed by DCS the next time you save the mission. -- However, if you create a new folder inside the miz file, which contains the sounds, it will not be deleted and can be used. -- @param #AIRBOSS self --- @param #string folderpath The path to the sound files, e.g. "Airboss Soundfiles/". +-- @param #string FolderPath The path to the sound files, e.g. "Airboss Soundfiles/". -- @return #AIRBOSS self -function AIRBOSS:SetSoundfilesFolder(folderpath) +function AIRBOSS:SetSoundfilesFolder( FolderPath ) -- Check that it ends with / - if folderpath then - local lastchar=string.sub(folderpath, -1) - if lastchar~="/" then - folderpath=folderpath.."/" + if FolderPath then + local lastchar = string.sub( FolderPath, -1 ) + if lastchar ~= "/" then + FolderPath = FolderPath .. "/" end end -- Folderpath. - self.soundfolder=folderpath + self.soundfolder = FolderPath -- Info message. - self:I(self.lid..string.format("Setting sound files folder to: %s", self.soundfolder)) + self:I( self.lid .. string.format( "Setting sound files folder to: %s", self.soundfolder ) ) return self end --- Set time interval for updating player status and other things. -- @param #AIRBOSS self --- @param #number interval Time interval in seconds. Default 0.5 sec. +-- @param #number TimeInterval Time interval in seconds. Default 0.5 sec. -- @return #AIRBOSS self -function AIRBOSS:SetStatusUpdateTime(interval) - self.dTstatus=interval or 0.5 +function AIRBOSS:SetStatusUpdateTime( TimeInterval ) + self.dTstatus = TimeInterval or 0.5 return self end --- Set duration how long messages are displayed to players. -- @param #AIRBOSS self --- @param #number duration Duration in seconds. Default 10 sec. +-- @param #number Duration Duration in seconds. Default 10 sec. -- @return #AIRBOSS self -function AIRBOSS:SetDefaultMessageDuration(duration) - self.Tmessage=duration or 10 +function AIRBOSS:SetDefaultMessageDuration( Duration ) + self.Tmessage = Duration or 10 return self end - --- Set glideslope error thresholds. -- @param #AIRBOSS self -- @param #number _max @@ -2830,13 +2811,13 @@ end -- @param #number Low -- @param #number LOW -- @return #AIRBOSS self -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 +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 @@ -2851,118 +2832,117 @@ end -- @param #number RightMed -- @param #number RIGHT -- @return #AIRBOSS self -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 +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 --- Set Case I Marshal radius. This is the radius of the valid zone around "the post" aircraft are supposed to be holding in the Case I Marshal stack. -- The post is 2.5 NM port of the carrier. -- @param #AIRBOSS self --- @param #number Radius in NM. Default 2.8 NM, which gives a diameter of 5.6 NM. +-- @param #number Radius Radius in NM. Default 2.8 NM, which gives a diameter of 5.6 NM. -- @return #AIRBOSS self -function AIRBOSS:SetMarshalRadius(radius) - self.marshalradius=UTILS.NMToMeters(radius or 2.8) +function AIRBOSS:SetMarshalRadius( Radius ) + self.marshalradius = UTILS.NMToMeters( Radius or 2.8 ) return self end --- Optimized F10 radio menu for a single carrier. The menu entries will be stored directly under F10 Other/Airboss/ and not F10 Other/Airboss/"Carrier Alias"/. -- **WARNING**: If you use this with two airboss objects/carriers, the radio menu will be screwed up! -- @param #AIRBOSS self --- @param #boolean switch If true or nil single menu is enabled. If false, menu is for multiple carriers in the mission. +-- @param #boolean Switch If true or nil single menu is enabled. If false, menu is for multiple carriers in the mission. -- @return #AIRBOSS self -function AIRBOSS:SetMenuSingleCarrier(switch) - if switch==true or switch==nil then - self.menusingle=true +function AIRBOSS:SetMenuSingleCarrier( Switch ) + if Switch == true or Switch == nil then + self.menusingle = true else - self.menusingle=false + self.menusingle = false end return self end --- Enable or disable F10 radio menu for marking zones via smoke or flares. -- @param #AIRBOSS self --- @param #boolean switch If true or nil, menu is enabled. If false, menu is not available to players. +-- @param #boolean Switch If true or nil, menu is enabled. If false, menu is not available to players. -- @return #AIRBOSS self -function AIRBOSS:SetMenuMarkZones(switch) - if switch==nil or switch==true then - self.menumarkzones=true +function AIRBOSS:SetMenuMarkZones( Switch ) + if Switch == nil or Switch == true then + self.menumarkzones = true else - self.menumarkzones=false + self.menumarkzones = false end return self end --- Enable or disable F10 radio menu for marking zones via smoke. -- @param #AIRBOSS self --- @param #boolean switch If true or nil, menu is enabled. If false, menu is not available to players. +-- @param #boolean Switch If true or nil, menu is enabled. If false, menu is not available to players. -- @return #AIRBOSS self -function AIRBOSS:SetMenuSmokeZones(switch) - if switch==nil or switch==true then - self.menusmokezones=true +function AIRBOSS:SetMenuSmokeZones( Switch ) + if Switch == nil or Switch == true then + self.menusmokezones = true else - self.menusmokezones=false + self.menusmokezones = false end return self end --- Enable saving of player's trap sheets and specify an optional directory path. -- @param #AIRBOSS self --- @param #string path (Optional) Path where to save the trap sheets. --- @param #string prefix (Optional) Prefix for trap sheet files. File name will be saved as *prefix_aircrafttype-0001.csv*, *prefix_aircrafttype-0002.csv*, etc. +-- @param #string Path (Optional) Path where to save the trap sheets. +-- @param #string Prefix (Optional) Prefix for trap sheet files. File name will be saved as *prefix_aircrafttype-0001.csv*, *prefix_aircrafttype-0002.csv*, etc. -- @return #AIRBOSS self -function AIRBOSS:SetTrapSheet(path, prefix) +function AIRBOSS:SetTrapSheet( Path, Prefix ) if io then - self.trapsheet=true - self.trappath=path - self.trapprefix=prefix + self.trapsheet = true + self.trappath = Path + self.trapprefix = Prefix else - self:E(self.lid.."ERROR: io is not desanitized. Cannot save trap sheet.") + self:E( self.lid .. "ERROR: io is not desanitized. Cannot save trap sheet." ) end return self end --- Specify weather the mission has set static or dynamic weather. -- @param #AIRBOSS self --- @param #boolean switch If true or nil, mission uses static weather. If false, dynamic weather is used in this mission. +-- @param #boolean Switch If true or nil, mission uses static weather. If false, dynamic weather is used in this mission. -- @return #AIRBOSS self -function AIRBOSS:SetStaticWeather(switch) - if switch==nil or switch==true then - self.staticweather=true +function AIRBOSS:SetStaticWeather( Switch ) + if Switch == nil or Switch == true then + self.staticweather = true else - self.staticweather=false + self.staticweather = false end return self end - --- Disable automatic TACAN activation -- @param #AIRBOSS self -- @return #AIRBOSS self function AIRBOSS:SetTACANoff() - self.TACANon=false + self.TACANon = false return self end ---- Set TACAN channel of carrier. +--- Set TACAN channel of carrier and switches TACAN on. -- @param #AIRBOSS self --- @param #number channel TACAN channel. Default 74. --- @param #string mode TACAN mode, i.e. "X" or "Y". Default "X". --- @param #string morsecode Morse code identifier. Three letters, e.g. "STN". +-- @param #number Channel (Optional) TACAN channel. Default 74. +-- @param #string Mode (Optional) TACAN mode, i.e. "X" or "Y". Default "X". +-- @param #string MorseCode (Optional) Morse code identifier. Three letters, e.g. "STN". Default "STN". -- @return #AIRBOSS self -function AIRBOSS:SetTACAN(channel, mode, morsecode) +function AIRBOSS:SetTACAN( Channel, Mode, MorseCode ) - self.TACANchannel=channel or 74 - self.TACANmode=mode or "X" - self.TACANmorse=morsecode or "STN" - self.TACANon=true + self.TACANchannel = Channel or 74 + self.TACANmode = Mode or "X" + self.TACANmorse = MorseCode or "STN" + self.TACANon = true return self end @@ -2971,79 +2951,77 @@ end -- @param #AIRBOSS self -- @return #AIRBOSS self function AIRBOSS:SetICLSoff() - self.ICLSon=false + self.ICLSon = false return self end --- Set ICLS channel of carrier. -- @param #AIRBOSS self --- @param #number channel ICLS channel. Default 1. --- @param #string morsecode Morse code identifier. Three letters, e.g. "STN". Default "STN". +-- @param #number Channel (Optional) ICLS channel. Default 1. +-- @param #string MorseCode (Optional) Morse code identifier. Three letters, e.g. "STN". Default "STN". -- @return #AIRBOSS self -function AIRBOSS:SetICLS(channel, morsecode) +function AIRBOSS:SetICLS( Channel, MorseCode ) - self.ICLSchannel=channel or 1 - self.ICLSmorse=morsecode or "STN" - self.ICLSon=true + self.ICLSchannel = Channel or 1 + self.ICLSmorse = MorseCode or "STN" + self.ICLSon = true return self end - --- Set beacon (TACAN/ICLS) time refresh interfal in case the beacons die. -- @param #AIRBOSS self --- @param #number interval Time interval in seconds. Default 1200 sec = 20 min. +-- @param #number TimeInterval (Optional) Time interval in seconds. Default 1200 sec = 20 min. -- @return #AIRBOSS self -function AIRBOSS:SetBeaconRefresh(interval) - self.dTbeacon=interval or 20*60 +function AIRBOSS:SetBeaconRefresh( TimeInterval ) + self.dTbeacon = TimeInterval or (20 * 60) return self end - --- Set LSO radio frequency and modulation. Default frequency is 264 MHz AM. -- @param #AIRBOSS self --- @param #number frequency Frequency in MHz. Default 264 MHz. --- @param #string modulation Modulation, i.e. "AM" (default) or "FM". +-- @param #number Frequency (Optional) Frequency in MHz. Default 264 MHz. +-- @param #string Modulation (Optional) Modulation, "AM" or "FM". Default "AM". -- @return #AIRBOSS self -function AIRBOSS:SetLSORadio(frequency, modulation) +function AIRBOSS:SetLSORadio( Frequency, Modulation ) - self.LSOFreq=(frequency or 264) - modulation=modulation or "AM" + self.LSOFreq = (Frequency or 264) + Modulation = Modulation or "AM" - if modulation=="FM" then - self.LSOModu=radio.modulation.FM + if Modulation == "FM" then + self.LSOModu = radio.modulation.FM else - self.LSOModu=radio.modulation.AM + self.LSOModu = radio.modulation.AM end - self.LSORadio={} --#AIRBOSS.Radio - self.LSORadio.frequency=self.LSOFreq - self.LSORadio.modulation=self.LSOModu - self.LSORadio.alias="LSO" + self.LSORadio = {} -- #AIRBOSS.Radio + self.LSORadio.frequency = self.LSOFreq + self.LSORadio.modulation = self.LSOModu + self.LSORadio.alias = "LSO" return self end --- Set carrier radio frequency and modulation. Default frequency is 305 MHz AM. -- @param #AIRBOSS self --- @param #number frequency Frequency in MHz. Default 305 MHz. --- @param #string modulation Modulation, i.e. "AM" (default) or "FM". +-- @param #number Frequency (Optional) Frequency in MHz. Default 305 MHz. +-- @param #string Modulation (Optional) Modulation, "AM" or "FM". Default "AM". -- @return #AIRBOSS self -function AIRBOSS:SetMarshalRadio(frequency, modulation) +function AIRBOSS:SetMarshalRadio( Frequency, Modulation ) - self.MarshalFreq=frequency or 305 - modulation=modulation or "AM" + self.MarshalFreq = Frequency or 305 + Modulation = Modulation or "AM" - if modulation=="FM" then - self.MarshalModu=radio.modulation.FM + if Modulation == "FM" then + self.MarshalModu = radio.modulation.FM else - self.MarshalModu=radio.modulation.AM + self.MarshalModu = radio.modulation.AM end - self.MarshalRadio={} --#AIRBOSS.Radio - self.MarshalRadio.frequency=self.MarshalFreq - self.MarshalRadio.modulation=self.MarshalModu - self.MarshalRadio.alias="MARSHAL" + self.MarshalRadio = {} -- #AIRBOSS.Radio + self.MarshalRadio.frequency = self.MarshalFreq + self.MarshalRadio.modulation = self.MarshalModu + self.MarshalRadio.alias = "MARSHAL" return self end @@ -3052,8 +3030,8 @@ end -- @param #AIRBOSS self -- @param #string unitname Name of the unit. -- @return #AIRBOSS self -function AIRBOSS:SetRadioUnitName(unitname) - self.senderac=unitname +function AIRBOSS:SetRadioUnitName( unitname ) + self.senderac = unitname return self end @@ -3061,8 +3039,8 @@ end -- @param #AIRBOSS self -- @param #string unitname Name of the unit. -- @return #AIRBOSS self -function AIRBOSS:SetRadioRelayLSO(unitname) - self.radiorelayLSO=unitname +function AIRBOSS:SetRadioRelayLSO( unitname ) + self.radiorelayLSO = unitname return self end @@ -3070,17 +3048,16 @@ end -- @param #AIRBOSS self -- @param #string unitname Name of the unit. -- @return #AIRBOSS self -function AIRBOSS:SetRadioRelayMarshal(unitname) - self.radiorelayMSH=unitname +function AIRBOSS:SetRadioRelayMarshal( unitname ) + self.radiorelayMSH = unitname return self end - --- Use user sound output instead of radio transmission for messages. Might be handy if radio transmissions are broken. -- @param #AIRBOSS self -- @return #AIRBOSS self function AIRBOSS:SetUserSoundRadio() - self.usersoundradio=true + self.usersoundradio = true return self end @@ -3088,34 +3065,33 @@ end -- @param #AIRBOSS self -- @param #number delay Delay in seconds be sound check starts. -- @return #AIRBOSS self -function AIRBOSS:SoundCheckLSO(delay) +function AIRBOSS:SoundCheckLSO( delay ) - if delay and delay>0 then + if delay and delay > 0 then -- Delayed call. - --SCHEDULER:New(nil, AIRBOSS.SoundCheckLSO, {self}, delay) - self:ScheduleOnce(delay, AIRBOSS.SoundCheckLSO, self) + -- SCHEDULER:New(nil, AIRBOSS.SoundCheckLSO, {self}, delay) + self:ScheduleOnce( delay, AIRBOSS.SoundCheckLSO, self ) else + local text = "Playing LSO sound files:" - local text="Playing LSO sound files:" - - for _name,_call in pairs(self.LSOCall) do - local call=_call --#AIRBOSS.RadioCall + for _name, _call in pairs( self.LSOCall ) do + local call = _call -- #AIRBOSS.RadioCall -- Debug text. - 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) + 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 ) -- Radio transmission to queue. - self:RadioTransmission(self.LSORadio, call, false) + self:RadioTransmission( self.LSORadio, call, false ) -- Also play the loud version. if call.loud then - self:RadioTransmission(self.LSORadio, call, true) + self:RadioTransmission( self.LSORadio, call, true ) end end -- Debug message. - self:I(self.lid..text) + self:I( self.lid .. text ) end end @@ -3124,34 +3100,33 @@ end -- @param #AIRBOSS self -- @param #number delay Delay in seconds be sound check starts. -- @return #AIRBOSS self -function AIRBOSS:SoundCheckMarshal(delay) +function AIRBOSS:SoundCheckMarshal( delay ) - if delay and delay>0 then + if delay and delay > 0 then -- Delayed call. - --SCHEDULER:New(nil, AIRBOSS.SoundCheckMarshal, {self}, delay) - self:ScheduleOnce(delay, AIRBOSS.SoundCheckMarshal, self) + -- SCHEDULER:New(nil, AIRBOSS.SoundCheckMarshal, {self}, delay) + self:ScheduleOnce( delay, AIRBOSS.SoundCheckMarshal, self ) else + local text = "Playing Marshal sound files:" - local text="Playing Marshal sound files:" - - for _name,_call in pairs(self.MarshalCall) do - local call=_call --#AIRBOSS.RadioCall + for _name, _call in pairs( self.MarshalCall ) do + local call = _call -- #AIRBOSS.RadioCall -- Debug text. - 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) + 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 ) -- Radio transmission to queue. - self:RadioTransmission(self.MarshalRadio, call, false) + self:RadioTransmission( self.MarshalRadio, call, false ) -- Also play the loud version. if call.loud then - self:RadioTransmission(self.MarshalRadio, call, true) + self:RadioTransmission( self.MarshalRadio, call, true ) end end -- Debug message. - self:I(self.lid..text) + self:I( self.lid .. text ) end end @@ -3160,11 +3135,11 @@ end -- @param #AIRBOSS self -- @param #number nmax Max number. Default 4. Minimum is 1, maximum is 6. -- @return #AIRBOSS self -function AIRBOSS:SetMaxLandingPattern(nmax) - nmax=nmax or 4 - nmax=math.max(nmax,1) - nmax=math.min(nmax,6) - self.Nmaxpattern=nmax +function AIRBOSS:SetMaxLandingPattern( nmax ) + nmax = nmax or 4 + nmax = math.max( nmax, 1 ) + nmax = math.min( nmax, 6 ) + self.Nmaxpattern = nmax return self end @@ -3173,9 +3148,9 @@ end -- @param #AIRBOSS self -- @param #number nmax Max number of stacks available to players and AI flights. Default 3, i.e. angels 2, 3, 4. Minimum is 1. -- @return #AIRBOSS self -function AIRBOSS:SetMaxMarshalStacks(nmax) - self.Nmaxmarshal=nmax or 3 - self.Nmaxmarshal=math.max(self.Nmaxmarshal, 1) +function AIRBOSS:SetMaxMarshalStacks( nmax ) + self.Nmaxmarshal = nmax or 3 + self.Nmaxmarshal = math.max( self.Nmaxmarshal, 1 ) return self end @@ -3183,11 +3158,11 @@ end -- @param #AIRBOSS self -- @param #number nmax Number of max allowed members including the lead itself. For example, Nmax=2 means a section lead plus one member. -- @return #AIRBOSS self -function AIRBOSS:SetMaxSectionSize(nmax) - nmax=nmax or 2 - nmax=math.max(nmax,1) - nmax=math.min(nmax,4) - self.NmaxSection=nmax-1 -- We substract one because internally the section lead is not counted! +function AIRBOSS:SetMaxSectionSize( nmax ) + nmax = nmax or 2 + nmax = math.max( nmax, 1 ) + nmax = math.min( nmax, 4 ) + self.NmaxSection = nmax - 1 -- We substract one because internally the section lead is not counted! return self end @@ -3195,20 +3170,19 @@ end -- @param #AIRBOSS self -- @param #number nmax Number of max allowed flights per stack. Default is two. Minimum is one, maximum is 4. -- @return #AIRBOSS self -function AIRBOSS:SetMaxFlightsPerStack(nmax) - nmax=nmax or 2 - nmax=math.max(nmax,1) - nmax=math.min(nmax,4) - self.NmaxStack=nmax +function AIRBOSS:SetMaxFlightsPerStack( nmax ) + nmax = nmax or 2 + nmax = math.max( nmax, 1 ) + nmax = math.min( nmax, 4 ) + self.NmaxStack = nmax return self end - --- Handle AI aircraft. -- @param #AIRBOSS self -- @return #AIRBOSS self function AIRBOSS:SetHandleAION() - self.handleai=true + self.handleai = true return self end @@ -3216,17 +3190,16 @@ end -- @param #AIRBOSS self -- @return #AIRBOSS self function AIRBOSS:SetHandleAIOFF() - self.handleai=false + self.handleai = false return self end - --- Define recovery tanker associated with the carrier. -- @param #AIRBOSS self -- @param Ops.RecoveryTanker#RECOVERYTANKER recoverytanker Recovery tanker object. -- @return #AIRBOSS self -function AIRBOSS:SetRecoveryTanker(recoverytanker) - self.tanker=recoverytanker +function AIRBOSS:SetRecoveryTanker( recoverytanker ) + self.tanker = recoverytanker return self end @@ -3234,8 +3207,8 @@ end -- @param #AIRBOSS self -- @param Ops.RecoveryTanker#RECOVERYTANKER awacs AWACS (recovery tanker) object. -- @return #AIRBOSS self -function AIRBOSS:SetAWACS(awacs) - self.awacs=awacs +function AIRBOSS:SetAWACS( awacs ) + self.awacs = awacs return self end @@ -3247,23 +3220,23 @@ end -- @param #AIRBOSS self -- @param #string skill Player skill. Default "Naval Aviator". -- @return #AIRBOSS self -function AIRBOSS:SetDefaultPlayerSkill(skill) +function AIRBOSS:SetDefaultPlayerSkill( skill ) -- Set skill or normal. - self.defaultskill=skill or AIRBOSS.Difficulty.NORMAL + self.defaultskill = skill or AIRBOSS.Difficulty.NORMAL -- Check that defualt skill is valid. - local gotit=false - for _,_skill in pairs(AIRBOSS.Difficulty) do - if _skill==self.defaultskill then - gotit=true + local gotit = false + for _, _skill in pairs( AIRBOSS.Difficulty ) do + if _skill == self.defaultskill then + gotit = true end end -- If invalid user input, fall back to normal. 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))) + 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 @@ -3274,10 +3247,10 @@ end -- @param #string path Path where to save the asset data file. Default is the DCS root installation directory or your "Saved Games\\DCS" folder if lfs was desanitized. -- @param #string filename File name. Default is generated automatically from airboss carrier name/alias. -- @return #AIRBOSS self -function AIRBOSS:SetAutoSave(path, filename) - self.autosave=true - self.autosavepath=path - self.autosavefile=filename +function AIRBOSS:SetAutoSave( path, filename ) + self.autosave = true + self.autosavepath = path + self.autosavefile = filename return self end @@ -3285,7 +3258,7 @@ end -- @param #AIRBOSS self -- @return #AIRBOSS self function AIRBOSS:SetDebugModeON() - self.Debug=true + self.Debug = true return self end @@ -3293,11 +3266,11 @@ end -- @param #AIRBOSS self -- @param #boolean switch If true or nil, patrol until the end of time. If false, go along the waypoints once and stop. -- @return #AIRBOSS self -function AIRBOSS:SetPatrolAdInfinitum(switch) - if switch==false then - self.adinfinitum=false +function AIRBOSS:SetPatrolAdInfinitum( switch ) + if switch == false then + self.adinfinitum = false else - self.adinfinitum=true + self.adinfinitum = true end return self end @@ -3306,8 +3279,8 @@ end -- @param #AIRBOSS self -- @param #number declination Declination in degrees or nil for default declination of the map. -- @return #AIRBOSS self -function AIRBOSS:SetMagneticDeclination(declination) - self.magvar=declination or UTILS.GetMagneticDeclination() +function AIRBOSS:SetMagneticDeclination( declination ) + self.magvar = declination or UTILS.GetMagneticDeclination() return self end @@ -3315,7 +3288,7 @@ end -- @param #AIRBOSS self -- @return #AIRBOSS self function AIRBOSS:SetDebugModeOFF() - self.Debug=false + self.Debug = false return self end @@ -3324,12 +3297,12 @@ end -- @param #boolean InSeconds If true, abs. mission time seconds is returned. Default is a clock #string. -- @return #string Clock start (or start time in abs. seconds). -- @return #string Clock stop (or stop time in abs. seconds). -function AIRBOSS:GetNextRecoveryTime(InSeconds) +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) + return UTILS.SecondsToClock( self.recoverywindow.START ), UTILS.SecondsToClock( self.recoverywindow.STOP ) end else if InSeconds then @@ -3344,42 +3317,42 @@ end -- @param #AIRBOSS self -- @return #boolean If true, time slot for recovery is open. function AIRBOSS:IsRecovering() - return self:is("Recovering") + return self:is( "Recovering" ) end --- Check if carrier is idle, i.e. no operations are carried out. -- @param #AIRBOSS self -- @return #boolean If true, carrier is in idle state. function AIRBOSS:IsIdle() - return self:is("Idle") + return self:is( "Idle" ) end --- Check if recovery of aircraft is paused. -- @param #AIRBOSS self -- @return #boolean If true, recovery is paused function AIRBOSS:IsPaused() - return self:is("Paused") + return self:is( "Paused" ) end --- Activate TACAN and ICLS beacons. -- @param #AIRBOSS self function AIRBOSS:_ActivateBeacons() - self:T(self.lid..string.format("Activating Beacons (TACAN=%s, ICLS=%s)", tostring(self.TACANon), tostring(self.ICLSon))) + self:T( self.lid .. string.format( "Activating Beacons (TACAN=%s, ICLS=%s)", tostring( self.TACANon ), tostring( self.ICLSon ) ) ) -- Activate TACAN. 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) + 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 -- Activate ICLS. 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) + self:I( self.lid .. string.format( "Activating ICLS Channel %d (%s)", self.ICLSchannel, self.ICLSmorse ) ) + self.beacon:ActivateICLS( self.ICLSchannel, self.ICLSmorse ) end -- Set time stamp. - self.Tbeacon=timer.getTime() + self.Tbeacon = timer.getTime() end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -3391,63 +3364,63 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function AIRBOSS:onafterStart(From, Event, To) +function AIRBOSS:onafterStart( From, Event, To ) -- Events are handled my MOOSE. - 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: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 ) ) -- Activate TACAN and ICLS if desired. self:_ActivateBeacons() -- Schedule radio queue checks. - --self.RQLid=self.radiotimer:Schedule(nil, AIRBOSS._CheckRadioQueue, {self, self.RQLSO, "LSO"}, 1, 0.1) - --self.RQMid=self.radiotimer:Schedule(nil, AIRBOSS._CheckRadioQueue, {self, self.RQMarshal, "MARSHAL"}, 1, 0.1) + -- self.RQLid=self.radiotimer:Schedule(nil, AIRBOSS._CheckRadioQueue, {self, self.RQLSO, "LSO"}, 1, 0.1) + -- self.RQMid=self.radiotimer:Schedule(nil, AIRBOSS._CheckRadioQueue, {self, self.RQMarshal, "MARSHAL"}, 1, 0.1) - --self:I("FF: starting timer.scheduleFunction") - --timer.scheduleFunction(AIRBOSS._CheckRadioQueueT, {airboss=self, radioqueue=self.RQLSO, name="LSO"}, timer.getTime()+1) - --timer.scheduleFunction(AIRBOSS._CheckRadioQueueT, {airboss=self, radioqueue=self.RQMarshal, name="MARSHAL"}, timer.getTime()+1) + -- self:I("FF: starting timer.scheduleFunction") + -- timer.scheduleFunction(AIRBOSS._CheckRadioQueueT, {airboss=self, radioqueue=self.RQLSO, name="LSO"}, timer.getTime()+1) + -- timer.scheduleFunction(AIRBOSS._CheckRadioQueueT, {airboss=self, radioqueue=self.RQMarshal, name="MARSHAL"}, timer.getTime()+1) -- Initial carrier position and orientation. - self.Cposition=self:GetCoordinate() - self.Corientation=self.carrier:GetOrientationX() - self.Corientlast=self.Corientation - self.Tpupdate=timer.getTime() + self.Cposition = self:GetCoordinate() + self.Corientation = self.carrier:GetOrientationX() + self.Corientlast = self.Corientation + self.Tpupdate = timer.getTime() -- Check if no recovery window is set. DISABLED! - if #self.recoverytimes==0 and false then + if #self.recoverytimes == 0 and false then -- Open window in 15 minutes for 3 hours. - local Topen=timer.getAbsTime()+15*60 - local Tclose=Topen+3*60*60 + local Topen = timer.getAbsTime() + 15 * 60 + local Tclose = Topen + 3 * 60 * 60 -- Add window. - self:AddRecoveryWindow(UTILS.SecondsToClock(Topen), UTILS.SecondsToClock(Tclose)) + self:AddRecoveryWindow( UTILS.SecondsToClock( Topen ), UTILS.SecondsToClock( Tclose ) ) end -- Check Recovery time.s self:_CheckRecoveryTimes() -- Time stamp for checking queues. We substract 60 seconds so the routine is called right after status is called the first time. - self.Tqueue=timer.getTime()-60 + self.Tqueue = timer.getTime() - 60 -- Handle events. - 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: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.StatusScheduler=SCHEDULER:New(self) - --self.StatusScheduler:Schedule(self, self._Status, {}, 1, 0.5) + -- self.StatusScheduler=SCHEDULER:New(self) + -- self.StatusScheduler:Schedule(self, self._Status, {}, 1, 0.5) - self.StatusTimer=TIMER:New(self._Status, self):Start(2, 0.5) + self.StatusTimer = TIMER:New( self._Status, self ):Start( 2, 0.5 ) -- Start status check in 1 second. - self:__Status(1) + self:__Status( 1 ) end --- On after Status event. Checks for new flights, updates queue and checks player status. @@ -3455,65 +3428,64 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function AIRBOSS:onafterStatus(From, Event, To) +function AIRBOSS:onafterStatus( From, Event, To ) -- Get current time. - local time=timer.getTime() + local time = timer.getTime() -- Update marshal and pattern queue every 30 seconds. - if time-self.Tqueue>self.dTqueue then + if time - self.Tqueue > self.dTqueue then -- Get time. - local clock=UTILS.SecondsToClock(timer.getAbsTime()) - local eta=UTILS.SecondsToClock(self:_GetETAatNextWP()) + local clock = UTILS.SecondsToClock( timer.getAbsTime() ) + local eta = UTILS.SecondsToClock( self:_GetETAatNextWP() ) -- Current heading and position of the carrier. - local hdg=self:GetHeading() - local pos=self:GetCoordinate() - local speed=self.carrier:GetVelocityKNOTS() + local hdg = self:GetHeading() + local pos = self:GetCoordinate() + local speed = self.carrier:GetVelocityKNOTS() -- Check water is ahead. - local collision=false --self:_CheckCollisionCoord(pos:Translate(self.collisiondist, hdg)) + local collision = false -- self:_CheckCollisionCoord(pos:Translate(self.collisiondist, hdg)) - local holdtime=0 + local holdtime = 0 if self.holdtimestamp then - holdtime=timer.getTime()-self.holdtimestamp + holdtime = timer.getTime() - self.holdtimestamp end -- Check if carrier is stationary. - 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 + 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() + 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) - --coord:MarkToAll("Re-route after standstill.") - self:CarrierResumeRoute(coord) - self.holdtimestamp=nil + if holdtime > 3 * 60 then + local coord = self:GetCoordinate():Translate( 500, hdg + 10 ) + -- coord:MarkToAll("Re-route after standstill.") + self:CarrierResumeRoute( coord ) + self.holdtimestamp = nil end end end -- Debug info. - 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) + 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 ) -- Players online: - text="Players:" - local i=0 - for _name,_player in pairs(self.players) do - i=i+1 - local player=_player --#AIRBOSS.FlightGroup - 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)) + text = "Players:" + local i = 0 + for _name, _player in pairs( self.players ) do + i = i + 1 + local player = _player -- #AIRBOSS.FlightGroup + 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" + if i == 0 then + text = text .. " none" end - self:I(self.lid..text) + self:I( self.lid .. text ) -- Check for collision. if collision then @@ -3522,24 +3494,23 @@ function AIRBOSS:onafterStatus(From, Event, To) if self.turnintowind then -- Carrier resumes its initial route. This disables turnintowind switch. - self:CarrierResumeRoute(self.Creturnto) + self:CarrierResumeRoute( self.Creturnto ) -- Since current window would stay open, we disable the WIND switch. if self:IsRecovering() and self.recoverywindow and self.recoverywindow.WIND then -- Disable turn into the wind for this window so that we do not do this all over again. - self.recoverywindow.WIND=false + self.recoverywindow.WIND = false end end end - -- Check recovery times and start/stop recovery mode if necessary. self:_CheckRecoveryTimes() -- Remove dead/zombie flight groups. Player leaving the server whilst in pattern etc. - --self:_RemoveDeadFlightGroups() + -- self:_RemoveDeadFlightGroups() -- Scan carrier zone for new aircraft. self:_ScanCarrierZone() @@ -3554,16 +3525,16 @@ function AIRBOSS:onafterStatus(From, Event, To) self:_CheckPatternUpdate() -- Time stamp. - self.Tqueue=time + self.Tqueue = time end -- (Re-)activate TACAN and ICLS channels. - if time-self.Tbeacon>self.dTbeacon then + if time - self.Tbeacon > self.dTbeacon then self:_ActivateBeacons() end -- Call status every ~0.5 seconds. - self:__Status(-30) + self:__Status( -30 ) end @@ -3584,27 +3555,27 @@ end function AIRBOSS:_CheckAIStatus() -- Loop over all flights in Marshal stack. - for _,_flight in pairs(self.Qmarshal) do - local flight=_flight --#AIRBOSS.FlightGroup + for _, _flight in pairs( self.Qmarshal ) do + local flight = _flight -- #AIRBOSS.FlightGroup -- Only AI! if flight.ai then -- Get fuel amount in %. - local fuel=flight.group:GetFuelMin()*100 + local fuel = flight.group:GetFuelMin() * 100 -- Debug text. - local text=string.format("Group %s fuel=%.1f %%", flight.groupname, fuel) - self:T3(self.lid..text) + local text = string.format( "Group %s fuel=%.1f %%", flight.groupname, fuel ) + self:T3( self.lid .. text ) -- Check if flight is low on fuel and not yet refueling. - if self.lowfuelAI and fuel=recovery.START then + if time >= recovery.START then -- Start time has passed. - if time0 then + if npattern > 0 then -- Extend recovery time. 5 min per flight. - local extmin=5*npattern - recovery.STOP=recovery.STOP+extmin*60 + 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) + 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 -- Set carrier to idle. self:RecoveryStop() - state="closing now" + state = "closing now" -- Closed. - recovery.OPEN=false + recovery.OPEN = false -- Window just closed. - recovery.OVER=true + recovery.OVER = true end else -- Carrier is already idle. - state="closed" + state = "closed" end end else -- This recovery is in the future. - state="in the future" + state = "in the future" -- This is the next to come as we sorted by start time. - if nextwindow==nil then - nextwindow=recovery - state="next in line" + if nextwindow == nil then + nextwindow = recovery + state = "next in line" end end -- Debug text. - 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) + 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 -- Debug output. - self:T(self.lid..text) + self:T( self.lid .. text ) -- Current recovery window. - self.recoverywindow=nil - + self.recoverywindow = nil if self:IsIdle() then ----------------------------------------------------------------------------------------------------------------- @@ -3897,48 +3869,48 @@ function AIRBOSS:_CheckRecoveryTimes() if nextwindow then -- Set case and offset of the next window. - self:RecoveryCase(nextwindow.CASE, nextwindow.OFFSET) + self:RecoveryCase( nextwindow.CASE, nextwindow.OFFSET ) -- Check if time is less than 5 minutes. - if nextwindow.WIND and nextwindow.START-time 5° different from the current heading. - local hdg=self:GetHeading() - local wind=self:GetHeadingIntoWind() - local delta=self:_GetDeltaHeading(hdg, wind) - local uturn=delta>5 + local hdg = self:GetHeading() + local wind = self:GetHeadingIntoWind() + local delta = self:_GetDeltaHeading( hdg, wind ) + local uturn = delta > 5 -- Check if wind is actually blowing (0.1 m/s = 0.36 km/h = 0.2 knots) - local _,vwind=self:GetWind() - if vwind<0.1 then - uturn=false + local _, vwind = self:GetWind() + if vwind < 0.1 then + uturn = false end -- U-turn disabled by user input. if not nextwindow.UTURN then - uturn=false + uturn = false end - --Debug info - 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))) + -- Debug info + 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 ) ) ) -- Time into the wind 1 day or if longer recovery time + the 5 min early. - local t=math.max(nextwindow.STOP-nextwindow.START+self.dTturn, 60*60*24) + local t = math.max( nextwindow.STOP - nextwindow.START + self.dTturn, 60 * 60 * 24 ) -- Recovery wind on deck in knots. - local v=UTILS.KnotsToMps(nextwindow.SPEED) + local v = UTILS.KnotsToMps( nextwindow.SPEED ) -- Check that we do not go above max possible speed. - local vmax=self.carrier:GetSpeedMax()/3.6 -- convert to m/s - v=math.min(v,vmax) + local vmax = self.carrier:GetSpeedMax() / 3.6 -- convert to m/s + v = math.min( v, vmax ) -- Route carrier into the wind. Sets self.turnintowind=true - self:CarrierTurnIntoWind(t, v, uturn) + self:CarrierTurnIntoWind( t, v, uturn ) end -- Set current recovery window. - self.recoverywindow=nextwindow + self.recoverywindow = nextwindow else -- No next window. Set default values. @@ -3951,29 +3923,29 @@ function AIRBOSS:_CheckRecoveryTimes() ------------------------------------------------------------------------------------- if currwindow then - self.recoverywindow=currwindow + self.recoverywindow = currwindow else - self.recoverywindow=nextwindow + self.recoverywindow = nextwindow end end - self:T2({"FF", recoverywindow=self.recoverywindow}) + self:T2( { "FF", recoverywindow = self.recoverywindow } ) end --- Get section lead of a flight. ---@param #AIRBOSS self ---@param #AIRBOSS.FlightGroup flight ---@return #AIRBOSS.FlightGroup The leader of the section. Could be the flight itself. ---@return #boolean If true, flight is lead. -function AIRBOSS:_GetFlightLead(flight) +-- @param #AIRBOSS self +-- @param #AIRBOSS.FlightGroup flight +-- @return #AIRBOSS.FlightGroup The leader of the section. Could be the flight itself. +-- @return #boolean If true, flight is lead. +function AIRBOSS:_GetFlightLead( flight ) - if flight.name~=flight.seclead then + if flight.name ~= flight.seclead then -- Section lead of flight. - local lead=self.players[flight.seclead] - return lead,false + local lead = self.players[flight.seclead] + return lead, false else -- Flight without section or section lead. - return flight,true + return flight, true end end @@ -3985,15 +3957,15 @@ end -- @param #string To To state. -- @param #number Case The recovery case (1, 2 or 3) to switch to. -- @param #number Offset Holding pattern offset angle in degrees for CASE II/III recoveries. -function AIRBOSS:onbeforeRecoveryCase(From, Event, To, Case, Offset) +function AIRBOSS:onbeforeRecoveryCase( From, Event, To, Case, Offset ) -- Input or default value. - Case=Case or self.defaultcase + Case = Case or self.defaultcase -- Input or default value - Offset=Offset or self.defaultoffset + Offset = Offset or self.defaultoffset - if Case==self.case and Offset==self.holdingoffset then + if Case == self.case and Offset == self.holdingoffset then return false end @@ -4007,46 +3979,46 @@ end -- @param #string To To state. -- @param #number Case The recovery case (1, 2 or 3) to switch to. -- @param #number Offset Holding pattern offset angle in degrees for CASE II/III recoveries. -function AIRBOSS:onafterRecoveryCase(From, Event, To, Case, Offset) +function AIRBOSS:onafterRecoveryCase( From, Event, To, Case, Offset ) -- Input or default value. - Case=Case or self.defaultcase + Case = Case or self.defaultcase -- Input or default value - Offset=Offset or self.defaultoffset + Offset = Offset or self.defaultoffset -- Debug output. - 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) + 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) + MESSAGE:New( text, 20, self.alias ):ToAllIf( self.Debug ) + self:T( self.lid .. text ) -- Set new recovery case. - self.case=Case + self.case = Case -- Set holding offset. - self.holdingoffset=Offset + self.holdingoffset = Offset -- Update case of all flights not in Marshal or Pattern queue. - for _,_flight in pairs(self.flights) do - local flight=_flight --#AIRBOSS.FlightGroup - if not (self:_InQueue(self.Qmarshal, flight.group) or self:_InQueue(self.Qpattern, flight.group)) then + for _, _flight in pairs( self.flights ) do + local flight = _flight -- #AIRBOSS.FlightGroup + if not (self:_InQueue( self.Qmarshal, flight.group ) or self:_InQueue( self.Qpattern, flight.group )) then -- Also not for section members. These are not in the marshal or pattern queue if the lead is. - if flight.name~=flight.seclead then - local lead=self.players[flight.seclead] + 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 + if lead and not (self:_InQueue( self.Qmarshal, lead.group ) or self:_InQueue( self.Qpattern, lead.group )) then -- This is section member and the lead is not in the Marshal or Pattern queue. - flight.case=self.case + flight.case = self.case end else -- This is a flight without section or the section lead. - flight.case=self.case + flight.case = self.case end @@ -4061,19 +4033,19 @@ end -- @param #string To To state. -- @param #number Case The recovery case (1, 2 or 3) to start. -- @param #number Offset Holding pattern offset angle in degrees for CASE II/III recoveries. -function AIRBOSS:onafterRecoveryStart(From, Event, To, Case, Offset) +function AIRBOSS:onafterRecoveryStart( From, Event, To, Case, Offset ) -- Input or default value. - Case=Case or self.defaultcase + Case = Case or self.defaultcase -- Input or default value. - Offset=Offset or self.defaultoffset + Offset = Offset or self.defaultoffset -- Radio message: "99, starting aircraft recovery case X ops. (Marshal radial XYZ degrees)" - self:_MarshalCallRecoveryStart(Case) + self:_MarshalCallRecoveryStart( Case ) -- Switch to case. - self:RecoveryCase(Case, Offset) + self:RecoveryCase( Case, Offset ) end --- On after "RecoveryStop" event. Recovery of aircraft is stopped and carrier switches to state "Idle". Running recovery window is deleted. @@ -4081,65 +4053,64 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function AIRBOSS:onafterRecoveryStop(From, Event, To) +function AIRBOSS:onafterRecoveryStop( From, Event, To ) -- Debug output. - self:T(self.lid..string.format("Stopping aircraft recovery.")) + self:T( self.lid .. string.format( "Stopping aircraft recovery." ) ) -- Recovery ops stopped message. - self:_MarshalCallRecoveryStopped(self.case) + self:_MarshalCallRecoveryStopped( self.case ) -- If carrier is currently heading into the wind, we resume the original route. if self.turnintowind then -- Coordinate to return to. - local coord=self.Creturnto + local coord = self.Creturnto -- No U-turn. - if self.recoverywindow and self.recoverywindow.UTURN==false then - coord=nil + if self.recoverywindow and self.recoverywindow.UTURN == false then + coord = nil end -- Carrier resumes route. - self:CarrierResumeRoute(coord) + self:CarrierResumeRoute( coord ) end -- Delete current recovery window if open. - if self.recoverywindow and self.recoverywindow.OPEN==true then - self.recoverywindow.OPEN=false - self.recoverywindow.OVER=true - self:DeleteRecoveryWindow(self.recoverywindow) + if self.recoverywindow and self.recoverywindow.OPEN == true then + self.recoverywindow.OPEN = false + self.recoverywindow.OVER = true + self:DeleteRecoveryWindow( self.recoverywindow ) end -- Check recovery windows. This sets self.recoverywindow to the next window. self:_CheckRecoveryTimes() end - --- On after "RecoveryPause" event. Recovery of aircraft is paused. Marshal queue stays intact. -- @param #AIRBOSS self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param #number duration Duration of pause in seconds. After that recovery is resumed automatically. -function AIRBOSS:onafterRecoveryPause(From, Event, To, duration) +function AIRBOSS:onafterRecoveryPause( From, Event, To, duration ) -- Debug output. - self:T(self.lid..string.format("Pausing aircraft recovery.")) + self:T( self.lid .. string.format( "Pausing aircraft recovery." ) ) -- Message text if duration then -- Auto resume. - self:__RecoveryUnpause(duration) + self:__RecoveryUnpause( duration ) -- Time to resume. - local clock=UTILS.SecondsToClock(timer.getAbsTime()+duration) + local clock = UTILS.SecondsToClock( timer.getAbsTime() + duration ) -- Marshal call: "99, aircraft recovery paused and will be resume at XX:YY." - self:_MarshalCallRecoveryPausedResumedAt(clock) + self:_MarshalCallRecoveryPausedResumedAt( clock ) else - local text=string.format("aircraft recovery is paused until further notice.") + local text = string.format( "aircraft recovery is paused until further notice." ) -- Marshal call: "99, aircraft recovery paused until further notice." self:_MarshalCallRecoveryPausedNotice() @@ -4153,9 +4124,9 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function AIRBOSS:onafterRecoveryUnpause(From, Event, To) +function AIRBOSS:onafterRecoveryUnpause( From, Event, To ) -- Debug output. - self:T(self.lid..string.format("Unpausing aircraft recovery.")) + self:T( self.lid .. string.format( "Unpausing aircraft recovery." ) ) -- Resume recovery. self:_MarshalCallResumeRecovery() @@ -4168,9 +4139,9 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param #number n Number of waypoint that was passed. -function AIRBOSS:onafterPassingWaypoint(From, Event, To, n) +function AIRBOSS:onafterPassingWaypoint( From, Event, To, n ) -- Debug output. - self:I(self.lid..string.format("Carrier passed waypoint %d.", n)) + self:I( self.lid .. string.format( "Carrier passed waypoint %d.", n ) ) end --- On after "Idle" event. Carrier goes to state "Idle". @@ -4178,9 +4149,9 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function AIRBOSS:onafterIdle(From, Event, To) +function AIRBOSS:onafterIdle( From, Event, To ) -- Debug output. - self:T(self.lid..string.format("Carrier goes to idle.")) + self:T( self.lid .. string.format( "Carrier goes to idle." ) ) end --- On after Stop event. Unhandle events. @@ -4188,18 +4159,18 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function AIRBOSS:onafterStop(From, Event, To) - self:I(self.lid..string.format("Stopping airboss script.")) +function AIRBOSS:onafterStop( From, Event, To ) + self:I( self.lid .. string.format( "Stopping airboss script." ) ) -- Unhandle events. - 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: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 @@ -4213,146 +4184,145 @@ end function AIRBOSS:_InitStennis() -- Carrier Parameters. - self.carrierparam.sterndist =-153 - self.carrierparam.deckheight = 19.06 + self.carrierparam.sterndist = -153 + self.carrierparam.deckheight = 19.06 -- Total size of the carrier (approx as rectangle). - self.carrierparam.totlength=310 -- Wiki says 332.8 meters overall length. - self.carrierparam.totwidthport=40 -- Wiki says 76.8 meters overall beam. - self.carrierparam.totwidthstarboard=30 + self.carrierparam.totlength = 310 -- Wiki says 332.8 meters overall length. + self.carrierparam.totwidthport = 40 -- Wiki says 76.8 meters overall beam. + self.carrierparam.totwidthstarboard = 30 -- Landing runway. - self.carrierparam.rwyangle = -9.1359 - self.carrierparam.rwylength = 225 - self.carrierparam.rwywidth = 20 + self.carrierparam.rwyangle = -9.1359 + self.carrierparam.rwylength = 225 + self.carrierparam.rwywidth = 20 -- Wires. - self.carrierparam.wire1 = 46 -- Distance from stern to first wire. - self.carrierparam.wire2 = 46+12 - self.carrierparam.wire3 = 46+24 - self.carrierparam.wire4 = 46+35 -- Last wire is strangely one meter closer. - + self.carrierparam.wire1 = 46 -- Distance from stern to first wire. + self.carrierparam.wire2 = 46 + 12 + self.carrierparam.wire3 = 46 + 24 + self.carrierparam.wire4 = 46 + 35 -- Last wire is strangely one meter closer. -- Platform at 5k. Reduce descent rate to 2000 ft/min to 1200 dirty up level flight. - self.Platform.name="Platform 5k" - self.Platform.Xmin=-UTILS.NMToMeters(22) -- Not more than 22 NM behind the boat. Last check was at 21 NM. - self.Platform.Xmax =nil - self.Platform.Zmin=-UTILS.NMToMeters(30) -- Not more than 30 NM port of boat. - self.Platform.Zmax= UTILS.NMToMeters(30) -- Not more than 30 NM starboard of boat. - self.Platform.LimitXmin=nil -- Limits via zone - self.Platform.LimitXmax=nil - self.Platform.LimitZmin=nil - self.Platform.LimitZmax=nil + self.Platform.name = "Platform 5k" + self.Platform.Xmin = -UTILS.NMToMeters( 22 ) -- Not more than 22 NM behind the boat. Last check was at 21 NM. + self.Platform.Xmax = nil + self.Platform.Zmin = -UTILS.NMToMeters( 30 ) -- Not more than 30 NM port of boat. + self.Platform.Zmax = UTILS.NMToMeters( 30 ) -- Not more than 30 NM starboard of boat. + self.Platform.LimitXmin = nil -- Limits via zone + self.Platform.LimitXmax = nil + self.Platform.LimitZmin = nil + self.Platform.LimitZmax = nil -- Level out at 1200 ft and dirty up. - self.DirtyUp.name="Dirty Up" - self.DirtyUp.Xmin=-UTILS.NMToMeters(21) -- Not more than 21 NM behind the boat. - self.DirtyUp.Xmax= nil - self.DirtyUp.Zmin=-UTILS.NMToMeters(30) -- Not more than 30 NM port of boat. - self.DirtyUp.Zmax= UTILS.NMToMeters(30) -- Not more than 30 NM starboard of boat. - self.DirtyUp.LimitXmin=nil -- Limits via zone - self.DirtyUp.LimitXmax=nil - self.DirtyUp.LimitZmin=nil - self.DirtyUp.LimitZmax=nil + self.DirtyUp.name = "Dirty Up" + self.DirtyUp.Xmin = -UTILS.NMToMeters( 21 ) -- Not more than 21 NM behind the boat. + self.DirtyUp.Xmax = nil + self.DirtyUp.Zmin = -UTILS.NMToMeters( 30 ) -- Not more than 30 NM port of boat. + self.DirtyUp.Zmax = UTILS.NMToMeters( 30 ) -- Not more than 30 NM starboard of boat. + self.DirtyUp.LimitXmin = nil -- Limits via zone + self.DirtyUp.LimitXmax = nil + self.DirtyUp.LimitZmin = nil + self.DirtyUp.LimitZmax = nil -- Intercept glide slope and follow bullseye. - self.Bullseye.name="Bullseye" - self.Bullseye.Xmin=-UTILS.NMToMeters(11) -- Not more than 11 NM behind the boat. Last check was at 10 NM. - self.Bullseye.Xmax= nil - self.Bullseye.Zmin=-UTILS.NMToMeters(30) -- Not more than 30 NM port. - self.Bullseye.Zmax= UTILS.NMToMeters(30) -- Not more than 30 NM starboard. - self.Bullseye.LimitXmin=nil -- Limits via zone. - self.Bullseye.LimitXmax=nil - self.Bullseye.LimitZmin=nil - self.Bullseye.LimitZmax=nil + self.Bullseye.name = "Bullseye" + self.Bullseye.Xmin = -UTILS.NMToMeters( 11 ) -- Not more than 11 NM behind the boat. Last check was at 10 NM. + self.Bullseye.Xmax = nil + self.Bullseye.Zmin = -UTILS.NMToMeters( 30 ) -- Not more than 30 NM port. + self.Bullseye.Zmax = UTILS.NMToMeters( 30 ) -- Not more than 30 NM starboard. + self.Bullseye.LimitXmin = nil -- Limits via zone. + self.Bullseye.LimitXmax = nil + self.Bullseye.LimitZmin = nil + self.Bullseye.LimitZmax = nil -- Break entry. - self.BreakEntry.name="Break Entry" - self.BreakEntry.Xmin=-UTILS.NMToMeters(4) -- Not more than 4 NM behind the boat. Check for initial is at 3 NM with a radius of 500 m and 100 m starboard. - self.BreakEntry.Xmax= nil - self.BreakEntry.Zmin=-UTILS.NMToMeters(0.5) -- Not more than 0.5 NM port of boat. - self.BreakEntry.Zmax= UTILS.NMToMeters(1.5) -- Not more than 1.5 NM starboard. - self.BreakEntry.LimitXmin=0 -- Check and next step when at carrier and starboard of carrier. - self.BreakEntry.LimitXmax=nil - self.BreakEntry.LimitZmin=nil - self.BreakEntry.LimitZmax=nil + self.BreakEntry.name = "Break Entry" + self.BreakEntry.Xmin = -UTILS.NMToMeters( 4 ) -- Not more than 4 NM behind the boat. Check for initial is at 3 NM with a radius of 500 m and 100 m starboard. + self.BreakEntry.Xmax = nil + self.BreakEntry.Zmin = -UTILS.NMToMeters( 0.5 ) -- Not more than 0.5 NM port of boat. + self.BreakEntry.Zmax = UTILS.NMToMeters( 1.5 ) -- Not more than 1.5 NM starboard. + self.BreakEntry.LimitXmin = 0 -- Check and next step when at carrier and starboard of carrier. + self.BreakEntry.LimitXmax = nil + self.BreakEntry.LimitZmin = nil + self.BreakEntry.LimitZmax = nil -- Early break. - self.BreakEarly.name="Early Break" - self.BreakEarly.Xmin=-UTILS.NMToMeters(1) -- Not more than 1 NM behind the boat. Last check was at 0. - self.BreakEarly.Xmax= UTILS.NMToMeters(5) -- Not more than 5 NM in front of the boat. Enough for late breaks? - self.BreakEarly.Zmin=-UTILS.NMToMeters(2) -- Not more than 2 NM port. - self.BreakEarly.Zmax= UTILS.NMToMeters(1) -- Not more than 1 NM starboard. - self.BreakEarly.LimitXmin= 0 -- Check and next step 0.2 NM port and in front of boat. - self.BreakEarly.LimitXmax= nil - self.BreakEarly.LimitZmin=-UTILS.NMToMeters(0.2) -- -370 m port - self.BreakEarly.LimitZmax= nil + self.BreakEarly.name = "Early Break" + self.BreakEarly.Xmin = -UTILS.NMToMeters( 1 ) -- Not more than 1 NM behind the boat. Last check was at 0. + self.BreakEarly.Xmax = UTILS.NMToMeters( 5 ) -- Not more than 5 NM in front of the boat. Enough for late breaks? + self.BreakEarly.Zmin = -UTILS.NMToMeters( 2 ) -- Not more than 2 NM port. + self.BreakEarly.Zmax = UTILS.NMToMeters( 1 ) -- Not more than 1 NM starboard. + self.BreakEarly.LimitXmin = 0 -- Check and next step 0.2 NM port and in front of boat. + self.BreakEarly.LimitXmax = nil + self.BreakEarly.LimitZmin = -UTILS.NMToMeters( 0.2 ) -- -370 m port + self.BreakEarly.LimitZmax = nil -- Late break. - self.BreakLate.name="Late Break" - self.BreakLate.Xmin=-UTILS.NMToMeters(1) -- Not more than 1 NM behind the boat. Last check was at 0. - self.BreakLate.Xmax= UTILS.NMToMeters(5) -- Not more than 5 NM in front of the boat. Enough for late breaks? - self.BreakLate.Zmin=-UTILS.NMToMeters(2) -- Not more than 2 NM port. - self.BreakLate.Zmax= UTILS.NMToMeters(1) -- Not more than 1 NM starboard. - self.BreakLate.LimitXmin= 0 -- Check and next step 0.8 NM port and in front of boat. - self.BreakLate.LimitXmax= nil - self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.8) -- -1470 m port - self.BreakLate.LimitZmax= nil + self.BreakLate.name = "Late Break" + self.BreakLate.Xmin = -UTILS.NMToMeters( 1 ) -- Not more than 1 NM behind the boat. Last check was at 0. + self.BreakLate.Xmax = UTILS.NMToMeters( 5 ) -- Not more than 5 NM in front of the boat. Enough for late breaks? + self.BreakLate.Zmin = -UTILS.NMToMeters( 2 ) -- Not more than 2 NM port. + self.BreakLate.Zmax = UTILS.NMToMeters( 1 ) -- Not more than 1 NM starboard. + self.BreakLate.LimitXmin = 0 -- Check and next step 0.8 NM port and in front of boat. + self.BreakLate.LimitXmax = nil + self.BreakLate.LimitZmin = -UTILS.NMToMeters( 0.8 ) -- -1470 m port + self.BreakLate.LimitZmax = nil -- Abeam position. - self.Abeam.name="Abeam Position" - self.Abeam.Xmin=-UTILS.NMToMeters(5) -- Not more then 5 NM astern of boat. Should be LIG call anyway. - self.Abeam.Xmax= UTILS.NMToMeters(5) -- Not more then 5 NM ahead of boat. - self.Abeam.Zmin=-UTILS.NMToMeters(2) -- Not more than 2 NM port. - self.Abeam.Zmax= 500 -- Not more than 500 m starboard. Must be port! - self.Abeam.LimitXmin=-200 -- Check and next step 200 meters behind the ship. - self.Abeam.LimitXmax= nil - self.Abeam.LimitZmin= nil - self.Abeam.LimitZmax= nil + self.Abeam.name = "Abeam Position" + self.Abeam.Xmin = -UTILS.NMToMeters( 5 ) -- Not more then 5 NM astern of boat. Should be LIG call anyway. + self.Abeam.Xmax = UTILS.NMToMeters( 5 ) -- Not more then 5 NM ahead of boat. + self.Abeam.Zmin = -UTILS.NMToMeters( 2 ) -- Not more than 2 NM port. + self.Abeam.Zmax = 500 -- Not more than 500 m starboard. Must be port! + self.Abeam.LimitXmin = -200 -- Check and next step 200 meters behind the ship. + self.Abeam.LimitXmax = nil + self.Abeam.LimitZmin = nil + self.Abeam.LimitZmax = nil -- At the Ninety. - self.Ninety.name="Ninety" - self.Ninety.Xmin=-UTILS.NMToMeters(4) -- Not more than 4 NM behind the boat. LIG check anyway. - self.Ninety.Xmax= 0 -- Must be behind the boat. - self.Ninety.Zmin=-UTILS.NMToMeters(2) -- Not more than 2 NM port of boat. - self.Ninety.Zmax= nil - self.Ninety.LimitXmin=nil - self.Ninety.LimitXmax=nil - self.Ninety.LimitZmin=nil - self.Ninety.LimitZmax=-UTILS.NMToMeters(0.6) -- Check and next step when 0.6 NM port. + self.Ninety.name = "Ninety" + self.Ninety.Xmin = -UTILS.NMToMeters( 4 ) -- Not more than 4 NM behind the boat. LIG check anyway. + self.Ninety.Xmax = 0 -- Must be behind the boat. + self.Ninety.Zmin = -UTILS.NMToMeters( 2 ) -- Not more than 2 NM port of boat. + self.Ninety.Zmax = nil + self.Ninety.LimitXmin = nil + self.Ninety.LimitXmax = nil + self.Ninety.LimitZmin = nil + self.Ninety.LimitZmax = -UTILS.NMToMeters( 0.6 ) -- Check and next step when 0.6 NM port. -- At the Wake. - self.Wake.name="Wake" - self.Wake.Xmin=-UTILS.NMToMeters(4) -- Not more than 4 NM behind the boat. - self.Wake.Xmax= 0 -- Must be behind the boat. - self.Wake.Zmin=-2000 -- Not more than 2 km port of boat. - self.Wake.Zmax= nil - self.Wake.LimitXmin=nil - self.Wake.LimitXmax=nil - self.Wake.LimitZmin=0 -- Check and next step when directly behind the boat. - self.Wake.LimitZmax=nil + self.Wake.name = "Wake" + self.Wake.Xmin = -UTILS.NMToMeters( 4 ) -- Not more than 4 NM behind the boat. + self.Wake.Xmax = 0 -- Must be behind the boat. + self.Wake.Zmin = -2000 -- Not more than 2 km port of boat. + self.Wake.Zmax = nil + self.Wake.LimitXmin = nil + self.Wake.LimitXmax = nil + self.Wake.LimitZmin = 0 -- Check and next step when directly behind the boat. + self.Wake.LimitZmax = nil -- Turn to final. - self.Final.name="Final" - self.Final.Xmin=-UTILS.NMToMeters(4) -- Not more than 4 NM behind the boat. - self.Final.Xmax= 0 -- Must be behind the boat. - self.Final.Zmin=-2000 -- Not more than 2 km port. - self.Final.Zmax= nil - self.Final.LimitXmin=nil -- No limits. Check is carried out differently. - self.Final.LimitXmax=nil - self.Final.LimitZmin=nil - self.Final.LimitZmax=nil + self.Final.name = "Final" + self.Final.Xmin = -UTILS.NMToMeters( 4 ) -- Not more than 4 NM behind the boat. + self.Final.Xmax = 0 -- Must be behind the boat. + self.Final.Zmin = -2000 -- Not more than 2 km port. + self.Final.Zmax = nil + self.Final.LimitXmin = nil -- No limits. Check is carried out differently. + self.Final.LimitXmax = nil + self.Final.LimitZmin = nil + self.Final.LimitZmax = nil -- In the Groove. - self.Groove.name="Groove" - self.Groove.Xmin=-UTILS.NMToMeters(4) -- Not more than 4 NM behind the boat. - self.Groove.Xmax= nil - self.Groove.Zmin=-UTILS.NMToMeters(2) -- Not more than 2 NM port - self.Groove.Zmax= UTILS.NMToMeters(2) -- Not more than 2 NM starboard. - self.Groove.LimitXmin=nil -- No limits. Check is carried out differently. - self.Groove.LimitXmax=nil - self.Groove.LimitZmin=nil - self.Groove.LimitZmax=nil + self.Groove.name = "Groove" + self.Groove.Xmin = -UTILS.NMToMeters( 4 ) -- Not more than 4 NM behind the boat. + self.Groove.Xmax = nil + self.Groove.Zmin = -UTILS.NMToMeters( 2 ) -- Not more than 2 NM port + self.Groove.Zmax = UTILS.NMToMeters( 2 ) -- Not more than 2 NM starboard. + self.Groove.LimitXmin = nil -- No limits. Check is carried out differently. + self.Groove.LimitXmax = nil + self.Groove.LimitZmin = nil + self.Groove.LimitZmax = nil end @@ -4364,24 +4334,24 @@ function AIRBOSS:_InitNimitz() self:_InitStennis() -- Carrier Parameters. - self.carrierparam.sterndist =-164 - self.carrierparam.deckheight = 20.1494 --DCS World OpenBeta\CoreMods\tech\USS_Nimitz\Database\USS_CVN_7X.lua + self.carrierparam.sterndist = -164 + self.carrierparam.deckheight = 20.1494 -- DCS World OpenBeta\CoreMods\tech\USS_Nimitz\Database\USS_CVN_7X.lua -- Total size of the carrier (approx as rectangle). - self.carrierparam.totlength=332.8 -- Wiki says 332.8 meters overall length. - self.carrierparam.totwidthport=45 -- Wiki says 76.8 meters overall beam. - self.carrierparam.totwidthstarboard=35 + self.carrierparam.totlength = 332.8 -- Wiki says 332.8 meters overall length. + self.carrierparam.totwidthport = 45 -- Wiki says 76.8 meters overall beam. + self.carrierparam.totwidthstarboard = 35 -- Landing runway. - self.carrierparam.rwyangle = -9.1359 --DCS World OpenBeta\CoreMods\tech\USS_Nimitz\scripts\USS_Nimitz_RunwaysAndRoutes.lua - self.carrierparam.rwylength = 250 - self.carrierparam.rwywidth = 25 + self.carrierparam.rwyangle = -9.1359 -- DCS World OpenBeta\CoreMods\tech\USS_Nimitz\scripts\USS_Nimitz_RunwaysAndRoutes.lua + self.carrierparam.rwylength = 250 + self.carrierparam.rwywidth = 25 -- Wires. - self.carrierparam.wire1 = 55 -- Distance from stern to first wire. - self.carrierparam.wire2 = 67 - self.carrierparam.wire3 = 79 - self.carrierparam.wire4 = 92 + self.carrierparam.wire1 = 55 -- Distance from stern to first wire. + self.carrierparam.wire2 = 67 + self.carrierparam.wire3 = 79 + self.carrierparam.wire4 = 92 end @@ -4393,24 +4363,24 @@ function AIRBOSS:_InitForrestal() self:_InitNimitz() -- Carrier Parameters. - self.carrierparam.sterndist =-135.5 - self.carrierparam.deckheight = 20 --20.1494 --DCS World OpenBeta\CoreMods\tech\USS_Nimitz\Database\USS_CVN_7X.lua + self.carrierparam.sterndist = -135.5 + self.carrierparam.deckheight = 20 -- 20.1494 --DCS World OpenBeta\CoreMods\tech\USS_Nimitz\Database\USS_CVN_7X.lua -- Total size of the carrier (approx as rectangle). - self.carrierparam.totlength=315 -- Wiki says 325 meters overall length. - self.carrierparam.totwidthport=45 -- Wiki says 73 meters overall beam. - self.carrierparam.totwidthstarboard=35 + self.carrierparam.totlength = 315 -- Wiki says 325 meters overall length. + self.carrierparam.totwidthport = 45 -- Wiki says 73 meters overall beam. + self.carrierparam.totwidthstarboard = 35 -- Landing runway. - self.carrierparam.rwyangle = -9.1359 --DCS World OpenBeta\CoreMods\tech\USS_Nimitz\scripts\USS_Nimitz_RunwaysAndRoutes.lua - self.carrierparam.rwylength = 212 - self.carrierparam.rwywidth = 25 + self.carrierparam.rwyangle = -9.1359 -- DCS World OpenBeta\CoreMods\tech\USS_Nimitz\scripts\USS_Nimitz_RunwaysAndRoutes.lua + self.carrierparam.rwylength = 212 + self.carrierparam.rwywidth = 25 -- Wires. - self.carrierparam.wire1 = 44 -- Distance from stern to first wire. Original from Frank - 42 - self.carrierparam.wire2 = 54 --51.5 - self.carrierparam.wire3 = 64 --62 - self.carrierparam.wire4 = 74 --72.5 + self.carrierparam.wire1 = 44 -- Distance from stern to first wire. Original from Frank - 42 + self.carrierparam.wire2 = 54 -- 51.5 + self.carrierparam.wire3 = 64 -- 62 + self.carrierparam.wire4 = 74 -- 72.5 end @@ -4422,35 +4392,35 @@ function AIRBOSS:_InitTarawa() self:_InitStennis() -- Carrier Parameters. - self.carrierparam.sterndist =-125 - self.carrierparam.deckheight = 21 --69 ft + self.carrierparam.sterndist = -125 + self.carrierparam.deckheight = 21 -- 69 ft -- Total size of the carrier (approx as rectangle). - self.carrierparam.totlength=245 - self.carrierparam.totwidthport=10 - self.carrierparam.totwidthstarboard=25 + self.carrierparam.totlength = 245 + self.carrierparam.totwidthport = 10 + self.carrierparam.totwidthstarboard = 25 -- Landing runway. - self.carrierparam.rwyangle = 0 + self.carrierparam.rwyangle = 0 self.carrierparam.rwylength = 225 - self.carrierparam.rwywidth = 15 + self.carrierparam.rwywidth = 15 -- Wires. - self.carrierparam.wire1=nil - self.carrierparam.wire2=nil - self.carrierparam.wire3=nil - self.carrierparam.wire4=nil + self.carrierparam.wire1 = nil + self.carrierparam.wire2 = nil + self.carrierparam.wire3 = nil + self.carrierparam.wire4 = nil -- Late break. - self.BreakLate.name="Late Break" - self.BreakLate.Xmin=-UTILS.NMToMeters(1) -- Not more than 1 NM behind the boat. Last check was at 0. - self.BreakLate.Xmax= UTILS.NMToMeters(5) -- Not more than 5 NM in front of the boat. Enough for late breaks? - self.BreakLate.Zmin=-UTILS.NMToMeters(1.6) -- Not more than 1.6 NM port. - self.BreakLate.Zmax= UTILS.NMToMeters(1) -- Not more than 1 NM starboard. - self.BreakLate.LimitXmin= 0 -- Check and next step 0.8 NM port and in front of boat. - self.BreakLate.LimitXmax= nil - self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.5) -- 926 m port, closer than the stennis as abeam is 0.8-1.0 rather than 1.2 - self.BreakLate.LimitZmax= nil + self.BreakLate.name = "Late Break" + self.BreakLate.Xmin = -UTILS.NMToMeters( 1 ) -- Not more than 1 NM behind the boat. Last check was at 0. + self.BreakLate.Xmax = UTILS.NMToMeters( 5 ) -- Not more than 5 NM in front of the boat. Enough for late breaks? + self.BreakLate.Zmin = -UTILS.NMToMeters( 1.6 ) -- Not more than 1.6 NM port. + self.BreakLate.Zmax = UTILS.NMToMeters( 1 ) -- Not more than 1 NM starboard. + self.BreakLate.LimitXmin = 0 -- Check and next step 0.8 NM port and in front of boat. + self.BreakLate.LimitXmax = nil + self.BreakLate.LimitZmin = -UTILS.NMToMeters( 0.5 ) -- 926 m port, closer than the stennis as abeam is 0.8-1.0 rather than 1.2 + self.BreakLate.LimitZmax = nil end @@ -4462,35 +4432,35 @@ function AIRBOSS:_InitAmerica() self:_InitStennis() -- Carrier Parameters. - self.carrierparam.sterndist =-125 - self.carrierparam.deckheight = 20 --67 ft + self.carrierparam.sterndist = -125 + self.carrierparam.deckheight = 20 -- 67 ft -- Total size of the carrier (approx as rectangle). - self.carrierparam.totlength=257 - self.carrierparam.totwidthport=11 - self.carrierparam.totwidthstarboard=25 + self.carrierparam.totlength = 257 + self.carrierparam.totwidthport = 11 + self.carrierparam.totwidthstarboard = 25 -- Landing runway. - self.carrierparam.rwyangle = 0 + self.carrierparam.rwyangle = 0 self.carrierparam.rwylength = 240 - self.carrierparam.rwywidth = 15 + self.carrierparam.rwywidth = 15 -- Wires. - self.carrierparam.wire1=nil - self.carrierparam.wire2=nil - self.carrierparam.wire3=nil - self.carrierparam.wire4=nil + self.carrierparam.wire1 = nil + self.carrierparam.wire2 = nil + self.carrierparam.wire3 = nil + self.carrierparam.wire4 = nil -- Late break. - self.BreakLate.name="Late Break" - self.BreakLate.Xmin=-UTILS.NMToMeters(1) -- Not more than 1 NM behind the boat. Last check was at 0. - self.BreakLate.Xmax= UTILS.NMToMeters(5) -- Not more than 5 NM in front of the boat. Enough for late breaks? - self.BreakLate.Zmin=-UTILS.NMToMeters(1.6) -- Not more than 1.6 NM port. - self.BreakLate.Zmax= UTILS.NMToMeters(1) -- Not more than 1 NM starboard. - self.BreakLate.LimitXmin= 0 -- Check and next step 0.8 NM port and in front of boat. - self.BreakLate.LimitXmax= nil - self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.5) -- 926 m port, closer than the stennis as abeam is 0.8-1.0 rather than 1.2 - self.BreakLate.LimitZmax= nil + self.BreakLate.name = "Late Break" + self.BreakLate.Xmin = -UTILS.NMToMeters( 1 ) -- Not more than 1 NM behind the boat. Last check was at 0. + self.BreakLate.Xmax = UTILS.NMToMeters( 5 ) -- Not more than 5 NM in front of the boat. Enough for late breaks? + self.BreakLate.Zmin = -UTILS.NMToMeters( 1.6 ) -- Not more than 1.6 NM port. + self.BreakLate.Zmax = UTILS.NMToMeters( 1 ) -- Not more than 1 NM starboard. + self.BreakLate.LimitXmin = 0 -- Check and next step 0.8 NM port and in front of boat. + self.BreakLate.LimitXmax = nil + self.BreakLate.LimitZmin = -UTILS.NMToMeters( 0.5 ) -- 926 m port, closer than the stennis as abeam is 0.8-1.0 rather than 1.2 + self.BreakLate.LimitZmax = nil end @@ -4502,338 +4472,334 @@ function AIRBOSS:_InitJcarlos() self:_InitStennis() -- Carrier Parameters. - self.carrierparam.sterndist =-125 - self.carrierparam.deckheight = 20 --67 ft + self.carrierparam.sterndist = -125 + self.carrierparam.deckheight = 20 -- 67 ft -- Total size of the carrier (approx as rectangle). - self.carrierparam.totlength=231 - self.carrierparam.totwidthport=10 - self.carrierparam.totwidthstarboard=22 + self.carrierparam.totlength = 231 + self.carrierparam.totwidthport = 10 + self.carrierparam.totwidthstarboard = 22 -- Landing runway. - self.carrierparam.rwyangle = 0 + self.carrierparam.rwyangle = 0 self.carrierparam.rwylength = 202 - self.carrierparam.rwywidth = 14 + self.carrierparam.rwywidth = 14 -- Wires. - self.carrierparam.wire1=nil - self.carrierparam.wire2=nil - self.carrierparam.wire3=nil - self.carrierparam.wire4=nil + self.carrierparam.wire1 = nil + self.carrierparam.wire2 = nil + self.carrierparam.wire3 = nil + self.carrierparam.wire4 = nil -- Late break. - self.BreakLate.name="Late Break" - self.BreakLate.Xmin=-UTILS.NMToMeters(1) -- Not more than 1 NM behind the boat. Last check was at 0. - self.BreakLate.Xmax= UTILS.NMToMeters(5) -- Not more than 5 NM in front of the boat. Enough for late breaks? - self.BreakLate.Zmin=-UTILS.NMToMeters(1.6) -- Not more than 1.6 NM port. - self.BreakLate.Zmax= UTILS.NMToMeters(1) -- Not more than 1 NM starboard. - self.BreakLate.LimitXmin= 0 -- Check and next step 0.8 NM port and in front of boat. - self.BreakLate.LimitXmax= nil - self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.5) -- 926 m port, closer than the stennis as abeam is 0.8-1.0 rather than 1.2 - self.BreakLate.LimitZmax= nil + self.BreakLate.name = "Late Break" + self.BreakLate.Xmin = -UTILS.NMToMeters( 1 ) -- Not more than 1 NM behind the boat. Last check was at 0. + self.BreakLate.Xmax = UTILS.NMToMeters( 5 ) -- Not more than 5 NM in front of the boat. Enough for late breaks? + self.BreakLate.Zmin = -UTILS.NMToMeters( 1.6 ) -- Not more than 1.6 NM port. + self.BreakLate.Zmax = UTILS.NMToMeters( 1 ) -- Not more than 1 NM starboard. + self.BreakLate.LimitXmin = 0 -- Check and next step 0.8 NM port and in front of boat. + self.BreakLate.LimitXmax = nil + self.BreakLate.LimitZmin = -UTILS.NMToMeters( 0.5 ) -- 926 m port, closer than the stennis as abeam is 0.8-1.0 rather than 1.2 + self.BreakLate.LimitZmax = nil end --- Init parameters for Marshal Voice overs *Gabriella* by HighwaymanEd. -- @param #AIRBOSS self -- @param #string mizfolder (Optional) Folder within miz file where the sound files are located. -function AIRBOSS:SetVoiceOversMarshalByGabriella(mizfolder) +function AIRBOSS:SetVoiceOversMarshalByGabriella( mizfolder ) -- Set sound files folder. if mizfolder then - local lastchar=string.sub(mizfolder, -1) - if lastchar~="/" then - mizfolder=mizfolder.."/" + local lastchar = string.sub( mizfolder, -1 ) + if lastchar ~= "/" then + mizfolder = mizfolder .. "/" end - self.soundfolderMSH=mizfolder + self.soundfolderMSH = mizfolder else -- Default is the general folder. - self.soundfolderMSH=self.soundfolder + self.soundfolderMSH = self.soundfolder end -- Report for duty. - self:I(self.lid..string.format("Marshal Gabriella reporting for duty! Soundfolder=%s", tostring(self.soundfolderMSH))) + 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 + 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 - - --- Init parameters for Marshal Voice overs by *Raynor*. -- @param #AIRBOSS self -- @param #string mizfolder (Optional) Folder within miz file where the sound files are located. -function AIRBOSS:SetVoiceOversMarshalByRaynor(mizfolder) +function AIRBOSS:SetVoiceOversMarshalByRaynor( mizfolder ) -- Set sound files folder. if mizfolder then - local lastchar=string.sub(mizfolder, -1) - if lastchar~="/" then - mizfolder=mizfolder.."/" + local lastchar = string.sub( mizfolder, -1 ) + if lastchar ~= "/" then + mizfolder = mizfolder .. "/" end - self.soundfolderMSH=mizfolder + self.soundfolderMSH = mizfolder else -- Default is the general folder. - self.soundfolderMSH=self.soundfolder + self.soundfolderMSH = self.soundfolder end -- Report for duty. - self:I(self.lid..string.format("Marshal Raynor reporting for duty! Soundfolder=%s", tostring(self.soundfolderMSH))) + 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 -- Strangely the file is actually a shorter ~2.4 sec. - self.MarshalCall.RECOVERYPAUSEDRESUMED.duration=2.75 - self.MarshalCall.REPORTSEEME.duration=1.06 --0.96 - 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 + 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 -- Strangely the file is actually a shorter ~2.4 sec. + self.MarshalCall.RECOVERYPAUSEDRESUMED.duration = 2.75 + self.MarshalCall.REPORTSEEME.duration = 1.06 -- 0.96 + 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 --- Set parameters for LSO Voice overs by *Raynor*. -- @param #AIRBOSS self -- @param #string mizfolder (Optional) Folder within miz file where the sound files are located. -function AIRBOSS:SetVoiceOversLSOByRaynor(mizfolder) +function AIRBOSS:SetVoiceOversLSOByRaynor( mizfolder ) -- Set sound files folder. if mizfolder then - local lastchar=string.sub(mizfolder, -1) - if lastchar~="/" then - mizfolder=mizfolder.."/" + local lastchar = string.sub( mizfolder, -1 ) + if lastchar ~= "/" then + mizfolder = mizfolder .. "/" end - self.soundfolderLSO=mizfolder + self.soundfolderLSO = mizfolder else -- Default is the general folder. - self.soundfolderLSO=self.soundfolder + self.soundfolderLSO = self.soundfolder end -- Report for duty. - self:I(self.lid..string.format("LSO Raynor reporting for duty! Soundfolder=%s", tostring(self.soundfolderLSO))) + self:I( self.lid .. string.format( "LSO Raynor reporting for duty! Soundfolder=%s", tostring( self.soundfolderLSO ) ) ) - self.LSOCall.BOLTER.duration=0.75 - self.LSOCall.CALLTHEBALL.duration=0.625 - self.LSOCall.CHECK.duration=0.40 - self.LSOCall.CLEAREDTOLAND.duration=0.85 - self.LSOCall.COMELEFT.duration=0.60 - self.LSOCall.DEPARTANDREENTER.duration=1.10 - self.LSOCall.EXPECTHEAVYWAVEOFF.duration=1.30 - self.LSOCall.EXPECTSPOT75.duration=1.85 - self.LSOCall.EXPECTSPOT5.duration=1.3 - self.LSOCall.FAST.duration=0.75 - self.LSOCall.FOULDECK.duration=0.75 - self.LSOCall.HIGH.duration=0.65 - self.LSOCall.IDLE.duration=0.40 - self.LSOCall.LONGINGROOVE.duration=1.25 - self.LSOCall.LOW.duration=0.60 - self.LSOCall.N0.duration=0.38 - self.LSOCall.N1.duration=0.30 - self.LSOCall.N2.duration=0.30 - self.LSOCall.N3.duration=0.30 - self.LSOCall.N4.duration=0.32 - self.LSOCall.N5.duration=0.41 - self.LSOCall.N6.duration=0.48 - self.LSOCall.N7.duration=0.51 - self.LSOCall.N8.duration=0.38 - self.LSOCall.N9.duration=0.34 - self.LSOCall.PADDLESCONTACT.duration=0.91 - self.LSOCall.POWER.duration=0.45 - self.LSOCall.RADIOCHECK.duration=0.90 - self.LSOCall.RIGHTFORLINEUP.duration=0.70 - self.LSOCall.ROGERBALL.duration=0.72 - self.LSOCall.SLOW.duration=0.63 - --self.LSOCall.SLOW.duration=0.59 --TODO - self.LSOCall.STABILIZED.duration=0.75 - self.LSOCall.WAVEOFF.duration=0.55 - self.LSOCall.WELCOMEABOARD.duration=0.80 + self.LSOCall.BOLTER.duration = 0.75 + self.LSOCall.CALLTHEBALL.duration = 0.625 + self.LSOCall.CHECK.duration = 0.40 + self.LSOCall.CLEAREDTOLAND.duration = 0.85 + self.LSOCall.COMELEFT.duration = 0.60 + self.LSOCall.DEPARTANDREENTER.duration = 1.10 + self.LSOCall.EXPECTHEAVYWAVEOFF.duration = 1.30 + self.LSOCall.EXPECTSPOT75.duration = 1.85 + self.LSOCall.EXPECTSPOT5.duration = 1.3 + self.LSOCall.FAST.duration = 0.75 + self.LSOCall.FOULDECK.duration = 0.75 + self.LSOCall.HIGH.duration = 0.65 + self.LSOCall.IDLE.duration = 0.40 + self.LSOCall.LONGINGROOVE.duration = 1.25 + self.LSOCall.LOW.duration = 0.60 + self.LSOCall.N0.duration = 0.38 + self.LSOCall.N1.duration = 0.30 + self.LSOCall.N2.duration = 0.30 + self.LSOCall.N3.duration = 0.30 + self.LSOCall.N4.duration = 0.32 + self.LSOCall.N5.duration = 0.41 + self.LSOCall.N6.duration = 0.48 + self.LSOCall.N7.duration = 0.51 + self.LSOCall.N8.duration = 0.38 + self.LSOCall.N9.duration = 0.34 + self.LSOCall.PADDLESCONTACT.duration = 0.91 + self.LSOCall.POWER.duration = 0.45 + self.LSOCall.RADIOCHECK.duration = 0.90 + self.LSOCall.RIGHTFORLINEUP.duration = 0.70 + self.LSOCall.ROGERBALL.duration = 0.72 + self.LSOCall.SLOW.duration = 0.63 + -- self.LSOCall.SLOW.duration=0.59 --TODO + self.LSOCall.STABILIZED.duration = 0.75 + self.LSOCall.WAVEOFF.duration = 0.55 + self.LSOCall.WELCOMEABOARD.duration = 0.80 end - - --- Set parameters for LSO Voice overs by *funkyfranky*. -- @param #AIRBOSS self -- @param #string mizfolder (Optional) Folder within miz file where the sound files are located. -function AIRBOSS:SetVoiceOversLSOByFF(mizfolder) +function AIRBOSS:SetVoiceOversLSOByFF( mizfolder ) -- Set sound files folder. if mizfolder then - local lastchar=string.sub(mizfolder, -1) - if lastchar~="/" then - mizfolder=mizfolder.."/" + local lastchar = string.sub( mizfolder, -1 ) + if lastchar ~= "/" then + mizfolder = mizfolder .. "/" end - self.soundfolderLSO=mizfolder + self.soundfolderLSO = mizfolder else -- Default is the general folder. - self.soundfolderLSO=self.soundfolder + self.soundfolderLSO = self.soundfolder end -- Report for duty. - self:I(self.lid..string.format("LSO FF reporting for duty! Soundfolder=%s", tostring(self.soundfolderLSO))) + self:I( self.lid .. string.format( "LSO FF reporting for duty! Soundfolder=%s", tostring( self.soundfolderLSO ) ) ) - self.LSOCall.BOLTER.duration=0.75 - self.LSOCall.CALLTHEBALL.duration=0.60 - self.LSOCall.CHECK.duration=0.45 - self.LSOCall.CLEAREDTOLAND.duration=1.00 - self.LSOCall.COMELEFT.duration=0.60 - self.LSOCall.DEPARTANDREENTER.duration=1.10 - self.LSOCall.EXPECTHEAVYWAVEOFF.duration=1.20 - self.LSOCall.EXPECTSPOT75.duration=2.00 - self.LSOCall.EXPECTSPOT5.duration=1.3 - self.LSOCall.FAST.duration=0.70 - self.LSOCall.FOULDECK.duration=0.62 - self.LSOCall.HIGH.duration=0.65 - self.LSOCall.IDLE.duration=0.45 - self.LSOCall.LONGINGROOVE.duration=1.20 - self.LSOCall.LOW.duration=0.50 - self.LSOCall.N0.duration=0.40 - self.LSOCall.N1.duration=0.25 - self.LSOCall.N2.duration=0.37 - self.LSOCall.N3.duration=0.37 - self.LSOCall.N4.duration=0.39 - self.LSOCall.N5.duration=0.39 - self.LSOCall.N6.duration=0.40 - self.LSOCall.N7.duration=0.40 - self.LSOCall.N8.duration=0.37 - self.LSOCall.N9.duration=0.40 - self.LSOCall.PADDLESCONTACT.duration=1.00 - self.LSOCall.POWER.duration=0.50 - self.LSOCall.RADIOCHECK.duration=1.10 - self.LSOCall.RIGHTFORLINEUP.duration=0.80 - self.LSOCall.ROGERBALL.duration=1.00 - self.LSOCall.SLOW.duration=0.65 - self.LSOCall.SLOW.duration=0.59 - self.LSOCall.STABILIZED.duration=0.90 - self.LSOCall.WAVEOFF.duration=0.60 - self.LSOCall.WELCOMEABOARD.duration=1.00 + self.LSOCall.BOLTER.duration = 0.75 + self.LSOCall.CALLTHEBALL.duration = 0.60 + self.LSOCall.CHECK.duration = 0.45 + self.LSOCall.CLEAREDTOLAND.duration = 1.00 + self.LSOCall.COMELEFT.duration = 0.60 + self.LSOCall.DEPARTANDREENTER.duration = 1.10 + self.LSOCall.EXPECTHEAVYWAVEOFF.duration = 1.20 + self.LSOCall.EXPECTSPOT75.duration = 2.00 + self.LSOCall.EXPECTSPOT5.duration = 1.3 + self.LSOCall.FAST.duration = 0.70 + self.LSOCall.FOULDECK.duration = 0.62 + self.LSOCall.HIGH.duration = 0.65 + self.LSOCall.IDLE.duration = 0.45 + self.LSOCall.LONGINGROOVE.duration = 1.20 + self.LSOCall.LOW.duration = 0.50 + self.LSOCall.N0.duration = 0.40 + self.LSOCall.N1.duration = 0.25 + self.LSOCall.N2.duration = 0.37 + self.LSOCall.N3.duration = 0.37 + self.LSOCall.N4.duration = 0.39 + self.LSOCall.N5.duration = 0.39 + self.LSOCall.N6.duration = 0.40 + self.LSOCall.N7.duration = 0.40 + self.LSOCall.N8.duration = 0.37 + self.LSOCall.N9.duration = 0.40 + self.LSOCall.PADDLESCONTACT.duration = 1.00 + self.LSOCall.POWER.duration = 0.50 + self.LSOCall.RADIOCHECK.duration = 1.10 + self.LSOCall.RIGHTFORLINEUP.duration = 0.80 + self.LSOCall.ROGERBALL.duration = 1.00 + self.LSOCall.SLOW.duration = 0.65 + self.LSOCall.SLOW.duration = 0.59 + self.LSOCall.STABILIZED.duration = 0.90 + self.LSOCall.WAVEOFF.duration = 0.60 + self.LSOCall.WELCOMEABOARD.duration = 1.00 end --- Intit parameters for Marshal Voice overs by *funkyfranky*. -- @param #AIRBOSS self -- @param #string mizfolder (Optional) Folder within miz file where the sound files are located. -function AIRBOSS:SetVoiceOversMarshalByFF(mizfolder) +function AIRBOSS:SetVoiceOversMarshalByFF( mizfolder ) -- Set sound files folder. if mizfolder then - local lastchar=string.sub(mizfolder, -1) - if lastchar~="/" then - mizfolder=mizfolder.."/" + local lastchar = string.sub( mizfolder, -1 ) + if lastchar ~= "/" then + mizfolder = mizfolder .. "/" end - self.soundfolderMSH=mizfolder + self.soundfolderMSH = mizfolder else -- Default is the general folder. - self.soundfolderMSH=self.soundfolder + self.soundfolderMSH = self.soundfolder end -- Report for duty. - self:I(self.lid..string.format("Marshal FF reporting for duty! Soundfolder=%s", tostring(self.soundfolderMSH))) + 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 -- Strangely the file is actually a shorter ~2.4 sec. - 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 + 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 -- Strangely the file is actually a shorter ~2.4 sec. + 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 @@ -4846,291 +4812,44 @@ function AIRBOSS:_InitVoiceOvers() --------------- -- LSO Radio Calls. - 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, --0.45 was too short - subduration=1, - }, - SLOW={ - file="LSO-Slow", - suffix="ogg", - loud=true, - subtitle="You're slow", - duration=0.65, - subduration=1, - }, - FAST={ - file="LSO-Fast", - suffix="ogg", - loud=true, - subtitle="You're fast", - duration=0.70, - subduration=1, - }, - ROGERBALL={ - file="LSO-RogerBall", - suffix="ogg", - loud=false, - subtitle="Roger ball", - duration=1.00, - subduration=2, - }, - WAVEOFF={ - file="LSO-WaveOff", - suffix="ogg", - loud=false, - subtitle="Wave off", - duration=0.6, - subduration=5, - }, - LONGINGROOVE={ - file="LSO-LongInTheGroove", - suffix="ogg", - loud=false, - subtitle="You're long in the groove", - duration=1.2, - subduration=5, - }, - FOULDECK={ - file="LSO-FoulDeck", - suffix="ogg", - loud=false, - subtitle="Foul deck", - duration=0.62, - subduration=5, - }, - DEPARTANDREENTER={ - file="LSO-DepartAndReenter", - suffix="ogg", - loud=false, - subtitle="Depart and re-enter", - duration=1.1, - subduration=5, - }, - PADDLESCONTACT={ - file="LSO-PaddlesContact", - suffix="ogg", - loud=false, - subtitle="Paddles, contact", - duration=1.0, - subduration=5, - }, - WELCOMEABOARD={ - file="LSO-WelcomeAboard", - suffix="ogg", - loud=false, - subtitle="Welcome aboard", - duration=1.0, - subduration=5, - }, - EXPECTHEAVYWAVEOFF={ - file="LSO-ExpectHeavyWaveoff", - suffix="ogg", - loud=false, - subtitle="Expect heavy waveoff", - duration=1.2, - subduration=5, - }, - EXPECTSPOT75={ - file="LSO-ExpectSpot75", - suffix="ogg", - loud=false, - subtitle="Expect spot 7.5", - duration=2.0, - subduration=5, - }, - EXPECTSPOT5={ - file="LSO-ExpectSpot5", - suffix="ogg", - loud=false, - subtitle="Expect spot 5", - duration=1.3, - subduration=5, - }, - STABILIZED={ - file="LSO-Stabilized", - suffix="ogg", - loud=false, - subtitle="Stabilized", - duration=0.9, - subduration=5, - }, - IDLE={ - file="LSO-Idle", - suffix="ogg", - loud=false, - subtitle="Idle", - duration=0.45, - subduration=5, - }, - N0={ - file="LSO-N0", - suffix="ogg", - loud=false, - subtitle="", - duration=0.40, - }, - N1={ - file="LSO-N1", - suffix="ogg", - loud=false, - subtitle="", - duration=0.25, - }, - N2={ - file="LSO-N2", - suffix="ogg", - loud=false, - subtitle="", - duration=0.37, - }, - N3={ - file="LSO-N3", - suffix="ogg", - loud=false, - subtitle="", - duration=0.37, - }, - N4={ - file="LSO-N4", - suffix="ogg", - loud=false, - subtitle="", - duration=0.39, - }, - N5={ - file="LSO-N5", - suffix="ogg", - loud=false, - subtitle="", - duration=0.39, - }, - N6={ - file="LSO-N6", - suffix="ogg", - loud=false, - subtitle="", - duration=0.40, - }, - N7={ - file="LSO-N7", - suffix="ogg", - loud=false, - subtitle="", - duration=0.40, - }, - N8={ - file="LSO-N8", - suffix="ogg", - loud=false, - subtitle="", - duration=0.37, - }, - N9={ - file="LSO-N9", - suffix="ogg", - loud=false, - subtitle="", - duration=0.40, - }, - CLICK={ - file="AIRBOSS-RadioClick", - suffix="ogg", - loud=false, - subtitle="", - duration=0.35, - }, - NOISE={ - file="AIRBOSS-Noise", - suffix="ogg", - loud=false, - subtitle="", - duration=3.6, - }, - SPINIT={ - file="AIRBOSS-SpinIt", - suffix="ogg", - loud=false, - subtitle="", - duration=0.73, - subduration=5, - }, + self.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 }, -- duration 0.45 was too short + SLOW = { file = "LSO-Slow", suffix = "ogg", loud = true, subtitle = "You're slow", duration = 0.65, subduration = 1 }, + FAST = { file = "LSO-Fast", suffix = "ogg", loud = true, subtitle = "You're fast", duration = 0.70, subduration = 1 }, + ROGERBALL = { file = "LSO-RogerBall", suffix = "ogg", loud = false, subtitle = "Roger ball", duration = 1.00, subduration = 2 }, + WAVEOFF = { file = "LSO-WaveOff", suffix = "ogg", loud = false, subtitle = "Wave off", duration = 0.6, subduration = 5 }, + LONGINGROOVE = { file = "LSO-LongInTheGroove", suffix = "ogg", loud = false, subtitle = "You're long in the groove", duration = 1.2, subduration = 5 }, + FOULDECK = { file = "LSO-FoulDeck", suffix = "ogg", loud = false, subtitle = "Foul deck", duration = 0.62, subduration = 5 }, + DEPARTANDREENTER = { file = "LSO-DepartAndReenter", suffix = "ogg", loud = false, subtitle = "Depart and re-enter", duration = 1.1, subduration = 5 }, + PADDLESCONTACT = { file = "LSO-PaddlesContact", suffix = "ogg", loud = false, subtitle = "Paddles, contact", duration = 1.0, subduration = 5 }, + WELCOMEABOARD = { file = "LSO-WelcomeAboard", suffix = "ogg", loud = false, subtitle = "Welcome aboard", duration = 1.0, subduration = 5 }, + EXPECTHEAVYWAVEOFF = { file = "LSO-ExpectHeavyWaveoff", suffix = "ogg", loud = false, subtitle = "Expect heavy waveoff", duration = 1.2, subduration = 5 }, + EXPECTSPOT75 = { file = "LSO-ExpectSpot75", suffix = "ogg", loud = false, subtitle = "Expect spot 7.5", duration = 2.0, subduration = 5 }, + EXPECTSPOT5 = { file = "LSO-ExpectSpot5", suffix = "ogg", loud = false, subtitle = "Expect spot 5", duration = 1.3, subduration = 5 }, + STABILIZED = { file = "LSO-Stabilized", suffix = "ogg", loud = false, subtitle = "Stabilized", duration = 0.9, subduration = 5 }, + IDLE = { file = "LSO-Idle", suffix = "ogg", loud = false, subtitle = "Idle", duration = 0.45, subduration = 5 }, + N0 = { file = "LSO-N0", suffix = "ogg", loud = false, subtitle = "", duration = 0.40 }, + N1 = { file = "LSO-N1", suffix = "ogg", loud = false, subtitle = "", duration = 0.25 }, + N2 = { file = "LSO-N2", suffix = "ogg", loud = false, subtitle = "", duration = 0.37 }, + N3 = { file = "LSO-N3", suffix = "ogg", loud = false, subtitle = "", duration = 0.37 }, + N4 = { file = "LSO-N4", suffix = "ogg", loud = false, subtitle = "", duration = 0.39 }, + N5 = { file = "LSO-N5", suffix = "ogg", loud = false, subtitle = "", duration = 0.39 }, + N6 = { file = "LSO-N6", suffix = "ogg", loud = false, subtitle = "", duration = 0.40 }, + N7 = { file = "LSO-N7", suffix = "ogg", loud = false, subtitle = "", duration = 0.40 }, + N8 = { file = "LSO-N8", suffix = "ogg", loud = false, subtitle = "", duration = 0.37 }, + N9 = { file = "LSO-N9", suffix = "ogg", loud = false, subtitle = "", duration = 0.40 }, + CLICK = { file = "AIRBOSS-RadioClick", suffix = "ogg", loud = false, subtitle = "", duration = 0.35 }, + NOISE = { file = "AIRBOSS-Noise", suffix = "ogg", loud = false, subtitle = "", duration = 3.6 }, + SPINIT = { file = "AIRBOSS-SpinIt", suffix = "ogg", loud = false, subtitle = "", duration = 0.73, subduration = 5 }, } ----------------- @@ -5138,161 +4857,28 @@ function AIRBOSS:_InitVoiceOvers() ----------------- -- Pilot Radio Calls. - 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.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 }, } ------------------- @@ -5300,309 +4886,48 @@ function AIRBOSS:_InitVoiceOvers() ------------------- -- MARSHAL Radio Calls. - 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.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 }, } -- Default timings by Raynor @@ -5619,77 +4944,77 @@ end -- @param #number subduration (Optional) Duration how long the subtitle is displayed. -- @param #string filename (Optional) Name of the voice over sound file. -- @param #string suffix (Optional) Extention of file. Default ".ogg". -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" +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 --- Get optimal aircraft AoA parameters.. -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data table. -- @return #AIRBOSS.AircraftAoA AoA parameters for the given aircraft type. -function AIRBOSS:_GetAircraftAoA(playerData) +function AIRBOSS:_GetAircraftAoA( playerData ) -- Get AC type. - 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 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 -- Table with AoA values. - local aoa={} -- #AIRBOSS.AircraftAoA + local aoa = {} -- #AIRBOSS.AircraftAoA if hornet then -- F/A-18C Hornet parameters. - aoa.SLOW = 9.8 - aoa.Slow = 9.3 + aoa.SLOW = 9.8 + aoa.Slow = 9.3 aoa.OnSpeedMax = 8.8 - aoa.OnSpeed = 8.1 + aoa.OnSpeed = 8.1 aoa.OnSpeedMin = 7.4 - aoa.Fast = 6.9 - aoa.FAST = 6.3 + aoa.Fast = 6.9 + aoa.FAST = 6.3 elseif tomcat then -- F-14A/B Tomcat parameters (taken from NATOPS). Converted from units 0-30 to degrees. -- Currently assuming a linear relationship with 0=-10 degrees and 30=+40 degrees as stated in NATOPS. - aoa.SLOW = self:_AoAUnit2Deg(playerData, 17.0) --18.33 --17.0 units - aoa.Slow = self:_AoAUnit2Deg(playerData, 16.0) --16.67 --16.0 units - aoa.OnSpeedMax = self:_AoAUnit2Deg(playerData, 15.5) --15.83 --15.5 units - aoa.OnSpeed = self:_AoAUnit2Deg(playerData, 15.0) --15.0 --15.0 units - aoa.OnSpeedMin = self:_AoAUnit2Deg(playerData, 14.5) --14.17 --14.5 units - aoa.Fast = self:_AoAUnit2Deg(playerData, 14.0) --13.33 --14.0 units - aoa.FAST = self:_AoAUnit2Deg(playerData, 13.0) --11.67 --13.0 units + aoa.SLOW = self:_AoAUnit2Deg( playerData, 17.0 ) -- 18.33 --17.0 units + aoa.Slow = self:_AoAUnit2Deg( playerData, 16.0 ) -- 16.67 --16.0 units + aoa.OnSpeedMax = self:_AoAUnit2Deg( playerData, 15.5 ) -- 15.83 --15.5 units + aoa.OnSpeed = self:_AoAUnit2Deg( playerData, 15.0 ) -- 15.0 --15.0 units + aoa.OnSpeedMin = self:_AoAUnit2Deg( playerData, 14.5 ) -- 14.17 --14.5 units + aoa.Fast = self:_AoAUnit2Deg( playerData, 14.0 ) -- 13.33 --14.0 units + aoa.FAST = self:_AoAUnit2Deg( playerData, 13.0 ) -- 11.67 --13.0 units elseif goshawk then -- T-45C Goshawk parameters. - aoa.SLOW = 8.00 --19 - aoa.Slow = 7.75 --18 - aoa.OnSpeedMax = 7.25 --17.5 - aoa.OnSpeed = 7.00 --17 - aoa.OnSpeedMin = 6.75 --16.5 - aoa.Fast = 6.25 --16 - aoa.FAST = 6.00 --15 + aoa.SLOW = 8.00 -- 19 + aoa.Slow = 7.75 -- 18 + aoa.OnSpeedMax = 7.25 -- 17.5 + aoa.OnSpeed = 7.00 -- 17 + aoa.OnSpeedMin = 6.75 -- 16.5 + aoa.Fast = 6.25 -- 16 + aoa.FAST = 6.00 -- 15 elseif skyhawk then -- A-4E-C Skyhawk parameters from https://forums.eagle.ru/showpost.php?p=3703467&postcount=390 -- Note that these are arbitrary UNITS and not degrees. We need a conversion formula! -- Github repo suggests they simply use a factor of two to get from degrees to units. - aoa.SLOW = 9.50 --=19.0/2 - aoa.Slow = 9.25 --=18.5/2 - aoa.OnSpeedMax = 9.00 --=18.0/2 - aoa.OnSpeed = 8.75 --=17.5/2 8.1 - aoa.OnSpeedMin = 8.50 --=17.0/2 - aoa.Fast = 8.25 --=17.5/2 - aoa.FAST = 8.00 --=16.5/2 + aoa.SLOW = 9.50 -- =19.0/2 + aoa.Slow = 9.25 -- =18.5/2 + aoa.OnSpeedMax = 9.00 -- =18.0/2 + aoa.OnSpeed = 8.75 -- =17.5/2 8.1 + aoa.OnSpeedMin = 8.50 -- =17.0/2 + aoa.Fast = 8.25 -- =17.5/2 + aoa.FAST = 8.00 -- =16.5/2 elseif harrier then -- AV-8B Harrier parameters. Tuning done on the Fast AoA to allow for abeam and ninety at Nozzles 60 - 73. - aoa.SLOW = 14.0 - aoa.Slow = 13.0 + aoa.SLOW = 14.0 + aoa.Slow = 13.0 aoa.OnSpeedMax = 12.0 - aoa.OnSpeed = 11.0 + aoa.OnSpeed = 11.0 aoa.OnSpeedMin = 10.0 - aoa.Fast = 8.0 - aoa.FAST = 7.5 + aoa.Fast = 8.0 + aoa.FAST = 7.5 end return aoa @@ -5700,13 +5025,13 @@ end -- @param #AIRBOSS.PlayerData playerData Player data table. -- @param #number aoaunits AoA in arbitrary units. -- @return #number AoA in degrees. -function AIRBOSS:_AoAUnit2Deg(playerData, aoaunits) +function AIRBOSS:_AoAUnit2Deg( playerData, aoaunits ) -- Init. - local degrees=aoaunits + local degrees = aoaunits -- Check aircraft type of player. - if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then + if playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B then ------------- -- F-14A/B -- @@ -5718,20 +5043,20 @@ function AIRBOSS:_AoAUnit2Deg(playerData, aoaunits) -- Assuming a linear relationship between these to points of the graph. -- However: AoA=15 Units ==> 15 degrees, which is too much. - degrees=-10+50/30*aoaunits + degrees = -10 + 50 / 30 * aoaunits -- HB Facebook page https://www.facebook.com/heatblur/photos/a.683612385159716/754368278084126 -- AoA=15 Units <==> AoA=10.359 degrees. - degrees=0.918*aoaunits-3.411 + degrees = 0.918 * aoaunits - 3.411 - elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then + elseif playerData.actype == AIRBOSS.AircraftCarrier.A4EC then ---------- -- A-4E -- ---------- -- A-4E-C source code suggests a simple factor of 1/2 for conversion. - degrees=0.5*aoaunits + degrees = 0.5 * aoaunits end @@ -5743,13 +5068,13 @@ end -- @param #AIRBOSS.PlayerData playerData Player data table. -- @param #number degrees AoA in degrees. -- @return #number AoA in arbitrary units. -function AIRBOSS:_AoADeg2Units(playerData, degrees) +function AIRBOSS:_AoADeg2Units( playerData, degrees ) -- Init. - local aoaunits=degrees + local aoaunits = degrees -- Check aircraft type of player. - if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then + if playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B then ------------- -- F-14A/B -- @@ -5760,20 +5085,20 @@ function AIRBOSS:_AoADeg2Units(playerData, degrees) -- unit=30 ==> alpha=+40 degrees. -- Assuming a linear relationship between these to points of the graph. - aoaunits=(degrees+10)*30/50 + aoaunits = (degrees + 10) * 30 / 50 -- HB Facebook page https://www.facebook.com/heatblur/photos/a.683612385159716/754368278084126 -- AoA=15 Units <==> AoA=10.359 degrees. - aoaunits=1.089*degrees+3.715 + aoaunits = 1.089 * degrees + 3.715 - elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then + elseif playerData.actype == AIRBOSS.AircraftCarrier.A4EC then ---------- -- A-4E -- ---------- -- A-4E source code suggests a simple factor of two as conversion. - aoaunits=2*degrees + aoaunits = 2 * degrees end @@ -5788,16 +5113,16 @@ end -- @return #number Angle of Attack or nil. -- @return #number Distance to carrier in meters or nil. -- @return #number Speed in m/s or nil. -function AIRBOSS:_GetAircraftParameters(playerData, step) +function AIRBOSS:_GetAircraftParameters( playerData, step ) -- Get parameters depended on step. - step=step or playerData.step + step = step or playerData.step -- Get AC type. - 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 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 -- Return values. local alt @@ -5806,153 +5131,153 @@ function AIRBOSS:_GetAircraftParameters(playerData, step) local speed -- Aircraft specific AoA. - local aoaac=self:_GetAircraftAoA(playerData) + local aoaac = self:_GetAircraftAoA( playerData ) - if step==AIRBOSS.PatternStep.PLATFORM then + if step == AIRBOSS.PatternStep.PLATFORM then - alt=UTILS.FeetToMeters(5000) + alt = UTILS.FeetToMeters( 5000 ) - --dist=UTILS.NMToMeters(20) + -- dist=UTILS.NMToMeters(20) - speed=UTILS.KnotsToMps(250) + speed = UTILS.KnotsToMps( 250 ) - elseif step==AIRBOSS.PatternStep.ARCIN then + elseif step == AIRBOSS.PatternStep.ARCIN then - if tomcat then - speed=UTILS.KnotsToMps(150) - else - speed=UTILS.KnotsToMps(250) - end + if tomcat then + speed = UTILS.KnotsToMps( 150 ) + else + speed = UTILS.KnotsToMps( 250 ) + end - elseif step==AIRBOSS.PatternStep.ARCOUT then + elseif step == AIRBOSS.PatternStep.ARCOUT then - if tomcat then - speed=UTILS.KnotsToMps(150) - else - speed=UTILS.KnotsToMps(250) - end + if tomcat then + speed = UTILS.KnotsToMps( 150 ) + else + speed = UTILS.KnotsToMps( 250 ) + end - elseif step==AIRBOSS.PatternStep.DIRTYUP then + elseif step == AIRBOSS.PatternStep.DIRTYUP then - alt=UTILS.FeetToMeters(1200) + alt = UTILS.FeetToMeters( 1200 ) - --speed=UTILS.KnotsToMps(250) + -- speed=UTILS.KnotsToMps(250) - elseif step==AIRBOSS.PatternStep.BULLSEYE then + elseif step == AIRBOSS.PatternStep.BULLSEYE then - alt=UTILS.FeetToMeters(1200) + alt = UTILS.FeetToMeters( 1200 ) - dist=-UTILS.NMToMeters(3) + dist = -UTILS.NMToMeters( 3 ) - aoa=aoaac.OnSpeed + aoa = aoaac.OnSpeed - elseif step==AIRBOSS.PatternStep.INITIAL then + elseif step == AIRBOSS.PatternStep.INITIAL then if hornet or tomcat or harrier then - alt=UTILS.FeetToMeters(800) - speed=UTILS.KnotsToMps(350) + 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) + 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 + elseif step == AIRBOSS.PatternStep.BREAKENTRY then if hornet or tomcat or harrier then - alt=UTILS.FeetToMeters(800) - speed=UTILS.KnotsToMps(350) + 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) + 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 + elseif step == AIRBOSS.PatternStep.EARLYBREAK then if hornet or tomcat or harrier or goshawk then - alt=UTILS.FeetToMeters(800) + alt = UTILS.FeetToMeters( 800 ) elseif skyhawk then - alt=UTILS.FeetToMeters(600) + alt = UTILS.FeetToMeters( 600 ) end - elseif step==AIRBOSS.PatternStep.LATEBREAK then + elseif step == AIRBOSS.PatternStep.LATEBREAK then if hornet or tomcat or harrier or goshawk then - alt=UTILS.FeetToMeters(800) + alt = UTILS.FeetToMeters( 800 ) elseif skyhawk then - alt=UTILS.FeetToMeters(600) + alt = UTILS.FeetToMeters( 600 ) end - elseif step==AIRBOSS.PatternStep.ABEAM then + elseif step == AIRBOSS.PatternStep.ABEAM then if hornet or tomcat or harrier or goshawk then - alt=UTILS.FeetToMeters(600) + alt = UTILS.FeetToMeters( 600 ) elseif skyhawk then - alt=UTILS.FeetToMeters(500) + alt = UTILS.FeetToMeters( 500 ) end - aoa=aoaac.OnSpeed + aoa = aoaac.OnSpeed if harrier then -- 0.8 to 1.0 NM - dist=UTILS.NMToMeters(0.9) + dist = UTILS.NMToMeters( 0.9 ) else - dist=UTILS.NMToMeters(1.2) + dist = UTILS.NMToMeters( 1.2 ) end - if goshawk then + if goshawk then -- 0.9 to 1.1 NM per natops ch.4 page 48 - dist=UTILS.NMToMeters(0.9) + dist = UTILS.NMToMeters( 0.9 ) else - dist=UTILS.NMToMeters(1.1) + dist = UTILS.NMToMeters( 1.1 ) end - elseif step==AIRBOSS.PatternStep.NINETY then + elseif step == AIRBOSS.PatternStep.NINETY then if hornet or tomcat then - alt=UTILS.FeetToMeters(500) - elseif goshawk then - alt=UTILS.FeetToMeters(450) + alt = UTILS.FeetToMeters( 500 ) + elseif goshawk then + alt = UTILS.FeetToMeters( 450 ) elseif skyhawk then - alt=UTILS.FeetToMeters(500) + alt = UTILS.FeetToMeters( 500 ) elseif harrier then - alt=UTILS.FeetToMeters(425) + alt = UTILS.FeetToMeters( 425 ) end - aoa=aoaac.OnSpeed + aoa = aoaac.OnSpeed - elseif step==AIRBOSS.PatternStep.WAKE then + elseif step == AIRBOSS.PatternStep.WAKE then if hornet or goshawk then - alt=UTILS.FeetToMeters(370) + alt = UTILS.FeetToMeters( 370 ) elseif tomcat then - alt=UTILS.FeetToMeters(430) -- Tomcat should be a bit higher as it intercepts the GS a bit higher. + alt = UTILS.FeetToMeters( 430 ) -- Tomcat should be a bit higher as it intercepts the GS a bit higher. elseif skyhawk then - alt=UTILS.FeetToMeters(370) --? + alt = UTILS.FeetToMeters( 370 ) -- ? end -- Harrier wont get into wake pos. Runway is not angled and it stays port. - aoa=aoaac.OnSpeed + aoa = aoaac.OnSpeed - elseif step==AIRBOSS.PatternStep.FINAL then + elseif step == AIRBOSS.PatternStep.FINAL then if hornet or goshawk then - alt=UTILS.FeetToMeters(300) + alt = UTILS.FeetToMeters( 300 ) elseif tomcat then - alt=UTILS.FeetToMeters(360) + alt = UTILS.FeetToMeters( 360 ) elseif skyhawk then - alt=UTILS.FeetToMeters(300) --? + alt = UTILS.FeetToMeters( 300 ) -- ? elseif harrier then -- 300-325 ft - alt=UTILS.FeetToMeters(300)-- Need to verify + alt = UTILS.FeetToMeters( 300 ) -- Need to verify end - aoa=aoaac.OnSpeed + aoa = aoaac.OnSpeed end @@ -5969,30 +5294,30 @@ end function AIRBOSS:_GetNextMarshalFight() -- Loop over all marshal flights. - for _,_flight in pairs(self.Qmarshal) do - local flight=_flight --#AIRBOSS.FlightGroup + for _, _flight in pairs( self.Qmarshal ) do + local flight = _flight -- #AIRBOSS.FlightGroup -- Current stack. - local stack=flight.flag + local stack = flight.flag -- Total marshal time in seconds. - local Tmarshal=timer.getAbsTime()-flight.time + local Tmarshal = timer.getAbsTime() - flight.time -- Min time in marshal stack. - local TmarshalMin=2*60 --Two minutes for human players. + local TmarshalMin = 2 * 60 -- Two minutes for human players. if flight.ai then - TmarshalMin=3*60 -- Three minutes for AI. + TmarshalMin = 3 * 60 -- Three minutes for AI. end -- Check if conditions are right. - if flight.holding~=nil and Tmarshal>=TmarshalMin then - if flight.case==1 and stack==1 or flight.case>1 then + 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 AI flight. return flight else -- Check for human player if they are already commencing. - if flight.step~=AIRBOSS.PatternStep.COMMENCING then + if flight.step ~= AIRBOSS.PatternStep.COMMENCING then return flight end end @@ -6009,34 +5334,34 @@ function AIRBOSS:_CheckQueue() -- Print queues. if self.Debug then - self:_PrintQueue(self.flights, "All Flights") + 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") + self:_PrintQueue( self.Qmarshal, "Marshal" ) + self:_PrintQueue( self.Qpattern, "Pattern" ) + self:_PrintQueue( self.Qwaiting, "Waiting" ) + self:_PrintQueue( self.Qspinning, "Spinning" ) -- If flights are waiting outside 10 NM zone and carrier switches from Case I to Case II/III, they should be added to the Marshal stack as now there is no stack limit any more. - if self.case>1 then - for _,_flight in pairs(self.Qwaiting) do - local flight=_flight --#AIRBOSS.FlightGroup + if self.case > 1 then + for _, _flight in pairs( self.Qwaiting ) do + local flight = _flight -- #AIRBOSS.FlightGroup -- Remove flight from waiting queue. - local removed=self:_RemoveFlightFromQueue(self.Qwaiting, flight) + local removed = self:_RemoveFlightFromQueue( self.Qwaiting, flight ) if removed then -- Get free stack - local stack=self:_GetFreeStack(flight.ai) + local stack = self:_GetFreeStack( flight.ai ) -- Debug info. - 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)) + 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 ) ) -- Send flight to marshal stack. if flight.ai then - self:_MarshalAI(flight, stack) + self:_MarshalAI( flight, stack ) else - self:_MarshalPlayer(flight, stack) + self:_MarshalPlayer( flight, stack ) end -- Break the loop so that only one flight per 30 seconds is removed. @@ -6054,40 +5379,40 @@ function AIRBOSS:_CheckQueue() ----------------------------- -- Loop over all flights currently in the marshal queue. - for _,_flight in pairs(self.Qmarshal) do - local flight=_flight --#AIRBOSS.FlightGroup + for _, _flight in pairs( self.Qmarshal ) do + local flight = _flight -- #AIRBOSS.FlightGroup -- TODO: In principle this should be done/necessary only if case 1-->2/3 or 2/3-->1, right? -- When recovery switches from 2->3 or 3-->2 nothing changes in the marshal stack. -- Check if a change of stack is necessary. - if (flight.case==1 and self.case>1) or (flight.case>1 and self.case==1) then + if (flight.case == 1 and self.case > 1) or (flight.case > 1 and self.case == 1) then -- Remove flight from marshal queue. - local removed=self:_RemoveFlightFromQueue(self.Qmarshal, flight) + local removed = self:_RemoveFlightFromQueue( self.Qmarshal, flight ) if removed then -- Get free stack - local stack=self:_GetFreeStack(flight.ai) + local stack = self:_GetFreeStack( flight.ai ) -- Debug output. - 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)) + 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 ) ) -- Send flight to marshal queue. if flight.ai then - self:_MarshalAI(flight, stack) + self:_MarshalAI( flight, stack ) else - self:_MarshalPlayer(flight, stack) + self:_MarshalPlayer( flight, stack ) end -- Break the loop so that only one flight per 30 seconds is removed. No spam of messages, no conflict with the loop over queue entries. break - elseif flight.case~=self.case then + elseif flight.case ~= self.case then -- This should handle 2-->3 or 3-->2 - flight.case=self.case + flight.case = self.case end @@ -6099,85 +5424,84 @@ function AIRBOSS:_CheckQueue() end -- Get number of airborne aircraft units(!) currently in pattern. - local _,npattern=self:_GetQueueInfo(self.Qpattern) + local _, npattern = self:_GetQueueInfo( self.Qpattern ) -- Get number of aircraft units spinning. - local _,nspinning=self:_GetQueueInfo(self.Qspinning) + local _, nspinning = self:_GetQueueInfo( self.Qspinning ) -- Get next marshal flight. - local marshalflight=self:_GetNextMarshalFight() + local marshalflight = self:_GetNextMarshalFight() -- Check if there are flights waiting in the Marshal stack and if the pattern is free. No one should be spinning. - if marshalflight and npattern0 then + local Tpattern = 9999 + local npunits = 1 + local pcase = 1 + if npattern > 0 then -- Last flight group send to pattern. - local patternflight=self.Qpattern[#self.Qpattern] --#AIRBOSS.FlightGroup + local patternflight = self.Qpattern[#self.Qpattern] -- #AIRBOSS.FlightGroup -- Recovery case of pattern flight. - pcase=patternflight.case + pcase = patternflight.case -- Number of airborne aircraft in this group. Count includes section members. - local npunits=self:_GetFlightUnits(patternflight, false) + local npunits = self:_GetFlightUnits( patternflight, false ) -- Get time in pattern. - 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)) + 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 -- Min time in pattern before next aircraft is allowed. local TpatternMin - if pcase==1 then - TpatternMin=2*60*npunits --45*npunits -- 45 seconds interval per plane! + if pcase == 1 then + TpatternMin = 2 * 60 * npunits -- 45*npunits -- 45 seconds interval per plane! else - TpatternMin=2*60*npunits --120*npunits -- 120 seconds interval per plane! + TpatternMin = 2 * 60 * npunits -- 120*npunits -- 120 seconds interval per plane! end -- Check interval to last pattern flight. - if Tpattern>TpatternMin then - self:T(self.lid..string.format("Sending marshal flight %s to pattern.", marshalflight.groupname)) - self:_ClearForLanding(marshalflight) + if Tpattern > TpatternMin then + self:T( self.lid .. string.format( "Sending marshal flight %s to pattern.", marshalflight.groupname ) ) + self:_ClearForLanding( marshalflight ) end end end - --- Clear flight for landing. AI are removed from Marshal queue and the Marshal stack is collapsed. -- If next in line is an AI flight, this is done. If human player is next, we wait for "Commence" via F10 radio menu command. -- @param #AIRBOSS self -- @param #AIRBOSS.FlightGroup flight Flight to go to pattern. -function AIRBOSS:_ClearForLanding(flight) +function AIRBOSS:_ClearForLanding( flight ) -- Check if flight is AI or human. If AI, we collapse the stack and commence. If human, we suggest to commence. if flight.ai then -- Collapse stack and send AI to pattern. - self:_RemoveFlightFromMarshalQueue(flight, false) - self:_LandAI(flight) + self:_RemoveFlightFromMarshalQueue( flight, false ) + self:_LandAI( flight ) -- Cleared for Case X recovery. - self:_MarshalCallClearedForRecovery(flight.onboard, flight.case) + self:_MarshalCallClearedForRecovery( flight.onboard, flight.case ) else -- Cleared for Case X recovery. - if flight.step~=AIRBOSS.PatternStep.COMMENCING then - self:_MarshalCallClearedForRecovery(flight.onboard, flight.case) - flight.time=timer.getAbsTime() + if flight.step ~= AIRBOSS.PatternStep.COMMENCING then + self:_MarshalCallClearedForRecovery( flight.onboard, flight.case ) + flight.time = timer.getAbsTime() end -- Set step to commencing. This will trigger the zone check until the player is in the right place. - self:_SetPlayerStep(flight, AIRBOSS.PatternStep.COMMENCING, 3) + self:_SetPlayerStep( flight, AIRBOSS.PatternStep.COMMENCING, 3 ) end @@ -6188,25 +5512,25 @@ end -- @param #AIRBOSS.PlayerData playerData Player data. -- @param #string step Next step. -- @param #number delay (Optional) Set set after a delay in seconds. -function AIRBOSS:_SetPlayerStep(playerData, step, delay) +function AIRBOSS:_SetPlayerStep( playerData, step, delay ) - if delay and delay>0 then + if delay and delay > 0 then -- Delayed call. - --SCHEDULER:New(nil, self._SetPlayerStep, {self, playerData, step}, delay) - self:ScheduleOnce(delay, self._SetPlayerStep, self, playerData, step) + -- SCHEDULER:New(nil, self._SetPlayerStep, {self, playerData, step}, delay) + self:ScheduleOnce( delay, self._SetPlayerStep, self, playerData, step ) else -- Check if player still exists after possible delay. if playerData then -- Set player step. - playerData.step=step + playerData.step = step -- Erase warning. - playerData.warning=nil + playerData.warning = nil -- Next step hint. - self:_StepHint(playerData) + self:_StepHint( playerData ) end end @@ -6218,92 +5542,89 @@ end function AIRBOSS:_ScanCarrierZone() -- Carrier position. - local coord=self:GetCoordinate() + local coord = self:GetCoordinate() -- Scan radius = radius of the CCA. - local RCCZ=self.zoneCCA:GetRadius() + local RCCZ = self.zoneCCA:GetRadius() -- Debug info. - self:T(self.lid..string.format("Scanning Carrier Controlled Area. Radius=%.1f NM.", UTILS.MetersToNM(RCCZ))) + self:T( self.lid .. string.format( "Scanning Carrier Controlled Area. Radius=%.1f NM.", UTILS.MetersToNM( RCCZ ) ) ) -- Scan units in carrier zone. - local _,_,_,unitscan=coord:ScanObjects(RCCZ, true, false, false) - + local _, _, _, unitscan = coord:ScanObjects( RCCZ, true, false, false ) -- Make a table with all groups currently in the CCA zone. - local insideCCA={} - for _,_unit in pairs(unitscan) do - local unit=_unit --Wrapper.Unit#UNIT + local insideCCA = {} + for _, _unit in pairs( unitscan ) do + local unit = _unit -- Wrapper.Unit#UNIT -- Necessary conditions to be met: - local airborne=unit:IsAir() --and unit:InAir() - local inzone=unit:IsInZone(self.zoneCCA) - local friendly=self:GetCoalition()==unit:GetCoalition() - local carrierac=self:_IsCarrierAircraft(unit) + local airborne = unit:IsAir() -- and unit:InAir() + local inzone = unit:IsInZone( self.zoneCCA ) + local friendly = self:GetCoalition() == unit:GetCoalition() + local carrierac = self:_IsCarrierAircraft( unit ) -- Check if this an aircraft and that it is airborne and closing in. if airborne and inzone and friendly and carrierac then - local group=unit:GetGroup() - local groupname=group:GetName() + local group = unit:GetGroup() + local groupname = group:GetName() - if insideCCA[groupname]==nil then - insideCCA[groupname]=group + if insideCCA[groupname] == nil then + insideCCA[groupname] = group end end end -- Find new flights that are inside CCA. - for groupname,_group in pairs(insideCCA) do - local group=_group --Wrapper.Group#GROUP + for groupname, _group in pairs( insideCCA ) do + local group = _group -- Wrapper.Group#GROUP -- Get flight group if possible. - local knownflight=self:_GetFlightFromGroupInQueue(group, self.flights) + local knownflight = self:_GetFlightFromGroupInQueue( group, self.flights ) -- Get aircraft type name. - local actype=group:GetTypeName() + local actype = group:GetTypeName() -- Create a new flight group if knownflight then -- Check if flight is AI and if we want to handle it at all. - if knownflight.ai and knownflight.flag==-100 and self.handleai then + if knownflight.ai and knownflight.flag == -100 and self.handleai then - local putintomarshal=false + local putintomarshal = false -- Get flight group. - local flight=_DATABASE:GetFlightGroup(groupname) + local flight = _DATABASE:GetFlightGroup( groupname ) - if flight and flight:IsInbound() and flight.destbase:GetName()==self.carrier:GetName() then + if flight and flight:IsInbound() and flight.destbase:GetName() == self.carrier:GetName() then if flight.ishelo then else - putintomarshal=true + putintomarshal = true end - flight.airboss=self + flight.airboss = self end - - -- Send AI flight to marshal stack. if putintomarshal then -- Get the next free stack for current recovery case. - local stack=self:_GetFreeStack(knownflight.ai) + local stack = self:_GetFreeStack( knownflight.ai ) -- Repawn. - local respawn=self.respawnAI + local respawn = self.respawnAI if stack then -- Send AI to marshal stack. We respawn the group to clean possible departure and destination airbases. - self:_MarshalAI(knownflight, stack, respawn) + self:_MarshalAI( knownflight, stack, respawn ) else -- Send AI to orbit outside 10 NM zone and wait until the next Marshal stack is available. - if not self:_InQueue(self.Qwaiting, knownflight.group) then - self:_WaitAI(knownflight, respawn) -- Group is respawned to clear any attached airfields. + if not self:_InQueue( self.Qwaiting, knownflight.group ) then + self:_WaitAI( knownflight, respawn ) -- Group is respawned to clear any attached airfields. end end @@ -6311,15 +5632,15 @@ function AIRBOSS:_ScanCarrierZone() -- Break the loop to not have all flights at once! Spams the message screen. break - end -- Closed in or tanker/AWACS + end -- Closed in or tanker/AWACS end else -- Unknown new AI flight. Create a new flight group. - if not self:_IsHuman(group) then - self:_CreateFlightGroup(group) + if not self:_IsHuman( group ) then + self:_CreateFlightGroup( group ) end end @@ -6327,20 +5648,20 @@ function AIRBOSS:_ScanCarrierZone() end -- Find flights that are not in CCA. - local remove={} - for _,_flight in pairs(self.flights) do - local flight=_flight --#AIRBOSS.FlightGroup - if insideCCA[flight.groupname]==nil then + local remove = {} + for _, _flight in pairs( self.flights ) do + local flight = _flight -- #AIRBOSS.FlightGroup + if insideCCA[flight.groupname] == nil then -- Do not remove flights in marshal pattern. At least for case 2 & 3. If zone is set small, they might be outside in the holding pattern. - if flight.ai and not (self:_InQueue(self.Qmarshal, flight.group) or self:_InQueue(self.Qpattern, flight.group)) then - table.insert(remove, flight) + 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 -- Remove flight groups outside CCA. - for _,flight in pairs(remove) do - self:_RemoveFlightFromQueue(self.flights, flight) + for _, flight in pairs( remove ) do + self:_RemoveFlightFromQueue( self.flights, flight ) end end @@ -6348,82 +5669,81 @@ end --- Tell player to wait outside the 10 NM zone until a Marshal stack is available. -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data. -function AIRBOSS:_WaitPlayer(playerData) +function AIRBOSS:_WaitPlayer( playerData ) -- Check if flight is known to the airboss already. if playerData then -- Number of waiting flights - local nwaiting=#self.Qwaiting + local nwaiting = #self.Qwaiting -- Radio message: Stack is full. - self:_MarshalCallStackFull(playerData.onboard, nwaiting) + self:_MarshalCallStackFull( playerData.onboard, nwaiting ) -- Add player flight to waiting queue. - table.insert(self.Qwaiting, playerData) + table.insert( self.Qwaiting, playerData ) -- Set time stamp. - playerData.time=timer.getAbsTime() + playerData.time = timer.getAbsTime() -- Set step to waiting. - playerData.step=AIRBOSS.PatternStep.WAITING - playerData.warning=nil + playerData.step = AIRBOSS.PatternStep.WAITING + playerData.warning = nil -- Set all flights in section to waiting. - for _,_flight in pairs(playerData.section) do - local flight=_flight --#AIRBOSS.PlayerData - flight.step=AIRBOSS.PatternStep.WAITING - flight.time=timer.getAbsTime() - flight.warning=nil + for _, _flight in pairs( playerData.section ) do + local flight = _flight -- #AIRBOSS.PlayerData + flight.step = AIRBOSS.PatternStep.WAITING + flight.time = timer.getAbsTime() + flight.warning = nil end end end - --- Orbit at a specified position at a specified altitude with a specified speed. -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data. -- @param #number stack The Marshal stack the player gets. -function AIRBOSS:_MarshalPlayer(playerData, stack) +function AIRBOSS:_MarshalPlayer( playerData, stack ) -- Check if flight is known to the airboss already. if playerData then -- Add group to marshal stack. - self:_AddMarshalGroup(playerData, stack) + self:_AddMarshalGroup( playerData, stack ) -- Set step to holding. - self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.HOLDING) + self:_SetPlayerStep( playerData, AIRBOSS.PatternStep.HOLDING ) -- Holding switch to nil until player arrives in the holding zone. - playerData.holding=nil + playerData.holding = nil -- Set same stack for all flights in section. - for _,_flight in pairs(playerData.section) do - local flight=_flight --#AIRBOSS.PlayerData + for _, _flight in pairs( playerData.section ) do + local flight = _flight -- #AIRBOSS.PlayerData -- XXX: Inform player? Should be done by lead via radio? -- Set step. - self:_SetPlayerStep(flight, AIRBOSS.PatternStep.HOLDING) + self:_SetPlayerStep( flight, AIRBOSS.PatternStep.HOLDING ) -- Holding to nil, until arrived. - flight.holding=nil + flight.holding = nil -- Set case to that of lead. - flight.case=playerData.case + flight.case = playerData.case -- Set stack flag. - flight.flag=stack + flight.flag = stack - -- Trigger Marshal event. - self:Marshal(flight) + -- Trigger Marshal event. + self:Marshal( flight ) end else - self:E(self.lid.."ERROR: Could not add player to Marshal stack! playerData=nil") + self:E( self.lid .. "ERROR: Could not add player to Marshal stack! playerData=nil" ) end end @@ -6433,61 +5753,61 @@ end -- @param #AIRBOSS self -- @param #AIRBOSS.FlightGroup flight Flight group. -- @param #boolean respawn If true respawn the group. Otherwise reset the mission task with new waypoints. -function AIRBOSS:_WaitAI(flight, respawn) +function AIRBOSS:_WaitAI( flight, respawn ) -- Set flag to something other than -100 and <0 - flight.flag=-99 + flight.flag = -99 -- Add AI flight to waiting queue. - table.insert(self.Qwaiting, flight) + table.insert( self.Qwaiting, flight ) -- Flight group name. - local group=flight.group - local groupname=flight.groupname + local group = flight.group + local groupname = flight.groupname -- Aircraft speed 274 knots TAS ~= 250 KIAS when orbiting the pattern. (Orbit expects m/s.) - local speedOrbitMps=UTILS.KnotsToMps(274) + local speedOrbitMps = UTILS.KnotsToMps( 274 ) -- Orbit speed in km/h for waypoints. - local speedOrbitKmh=UTILS.KnotsToKmph(274) + local speedOrbitKmh = UTILS.KnotsToKmph( 274 ) -- Aircraft speed 400 knots when transiting to holding zone. (Waypoint expects km/h.) - local speedTransit=UTILS.KnotsToKmph(370) + local speedTransit = UTILS.KnotsToKmph( 370 ) -- Carrier coordinate - local cv=self:GetCoordinate() + local cv = self:GetCoordinate() -- Coordinate of flight group - local fc=group:GetCoordinate() + local fc = group:GetCoordinate() -- Carrier heading - local hdg=self:GetHeading(false) + local hdg = self:GetHeading( false ) -- Heading from carrier to flight group - local hdgto=cv:HeadingTo(fc) + local hdgto = cv:HeadingTo( fc ) -- Holding alitude between angels 6 and 10 (random). - local angels=math.random(6,10) - local altitude=UTILS.FeetToMeters(angels*1000) + local angels = math.random( 6, 10 ) + local altitude = UTILS.FeetToMeters( angels * 1000 ) -- Point outsize 10 NM zone of the carrier. - local p0=cv:Translate(UTILS.NMToMeters(11), hdgto):Translate(UTILS.NMToMeters(5), hdg):SetAltitude(altitude) + local p0 = cv:Translate( UTILS.NMToMeters( 11 ), hdgto ):Translate( UTILS.NMToMeters( 5 ), hdg ):SetAltitude( altitude ) -- Waypoints array to be filled depending on case etc. - local wp={} + local wp = {} -- Current position. Always good for as the first waypoint. - wp[1]=group:GetCoordinate():WaypointAirTurningPoint(nil, speedTransit, {}, "Current Position") + wp[1] = group:GetCoordinate():WaypointAirTurningPoint( nil, speedTransit, {}, "Current Position" ) -- Set orbit task. - local taskorbit=group:TaskOrbit(p0, altitude, speedOrbitMps) + local taskorbit = group:TaskOrbit( p0, altitude, speedOrbitMps ) -- Orbit at waypoint. - wp[#wp+1]=p0:WaypointAirTurningPoint(nil, speedOrbitKmh, {taskorbit}, string.format("Waiting Orbit at Angels %d", angels)) + wp[#wp + 1] = p0:WaypointAirTurningPoint( nil, speedOrbitKmh, { taskorbit }, string.format( "Waiting Orbit at Angels %d", angels ) ) -- Debug markers. if self.Debug then - p0:MarkToAll(string.format("Waiting Orbit of flight %s at Angels %s", groupname, angels)) + p0:MarkToAll( string.format( "Waiting Orbit of flight %s at Angels %s", groupname, angels ) ) end if respawn then @@ -6496,21 +5816,21 @@ function AIRBOSS:_WaitAI(flight, respawn) -- Note: This resets the weapons and the fuel state. But not the units fortunately. -- Get group template. - local Template=group:GetTemplate() + local Template = group:GetTemplate() -- Set route points. - Template.route.points=wp + Template.route.points = wp -- Respawn the group. - group=group:Respawn(Template, true) + group = group:Respawn( Template, true ) end -- Reinit waypoints. - group:WayPointInitialize(wp) + group:WayPointInitialize( wp ) -- Route group. - group:Route(wp, 1) + group:Route( wp, 1 ) end @@ -6520,69 +5840,69 @@ end -- @param #AIRBOSS.FlightGroup flight Flight group. -- @param #number nstack Stack number of group. Can also be the current stack if AI position needs to be updated wrt to changed carrier position. -- @param #boolean respawn If true, respawn the flight otherwise update mission task with new waypoints. -function AIRBOSS:_MarshalAI(flight, nstack, respawn) - self:F2({flight=flight, nstack=nstack, respawn=respawn}) +function AIRBOSS:_MarshalAI( flight, nstack, respawn ) + self:F2( { flight = flight, nstack = nstack, respawn = respawn } ) -- Nil check. - if flight==nil or flight.group==nil then - self:E(self.lid.."ERROR: flight or flight.group is nil.") + if flight == nil or flight.group == nil then + self:E( self.lid .. "ERROR: flight or flight.group is nil." ) return end -- Nil check. - if flight.group:GetCoordinate()==nil then - self:E(self.lid.."ERROR: cannot get coordinate of flight group.") + if flight.group:GetCoordinate() == nil then + self:E( self.lid .. "ERROR: cannot get coordinate of flight group." ) return end -- Check if flight is already in Marshal queue. - if not self:_InQueue(self.Qmarshal,flight.group) then + if not self:_InQueue( self.Qmarshal, flight.group ) then -- Add group to marshal stack queue. - self:_AddMarshalGroup(flight, nstack) + self:_AddMarshalGroup( flight, nstack ) end -- Explode unit for testing. Worked! - --local u1=flight.group:GetUnit(1) --Wrapper.Unit#UNIT - --u1:Explode(500, 10) + -- local u1=flight.group:GetUnit(1) --Wrapper.Unit#UNIT + -- u1:Explode(500, 10) -- Recovery case. - local case=flight.case + local case = flight.case -- Get old/current stack. - local ostack=flight.flag + local ostack = flight.flag -- Flight group name. - local group=flight.group - local groupname=flight.groupname + local group = flight.group + local groupname = flight.groupname -- Set new stack. - flight.flag=nstack + flight.flag = nstack -- Current carrier position. - local Carrier=self:GetCoordinate() + local Carrier = self:GetCoordinate() -- Carrier heading. - local hdg=self:GetHeading() + local hdg = self:GetHeading() -- Aircraft speed 274 knots TAS ~= 250 KIAS when orbiting the pattern. (Orbit expects m/s.) - local speedOrbitMps=UTILS.KnotsToMps(274) + local speedOrbitMps = UTILS.KnotsToMps( 274 ) -- Orbit speed in km/h for waypoints. - local speedOrbitKmh=UTILS.KnotsToKmph(274) + local speedOrbitKmh = UTILS.KnotsToKmph( 274 ) -- Aircraft speed 400 knots when transiting to holding zone. (Waypoint expects km/h.) - local speedTransit=UTILS.KnotsToKmph(370) + local speedTransit = UTILS.KnotsToKmph( 370 ) local altitude - local p0 --Core.Point#COORDINATE - local p1 --Core.Point#COORDINATE - local p2 --Core.Point#COORDINATE + local p0 -- Core.Point#COORDINATE + local p1 -- Core.Point#COORDINATE + local p2 -- Core.Point#COORDINATE -- Get altitude and positions. - altitude, p1, p2=self:_GetMarshalAltitude(nstack, case) + altitude, p1, p2 = self:_GetMarshalAltitude( nstack, case ) -- Waypoints array to be filled depending on case etc. - local wp={} + local wp = {} -- If flight has not arrived in the holding zone, we guide it there. if not flight.holding then @@ -6592,36 +5912,36 @@ function AIRBOSS:_MarshalAI(flight, nstack, respawn) ---------------------- -- Debug info. - self:T(self.lid..string.format("Guiding AI flight %s to marshal stack %d-->%d.", groupname, ostack, nstack)) + self:T( self.lid .. string.format( "Guiding AI flight %s to marshal stack %d-->%d.", groupname, ostack, nstack ) ) -- Current position. Always good for as the first waypoint. - wp[1]=group:GetCoordinate():WaypointAirTurningPoint(nil, speedTransit, {}, "Current Position") + wp[1] = group:GetCoordinate():WaypointAirTurningPoint( nil, speedTransit, {}, "Current Position" ) -- Task function when arriving at the holding zone. This will set flight.holding=true. - local TaskArrivedHolding=flight.group:TaskFunction("AIRBOSS._ReachedHoldingZone", self, flight) + local TaskArrivedHolding = flight.group:TaskFunction( "AIRBOSS._ReachedHoldingZone", self, flight ) -- Select case. - if case==1 then + if case == 1 then -- Initial point 7 NM and a bit port of carrier. - local pE=Carrier:Translate(UTILS.NMToMeters(7), hdg-30):SetAltitude(altitude) + local pE = Carrier:Translate( UTILS.NMToMeters( 7 ), hdg - 30 ):SetAltitude( altitude ) -- Entry point 5 NM port and slightly astern the boat. - p0=Carrier:Translate(UTILS.NMToMeters(5), hdg-135):SetAltitude(altitude) + p0 = Carrier:Translate( UTILS.NMToMeters( 5 ), hdg - 135 ):SetAltitude( altitude ) -- Waypoint ahead of carrier's holding zone. - wp[#wp+1]=pE:WaypointAirTurningPoint(nil, speedTransit, {TaskArrivedHolding}, "Entering Case I Marshal Pattern") + wp[#wp + 1] = pE:WaypointAirTurningPoint( nil, speedTransit, { TaskArrivedHolding }, "Entering Case I Marshal Pattern" ) else -- Get correct radial depending on recovery case including offset. - local radial=self:GetRadial(case, false, true) + local radial = self:GetRadial( case, false, true ) -- Point in the middle of the race track and a 5 NM more port perpendicular. - p0=p2:Translate(UTILS.NMToMeters(5), radial+90):Translate(UTILS.NMToMeters(5), radial, true) + p0 = p2:Translate( UTILS.NMToMeters( 5 ), radial + 90 ):Translate( UTILS.NMToMeters( 5 ), radial, true ) -- Entering Case II/III marshal pattern waypoint. - wp[#wp+1]=p0:WaypointAirTurningPoint(nil, speedTransit, {TaskArrivedHolding}, "Entering Case II/III Marshal Pattern") + wp[#wp + 1] = p0:WaypointAirTurningPoint( nil, speedTransit, { TaskArrivedHolding }, "Entering Case II/III Marshal Pattern" ) end @@ -6632,27 +5952,27 @@ function AIRBOSS:_MarshalAI(flight, nstack, respawn) ------------------------ -- Debug info. - self:T(self.lid..string.format("Updating AI flight %s at marshal stack %d-->%d.", groupname, ostack, nstack)) + self:T( self.lid .. string.format( "Updating AI flight %s at marshal stack %d-->%d.", groupname, ostack, nstack ) ) -- Current position. Speed expected in km/h. - wp[1]=group:GetCoordinate():WaypointAirTurningPoint(nil, speedOrbitKmh, {}, "Current Position") + wp[1] = group:GetCoordinate():WaypointAirTurningPoint( nil, speedOrbitKmh, {}, "Current Position" ) -- Create new waypoint 0.2 Nm ahead of current positon. - p0=group:GetCoordinate():Translate(UTILS.NMToMeters(0.2), group:GetHeading(), true) + p0 = group:GetCoordinate():Translate( UTILS.NMToMeters( 0.2 ), group:GetHeading(), true ) end -- Set orbit task. - local taskorbit=group:TaskOrbit(p1, altitude, speedOrbitMps, p2) + local taskorbit = group:TaskOrbit( p1, altitude, speedOrbitMps, p2 ) -- Orbit at waypoint. - wp[#wp+1]=p0:WaypointAirTurningPoint(nil, speedOrbitKmh, {taskorbit}, string.format("Marshal Orbit Stack %d", nstack)) + wp[#wp + 1] = p0:WaypointAirTurningPoint( nil, speedOrbitKmh, { taskorbit }, string.format( "Marshal Orbit Stack %d", nstack ) ) -- Debug markers. if self.Debug then - p0:MarkToAll("WP P0 "..groupname) - p1:MarkToAll("RT P1 "..groupname) - p2:MarkToAll("RT P2 "..groupname) + p0:MarkToAll( "WP P0 " .. groupname ) + p1:MarkToAll( "RT P1 " .. groupname ) + p2:MarkToAll( "RT P2 " .. groupname ) end if respawn then @@ -6661,40 +5981,40 @@ function AIRBOSS:_MarshalAI(flight, nstack, respawn) -- Note: This resets the weapons and the fuel state. But not the units fortunately. -- Get group template. - local Template=group:GetTemplate() + local Template = group:GetTemplate() -- Set route points. - Template.route.points=wp + Template.route.points = wp -- Respawn the group. - flight.group=group:Respawn(Template, true) + flight.group = group:Respawn( Template, true ) end -- Reinit waypoints. - flight.group:WayPointInitialize(wp) + flight.group:WayPointInitialize( wp ) -- Route group. - flight.group:Route(wp, 1) + flight.group:Route( wp, 1 ) -- Trigger Marshal event. - self:Marshal(flight) + self:Marshal( flight ) end --- Tell AI to refuel. Either at the recovery tanker or at the nearest divert airfield. -- @param #AIRBOSS self -- @param #AIRBOSS.FlightGroup flight Flight group. -function AIRBOSS:_RefuelAI(flight) +function AIRBOSS:_RefuelAI( flight ) -- Waypoints array. - local wp={} + local wp = {} -- Current speed. - local CurrentSpeed=flight.group:GetVelocityKMH() + local CurrentSpeed = flight.group:GetVelocityKMH() -- Current positon. - wp[#wp+1]=flight.group:GetCoordinate():WaypointAirTurningPoint(nil, CurrentSpeed, {}, "Current position") + wp[#wp + 1] = flight.group:GetCoordinate():WaypointAirTurningPoint( nil, CurrentSpeed, {}, "Current position" ) -- Check if aircraft can be refueled. -- TODO: This should also depend on the tanker type AC. @@ -6712,25 +6032,25 @@ function AIRBOSS:_RefuelAI(flight) end -- Message. - local text="" + local text = "" -- Refuel or divert? if self.tanker and refuelac then -- Current Tanker position. - local tankerpos=self.tanker.tanker:GetCoordinate() + local tankerpos = self.tanker.tanker:GetCoordinate() -- Task refueling. - local TaskRefuel=flight.group:TaskRefueling() + local TaskRefuel = flight.group:TaskRefueling() -- Task to go back to Marshal. - local TaskMarshal=flight.group:TaskFunction("AIRBOSS._TaskFunctionMarshalAI", self, flight) + local TaskMarshal = flight.group:TaskFunction( "AIRBOSS._TaskFunctionMarshalAI", self, flight ) -- Waypoint with tasks. - wp[#wp+1]=tankerpos:WaypointAirTurningPoint(nil, CurrentSpeed, {TaskRefuel, TaskMarshal}, "Refueling") + wp[#wp + 1] = tankerpos:WaypointAirTurningPoint( nil, CurrentSpeed, { TaskRefuel, TaskMarshal }, "Refueling" ) -- Marshal Message. - self:_MarshalCallGasAtTanker(flight.onboard) + self:_MarshalCallGasAtTanker( flight.onboard ) else @@ -6739,108 +6059,108 @@ function AIRBOSS:_RefuelAI(flight) ------------------------------ -- Closest Airfield of the coaliton. - local divertfield=self:GetCoordinate():GetClosestAirbase(Airbase.Category.AIRDROME, self:GetCoalition()) + local divertfield = self:GetCoordinate():GetClosestAirbase( Airbase.Category.AIRDROME, self:GetCoalition() ) -- Handle case where there is no divert field of the own coalition and try neutral instead. - if divertfield==nil then - divertfield=self:GetCoordinate():GetClosestAirbase(Airbase.Category.AIRDROME, 0) + if divertfield == nil then + divertfield = self:GetCoordinate():GetClosestAirbase( Airbase.Category.AIRDROME, 0 ) end if divertfield then -- Coordinate. - local divertcoord=divertfield:GetCoordinate() + local divertcoord = divertfield:GetCoordinate() -- Landing waypoint. - wp[#wp+1]=divertcoord:WaypointAirLanding(UTILS.KnotsToKmph(300), divertfield, {}, "Divert Field") + wp[#wp + 1] = divertcoord:WaypointAirLanding( UTILS.KnotsToKmph( 300 ), divertfield, {}, "Divert Field" ) -- Marshal Message. - self:_MarshalCallGasAtDivert(flight.onboard, divertfield:GetName()) + self:_MarshalCallGasAtDivert( flight.onboard, divertfield:GetName() ) -- Respawn! -- Get group template. - local Template=flight.group:GetTemplate() + local Template = flight.group:GetTemplate() -- Set route points. - Template.route.points=wp + Template.route.points = wp -- Respawn the group. - flight.group=flight.group:Respawn(Template, true) + flight.group = flight.group:Respawn( Template, true ) else -- Set flight to refueling so this is not called again. - self:E(self.lid..string.format("WARNING: No recovery tanker or divert field available for group %s.", flight.groupname)) - flight.refueling=true + 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 -- Reinit waypoints. - flight.group:WayPointInitialize(wp) + flight.group:WayPointInitialize( wp ) -- Route group. - flight.group:Route(wp, 1) + flight.group:Route( wp, 1 ) -- Set refueling switch. - flight.refueling=true + flight.refueling = true end --- Tell AI to land on the carrier. -- @param #AIRBOSS self -- @param #AIRBOSS.FlightGroup flight Flight group. -function AIRBOSS:_LandAI(flight) +function AIRBOSS:_LandAI( flight ) - -- Debug info. - self:T(self.lid..string.format("Landing AI flight %s.", flight.groupname)) + -- Debug info. + self:T( self.lid .. string.format( "Landing AI flight %s.", flight.groupname ) ) -- NOTE: Looks like the AI needs to approach at the "correct" speed. If they are too fast, they fly an unnecessary circle to bleed of speed first. -- Unfortunately, the correct speed depends on the aircraft type! -- Aircraft speed when flying the pattern. - local Speed=UTILS.KnotsToKmph(200) + 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) + 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 -- Carrier position. - local Carrier=self:GetCoordinate() + local Carrier = self:GetCoordinate() -- Carrier heading. - local hdg=self:GetHeading() + local hdg = self:GetHeading() -- Waypoints array. - local wp={} + local wp = {} - local CurrentSpeed=flight.group:GetVelocityKMH() + local CurrentSpeed = flight.group:GetVelocityKMH() -- Current positon. - wp[#wp+1]=flight.group:GetCoordinate():WaypointAirTurningPoint(nil, CurrentSpeed, {}, "Current position") + wp[#wp + 1] = flight.group:GetCoordinate():WaypointAirTurningPoint( nil, CurrentSpeed, {}, "Current position" ) -- Altitude 800 ft. Looks like this works best. - local alt=UTILS.FeetToMeters(800) + local alt = UTILS.FeetToMeters( 800 ) -- Landing waypoint 5 NM behind carrier at 2000 ft = 610 meters ASL. - wp[#wp+1]=Carrier:Translate(UTILS.NMToMeters(4), hdg-160):SetAltitude(alt):WaypointAirLanding(Speed, self.airbase, nil, "Landing") - --wp[#wp+1]=Carrier:Translate(UTILS.NMToMeters(4), hdg-160):SetAltitude(alt):WaypointAirLandingReFu(Speed, self.airbase, nil, "Landing") + wp[#wp + 1] = Carrier:Translate( UTILS.NMToMeters( 4 ), hdg - 160 ):SetAltitude( alt ):WaypointAirLanding( Speed, self.airbase, nil, "Landing" ) + -- wp[#wp+1]=Carrier:Translate(UTILS.NMToMeters(4), hdg-160):SetAltitude(alt):WaypointAirLandingReFu(Speed, self.airbase, nil, "Landing") - --wp[#wp+1]=self:GetCoordinate():Translate(UTILS.NMToMeters(3), hdg-160):SetAltitude(alt):WaypointAirTurningPoint(nil,Speed, {}, "Before Initial") ---WaypointAirLanding(Speed, self.airbase, nil, "Landing") - --wp[#wp+1]=self:GetCoordinate():WaypointAirLanding(Speed, self.airbase, nil, "Landing") + -- wp[#wp+1]=self:GetCoordinate():Translate(UTILS.NMToMeters(3), hdg-160):SetAltitude(alt):WaypointAirTurningPoint(nil,Speed, {}, "Before Initial") ---WaypointAirLanding(Speed, self.airbase, nil, "Landing") + -- wp[#wp+1]=self:GetCoordinate():WaypointAirLanding(Speed, self.airbase, nil, "Landing") -- Reinit waypoints. - flight.group:WayPointInitialize(wp) + flight.group:WayPointInitialize( wp ) -- Route group. - flight.group:Route(wp, 0) + flight.group:Route( wp, 0 ) end --- Get marshal altitude and two positions of a counter-clockwise race track pattern. @@ -6850,83 +6170,83 @@ end -- @return #number Holding altitude in meters. -- @return Core.Point#COORDINATE First race track coordinate. -- @return Core.Point#COORDINATE Second race track coordinate. -function AIRBOSS:_GetMarshalAltitude(stack, case) +function AIRBOSS:_GetMarshalAltitude( stack, case ) -- Stack <= 0. - if stack<=0 then - return 0,nil,nil + if stack <= 0 then + return 0, nil, nil end -- Recovery case. - case=case or self.case + case = case or self.case -- Carrier position. - local Carrier=self:GetCoordinate() + local Carrier = self:GetCoordinate() -- Altitude of first stack. Depends on recovery case. local angels0 local Dist - local p1=nil --Core.Point#COORDINATE - local p2=nil --Core.Point#COORDINATE + local p1 = nil -- Core.Point#COORDINATE + local p2 = nil -- Core.Point#COORDINATE -- Stack number. - local nstack=stack-1 + local nstack = stack - 1 - if case==1 then + if case == 1 then -- CASE I: Holding at 2000 ft on a circular pattern port of the carrier. Interval +1000 ft for next stack. - angels0=2 + angels0 = 2 -- Get true heading of carrier. - local hdg=self.carrier:GetHeading() + local hdg = self.carrier:GetHeading() -- For CCW pattern: First point astern, second ahead of the carrier. -- First point over carrier. - p1=Carrier + p1 = Carrier -- Second point 1.5 NM ahead. - p2=Carrier:Translate(UTILS.NMToMeters(1.5), hdg) + p2 = Carrier:Translate( UTILS.NMToMeters( 1.5 ), hdg ) -- Tarawa,LHA,LHD Delta patterns. - if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then -- Pattern is directly overhead the carrier. - p1=Carrier:Translate(UTILS.NMToMeters(1.0), hdg+90) - p2=p1:Translate(2.5, hdg) + p1 = Carrier:Translate( UTILS.NMToMeters( 1.0 ), hdg + 90 ) + p2 = p1:Translate( 2.5, hdg ) end else -- CASE II/III: Holding at 6000 ft on a racetrack pattern astern the carrier. - angels0=6 + angels0 = 6 -- Distance: d=n*angels0+15 NM, so first stack is at 15+6=21 NM - Dist=UTILS.NMToMeters(nstack+angels0+15) + Dist = UTILS.NMToMeters( nstack + angels0 + 15 ) -- Get correct radial depending on recovery case including offset. - local radial=self:GetRadial(case, false, true) + local radial = self:GetRadial( case, false, true ) -- For CCW pattern: p1 further astern than p2. -- Length of the race track pattern. - local l=UTILS.NMToMeters(10) + local l = UTILS.NMToMeters( 10 ) -- First point of race track pattern. - p1=Carrier:Translate(Dist+l, radial) + p1 = Carrier:Translate( Dist + l, radial ) -- Second point. - p2=Carrier:Translate(Dist, radial) + p2 = Carrier:Translate( Dist, radial ) end -- Pattern altitude. - local altitude=UTILS.FeetToMeters((nstack+angels0)*1000) + local altitude = UTILS.FeetToMeters( (nstack + angels0) * 1000 ) -- Set altitude of coordinate. - p1:SetAltitude(altitude, true) - p2:SetAltitude(altitude, true) + p1:SetAltitude( altitude, true ) + p2:SetAltitude( altitude, true ) return altitude, p1, p2 end @@ -6935,95 +6255,95 @@ end -- @param #AIRBOSS self -- @param #AIRBOSS.FlightGroup flightgroup Flight data. -- @return #number Charlie (abs) time in seconds. Or nil, if stack<0 or no recovery window will open. -function AIRBOSS:_GetCharlieTime(flightgroup) +function AIRBOSS:_GetCharlieTime( flightgroup ) -- Get current stack of player. - local stack=flightgroup.flag + local stack = flightgroup.flag -- Flight is not in marshal stack. - if stack<=0 then + if stack <= 0 then return nil end -- Current abs time. - local Tnow=timer.getAbsTime() + local Tnow = timer.getAbsTime() -- Time the player has to spend in marshal stack until all lower stacks are emptied. - local Tcharlie=0 + local Tcharlie = 0 - local Trecovery=0 + local Trecovery = 0 if self.recoverywindow then -- Time in seconds until the next recovery starts or 0 if window is already open. - Trecovery=math.max(self.recoverywindow.START-Tnow, 0) + Trecovery = math.max( self.recoverywindow.START - Tnow, 0 ) else -- Set ~7 min if no future recovery window is defined. Otherwise radio call function crashes. - Trecovery=7*60 + Trecovery = 7 * 60 end -- Loop over flights currently in the marshal queue. - for _,_flight in pairs(self.Qmarshal) do - local flight=_flight --#AIRBOSS.FlightGroup + for _, _flight in pairs( self.Qmarshal ) do + local flight = _flight -- #AIRBOSS.FlightGroup -- Stack of marshal flight. - local mstack=flight.flag + local mstack = flight.flag -- Time to get to the marshal stack if not holding already. - local Tarrive=0 + local Tarrive = 0 -- Minimum holding time per stack. - local Tholding=3*60 + local Tholding = 3 * 60 - if stack>0 and mstack>0 and mstack<=stack then + if stack > 0 and mstack > 0 and mstack <= stack then -- Check if flight is already holding or just on its way. - if flight.holding==nil then + if flight.holding == nil then -- Flight is on its way to the marshal stack. -- Coordinate of the holding zone. - local holdingzone=self:_GetZoneHolding(flight.case, 1):GetCoordinate() + local holdingzone = self:_GetZoneHolding( flight.case, 1 ):GetCoordinate() -- Distance to holding zone. - local d0=holdingzone:Get2DDistance(flight.group:GetCoordinate()) + local d0 = holdingzone:Get2DDistance( flight.group:GetCoordinate() ) -- Current velocity. - local v0=flight.group:GetVelocityMPS() + local v0 = flight.group:GetVelocityMPS() -- Time to get to the carrier. - Tarrive=d0/v0 + Tarrive = d0 / v0 - self:T3(self.lid..string.format("Tarrive=%.1f seconds, Clock %s", Tarrive, UTILS.SecondsToClock(Tnow+Tarrive))) + self:T3( self.lid .. string.format( "Tarrive=%.1f seconds, Clock %s", Tarrive, UTILS.SecondsToClock( Tnow + Tarrive ) ) ) else -- Flight is already holding. -- Next in line. - if mstack==1 then + if mstack == 1 then -- Current holding time. flight.time stamp should be when entering holding or last time the stack collapsed. - local tholding=timer.getAbsTime()-flight.time + local tholding = timer.getAbsTime() - flight.time -- Deduce current holding time. Ensure that is >=0. - Tholding=math.max(3*60-tholding, 0) + Tholding = math.max( 3 * 60 - tholding, 0 ) end end -- This is the approx time needed to get to the pattern. If we are already there, it is the time until the recovery window opens or 0 if it is already open. - local Tmin=math.max(Tarrive, Trecovery) + local Tmin = math.max( Tarrive, Trecovery ) -- Charlie time + 2 min holding in stack 1. - Tcharlie=math.max(Tmin, Tcharlie)+Tholding + Tcharlie = math.max( Tmin, Tcharlie ) + Tholding end end -- Convert to abs time. - Tcharlie=Tcharlie+Tnow + Tcharlie = Tcharlie + Tnow -- Debug info. - 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) + 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 @@ -7032,51 +6352,51 @@ end -- @param #AIRBOSS self -- @param #AIRBOSS.FlightGroup flight Flight group. -- @param #number stack Marshal stack. This (re-)sets the flag value. -function AIRBOSS:_AddMarshalGroup(flight, stack) +function AIRBOSS:_AddMarshalGroup( flight, stack ) -- Set flag value. This corresponds to the stack number which starts at 1. - flight.flag=stack + flight.flag = stack -- Set recovery case. - flight.case=self.case + flight.case = self.case -- Add to marshal queue. - table.insert(self.Qmarshal, flight) + table.insert( self.Qmarshal, flight ) -- Pressure. - local P=UTILS.hPa2inHg(self:GetCoordinate():GetPressure()) + local P = UTILS.hPa2inHg( self:GetCoordinate():GetPressure() ) -- Stack altitude. - --local alt=UTILS.MetersToFeet(self:_GetMarshalAltitude(stack, flight.case)) - local alt=self:_GetMarshalAltitude(stack, flight.case) + -- local alt=UTILS.MetersToFeet(self:_GetMarshalAltitude(stack, flight.case)) + local alt = self:_GetMarshalAltitude( stack, flight.case ) -- Current BRC. - local brc=self:GetBRC() + local brc = self:GetBRC() -- If the carrier is supposed to turn into the wind, we take the wind coordinate. if self.recoverywindow and self.recoverywindow.WIND then - brc=self:GetBRCintoWind() + brc = self:GetBRCintoWind() end -- Get charlie time estimate. - flight.Tcharlie=self:_GetCharlieTime(flight) + flight.Tcharlie = self:_GetCharlieTime( flight ) -- Convert to clock string. - local Ccharlie=UTILS.SecondsToClock(flight.Tcharlie) + local Ccharlie = UTILS.SecondsToClock( flight.Tcharlie ) -- Combined marshal call. - self:_MarshalCallArrived(flight.onboard, flight.case, brc, alt, Ccharlie, P) + self:_MarshalCallArrived( flight.onboard, flight.case, brc, alt, Ccharlie, P ) -- Hint about TACAN bearing. - if self.TACANon and (not flight.ai) and flight.difficulty==AIRBOSS.Difficulty.EASY then + if self.TACANon and (not flight.ai) and flight.difficulty == AIRBOSS.Difficulty.EASY then -- Get inverse magnetic radial potential offset. - local radial=self:GetRadial(flight.case, true, true, true) - if flight.case==1 then + local radial = self:GetRadial( flight.case, true, true, true ) + if flight.case == 1 then -- For case 1 we want the BRC but above routine return FB. - radial=self:GetBRC() + 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, "") + 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 @@ -7085,87 +6405,87 @@ end -- @param #AIRBOSS self -- @param #AIRBOSS.FlightGroup flight Flight that left the marshal stack. -- @param #boolean nopattern If true, flight does not go to pattern. -function AIRBOSS:_CollapseMarshalStack(flight, nopattern) - self:F2({flight=flight, nopattern=nopattern}) +function AIRBOSS:_CollapseMarshalStack( flight, nopattern ) + self:F2( { flight = flight, nopattern = nopattern } ) -- Recovery case of flight. - local case=flight.case + local case = flight.case -- Stack of flight. - local stack=flight.flag + local stack = flight.flag -- Check that stack > 0. - 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)) + 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 -- Memorize time when stack collapsed. Should better depend on case but for now we assume there are no two different stacks Case I or II/III. - self.Tcollapse=timer.getTime() + self.Tcollapse = timer.getTime() -- Decrease flag values of all flight groups in marshal stack. - for _,_flight in pairs(self.Qmarshal) do - local mflight=_flight --#AIRBOSS.PlayerData + for _, _flight in pairs( self.Qmarshal ) do + local mflight = _flight -- #AIRBOSS.PlayerData -- Only collapse stack of which the flight left. CASE II/III stacks are not collapsed. - if (case==1 and mflight.case==1) then --or (case>1 and mflight.case>1) then + if (case == 1 and mflight.case == 1) then -- or (case>1 and mflight.case>1) then -- Get current flag/stack value. - local mstack=mflight.flag + local mstack = mflight.flag -- Only collapse stacks above the new pattern flight. - if mstack>stack then + if mstack > stack then -- TODO: Is this now right as we allow more flights per stack? -- Question is, does the stack collapse if the lower stack is completely empty or do aircraft descent if just one flight leaves. -- For now, assuming that the stack must be completely empty before the next higher AC are allowed to descent. - local newstack=self:_GetFreeStack(mflight.ai, mflight.case, true) + local newstack = self:_GetFreeStack( mflight.ai, mflight.case, true ) -- Free stack has to be below. - if newstack and newstack %d.", mflight.groupname, mflight.case, mstack, newstack)) + self:T( self.lid .. string.format( "Collapse Marshal: Flight %s (case %d) is changing marshal stack %d --> %d.", mflight.groupname, mflight.case, mstack, newstack ) ) if mflight.ai then -- Command AI to decrease stack. Flag is set in the routine. - self:_MarshalAI(mflight, newstack) + self:_MarshalAI( mflight, newstack ) else -- Decrease stack/flag. Human player needs to take care himself. - mflight.flag=newstack + mflight.flag = newstack -- Angels of new stack. - local angels=self:_GetAngels(self:_GetMarshalAltitude(newstack, case)) + local angels = self:_GetAngels( self:_GetMarshalAltitude( newstack, case ) ) -- Inform players. - if mflight.difficulty~=AIRBOSS.Difficulty.HARD then + if mflight.difficulty ~= AIRBOSS.Difficulty.HARD then -- Send message to all non-pros that they can descent. - local text=string.format("descent to stack at Angels %d.", angels) - self:MessageToPlayer(mflight, text, "MARSHAL") + local text = string.format( "descent to stack at Angels %d.", angels ) + self:MessageToPlayer( mflight, text, "MARSHAL" ) end -- Set time stamp. - mflight.time=timer.getAbsTime() + mflight.time = timer.getAbsTime() -- Loop over section members. - for _,_sec in pairs(mflight.section) do - local sec=_sec --#AIRBOSS.PlayerData + for _, _sec in pairs( mflight.section ) do + local sec = _sec -- #AIRBOSS.PlayerData -- Also decrease flag for section members of flight. - sec.flag=newstack + sec.flag = newstack -- Set new time stamp. - sec.time=timer.getAbsTime() + sec.time = timer.getAbsTime() -- Inform section member. - if sec.difficulty~=AIRBOSS.Difficulty.HARD then - local text=string.format("descent to stack at Angels %d.", angels) - self:MessageToPlayer(sec, text, "MARSHAL") + 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 @@ -7177,28 +6497,27 @@ function AIRBOSS:_CollapseMarshalStack(flight, nopattern) end end - if nopattern then -- Debug message. - self:T(self.lid..string.format("Flight %s is leaving stack but not going to pattern.", flight.groupname)) + self:T( self.lid .. string.format( "Flight %s is leaving stack but not going to pattern.", flight.groupname ) ) else -- Debug message. - 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)) + 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 ) ) -- Add flight to pattern queue. - self:_AddFlightToPatternQueue(flight) + self:_AddFlightToPatternQueue( flight ) end -- Set flag to -1 (-1 is rather arbitrary but it should not be positive or -100 or -42). - flight.flag=-1 + flight.flag = -1 -- New time stamp for time in pattern. - flight.time=timer.getAbsTime() + flight.time = timer.getAbsTime() end @@ -7208,87 +6527,87 @@ end -- @param #number case Recovery case. Default current (self) case in progress. -- @param #boolean empty Return lowest stack that is completely empty. -- @return #number Lowest free stack available for the given case or nil if all Case I stacks are taken. -function AIRBOSS:_GetFreeStack(ai, case, empty) +function AIRBOSS:_GetFreeStack( ai, case, empty ) -- Recovery case. - case=case or self.case + case = case or self.case - if case==1 then - return self:_GetFreeStack_Old(ai, case, empty) + if case == 1 then + return self:_GetFreeStack_Old( ai, case, empty ) end -- Max number of stacks available. - local nmaxstacks=100 - if case==1 then - nmaxstacks=self.Nmaxmarshal + local nmaxstacks = 100 + if case == 1 then + nmaxstacks = self.Nmaxmarshal end -- Assume up to two (human) flights per stack. All are free. - local stack={} - for i=1,nmaxstacks do - stack[i]=self.NmaxStack -- Number of human flights per stack. + local stack = {} + for i = 1, nmaxstacks do + stack[i] = self.NmaxStack -- Number of human flights per stack. end - local nmax=1 + local nmax = 1 -- Loop over all flights in marshal stack. - for _,_flight in pairs(self.Qmarshal) do - local flight=_flight --#AIRBOSS.FlightGroup + for _, _flight in pairs( self.Qmarshal ) do + local flight = _flight -- #AIRBOSS.FlightGroup -- Check that the case is right. - if flight.case==case then + if flight.case == case then -- Get stack of flight. - local n=flight.flag + local n = flight.flag - if n>nmax then - nmax=n + if n > nmax then + nmax = n end - if n>0 then - if flight.ai or flight.case>1 then - stack[n]=0 -- AI get one stack on their own. Also CASE II/III get one stack each. + if n > 0 then + if flight.ai or flight.case > 1 then + stack[n] = 0 -- AI get one stack on their own. Also CASE II/III get one stack each. else - stack[n]=stack[n]-1 + 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)) + 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 + local nfree = nil + if stack[nmax] == 0 then -- Max occupied stack is completely full! - if case==1 then - if nmax>=nmaxstacks then + if case == 1 then + if nmax >= nmaxstacks then -- Already all Case I stacks are occupied ==> wait outside 10 NM zone. - nfree=nil + nfree = nil else -- Return next free stack. - nfree=nmax+1 + nfree = nmax + 1 end else -- Case II/III return next stack - nfree=nmax+1 + nfree = nmax + 1 end - elseif stack[nmax]==self.NmaxStack then + elseif stack[nmax] == self.NmaxStack then -- Max occupied stack is completely empty! This should happen only when there is no other flight in the marshal queue. - 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 + 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 -- Max occupied stack is partly full. - if ai or empty or case>1 then - nfree=nmax+1 + if ai or empty or case > 1 then + nfree = nmax + 1 else - nfree=nmax + nfree = nmax end end - self:I(self.lid..string.format("Returning free stack %s", tostring(nfree))) + self:I( self.lid .. string.format( "Returning free stack %s", tostring( nfree ) ) ) return nfree end @@ -7298,60 +6617,60 @@ end -- @param #number case Recovery case. Default current (self) case in progress. -- @param #boolean empty Return lowest stack that is completely empty. -- @return #number Lowest free stack available for the given case or nil if all Case I stacks are taken. -function AIRBOSS:_GetFreeStack_Old(ai, case, empty) +function AIRBOSS:_GetFreeStack_Old( ai, case, empty ) -- Recovery case. - case=case or self.case + case = case or self.case -- Max number of stacks available. - local nmaxstacks=100 - if case==1 then - nmaxstacks=self.Nmaxmarshal + local nmaxstacks = 100 + if case == 1 then + nmaxstacks = self.Nmaxmarshal end -- Assume up to two (human) flights per stack. All are free. - local stack={} - for i=1,nmaxstacks do - stack[i]=self.NmaxStack -- Number of human flights per stack. + local stack = {} + for i = 1, nmaxstacks do + stack[i] = self.NmaxStack -- Number of human flights per stack. end -- Loop over all flights in marshal stack. - for _,_flight in pairs(self.Qmarshal) do - local flight=_flight --#AIRBOSS.FlightGroup + for _, _flight in pairs( self.Qmarshal ) do + local flight = _flight -- #AIRBOSS.FlightGroup -- Check that the case is right. - if flight.case==case then + if flight.case == case then -- Get stack of flight. - local n=flight.flag + local n = flight.flag - if n>0 then - if flight.ai or flight.case>1 then - stack[n]=0 -- AI get one stack on their own. Also CASE II/III get one stack each. + if n > 0 then + if flight.ai or flight.case > 1 then + stack[n] = 0 -- AI get one stack on their own. Also CASE II/III get one stack each. else - stack[n]=stack[n]-1 + 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)) + self:E( string.format( "ERROR: Flight %s in marshal stack has stack value <= 0. Stack value is %d.", flight.groupname, n ) ) end end end -- Loop over stacks and check which one has a place left. - 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 + 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 -- AI need the whole stack. - if stack[i]==self.NmaxStack then - nfree=i + if stack[i] == self.NmaxStack then + nfree = i return i end else -- Human players only need one free spot. - if stack[i]>0 then - nfree=i + if stack[i] > 0 then + nfree = i return i end end @@ -7367,32 +6686,32 @@ end -- @return #number Number of units in flight including section members. -- @return #number Number of units in flight excluding section members. -- @return #number Number of section members. -function AIRBOSS:_GetFlightUnits(flight, onground) +function AIRBOSS:_GetFlightUnits( flight, onground ) -- Default is only airborne. - local inair=true - if onground==true then - inair=false + local inair = true + if onground == true then + inair = false end --- Count units of a group which are alive and in the air. - local function countunits(_group, inair) - local group=_group --Wrapper.Group#GROUP - local units=group:GetUnits() - local n=0 + local function countunits( _group, inair ) + local group = _group -- Wrapper.Group#GROUP + local units = group:GetUnits() + local n = 0 if units then - for _,_unit in pairs(units) do - local unit=_unit --Wrapper.Unit#UNIT - if unit and unit:IsAlive() then + for _, _unit in pairs( units ) do + local unit = _unit -- Wrapper.Unit#UNIT + if unit and unit:IsAlive() then if inair then -- Only count units in air. if unit:InAir() then - self:T2(self.lid..string.format("Unit %s is in AIR", unit:GetName())) - n=n+1 + self:T2( self.lid .. string.format( "Unit %s is in AIR", unit:GetName() ) ) + n = n + 1 end else -- Count units in air or on the ground. - n=n+1 + n = n + 1 end end end @@ -7400,19 +6719,18 @@ function AIRBOSS:_GetFlightUnits(flight, onground) return n end - -- Count units of the group itself (alive units in air). - local nunits=countunits(flight.group, inair) + local nunits = countunits( flight.group, inair ) -- Count section members. - local nsection=0 - for _,sec in pairs(flight.section) do - local secflight=sec --#AIRBOSS.PlayerData + local nsection = 0 + for _, sec in pairs( flight.section ) do + local secflight = sec -- #AIRBOSS.PlayerData -- Count alive units in air. - nsection=nsection+countunits(secflight.group, inair) + nsection = nsection + countunits( secflight.group, inair ) end - return nunits+nsection, nunits, nsection + return nunits + nsection, nunits, nsection end --- Get number of groups and units in queue, which are alive and airborne. In units we count the section members as well. @@ -7421,14 +6739,14 @@ end -- @param #number case (Optional) Only count flights, which are in a specific recovery case. Note that you can use case=23 for flights that are either in Case II or III. By default all groups/units regardless of case are counted. -- @return #number Total number of flight groups in queue. -- @return #number Total number of aircraft in queue since each flight group can contain multiple aircraft. -function AIRBOSS:_GetQueueInfo(queue, case) +function AIRBOSS:_GetQueueInfo( queue, case ) - local ngroup=0 - local Nunits=0 + local ngroup = 0 + local Nunits = 0 -- Loop over flight groups. - for _,_flight in pairs(queue) do - local flight=_flight --#AIRBOSS.FlightGroup + for _, _flight in pairs( queue ) do + local flight = _flight -- #AIRBOSS.FlightGroup -- Check if a specific case was requested. if case then @@ -7437,17 +6755,17 @@ function AIRBOSS:_GetQueueInfo(queue, case) -- Only count specific case with special 23 = CASE II and III combined. ------------------------------------------------------------------------ - if (flight.case==case) or (case==23 and (flight.case==2 or flight.case==3)) then + if (flight.case == case) or (case == 23 and (flight.case == 2 or flight.case == 3)) then -- Number of total units, units in flight and section members ALIVE and AIRBORNE. - local ntot,nunits,nsection=self:_GetFlightUnits(flight) + local ntot, nunits, nsection = self:_GetFlightUnits( flight ) -- Add up total unit number. - Nunits=Nunits+ntot + Nunits = Nunits + ntot -- Increase group count. - if ntot>0 then - ngroup=ngroup+1 + if ntot > 0 then + ngroup = ngroup + 1 end end @@ -7459,14 +6777,14 @@ function AIRBOSS:_GetQueueInfo(queue, case) --------------------------------------------------------------------------- -- Number of total units, units in flight and section members ALIVE and AIRBORNE. - local ntot,nunits,nsection=self:_GetFlightUnits(flight) + local ntot, nunits, nsection = self:_GetFlightUnits( flight ) -- Add up total unit number. - Nunits=Nunits+ntot + Nunits = Nunits + ntot -- Increase group count. - if ntot>0 then - ngroup=ngroup+1 + if ntot > 0 then + ngroup = ngroup + 1 end end @@ -7480,47 +6798,45 @@ end -- @param #AIRBOSS self -- @param #table queue Queue to print. -- @param #string name Queue name. -function AIRBOSS:_PrintQueue(queue, name) +function AIRBOSS:_PrintQueue( queue, name ) - --local nqueue=#queue - local Nqueue, nqueue=self:_GetQueueInfo(queue) + -- local nqueue=#queue + 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." + 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 --#AIRBOSS.FlightGroup + for i, _flight in pairs( queue ) do + local flight = _flight -- #AIRBOSS.FlightGroup - 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 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 ) -- Airborne units. - local _, nunits, nsec=self:_GetFlightUnits(flight, false) + local _, nunits, nsec = self:_GetFlightUnits( flight, false ) -- Text. - 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) + 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 --#AIRBOSS.FlightElement - 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)) + for j, _element in pairs( flight.elements ) do + local element = _element -- #AIRBOSS.FlightElement + 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) + self:T( self.lid .. text ) end ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -7531,159 +6847,158 @@ end -- @param #AIRBOSS self -- @param Wrapper.Group#GROUP group Aircraft group. -- @return #AIRBOSS.FlightGroup Flight group. -function AIRBOSS:_CreateFlightGroup(group) +function AIRBOSS:_CreateFlightGroup( group ) -- Debug info. - self:T(self.lid..string.format("Creating new flight for group %s of aircraft type %s.", group:GetName(), group:GetTypeName())) + self:T( self.lid .. string.format( "Creating new flight for group %s of aircraft type %s.", group:GetName(), group:GetTypeName() ) ) -- New flight. - local flight={} --#AIRBOSS.FlightGroup + local flight = {} -- #AIRBOSS.FlightGroup -- Check if not already in flights - if not self:_InQueue(self.flights, group) then + if not self:_InQueue( self.flights, group ) then -- Flight group name - local groupname=group:GetName() - local human, playername=self:_IsHuman(group) + local groupname = group:GetName() + local human, playername = self:_IsHuman( group ) -- Queue table item. - 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() -- Sec lead is first unitname of group but player name for players. - flight.section={} - flight.ballcall=false - flight.refueling=false - flight.holding=nil - flight.name=flight.group:GetUnit(1):GetName() --Will be overwritten in _Newplayer with player name if human player in the 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() -- Sec lead is first unitname of group but player name for players. + flight.section = {} + flight.ballcall = false + flight.refueling = false + flight.holding = nil + flight.name = flight.group:GetUnit( 1 ):GetName() -- Will be overwritten in _Newplayer with player name if human player in the group. -- Note, this should be re-set elsewhere! - flight.case=self.case + flight.case = self.case -- Flight elements. - 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 --Wrapper.Unit#UNIT - local element={} --#AIRBOSS.FlightElement - 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) + 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 -- Wrapper.Unit#UNIT + local element = {} -- #AIRBOSS.FlightElement + 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) + self:T( self.lid .. text ) -- Onboard if flight.ai then - local onboard=flight.onboardnumbers[flight.seclead] - flight.onboard=onboard + local onboard = flight.onboardnumbers[flight.seclead] + flight.onboard = onboard else - flight.onboard=self:_GetOnboardNumberPlayer(group) + flight.onboard = self:_GetOnboardNumberPlayer( group ) end -- Add to known flights. - table.insert(self.flights, flight) + table.insert( self.flights, flight ) else - self:E(self.lid..string.format("ERROR: Flight group %s already exists in self.flights!", group:GetName())) + self:E( self.lid .. string.format( "ERROR: Flight group %s already exists in self.flights!", group:GetName() ) ) return nil end return flight end - --- Initialize player data after birth event of player unit. -- @param #AIRBOSS self -- @param #string unitname Name of the player unit. -- @return #AIRBOSS.PlayerData Player data. -function AIRBOSS:_NewPlayer(unitname) +function AIRBOSS:_NewPlayer( unitname ) -- Get player unit and name. - local playerunit, playername=self:_GetPlayerUnitAndName(unitname) + local playerunit, playername = self:_GetPlayerUnitAndName( unitname ) if playerunit and playername then -- Get group. - local group=playerunit:GetGroup() + local group = playerunit:GetGroup() -- Player data. - local playerData --#AIRBOSS.PlayerData + local playerData -- #AIRBOSS.PlayerData -- Create a flight group for the player. - playerData=self:_CreateFlightGroup(group) + playerData = self:_CreateFlightGroup( group ) -- Nil check. if playerData then -- Player unit, client and callsign. - playerData.unit = playerunit + playerData.unit = playerunit playerData.unitname = unitname - playerData.name = playername + playerData.name = playername playerData.callsign = playerData.unit:GetCallsign() - playerData.client = CLIENT:FindByName(unitname, nil, true) - playerData.seclead = playername + playerData.client = CLIENT:FindByName( unitname, nil, true ) + playerData.seclead = playername -- Number of passes done by player in this slot. - playerData.passes=0 --playerData.passes or 0 + playerData.passes = 0 -- playerData.passes or 0 -- Messages for player. - playerData.messages={} + playerData.messages = {} -- Debriefing tables. - playerData.lastdebrief=playerData.lastdebrief or {} + playerData.lastdebrief = playerData.lastdebrief or {} -- Attitude monitor. - playerData.attitudemonitor=false + playerData.attitudemonitor = false -- Trap sheet save. - if playerData.trapon==nil then - playerData.trapon=self.trapsheet + if playerData.trapon == nil then + playerData.trapon = self.trapsheet end -- Set difficulty level. - playerData.difficulty=playerData.difficulty or self.defaultskill + playerData.difficulty = playerData.difficulty or self.defaultskill -- Subtitles of player. - if playerData.subtitles==nil then - playerData.subtitles=true + if playerData.subtitles == nil then + playerData.subtitles = true end -- Show step hints. - if playerData.showhints==nil then - if playerData.difficulty==AIRBOSS.Difficulty.HARD then - playerData.showhints=false + if playerData.showhints == nil then + if playerData.difficulty == AIRBOSS.Difficulty.HARD then + playerData.showhints = false else - playerData.showhints=true + playerData.showhints = true end end -- Points rewarded. - playerData.points={} + playerData.points = {} -- Init stuff for this round. - playerData=self:_InitPlayer(playerData) + playerData = self:_InitPlayer( playerData ) -- Init player data. - self.players[playername]=playerData + self.players[playername] = playerData -- Init player grades table if necessary. - self.playerscores[playername]=self.playerscores[playername] or {} + self.playerscores[playername] = self.playerscores[playername] or {} -- Welcome player message. if self.welcome then - self:MessageToPlayer(playerData, string.format("Welcome, %s %s!", playerData.difficulty, playerData.name), string.format("AIRBOSS %s", self.alias), "", 5) + self:MessageToPlayer( playerData, string.format( "Welcome, %s %s!", playerData.difficulty, playerData.name ), string.format( "AIRBOSS %s", self.alias ), "", 5 ) end end @@ -7700,72 +7015,71 @@ end -- @param #AIRBOSS.PlayerData playerData Player data. -- @param #string step (Optional) New player step. Default UNDEFINED. -- @return #AIRBOSS.PlayerData Initialized player data. -function AIRBOSS:_InitPlayer(playerData, step) - self:T(self.lid..string.format("Initializing player data for %s callsign %s.", playerData.name, playerData.callsign)) +function AIRBOSS:_InitPlayer( playerData, step ) + self:T( self.lid .. string.format( "Initializing player data for %s callsign %s.", playerData.name, playerData.callsign ) ) - playerData.step=step or AIRBOSS.PatternStep.UNDEFINED - playerData.groove={} - playerData.debrief={} - playerData.trapsheet={} - playerData.warning=nil - playerData.holding=nil - playerData.refueling=false - playerData.valid=false - playerData.lig=false - playerData.wop=false - playerData.waveoff=false - playerData.wofd=false - playerData.owo=false - playerData.boltered=false - playerData.hover=false - playerData.stable=false - playerData.landed=false - playerData.Tlso=timer.getTime() - playerData.Tgroove=nil - playerData.TIG0=nil - playerData.wire=nil - playerData.flag=-100 - playerData.debriefschedulerID=nil + playerData.step = step or AIRBOSS.PatternStep.UNDEFINED + playerData.groove = {} + playerData.debrief = {} + playerData.trapsheet = {} + playerData.warning = nil + playerData.holding = nil + playerData.refueling = false + playerData.valid = false + playerData.lig = false + playerData.wop = false + playerData.waveoff = false + playerData.wofd = false + playerData.owo = false + playerData.boltered = false + playerData.hover = false + playerData.stable = false + playerData.landed = false + playerData.Tlso = timer.getTime() + playerData.Tgroove = nil + playerData.TIG0 = nil + playerData.wire = nil + playerData.flag = -100 + playerData.debriefschedulerID = nil -- Set us up on final if group name contains "Groove". But only for the first pass. - 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 + 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 - --- Get flight from group in a queue. -- @param #AIRBOSS self -- @param Wrapper.Group#GROUP group Group that will be removed from queue. -- @param #table queue The queue from which the group will be removed. -- @return #AIRBOSS.FlightGroup Flight group or nil. -- @return #number Queue index or nil. -function AIRBOSS:_GetFlightFromGroupInQueue(group, queue) +function AIRBOSS:_GetFlightFromGroupInQueue( group, queue ) if group then -- Group name - local name=group:GetName() + local name = group:GetName() -- Loop over all flight groups in queue - for i,_flight in pairs(queue) do - local flight=_flight --#AIRBOSS.FlightGroup + for i, _flight in pairs( queue ) do + local flight = _flight -- #AIRBOSS.FlightGroup - if flight.groupname==name then + 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)) + 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!")) + self:T2( self.lid .. string.format( "WARNING: Flight group could not be found in queue. Group is nil!" ) ) return nil, nil end @@ -7775,30 +7089,30 @@ end -- @return #AIRBOSS.FlightElement Element of the flight or nil. -- @return #number Element index or nil. -- @return #AIRBOSS.FlightGroup The Flight group or nil -function AIRBOSS:_GetFlightElement(unitname) +function AIRBOSS:_GetFlightElement( unitname ) -- Get the unit. - local unit=UNIT:FindByName(unitname) + local unit = UNIT:FindByName( unitname ) -- Check if unit exists. if unit then -- Get flight element from all flights. - local flight=self:_GetFlightFromGroupInQueue(unit:GetGroup(), self.flights) + local flight = self:_GetFlightFromGroupInQueue( unit:GetGroup(), self.flights ) -- Check if fight exists. if flight then -- Loop over all elements in flight group. - for i,_element in pairs(flight.elements) do - local element=_element --#AIRBOSS.FlightElement + for i, _element in pairs( flight.elements ) do + local element = _element -- #AIRBOSS.FlightElement - if element.unit:GetName()==unitname then + 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)) + self:T2( self.lid .. string.format( "WARNING: Flight element %s could not be found in flight group.", unitname, flight.groupname ) ) end end @@ -7809,16 +7123,16 @@ end -- @param #AIRBOSS self -- @param #string unitname Name of the unit. -- @return #boolean If true, element could be removed or nil otherwise. -function AIRBOSS:_RemoveFlightElement(unitname) +function AIRBOSS:_RemoveFlightElement( unitname ) -- Get table index. - local element,idx, flight=self:_GetFlightElement(unitname) + local element, idx, flight = self:_GetFlightElement( unitname ) if idx then - table.remove(flight.elements, idx) + table.remove( flight.elements, idx ) return true else - self:T("WARNING: Flight element could not be removed from flight group. Index=nil!") + self:T( "WARNING: Flight element could not be removed from flight group. Index=nil!" ) return nil end end @@ -7828,11 +7142,11 @@ end -- @param #table queue The queue to check. -- @param Wrapper.Group#GROUP group The group to be checked. -- @return #boolean If true, group is in the queue. False otherwise. -function AIRBOSS:_InQueue(queue, group) - local name=group:GetName() - for _,_flight in pairs(queue) do - local flight=_flight --#AIRBOSS.FlightGroup - if name==flight.groupname then +function AIRBOSS:_InQueue( queue, group ) + local name = group:GetName() + for _, _flight in pairs( queue ) do + local flight = _flight -- #AIRBOSS.FlightGroup + if name == flight.groupname then return true end end @@ -7846,29 +7160,29 @@ end function AIRBOSS:_RemoveDeadFlightGroups() -- Remove dead flights from all flights table. - for i=#self.flight,1,-1 do - local flight=self.flights[i] --#AIRBOSS.FlightGroup + for i = #self.flight, 1, -1 do + local flight = self.flights[i] -- #AIRBOSS.FlightGroup 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) + self:T( string.format( "Removing dead flight group %s from ALL flights table.", flight.groupname ) ) + table.remove( self.flights, i ) end end -- Remove dead flights from Marhal queue table. - for i=#self.Qmarshal,1,-1 do - local flight=self.Qmarshal[i] --#AIRBOSS.FlightGroup + for i = #self.Qmarshal, 1, -1 do + local flight = self.Qmarshal[i] -- #AIRBOSS.FlightGroup 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) + self:T( string.format( "Removing dead flight group %s from Marshal Queue table.", flight.groupname ) ) + table.remove( self.Qmarshal, i ) end end -- Remove dead flights from Pattern queue table. - for i=#self.Qpattern,1,-1 do - local flight=self.Qpattern[i] --#AIRBOSS.FlightGroup + for i = #self.Qpattern, 1, -1 do + local flight = self.Qpattern[i] -- #AIRBOSS.FlightGroup 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) + self:T( string.format( "Removing dead flight group %s from Pattern Queue table.", flight.groupname ) ) + table.remove( self.Qpattern, i ) end end @@ -7878,14 +7192,14 @@ end -- @param #AIRBOSS self -- @param #AIRBOSS.FlightGroup flight Flight group to check. -- @return #AIRBOSS.FlightGroup Flight group of the leader or flight itself if no other leader. -function AIRBOSS:_GetLeadFlight(flight) +function AIRBOSS:_GetLeadFlight( flight ) -- Init. - local lead=flight + local lead = flight -- Only human players can be section leads of other players. - if flight.name~=flight.seclead then - lead=self.players[flight.seclead] + if flight.name ~= flight.seclead then + lead = self.players[flight.seclead] end return lead @@ -7896,31 +7210,31 @@ end -- @param #AIRBOSS self -- @param #AIRBOSS.FlightGroup flight Flight group to check. -- @return #boolean If true, all elements landed. -function AIRBOSS:_CheckSectionRecovered(flight) +function AIRBOSS:_CheckSectionRecovered( flight ) -- Nil check. - if flight==nil then + if flight == nil then return true end -- Get the lead flight first, so that we can also check all section members. - local lead=self:_GetLeadFlight(flight) + local lead = self:_GetLeadFlight( flight ) -- Check all elements of the lead flight group. - for _,_element in pairs(lead.elements) do - local element=_element --#AIRBOSS.FlightElement + for _, _element in pairs( lead.elements ) do + local element = _element -- #AIRBOSS.FlightElement if not element.recovered then return false end end -- Now check all section members, if any. - for _,_section in pairs(lead.section) do - local sectionmember=_section --#AIRBOSS.FlightGroup + for _, _section in pairs( lead.section ) do + local sectionmember = _section -- #AIRBOSS.FlightGroup -- Check all elements of the secmember flight group. - for _,_element in pairs(sectionmember.elements) do - local element=_element --#AIRBOSS.FlightElement + for _, _element in pairs( sectionmember.elements ) do + local element = _element -- #AIRBOSS.FlightElement if not element.recovered then return false end @@ -7928,17 +7242,17 @@ function AIRBOSS:_CheckSectionRecovered(flight) end -- Remove lead flight from pattern queue. It is this flight who is added to the queue. - self:_RemoveFlightFromQueue(self.Qpattern, lead) + self:_RemoveFlightFromQueue( self.Qpattern, lead ) -- Just for now, check if it is in other queues as well. - 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) + 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 -- Just for now, check if it is in other queues as well. - 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) + 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 @@ -7947,29 +7261,29 @@ end --- Add flight to pattern queue and set recoverd to false for all elements of the flight and its section members. -- @param #AIRBOSS self -- @param #AIRBOSS.FlightGroup Flight group of element. -function AIRBOSS:_AddFlightToPatternQueue(flight) +function AIRBOSS:_AddFlightToPatternQueue( flight ) -- Add flight to table. - table.insert(self.Qpattern, flight) + table.insert( self.Qpattern, flight ) -- Set flag to -1 (-1 is rather arbitrary but it should not be positive or -100 or -42). - flight.flag=-1 + flight.flag = -1 -- New time stamp for time in pattern. - flight.time=timer.getAbsTime() + flight.time = timer.getAbsTime() -- Init recovered switch. - flight.recovered=false - for _,elem in pairs(flight.elements) do - elem.recoverd=false + flight.recovered = false + for _, elem in pairs( flight.elements ) do + elem.recoverd = false end -- Set recovered for all section members. - for _,sec in pairs(flight.section) do + for _, sec in pairs( flight.section ) do -- Set flag and timestamp for section members - sec.flag=-1 - sec.time=timer.getAbsTime() - for _,elem in pairs(sec.elements) do - elem.recoverd=false + sec.flag = -1 + sec.time = timer.getAbsTime() + for _, elem in pairs( sec.elements ) do + elem.recoverd = false end end end @@ -7978,14 +7292,14 @@ end -- @param #AIRBOSS self -- @param Wrapper.Unit#UNIT unit The aircraft unit that was recovered. -- @return #AIRBOSS.FlightGroup Flight group of element. -function AIRBOSS:_RecoveredElement(unit) +function AIRBOSS:_RecoveredElement( unit ) -- Get element of flight. - local element, idx, flight=self:_GetFlightElement(unit:GetName()) --#AIRBOSS.FlightElement + local element, idx, flight = self:_GetFlightElement( unit:GetName() ) -- #AIRBOSS.FlightElement -- Nil check. Could be if a helo landed or something else we dont know! if element then - element.recovered=true + element.recovered = true end return flight @@ -7997,44 +7311,44 @@ end -- @param #boolean nopattern If true, flight is NOT going to landing pattern. -- @return #boolean True, flight was removed or false otherwise. -- @return #number Table index of the flight in the Marshal queue. -function AIRBOSS:_RemoveFlightFromMarshalQueue(flight, nopattern) +function AIRBOSS:_RemoveFlightFromMarshalQueue( flight, nopattern ) -- Remove flight from marshal queue if it is in. - local removed, idx=self:_RemoveFlightFromQueue(self.Qmarshal, flight) + local removed, idx = self:_RemoveFlightFromQueue( self.Qmarshal, flight ) -- Collapse marshal stack if flight was removed. if removed then -- Flight is not holding any more. - flight.holding=nil + flight.holding = nil -- Collapse marshal stack if flight was removed. - self:_CollapseMarshalStack(flight, nopattern) + self:_CollapseMarshalStack( flight, nopattern ) -- Stacks are only limited for Case I. - if flight.case==1 and #self.Qwaiting>0 then + if flight.case == 1 and #self.Qwaiting > 0 then -- Next flight in line waiting. - local nextflight=self.Qwaiting[1] --#AIRBOSS.FlightGroup + local nextflight = self.Qwaiting[1] -- #AIRBOSS.FlightGroup -- Get free stack. - local freestack=self:_GetFreeStack(nextflight.ai) + local freestack = self:_GetFreeStack( nextflight.ai ) -- Send next flight to marshal stack. if nextflight.ai then -- Send AI to Marshal Stack. - self:_MarshalAI(nextflight, freestack) + self:_MarshalAI( nextflight, freestack ) else -- Send player to Marshal stack. - self:_MarshalPlayer(nextflight, freestack) + self:_MarshalPlayer( nextflight, freestack ) end -- Remove flight from waiting queue. - self:_RemoveFlightFromQueue(self.Qwaiting, nextflight) + self:_RemoveFlightFromQueue( self.Qwaiting, nextflight ) end end @@ -8048,16 +7362,16 @@ end -- @param #AIRBOSS.FlightGroup flight Flight group that will be removed from queue. -- @return #boolean True, flight was in Queue and removed. False otherwise. -- @return #number Table index of removed queue element or nil. -function AIRBOSS:_RemoveFlightFromQueue(queue, flight) +function AIRBOSS:_RemoveFlightFromQueue( queue, flight ) -- Loop over all flights in group. - for i,_flight in pairs(queue) do - local qflight=_flight --#AIRBOSS.FlightGroup + for i, _flight in pairs( queue ) do + local qflight = _flight -- #AIRBOSS.FlightGroup -- Check for name. - if qflight.groupname==flight.groupname then - self:T(self.lid..string.format("Removing flight group %s from queue.", flight.groupname)) - table.remove(queue, i) + 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 @@ -8068,41 +7382,41 @@ end --- Remove a unit and its element from a flight group (e.g. when landed) and update all queues if the whole flight group is gone. -- @param #AIRBOSS self -- @param Wrapper.Unit#UNIT unit The unit to be removed. -function AIRBOSS:_RemoveUnitFromFlight(unit) +function AIRBOSS:_RemoveUnitFromFlight( unit ) -- Check if unit exists. - if unit and unit:IsInstanceOf("UNIT") then + if unit and unit:IsInstanceOf( "UNIT" ) then -- Get group. - local group=unit:GetGroup() + local group = unit:GetGroup() -- Check if group exists. if group then -- Get flight. - local flight=self:_GetFlightFromGroupInQueue(group, self.flights) + local flight = self:_GetFlightFromGroupInQueue( group, self.flights ) -- Check if flight exists. if flight then -- Remove element from flight group. - local removed=self:_RemoveFlightElement(unit:GetName()) + local removed = self:_RemoveFlightElement( unit:GetName() ) if removed then -- Get number of units (excluding section members). For AI only those that are still in air as we assume once they landed, they are out of the game. - local _,nunits=self:_GetFlightUnits(flight, not flight.ai) + local _, nunits = self:_GetFlightUnits( flight, not flight.ai ) -- Number of flight elements still left. - local nelements=#flight.elements + local nelements = #flight.elements -- Debug info. - self:T(self.lid..string.format("Removed unit %s: nunits=%d, nelements=%d", unit:GetName(), nunits, nelements)) + self:T( self.lid .. string.format( "Removed unit %s: nunits=%d, nelements=%d", unit:GetName(), nunits, nelements ) ) -- Check if no units are left. - if nunits==0 or nelements==0 then + if nunits == 0 or nelements == 0 then -- Remove flight from all queues. - self:_RemoveFlight(flight) + self:_RemoveFlight( flight ) end end @@ -8115,18 +7429,18 @@ end --- Remove a flight, which is a member of a section, from this section. -- @param #AIRBOSS self -- @param #AIRBOSS.FlightGroup flight The flight to be removed from the section -function AIRBOSS:_RemoveFlightFromSection(flight) +function AIRBOSS:_RemoveFlightFromSection( flight ) -- First check if player is not the lead. - if flight.name~=flight.seclead then + if flight.name ~= flight.seclead then -- Remove this flight group from the section of the leader. - local lead=self.players[flight.seclead] --#AIRBOSS.FlightGroup + local lead = self.players[flight.seclead] -- #AIRBOSS.FlightGroup if lead then - for i,sec in pairs(lead.section) do - local sectionmember=sec --#AIRBOSS.FlightGroup - if sectionmember.name==flight.name then - table.remove(lead.section, i) + for i, sec in pairs( lead.section ) do + local sectionmember = sec -- #AIRBOSS.FlightGroup + if sectionmember.name == flight.name then + table.remove( lead.section, i ) break end end @@ -8140,37 +7454,37 @@ end -- If removed flight is the section lead, we try to find a new leader. -- @param #AIRBOSS self -- @param #AIRBOSS.FlightGroup flight The flight to be removed. -function AIRBOSS:_UpdateFlightSection(flight) +function AIRBOSS:_UpdateFlightSection( flight ) -- Check if this player is the leader of a section. - if flight.seclead==flight.name then + if flight.seclead == flight.name then -------------------- -- Section Leader -- -------------------- -- This player is the leader ==> We need a new one. - if #flight.section>=1 then + if #flight.section >= 1 then -- New leader. - local newlead=flight.section[1] --#AIRBOSS.FlightGroup - newlead.seclead=newlead.name + local newlead = flight.section[1] -- #AIRBOSS.FlightGroup + newlead.seclead = newlead.name -- Adjust new section members. - for i=2,#flight.section do - local member=flight.section[i] --#AIRBOSS.FlightGroup + for i = 2, #flight.section do + local member = flight.section[i] -- #AIRBOSS.FlightGroup -- Add remaining members new leaders table. - table.insert(newlead.section, member) + table.insert( newlead.section, member ) -- Set new section lead of member. - member.seclead=newlead.name + member.seclead = newlead.name end end -- Flight section empty - flight.section={} + flight.section = {} else @@ -8179,7 +7493,7 @@ function AIRBOSS:_UpdateFlightSection(flight) -------------------- -- Remove flight from its leaders section. - self:_RemoveFlightFromSection(flight) + self:_RemoveFlightFromSection( flight ) end @@ -8190,28 +7504,28 @@ end -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData flight The flight to be removed. -- @param #boolean completely If true, also remove human flight from all flights table. -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))) +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 ) ) ) -- Remove flight from all queues. - self:_RemoveFlightFromMarshalQueue(flight, true) - self:_RemoveFlightFromQueue(self.Qpattern, flight) - self:_RemoveFlightFromQueue(self.Qwaiting, flight) - self:_RemoveFlightFromQueue(self.Qspinning, flight) + self:_RemoveFlightFromMarshalQueue( flight, true ) + self:_RemoveFlightFromQueue( self.Qpattern, flight ) + self:_RemoveFlightFromQueue( self.Qwaiting, flight ) + self:_RemoveFlightFromQueue( self.Qspinning, flight ) -- Check if player or AI if flight.ai then -- Remove AI flight completely. Pure AI flights have no sections and cannot be members. - self:_RemoveFlightFromQueue(self.flights, flight) + self:_RemoveFlightFromQueue( self.flights, flight ) else -- Remove all grades until a final grade is reached. - 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) + 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 @@ -8219,36 +7533,36 @@ function AIRBOSS:_RemoveFlight(flight, completely) if completely then -- Update flight section. Remove flight from section or find new section leader if flight was the lead. - self:_UpdateFlightSection(flight) + self:_UpdateFlightSection( flight ) -- Remove completely. - self:_RemoveFlightFromQueue(self.flights, flight) + self:_RemoveFlightFromQueue( self.flights, flight ) -- Remove player from players table. - local playerdata=self.players[flight.name] + 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 + self:I( self.lid .. string.format( "Removing player %s completely.", flight.name ) ) + self.players[flight.name] = nil end -- Remove flight. - flight=nil + flight = nil else -- Set player step to undefined. - self:_SetPlayerStep(flight, AIRBOSS.PatternStep.UNDEFINED) + self:_SetPlayerStep( flight, AIRBOSS.PatternStep.UNDEFINED ) -- Also set this for the section members as they are in the same boat. - for _,sectionmember in pairs(flight.section) do - self:_SetPlayerStep(sectionmember, AIRBOSS.PatternStep.UNDEFINED) + for _, sectionmember in pairs( flight.section ) do + self:_SetPlayerStep( sectionmember, AIRBOSS.PatternStep.UNDEFINED ) -- Also remove section member in case they are in the spinning queue. - self:_RemoveFlightFromQueue(self.Qspinning, sectionmember) + self:_RemoveFlightFromQueue( self.Qspinning, sectionmember ) end -- What if flight is member of a section. His status is now undefined. Should he be removed from the section? -- I think yes, if he pulls the trigger. - self:_RemoveFlightFromSection(flight) + self:_RemoveFlightFromSection( flight ) end end @@ -8264,212 +7578,211 @@ end function AIRBOSS:_CheckPlayerStatus() -- Loop over all players. - for _playerName,_playerData in pairs(self.players) do - local playerData=_playerData --#AIRBOSS.PlayerData + for _playerName, _playerData in pairs( self.players ) do + local playerData = _playerData -- #AIRBOSS.PlayerData if playerData then -- Player unit. - local unit=playerData.unit + local unit = playerData.unit -- Check if unit is alive. if unit and unit:IsAlive() then -- Check if player is in carrier controlled area (zone with R=50 NM around the carrier). -- TODO: This might cause problems if the CCA is set to be very small! - if unit:IsInZone(self.zoneCCA) then + if unit:IsInZone( self.zoneCCA ) then -- Display aircraft attitude and other parameters as message text. if playerData.attitudemonitor then - self:_AttitudeMonitor(playerData) + self:_AttitudeMonitor( playerData ) end -- Check distance to other flights. - self:_CheckPlayerPatternDistance(playerData) + self:_CheckPlayerPatternDistance( playerData ) -- Foul deck check. - self:_CheckFoulDeck(playerData) + self:_CheckFoulDeck( playerData ) -- Check current step. - if playerData.step==AIRBOSS.PatternStep.UNDEFINED then + if playerData.step == AIRBOSS.PatternStep.UNDEFINED then -- Status undefined. - --local time=timer.getAbsTime() - --local clock=UTILS.SecondsToClock(time) - --self:T3(string.format("Player status undefined. Waiting for next step. Time %s", clock)) + -- local time=timer.getAbsTime() + -- local clock=UTILS.SecondsToClock(time) + -- self:T3(string.format("Player status undefined. Waiting for next step. Time %s", clock)) - elseif playerData.step==AIRBOSS.PatternStep.REFUELING then + elseif playerData.step == AIRBOSS.PatternStep.REFUELING then -- Nothing to do here at the moment. - elseif playerData.step==AIRBOSS.PatternStep.SPINNING then + elseif playerData.step == AIRBOSS.PatternStep.SPINNING then -- Player is spinning. - self:_Spinning(playerData) + self:_Spinning( playerData ) - elseif playerData.step==AIRBOSS.PatternStep.HOLDING then + elseif playerData.step == AIRBOSS.PatternStep.HOLDING then -- CASE I/II/III: In holding pattern. - self:_Holding(playerData) + self:_Holding( playerData ) - elseif playerData.step==AIRBOSS.PatternStep.WAITING then + elseif playerData.step == AIRBOSS.PatternStep.WAITING then -- CASE I: Waiting outside 10 NM zone for next free Marshal stack. - self:_Waiting(playerData) + self:_Waiting( playerData ) - elseif playerData.step==AIRBOSS.PatternStep.COMMENCING then + elseif playerData.step == AIRBOSS.PatternStep.COMMENCING then -- CASE I/II/III: New approach. - self:_Commencing(playerData, true) + self:_Commencing( playerData, true ) - elseif playerData.step==AIRBOSS.PatternStep.BOLTER then + elseif playerData.step == AIRBOSS.PatternStep.BOLTER then -- CASE I/II/III: Bolter pattern. - self:_BolterPattern(playerData) + self:_BolterPattern( playerData ) - elseif playerData.step==AIRBOSS.PatternStep.PLATFORM then + elseif playerData.step == AIRBOSS.PatternStep.PLATFORM then -- CASE II/III: Player has reached 5k "Platform". - self:_Platform(playerData) + self:_Platform( playerData ) - elseif playerData.step==AIRBOSS.PatternStep.ARCIN then + elseif playerData.step == AIRBOSS.PatternStep.ARCIN then -- Case II/III if offset. - self:_ArcInTurn(playerData) + self:_ArcInTurn( playerData ) - elseif playerData.step==AIRBOSS.PatternStep.ARCOUT then + elseif playerData.step == AIRBOSS.PatternStep.ARCOUT then -- Case II/III if offset. - self:_ArcOutTurn(playerData) + self:_ArcOutTurn( playerData ) - elseif playerData.step==AIRBOSS.PatternStep.DIRTYUP then + elseif playerData.step == AIRBOSS.PatternStep.DIRTYUP then -- CASE III: Player has descended to 1200 ft and is going level from now on. - self:_DirtyUp(playerData) + self:_DirtyUp( playerData ) - elseif playerData.step==AIRBOSS.PatternStep.BULLSEYE then + elseif playerData.step == AIRBOSS.PatternStep.BULLSEYE then -- CASE III: Player has intercepted the glide slope and should follow "Bullseye" (ICLS). - self:_Bullseye(playerData) + self:_Bullseye( playerData ) - elseif playerData.step==AIRBOSS.PatternStep.INITIAL then + elseif playerData.step == AIRBOSS.PatternStep.INITIAL then -- CASE I/II: Player is at the initial position entering the landing pattern. - self:_Initial(playerData) + self:_Initial( playerData ) - elseif playerData.step==AIRBOSS.PatternStep.BREAKENTRY then + elseif playerData.step == AIRBOSS.PatternStep.BREAKENTRY then -- CASE I/II: Break entry. - self:_BreakEntry(playerData) + self:_BreakEntry( playerData ) - elseif playerData.step==AIRBOSS.PatternStep.EARLYBREAK then + elseif playerData.step == AIRBOSS.PatternStep.EARLYBREAK then -- CASE I/II: Early break. - self:_Break(playerData, AIRBOSS.PatternStep.EARLYBREAK) + self:_Break( playerData, AIRBOSS.PatternStep.EARLYBREAK ) - elseif playerData.step==AIRBOSS.PatternStep.LATEBREAK then + elseif playerData.step == AIRBOSS.PatternStep.LATEBREAK then -- CASE I/II: Late break. - self:_Break(playerData, AIRBOSS.PatternStep.LATEBREAK) + self:_Break( playerData, AIRBOSS.PatternStep.LATEBREAK ) - elseif playerData.step==AIRBOSS.PatternStep.ABEAM then + elseif playerData.step == AIRBOSS.PatternStep.ABEAM then -- CASE I/II: Abeam position. - self:_Abeam(playerData) + self:_Abeam( playerData ) - elseif playerData.step==AIRBOSS.PatternStep.NINETY then + elseif playerData.step == AIRBOSS.PatternStep.NINETY then -- CASE:I/II: Check long down wind leg. - self:_CheckForLongDownwind(playerData) + self:_CheckForLongDownwind( playerData ) -- At the ninety. - self:_Ninety(playerData) + self:_Ninety( playerData ) - elseif playerData.step==AIRBOSS.PatternStep.WAKE then + elseif playerData.step == AIRBOSS.PatternStep.WAKE then -- CASE I/II: In the wake. - self:_Wake(playerData) + self:_Wake( playerData ) - elseif playerData.step==AIRBOSS.PatternStep.EMERGENCY then + elseif playerData.step == AIRBOSS.PatternStep.EMERGENCY then -- Emergency landing. Player pos is not checked. - self:_Final(playerData, true) + self:_Final( playerData, true ) - elseif playerData.step==AIRBOSS.PatternStep.FINAL then + elseif playerData.step == AIRBOSS.PatternStep.FINAL then -- CASE I/II: Turn to final and enter the groove. - self:_Final(playerData) + 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 + 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 -- CASE I/II: In the groove. - self:_Groove(playerData) + self:_Groove( playerData ) - elseif playerData.step==AIRBOSS.PatternStep.DEBRIEF then + elseif playerData.step == AIRBOSS.PatternStep.DEBRIEF then -- Debriefing in 5 seconds. - --SCHEDULER:New(nil, self._Debrief, {self, playerData}, 5) - playerData.debriefschedulerID=self:ScheduleOnce(5, self._Debrief, self, playerData) + -- SCHEDULER:New(nil, self._Debrief, {self, playerData}, 5) + playerData.debriefschedulerID = self:ScheduleOnce( 5, self._Debrief, self, playerData ) -- Undefined status. - playerData.step=AIRBOSS.PatternStep.UNDEFINED + playerData.step = AIRBOSS.PatternStep.UNDEFINED else -- Error, unknown step! - self:E(self.lid..string.format("ERROR: Unknown player step %s. Please report!", tostring(playerData.step))) + self:E( self.lid .. string.format( "ERROR: Unknown player step %s. Please report!", tostring( playerData.step ) ) ) end -- Check if player missed a step during Case II/III and allow him to enter the landing pattern. - self:_CheckMissedStepOnEntry(playerData) + self:_CheckMissedStepOnEntry( playerData ) else - self:T2(self.lid.."WARNING: Player unit not inside the CCA!") + self:T2( self.lid .. "WARNING: Player unit not inside the CCA!" ) end else -- Unit not alive. - self:T(self.lid.."WARNING: Player unit is not alive!") + self:T( self.lid .. "WARNING: Player unit is not alive!" ) end end end end - --- Checks if a player is in the pattern queue and has missed a step in Case II/III approach. -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data. -function AIRBOSS:_CheckMissedStepOnEntry(playerData) +function AIRBOSS:_CheckMissedStepOnEntry( playerData ) -- Conditions to be met: Case II/III, in pattern queue, flag!=42 (will be set to 42 at the end if player missed a step). - local rightcase=playerData.case>1 - local rightqueue=self:_InQueue(self.Qpattern, playerData.group) - local rightflag=playerData.flag~=-42 + local rightcase = playerData.case > 1 + local rightqueue = self:_InQueue( self.Qpattern, playerData.group ) + local rightflag = playerData.flag ~= -42 -- Steps that the player could have missed during Case II/III. - 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 + 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 -- Check if player is about to enter the initial or bullseye zones and maybe has missed a step in the pattern. if rightcase and rightqueue and rightflag then -- Get right zone. - local zone=nil - if playerData.case==2 and missedstep then + local zone = nil + if playerData.case == 2 and missedstep then - zone=self:_GetZoneInitial(playerData.case) + zone = self:_GetZoneInitial( playerData.case ) - elseif playerData.case==3 and missedstep then + elseif playerData.case == 3 and missedstep then - zone=self:_GetZoneBullseye(playerData.case) + zone = self:_GetZoneBullseye( playerData.case ) end @@ -8477,28 +7790,28 @@ function AIRBOSS:_CheckMissedStepOnEntry(playerData) if zone then -- Check if player is in initial or bullseye zone. - local inzone=playerData.unit:IsInZone(zone) + local inzone = playerData.unit:IsInZone( zone ) -- Relative heading to carrier direction. - local relheading=self:_GetRelativeHeading(playerData.unit, false) + local relheading = self:_GetRelativeHeading( playerData.unit, false ) -- Check if player is in zone and flying roughly in the right direction. - if inzone and math.abs(relheading)<60 then + if inzone and math.abs( relheading ) < 60 then -- Player is in one of the initial zones short before the landing pattern. - 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) + 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 + if playerData.case == 2 then -- Set next step to initial. - playerData.step=AIRBOSS.PatternStep.INITIAL - elseif playerData.case==3 then + playerData.step = AIRBOSS.PatternStep.INITIAL + elseif playerData.case == 3 then -- Set next step to bullseye. - playerData.step=AIRBOSS.PatternStep.BULLSEYE + playerData.step = AIRBOSS.PatternStep.BULLSEYE end -- Set flag value to -42. This is the value to ensure that this routine is not called again! - playerData.flag=-42 + playerData.flag = -42 end end end @@ -8507,13 +7820,13 @@ end --- Set time in the groove for player. -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data. -function AIRBOSS:_SetTimeInGroove(playerData) +function AIRBOSS:_SetTimeInGroove( playerData ) -- Set time in the groove if playerData.TIG0 then - playerData.Tgroove=timer.getTime()-playerData.TIG0 + playerData.Tgroove = timer.getTime() - playerData.TIG0 else - playerData.Tgroove=999 + playerData.Tgroove = 999 end end @@ -8522,19 +7835,18 @@ end -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data. -- @return #number Player's time in groove in seconds. -function AIRBOSS:_GetTimeInGroove(playerData) +function AIRBOSS:_GetTimeInGroove( playerData ) - local Tgroove=999 + local Tgroove = 999 -- Get time in the groove. if playerData.TIG0 then - Tgroove=timer.getTime()-playerData.TIG0 + Tgroove = timer.getTime() - playerData.TIG0 end return Tgroove end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- EVENT functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -8542,62 +7854,62 @@ end --- Airboss event handler for event birth. -- @param #AIRBOSS self -- @param Core.Event#EVENTDATA EventData -function AIRBOSS:OnEventBirth(EventData) - self:F3({eventbirth = EventData}) +function AIRBOSS:OnEventBirth( EventData ) + self:F3( { eventbirth = EventData } ) -- Nil checks. - if EventData==nil then - self:E(self.lid.."ERROR: EventData=nil in event BIRTH!") - self:E(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) + 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) + 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)) + 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 _uid = _unit:GetID() + local _group = _unit:GetGroup() + local _callsign = _unit:GetCallsign() -- Debug output. - 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 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 ) -- Check if aircraft type the player occupies is carrier capable. - 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) + 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 -- Check that coalition of the carrier and aircraft match. - 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) + 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 -- Add Menu commands. - self:_AddF10Commands(_unitName) + self:_AddF10Commands( _unitName ) -- Delaying the new player for a second, because AI units of the flight would not be registered correctly. - --SCHEDULER:New(nil, self._NewPlayer, {self, _unitName}, 1) - self:ScheduleOnce(1, self._NewPlayer, self, _unitName) + -- SCHEDULER:New(nil, self._NewPlayer, {self, _unitName}, 1) + self:ScheduleOnce( 1, self._NewPlayer, self, _unitName ) end end @@ -8605,51 +7917,51 @@ end --- Airboss event handler for event land. -- @param #AIRBOSS self -- @param Core.Event#EVENTDATA EventData -function AIRBOSS:OnEventLand(EventData) - self:F3({eventland = EventData}) +function AIRBOSS:OnEventLand( EventData ) + self:F3( { eventland = EventData } ) -- Nil checks. - if EventData==nil then - self:E(self.lid.."ERROR: EventData=nil in event LAND!") - self:E(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) + if EventData.IniUnit == nil then + self:E( self.lid .. "ERROR: EventData.IniUnit=nil in event LAND!" ) + self:E( EventData ) return end -- Get unit name that landed. - local _unitName=EventData.IniUnitName + local _unitName = EventData.IniUnitName -- Check if this was a player. - local _unit, _playername=self:_GetPlayerUnitAndName(_unitName) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) -- Debug output. - 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)) + 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 ) ) -- This would be the closest airbase. - local airbase=EventData.Place + local airbase = EventData.Place -- Nil check for airbase. Crashed as player gave me no airbase. - if airbase==nil then + if airbase == nil then return end -- Get airbase name. - local airbasename=tostring(airbase:GetName()) + local airbasename = tostring( airbase:GetName() ) -- Check if aircraft landed on the right airbase. - if airbasename==self.airbase:GetName() then + if airbasename == self.airbase:GetName() then -- Stern coordinate at the rundown. - local stern=self:_GetSternCoord() + local stern = self:_GetSternCoord() -- Polygon zone close around the carrier. - local zoneCarrier=self:_GetZoneCarrierBox() + local zoneCarrier = self:_GetZoneCarrierBox() -- Check if player or AI landed. if _unit and _playername then @@ -8659,41 +7971,41 @@ function AIRBOSS:OnEventLand(EventData) ------------------------- -- Get info. - local _uid=_unit:GetID() - local _group=_unit:GetGroup() - local _callsign=_unit:GetCallsign() + local _uid = _unit:GetID() + local _group = _unit:GetGroup() + local _callsign = _unit:GetCallsign() -- Debug output. - 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 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 ) -- Player data. - local playerData=self.players[_playername] --#AIRBOSS.PlayerData + local playerData = self.players[_playername] -- #AIRBOSS.PlayerData -- Check if playerData is okay. - if playerData==nil then - self:E(self.lid..string.format("ERROR: playerData nil in landing event. unit=%s player=%s", tostring(_unitName), tostring(_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 -- Check that player landed on the carrier. - if _unit:IsInZone(zoneCarrier) then + if _unit:IsInZone( zoneCarrier ) then -- Check if this was a valid approach. if not playerData.valid then -- Player missed at least one step in the pattern. - 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) + 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 ) -- Clear queues just in case. - self:_RemoveFlightFromMarshalQueue(playerData, true) - self:_RemoveFlightFromQueue(self.Qpattern, playerData) - self:_RemoveFlightFromQueue(self.Qwaiting, playerData) - self:_RemoveFlightFromQueue(self.Qspinning, playerData) + self:_RemoveFlightFromMarshalQueue( playerData, true ) + self:_RemoveFlightFromQueue( self.Qpattern, playerData ) + self:_RemoveFlightFromQueue( self.Qwaiting, playerData ) + self:_RemoveFlightFromQueue( self.Qspinning, playerData ) -- Reinitialize player data. - self:_InitPlayer(playerData) + self:_InitPlayer( playerData ) return end @@ -8701,57 +8013,57 @@ function AIRBOSS:OnEventLand(EventData) -- Check if player already landed. We dont need a second time. if playerData.landed then - self:E(self.lid..string.format("Player %s just landed a second time.", _playername)) + self:E( self.lid .. string.format( "Player %s just landed a second time.", _playername ) ) else -- We did land. - playerData.landed=true + playerData.landed = true -- Switch attitude monitor off if on. - playerData.attitudemonitor=false + playerData.attitudemonitor = false -- Coordinate at landing event. - local coord=playerData.unit:GetCoordinate() + local coord = playerData.unit:GetCoordinate() -- Get distances relative to - local X,Z,rho,phi=self:_GetDistances(_unit) + local X, Z, rho, phi = self:_GetDistances( _unit ) -- Landing distance wrt to stern position. - local dist=coord:Get2DDistance(stern) + local dist = coord:Get2DDistance( stern ) -- Debug mark of player landing coord. if self.Debug and false then -- Debug mark of player landing coord. - local lp=coord:MarkToAll("Landing coord.") + local lp = coord:MarkToAll( "Landing coord." ) coord:SmokeGreen() end -- Set time in the groove of player. - self:_SetTimeInGroove(playerData) + self:_SetTimeInGroove( playerData ) -- Debug text. - 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) + 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 ) -- Check carrier type. - if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then -- Power "Idle". - self:RadioTransmission(self.LSORadio, self.LSOCall.IDLE, false, 1, nil, true) + self:RadioTransmission( self.LSORadio, self.LSOCall.IDLE, false, 1, nil, true ) -- Next step debrief. - self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.DEBRIEF) + self:_SetPlayerStep( playerData, AIRBOSS.PatternStep.DEBRIEF ) else -- Next step undefined until we know more. - self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.UNDEFINED) + self:_SetPlayerStep( playerData, AIRBOSS.PatternStep.UNDEFINED ) -- Call trapped function in 1 second to make sure we did not bolter. - --SCHEDULER:New(nil, self._Trapped, {self, playerData}, 1) - self:ScheduleOnce(1, self._Trapped, self, playerData) + -- SCHEDULER:New(nil, self._Trapped, {self, playerData}, 1) + self:ScheduleOnce( 1, self._Trapped, self, playerData ) end @@ -8761,7 +8073,7 @@ function AIRBOSS:OnEventLand(EventData) -- Handle case where player did not land on the carrier. -- Well, I guess, he leaves the slot or ejects. Both should be handled. 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)) + 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 @@ -8771,31 +8083,31 @@ function AIRBOSS:OnEventLand(EventData) -- AI unit landed -- -------------------- - if self.carriertype~=AIRBOSS.CarrierType.TARAWA or self.carriertype~=AIRBOSS.CarrierType.AMERICA or self.carriertype~=AIRBOSS.CarrierType.JCARLOS or self.carriertype~=AIRBOSS.CarrierType.CANBERRA then + if self.carriertype ~= AIRBOSS.CarrierType.TARAWA or self.carriertype ~= AIRBOSS.CarrierType.AMERICA or self.carriertype ~= AIRBOSS.CarrierType.JCARLOS or self.carriertype ~= AIRBOSS.CarrierType.CANBERRA then -- Coordinate at landing event - local coord=EventData.IniUnit:GetCoordinate() + local coord = EventData.IniUnit:GetCoordinate() -- Debug mark of player landing coord. - local dist=coord:Get2DDistance(self:GetCoordinate()) + local dist = coord:Get2DDistance( self:GetCoordinate() ) -- Get wire - local wire=self:_GetWire(coord, 0) + local wire = self:_GetWire( coord, 0 ) -- Aircraft type. - local _type=EventData.IniUnit:GetTypeName() + local _type = EventData.IniUnit:GetTypeName() -- Debug text. - 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) + 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 -- AI always lands ==> remove unit from flight group and queues. - local flight=self:_RecoveredElement(EventData.IniUnit) + local flight = self:_RecoveredElement( EventData.IniUnit ) -- Check if all were recovered. If so update pattern queue. - self:_CheckSectionRecovered(flight) + self:_CheckSectionRecovered( flight ) end end @@ -8804,62 +8116,61 @@ end --- Airboss event handler for event that a unit shuts down its engines. -- @param #AIRBOSS self -- @param Core.Event#EVENTDATA EventData -function AIRBOSS:OnEventEngineShutdown(EventData) - self:F3({eventengineshutdown=EventData}) +function AIRBOSS:OnEventEngineShutdown( EventData ) + self:F3( { eventengineshutdown = EventData } ) -- Nil checks. - if EventData==nil then - self:E(self.lid.."ERROR: EventData=nil in event ENGINESHUTDOWN!") - self:E(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) + 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 ) - 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)) + 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 -- Debug message. - self:T(self.lid..string.format("Player %s shut down its engines!",_playername)) + self:T( self.lid .. string.format( "Player %s shut down its engines!", _playername ) ) else -- Debug message. - self:T(self.lid..string.format("AI unit %s shut down its engines!", _unitName)) + self:T( self.lid .. string.format( "AI unit %s shut down its engines!", _unitName ) ) -- Get flight. - local flight=self:_GetFlightFromGroupInQueue(EventData.IniGroup, self.flights) + local flight = self:_GetFlightFromGroupInQueue( EventData.IniGroup, self.flights ) -- Only AI flights. if flight and flight.ai then -- Check if all elements were recovered. - local recovered=self:_CheckSectionRecovered(flight) + local recovered = self:_CheckSectionRecovered( flight ) -- Despawn group and completely remove 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:T( self.lid .. string.format( "AI group %s completely recovered. Despawning group after engine shutdown event as requested in 5 seconds.", tostring( EventData.IniGroupName ) ) ) -- Remove flight. - self:_RemoveFlight(flight) + self:_RemoveFlight( flight ) -- Check if this is a tanker or AWACS associated with the carrier. - local istanker=self.tanker and self.tanker.tanker:GetName()==EventData.IniGroupName - local isawacs=self.awacs and self.awacs.tanker:GetName()==EventData.IniGroupName + local istanker = self.tanker and self.tanker.tanker:GetName() == EventData.IniGroupName + local isawacs = self.awacs and self.awacs.tanker:GetName() == EventData.IniGroupName -- Destroy group if desired. Recovery tankers have their own logic for despawning. if self.despawnshutdown and not (istanker or isawacs) then - EventData.IniGroup:Destroy(nil, 5) + EventData.IniGroup:Destroy( nil, 5 ) end end @@ -8871,61 +8182,60 @@ end --- Airboss event handler for event that a unit takes off. -- @param #AIRBOSS self -- @param Core.Event#EVENTDATA EventData -function AIRBOSS:OnEventTakeoff(EventData) - self:F3({eventtakeoff=EventData}) +function AIRBOSS:OnEventTakeoff( EventData ) + self:F3( { eventtakeoff = EventData } ) -- Nil checks. - if EventData==nil then - self:E(self.lid.."ERROR: EventData=nil in event TAKEOFF!") - self:E(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) + 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 ) - 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)) + 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 ) ) -- Airbase. - local airbase=EventData.Place + local airbase = EventData.Place -- Airbase name. - local airbasename="unknown" + local airbasename = "unknown" if airbase then - airbasename=airbase:GetName() + airbasename = airbase:GetName() end -- Check right airbase. - if airbasename==self.airbase:GetName() then + if airbasename == self.airbase:GetName() then if _unit and _playername then -- Debug message. - self:T(self.lid..string.format("Player %s took off at %s!",_playername, airbasename)) + self:T( self.lid .. string.format( "Player %s took off at %s!", _playername, airbasename ) ) else -- Debug message. - self:T2(self.lid..string.format("AI unit %s took off at %s!", _unitName, airbasename)) + self:T2( self.lid .. string.format( "AI unit %s took off at %s!", _unitName, airbasename ) ) -- Get flight. - local flight=self:_GetFlightFromGroupInQueue(EventData.IniGroup, self.flights) + local flight = self:_GetFlightFromGroupInQueue( EventData.IniGroup, self.flights ) if flight then -- Set ballcall and recoverd status. - for _,elem in pairs(flight.elements) do - local element=elem --#AIRBOSS.FlightElement - element.ballcall=false - element.recovered=nil + for _, elem in pairs( flight.elements ) do + local element = elem -- #AIRBOSS.FlightElement + element.ballcall = false + element.recovered = nil end end end @@ -8936,48 +8246,47 @@ end --- Airboss event handler for event crash. -- @param #AIRBOSS self -- @param Core.Event#EVENTDATA EventData -function AIRBOSS:OnEventCrash(EventData) - self:F3({eventcrash = EventData}) +function AIRBOSS:OnEventCrash( EventData ) + self:F3( { eventcrash = EventData } ) -- Nil checks. - if EventData==nil then - self:E(self.lid.."ERROR: EventData=nil in event CRASH!") - self:E(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) + 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 ) - 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)) + 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 -- Debug message. - self:T(self.lid..string.format("Player %s crashed!",_playername)) + self:T( self.lid .. string.format( "Player %s crashed!", _playername ) ) -- Get player flight. - local flight=self.players[_playername] + local flight = self.players[_playername] -- Remove flight completely from all queues and collapse marshal if necessary. -- This also updates the section, if any and removes any unfinished gradings of the player. if flight then - self:_RemoveFlight(flight, true) + self:_RemoveFlight( flight, true ) end else -- Debug message. - self:T2(self.lid..string.format("AI unit %s crashed!", EventData.IniUnitName)) + self:T2( self.lid .. string.format( "AI unit %s crashed!", EventData.IniUnitName ) ) -- Remove unit from flight and queues. - self:_RemoveUnitFromFlight(EventData.IniUnit) + self:_RemoveUnitFromFlight( EventData.IniUnit ) end end @@ -8985,51 +8294,50 @@ end --- Airboss event handler for event Ejection. -- @param #AIRBOSS self -- @param Core.Event#EVENTDATA EventData -function AIRBOSS:OnEventEjection(EventData) - self:F3({eventland = EventData}) +function AIRBOSS:OnEventEjection( EventData ) + self:F3( { eventland = EventData } ) -- Nil checks. - if EventData==nil then - self:E(self.lid.."ERROR: EventData=nil in event EJECTION!") - self:E(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) + 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 ) - 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)) + 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)) + self:T( self.lid .. string.format( "Player %s ejected!", _playername ) ) -- Get player flight. - local flight=self.players[_playername] + local flight = self.players[_playername] -- Remove flight completely from all queues and collapse marshal if necessary. if flight then - self:_RemoveFlight(flight, true) + self:_RemoveFlight( flight, true ) end else -- Debug message. - self:T(self.lid..string.format("AI unit %s ejected!", EventData.IniUnitName)) + self:T( self.lid .. string.format( "AI unit %s ejected!", EventData.IniUnitName ) ) -- Remove element/unit from flight group and from all queues if no elements alive. - self:_RemoveUnitFromFlight(EventData.IniUnit) + self:_RemoveUnitFromFlight( EventData.IniUnit ) -- What could happen is, that another element has landed (recovered) already and this one crashes. -- This would mean that the flight would not be deleted from the queue ==> Check if section recovered. - local flight=self:_GetFlightFromGroupInQueue(EventData.IniGroup, self.flights) - self:_CheckSectionRecovered(flight) + local flight = self:_GetFlightFromGroupInQueue( EventData.IniGroup, self.flights ) + self:_CheckSectionRecovered( flight ) end end @@ -9037,51 +8345,50 @@ end --- Airboss event handler for event REMOVEUNIT. -- @param #AIRBOSS self -- @param Core.Event#EVENTDATA EventData -function AIRBOSS:OnEventRemoveUnit(EventData) - self:F3({eventland = EventData}) +function AIRBOSS:OnEventRemoveUnit( EventData ) + self:F3( { eventland = EventData } ) -- Nil checks. - if EventData==nil then - self:E(self.lid.."ERROR: EventData=nil in event REMOVEUNIT!") - self:E(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) + 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 ) - 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)) + 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)) + self:T( self.lid .. string.format( "Player %s removed!", _playername ) ) -- Get player flight. - local flight=self.players[_playername] + local flight = self.players[_playername] -- Remove flight completely from all queues and collapse marshal if necessary. if flight then - self:_RemoveFlight(flight, true) + self:_RemoveFlight( flight, true ) end else -- Debug message. - self:T(self.lid..string.format("AI unit %s removed!", EventData.IniUnitName)) + self:T( self.lid .. string.format( "AI unit %s removed!", EventData.IniUnitName ) ) -- Remove element/unit from flight group and from all queues if no elements alive. - self:_RemoveUnitFromFlight(EventData.IniUnit) + self:_RemoveUnitFromFlight( EventData.IniUnit ) -- What could happen is, that another element has landed (recovered) already and this one crashes. -- This would mean that the flight would not be deleted from the queue ==> Check if section recovered. - local flight=self:_GetFlightFromGroupInQueue(EventData.IniGroup, self.flights) - self:_CheckSectionRecovered(flight) + local flight = self:_GetFlightFromGroupInQueue( EventData.IniGroup, self.flights ) + self:_CheckSectionRecovered( flight ) end end @@ -9089,41 +8396,40 @@ end --- Airboss event handler for event player leave unit. -- @param #AIRBOSS self -- @param Core.Event#EVENTDATA EventData ---function AIRBOSS:OnEventPlayerLeaveUnit(EventData) -function AIRBOSS:_PlayerLeft(EventData) - self:F3({eventleave=EventData}) +-- function AIRBOSS:OnEventPlayerLeaveUnit(EventData) +function AIRBOSS:_PlayerLeft( EventData ) + self:F3( { eventleave = EventData } ) -- Nil checks. - if EventData==nil then - self:E(self.lid.."ERROR: EventData=nil in event PLAYERLEFTUNIT!") - self:E(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) + 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 ) - 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)) + 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 -- Debug info. - self:T(self.lid..string.format("Player %s left unit %s!",_playername, _unitName)) + self:T( self.lid .. string.format( "Player %s left unit %s!", _playername, _unitName ) ) -- Get player flight. - local flight=self.players[_playername] + local flight = self.players[_playername] -- Remove flight completely from all queues and collapse marshal if necessary. if flight then - self:_RemoveFlight(flight, true) + self:_RemoveFlight( flight, true ) end end @@ -9134,8 +8440,8 @@ end -- Handles the case when the mission is ended. -- @param #AIRBOSS self -- @param Core.Event#EVENTDATA EventData Event data. -function AIRBOSS:OnEventMissionEnd(EventData) - self:T3(self.lid.."Mission Ended") +function AIRBOSS:OnEventMissionEnd( EventData ) + self:T3( self.lid .. "Mission Ended" ) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -9145,31 +8451,31 @@ end --- Spinning -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data. -function AIRBOSS:_Spinning(playerData) +function AIRBOSS:_Spinning( playerData ) -- Early break. - local SpinIt={} - SpinIt.name="Spinning" - SpinIt.Xmin=-UTILS.NMToMeters(6) -- Not more than 5 NM behind the boat. - SpinIt.Xmax= UTILS.NMToMeters(5) -- Not more than 5 NM in front of the boat. - SpinIt.Zmin=-UTILS.NMToMeters(6) -- Not more than 5 NM port. - SpinIt.Zmax= UTILS.NMToMeters(2) -- Not more than 3 NM starboard. - SpinIt.LimitXmin=-100 -- 100 meters behind the boat - SpinIt.LimitXmax=nil - SpinIt.LimitZmin=-UTILS.NMToMeters(1) -- 1 NM port - SpinIt.LimitZmax=nil + local SpinIt = {} + SpinIt.name = "Spinning" + SpinIt.Xmin = -UTILS.NMToMeters( 6 ) -- Not more than 5 NM behind the boat. + SpinIt.Xmax = UTILS.NMToMeters( 5 ) -- Not more than 5 NM in front of the boat. + SpinIt.Zmin = -UTILS.NMToMeters( 6 ) -- Not more than 5 NM port. + SpinIt.Zmax = UTILS.NMToMeters( 2 ) -- Not more than 3 NM starboard. + SpinIt.LimitXmin = -100 -- 100 meters behind the boat + SpinIt.LimitXmax = nil + SpinIt.LimitZmin = -UTILS.NMToMeters( 1 ) -- 1 NM port + SpinIt.LimitZmax = nil -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) - local X, Z, rho, phi=self:_GetDistances(playerData.unit) + local X, Z, rho, phi = self:_GetDistances( playerData.unit ) -- Check if we are in front of the boat (diffX > 0). - if self:_CheckLimits(X, Z, SpinIt) then + if self:_CheckLimits( X, Z, SpinIt ) then -- Player is "de-spinned". Should go to initial again. - self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.INITIAL) + self:_SetPlayerStep( playerData, AIRBOSS.PatternStep.INITIAL ) -- Remove player from spinning queue. - self:_RemoveFlightFromQueue(self.Qspinning, playerData) + self:_RemoveFlightFromQueue( self.Qspinning, playerData ) end @@ -9178,28 +8484,28 @@ end --- Waiting outside 10 NM zone for free Marshal stack. -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data. -function AIRBOSS:_Waiting(playerData) +function AIRBOSS:_Waiting( playerData ) -- Create 10 NM zone around the carrier. - local radius=UTILS.NMToMeters(10) - local zone=ZONE_RADIUS:New("Carrier 10 NM Zone", self.carrier:GetVec2(), radius) + local radius = UTILS.NMToMeters( 10 ) + local zone = ZONE_RADIUS:New( "Carrier 10 NM Zone", self.carrier:GetVec2(), radius ) -- Check if player is inside 10 NM radius of the carrier. - local inzone=playerData.unit:IsInZone(zone) + local inzone = playerData.unit:IsInZone( zone ) -- Time player is waiting. - local Twaiting=timer.getAbsTime()-playerData.time + local Twaiting = timer.getAbsTime() - playerData.time -- Warning if player is inside the zone. - 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 + 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 -- Reset warning. - if inzone==false and playerData.warning==true then - playerData.warning=nil + if inzone == false and playerData.warning == true then + playerData.warning = nil end end @@ -9207,18 +8513,18 @@ end --- Holding. -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data. -function AIRBOSS:_Holding(playerData) +function AIRBOSS:_Holding( playerData ) -- Player unit and flight. - local unit=playerData.unit + local unit = playerData.unit -- Current stack. - local stack=playerData.flag + local stack = playerData.flag -- Check for reported error. - 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) + 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 --------------------------- @@ -9226,99 +8532,99 @@ function AIRBOSS:_Holding(playerData) --------------------------- -- Pattern altitude. - local patternalt=self:_GetMarshalAltitude(stack, playerData.case) + local patternalt = self:_GetMarshalAltitude( stack, playerData.case ) -- Player altitude. - local playeralt=unit:GetAltitude() + local playeralt = unit:GetAltitude() -- Get holding zone of player. - local zoneHolding=self:_GetZoneHolding(playerData.case, stack) + local zoneHolding = self:_GetZoneHolding( playerData.case, stack ) -- Nil check. - if zoneHolding==nil then - self:E(self.lid.."ERROR: zoneHolding is nil!") - self:E({playerData=playerData}) + if zoneHolding == nil then + self:E( self.lid .. "ERROR: zoneHolding is nil!" ) + self:E( { playerData = playerData } ) return end -- Check if player is in holding zone. - local inholdingzone=unit:IsInZone(zoneHolding) + local inholdingzone = unit:IsInZone( zoneHolding ) -- Altitude difference between player and assigned stack. - local altdiff=playeralt-patternalt + local altdiff = playeralt - patternalt -- Acceptable altitude depending on player skill. - local altgood=UTILS.FeetToMeters(500) - if playerData.difficulty==AIRBOSS.Difficulty.HARD then + local altgood = UTILS.FeetToMeters( 500 ) + if playerData.difficulty == AIRBOSS.Difficulty.HARD then -- Pros can be expected to be within +-200 ft. - altgood=UTILS.FeetToMeters(200) - elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then + altgood = UTILS.FeetToMeters( 200 ) + elseif playerData.difficulty == AIRBOSS.Difficulty.NORMAL then -- Normal guys should be within +-350 ft. - altgood=UTILS.FeetToMeters(350) - elseif playerData.difficulty==AIRBOSS.Difficulty.EASY then + altgood = UTILS.FeetToMeters( 350 ) + elseif playerData.difficulty == AIRBOSS.Difficulty.EASY then -- Students should be within +-500 ft. - altgood=UTILS.FeetToMeters(500) + altgood = UTILS.FeetToMeters( 500 ) end -- When back to good altitude = 50%. - local altback=altgood*0.5 + local altback = altgood * 0.5 -- Check if stack just collapsed and give the player one minute to change the altitude. - local justcollapsed=false + local justcollapsed = false if self.Tcollapse then -- Time since last stack change. - local dT=timer.getTime()-self.Tcollapse + local dT = timer.getTime() - self.Tcollapse -- TODO: check if this works. - --local dT=timer.getAbsTime()-playerData.time + -- local dT=timer.getAbsTime()-playerData.time -- Check if less then 90 seconds. - if dT<=90 then - justcollapsed=true + if dT <= 90 then + justcollapsed = true end end -- Check if altitude is acceptable. - local goodalt=math.abs(altdiff)altgood then + if altdiff > altgood then -- Issue warning for being too high. if not playerData.warning then - text=text..string.format("You left your assigned altitude. Descent to angels %d.", angels) - playerData.warning=true + text = text .. string.format( "You left your assigned altitude. Descent to angels %d.", angels ) + playerData.warning = true end - elseif altdiff<-altgood then + elseif altdiff < -altgood then -- Issue warning for being too low. if not playerData.warning then - text=text..string.format("You left your assigned altitude. Climb to angels %d.", angels) - playerData.warning=true + text = text .. string.format( "You left your assigned altitude. Climb to angels %d.", angels ) + playerData.warning = true end end @@ -9326,62 +8632,61 @@ function AIRBOSS:_Holding(playerData) end -- Back to assigned altitude. - if playerData.warning and math.abs(altdiff)<=altback then - text=text..string.format("Altitude is looking good again.") - playerData.warning=nil + 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 + elseif playerData.holding == false then -- Player left holding zone if inholdingzone then -- Player is back in the holding zone. - text=text..string.format("You are back in the holding zone. Now stay there!") - playerData.holding=true + text = text .. string.format( "You are back in the holding zone. Now stay there!" ) + playerData.holding = true else -- Player is still outside the holding zone. - self:T3("Player still outside the holding zone. What are you doing man?!") + self:T3( "Player still outside the holding zone. What are you doing man?!" ) end - elseif playerData.holding==nil then + elseif playerData.holding == nil then -- Player did not entered the holding zone yet. if inholdingzone then -- Player arrived in holding zone. - playerData.holding=true + playerData.holding = true -- Inform player. - text=text..string.format("You arrived at the holding zone.") + text = text .. string.format( "You arrived at the holding zone." ) -- Feedback on altitude. if goodalt then - text=text..string.format(" Altitude is good.") + text = text .. string.format( " Altitude is good." ) else - if altdiff<0 then - text=text..string.format(" But you're too low.") + if altdiff < 0 then + text = text .. string.format( " But you're too low." ) else - text=text..string.format(" But you're too high.") + 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 + text = text .. string.format( "\nCurrently assigned altitude is %d ft.", UTILS.MetersToFeet( patternalt ) ) + playerData.warning = true end else -- Player did not yet arrive in holding zone. - self:T3("Waiting for player to arrive in the holding zone.") + self:T3( "Waiting for player to arrive in the holding zone." ) end end -- Send message. if playerData.showhints then - self:MessageToPlayer(playerData, text, "MARSHAL") + self:MessageToPlayer( playerData, text, "MARSHAL" ) end end - --- Commence approach. This step initializes the player data. Section members are also set to commence. Next step depends on recovery case: -- -- * Case 1: Initial @@ -9390,24 +8695,24 @@ end -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data. -- @param #boolean zonecheck If true, zone is checked before player is released. -function AIRBOSS:_Commencing(playerData, zonecheck) +function AIRBOSS:_Commencing( playerData, zonecheck ) -- Check for auto commence if zonecheck then -- Get auto commence zone. - local zoneCommence=self:_GetZoneCommence(playerData.case, playerData.flag) + local zoneCommence = self:_GetZoneCommence( playerData.case, playerData.flag ) -- Check if unit is in the zone. - local inzone=playerData.unit:IsInZone(zoneCommence) + local inzone = playerData.unit:IsInZone( zoneCommence ) -- Skip the rest if not in the zone yet. if not inzone then -- Friendly reminder. - if timer.getAbsTime()-playerData.time>180 then - self:_MarshalCallClearedForRecovery(playerData.onboard, playerData.case) - playerData.time=timer.getAbsTime() + if timer.getAbsTime() - playerData.time > 180 then + self:_MarshalCallClearedForRecovery( playerData.onboard, playerData.case ) + playerData.time = timer.getAbsTime() end -- Skip the rest. @@ -9417,48 +8722,48 @@ function AIRBOSS:_Commencing(playerData, zonecheck) end -- Remove flight from Marshal queue. If flight was in queue, stack is collapsed and flight added to the pattern queue. - self:_RemoveFlightFromMarshalQueue(playerData) + self:_RemoveFlightFromMarshalQueue( playerData ) -- Initialize player data for new approach. - self:_InitPlayer(playerData) + self:_InitPlayer( playerData ) -- Commencing message to player only. - if playerData.difficulty~=AIRBOSS.Difficulty.HARD then + if playerData.difficulty ~= AIRBOSS.Difficulty.HARD then -- Text - local text="" + local text = "" -- Positive response. - if playerData.case==1 then - text=text.."Proceed to initial." + 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." + 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 -- Message to player. - self:MessageToPlayer(playerData, text, "MARSHAL") + self:MessageToPlayer( playerData, text, "MARSHAL" ) end -- Next step: depends on case recovery. local nextstep - if playerData.case==1 then + if playerData.case == 1 then -- CASE I: Player has to fly to the initial which is 3 NM DME astern of the boat. - nextstep=AIRBOSS.PatternStep.INITIAL + nextstep = AIRBOSS.PatternStep.INITIAL else -- CASE II/III: Player has to start the descent at 4000 ft/min to the platform at 5k ft. - nextstep=AIRBOSS.PatternStep.PLATFORM + nextstep = AIRBOSS.PatternStep.PLATFORM end -- Next step hint. - self:_SetPlayerStep(playerData, nextstep) + self:_SetPlayerStep( playerData, nextstep ) -- Commence section members as well but dont check the zone. - for i,_flight in pairs(playerData.section) do - local flight=_flight --#AIRBOSS.PlayerData - self:_Commencing(flight, false) + for i, _flight in pairs( playerData.section ) do + local flight = _flight -- #AIRBOSS.PlayerData + self:_Commencing( flight, false ) end end @@ -9467,40 +8772,40 @@ end -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data table. -- @return #boolean True if player is in the initial zone. -function AIRBOSS:_Initial(playerData) +function AIRBOSS:_Initial( playerData ) -- Check if player is in initial zone and entering the CASE I pattern. - local inzone=playerData.unit:IsInZone(self:_GetZoneInitial(playerData.case)) + local inzone = playerData.unit:IsInZone( self:_GetZoneInitial( playerData.case ) ) -- Relative heading to carrier direction. - local relheading=self:_GetRelativeHeading(playerData.unit, false) + local relheading = self:_GetRelativeHeading( playerData.unit, false ) -- Alitude of player in feet. - local altitude=playerData.unit:GetAltitude() + local altitude = playerData.unit:GetAltitude() -- Check if player is in zone and flying roughly in the right direction. - if inzone and math.abs(relheading)<60 and altitude<=self.initialmaxalt then + if inzone and math.abs( relheading ) < 60 and altitude <= self.initialmaxalt then -- Send message for normal and easy difficulty. if playerData.showhints then -- Inform player. - local hint=string.format("Initial") + local hint = string.format( "Initial" ) -- Hook down for students. - 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°!" + 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!" + hint = hint .. " - Hook down!" end end - self:MessageToPlayer(playerData, hint, "MARSHAL") + self:MessageToPlayer( playerData, hint, "MARSHAL" ) end -- Next step: Break entry. - self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.BREAKENTRY) + self:_SetPlayerStep( playerData, AIRBOSS.PatternStep.BREAKENTRY ) return true end @@ -9511,24 +8816,24 @@ end --- Check if player is in CASE II/III approach corridor. -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data table. -function AIRBOSS:_CheckCorridor(playerData) +function AIRBOSS:_CheckCorridor( playerData ) -- Check if player is in valid zone - local validzone=self:_GetZoneCorridor(playerData.case) + local validzone = self:_GetZoneCorridor( playerData.case ) -- Check if we are inside the moving zone. - local invalid=playerData.unit:IsNotInZone(validzone) + local invalid = playerData.unit:IsNotInZone( validzone ) -- Issue warning. if invalid and (not playerData.warning) then - self:MessageToPlayer(playerData, "you left the approach corridor!", "AIRBOSS") - playerData.warning=true + self:MessageToPlayer( playerData, "you left the approach corridor!", "AIRBOSS" ) + playerData.warning = true end -- Back in zone. if (not invalid) and playerData.warning then - self:MessageToPlayer(playerData, "you're back in the approach corridor.", "AIRBOSS") - playerData.warning=false + self:MessageToPlayer( playerData, "you're back in the approach corridor.", "AIRBOSS" ) + playerData.warning = false end end @@ -9536,60 +8841,59 @@ end --- Platform at 5k ft for case II/III recoveries. Descent at 2000 ft/min. -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data table. -function AIRBOSS:_Platform(playerData) +function AIRBOSS:_Platform( playerData ) -- Check if player left or got back to the approach corridor. - self:_CheckCorridor(playerData) + self:_CheckCorridor( playerData ) -- Check if we are inside the moving zone. - local inzone=playerData.unit:IsInZone(self:_GetZonePlatform(playerData.case)) + local inzone = playerData.unit:IsInZone( self:_GetZonePlatform( playerData.case ) ) -- Check if we are in zone. if inzone then -- Hint for player about altitude, AoA etc. - self:_PlayerHint(playerData) + self:_PlayerHint( playerData ) -- Next step: depends. local nextstep - if math.abs(self.holdingoffset)>0 and playerData.case>1 then + if math.abs( self.holdingoffset ) > 0 and playerData.case > 1 then -- Turn to BRC (case II) or FB (case III). - nextstep=AIRBOSS.PatternStep.ARCIN + nextstep = AIRBOSS.PatternStep.ARCIN else - if playerData.case==2 then + if playerData.case == 2 then -- Case II: Initial zone then Case I recovery. - nextstep=AIRBOSS.PatternStep.INITIAL - elseif playerData.case==3 then + nextstep = AIRBOSS.PatternStep.INITIAL + elseif playerData.case == 3 then -- CASE III: Dirty up. - nextstep=AIRBOSS.PatternStep.DIRTYUP + nextstep = AIRBOSS.PatternStep.DIRTYUP end end -- Next step hint. - self:_SetPlayerStep(playerData, nextstep) + self:_SetPlayerStep( playerData, nextstep ) end end - --- Arc in turn for case II/III recoveries. -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data table. -function AIRBOSS:_ArcInTurn(playerData) +function AIRBOSS:_ArcInTurn( playerData ) -- Check if player left or got back to the approach corridor. - self:_CheckCorridor(playerData) + self:_CheckCorridor( playerData ) -- Check if we are inside the moving zone. - local inzone=playerData.unit:IsInZone(self:_GetZoneArcIn(playerData.case)) + local inzone = playerData.unit:IsInZone( self:_GetZoneArcIn( playerData.case ) ) if inzone then -- Hint for player about altitude, AoA etc. - self:_PlayerHint(playerData) + self:_PlayerHint( playerData ) -- Next step: Arc Out Turn. - self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.ARCOUT) + self:_SetPlayerStep( playerData, AIRBOSS.PatternStep.ARCOUT ) end end @@ -9597,62 +8901,62 @@ end --- Arc out turn for case II/III recoveries. -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data table. -function AIRBOSS:_ArcOutTurn(playerData) +function AIRBOSS:_ArcOutTurn( playerData ) -- Check if player left or got back to the approach corridor. - self:_CheckCorridor(playerData) + self:_CheckCorridor( playerData ) -- Check if we are inside the moving zone. - local inzone=playerData.unit:IsInZone(self:_GetZoneArcOut(playerData.case)) + local inzone = playerData.unit:IsInZone( self:_GetZoneArcOut( playerData.case ) ) if inzone then -- Hint for player about altitude, AoA etc. - self:_PlayerHint(playerData) + self:_PlayerHint( playerData ) -- Next step: local nextstep - if playerData.case==3 then + if playerData.case == 3 then -- Case III: Dirty up. - nextstep=AIRBOSS.PatternStep.DIRTYUP + nextstep = AIRBOSS.PatternStep.DIRTYUP else -- Case II: Initial. - nextstep=AIRBOSS.PatternStep.INITIAL + nextstep = AIRBOSS.PatternStep.INITIAL end -- Next step hint. - self:_SetPlayerStep(playerData, nextstep) + self:_SetPlayerStep( playerData, nextstep ) end end --- Dirty up and level out at 1200 ft for case III recovery. -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data table. -function AIRBOSS:_DirtyUp(playerData) +function AIRBOSS:_DirtyUp( playerData ) -- Check if player left or got back to the approach corridor. - self:_CheckCorridor(playerData) + self:_CheckCorridor( playerData ) -- Check if we are inside the moving zone. - local inzone=playerData.unit:IsInZone(self:_GetZoneDirtyUp(playerData.case)) + local inzone = playerData.unit:IsInZone( self:_GetZoneDirtyUp( playerData.case ) ) if inzone then -- Hint for player about altitude, AoA etc. - self:_PlayerHint(playerData) + self:_PlayerHint( playerData ) -- Radio call "Say/Fly needles". Delayed by 10/15 seconds. - 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) + 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 -- TODO: Make Fly Bullseye call if no automatic ICLS is active. -- Next step: CASE III: Intercept glide slope and follow bullseye (ICLS). - self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.BULLSEYE) + self:_SetPlayerStep( playerData, AIRBOSS.PatternStep.BULLSEYE ) end end @@ -9661,34 +8965,34 @@ end -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data table. -- @return #boolean If true, player is in bullseye zone. -function AIRBOSS:_Bullseye(playerData) +function AIRBOSS:_Bullseye( playerData ) -- Check if player left or got back to the approach corridor. - self:_CheckCorridor(playerData) + self:_CheckCorridor( playerData ) -- Check if we are inside the moving zone. - local inzone=playerData.unit:IsInZone(self:_GetZoneBullseye(playerData.case)) + local inzone = playerData.unit:IsInZone( self:_GetZoneBullseye( playerData.case ) ) -- Relative heading to carrier direction of the runway. - local relheading=self:_GetRelativeHeading(playerData.unit, true) + local relheading = self:_GetRelativeHeading( playerData.unit, true ) -- Check if player is in zone and flying roughly in the right direction. - if inzone and math.abs(relheading)<60 then + if inzone and math.abs( relheading ) < 60 then -- Hint for player about altitude, AoA etc. - self:_PlayerHint(playerData) + self:_PlayerHint( playerData ) -- LSO expect spot 5 or 7.5 call - if playerData.actype==AIRBOSS.AircraftCarrier.AV8B and self.carriertype==AIRBOSS.CarrierType.JCARLOS then - self:RadioTransmission(self.LSORadio, self.LSOCall.EXPECTSPOT5, nil, nil, nil, true) - elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B and self.carriertype==AIRBOSS.CarrierType.CANBERRA then - self:RadioTransmission(self.LSORadio, self.LSOCall.EXPECTSPOT5, nil, nil, nil, true) - elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B then - self:RadioTransmission(self.LSORadio, self.LSOCall.EXPECTSPOT75, nil, nil, nil, true) + if playerData.actype == AIRBOSS.AircraftCarrier.AV8B and self.carriertype == AIRBOSS.CarrierType.JCARLOS then + self:RadioTransmission( self.LSORadio, self.LSOCall.EXPECTSPOT5, nil, nil, nil, true ) + elseif playerData.actype == AIRBOSS.AircraftCarrier.AV8B and self.carriertype == AIRBOSS.CarrierType.CANBERRA then + self:RadioTransmission( self.LSORadio, self.LSOCall.EXPECTSPOT5, nil, nil, nil, true ) + elseif playerData.actype == AIRBOSS.AircraftCarrier.AV8B then + self:RadioTransmission( self.LSORadio, self.LSOCall.EXPECTSPOT75, nil, nil, nil, true ) end -- Next step: Groove Call the ball. - self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.GROOVE_XX) + self:_SetPlayerStep( playerData, AIRBOSS.PatternStep.GROOVE_XX ) end end @@ -9696,150 +9000,147 @@ end --- Bolter pattern. Sends player to abeam for Case I/II or Bullseye for Case III ops. -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data table. -function AIRBOSS:_BolterPattern(playerData) +function AIRBOSS:_BolterPattern( playerData ) -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) - local X, Z, rho, phi=self:_GetDistances(playerData.unit) + local X, Z, rho, phi = self:_GetDistances( playerData.unit ) -- Bolter Pattern thresholds. - local Bolter={} - Bolter.name="Bolter Pattern" - Bolter.Xmin=-UTILS.NMToMeters(5) -- Not more then 5 NM astern of boat. - Bolter.Xmax= UTILS.NMToMeters(3) -- Not more then 3 NM ahead of boat. - Bolter.Zmin=-UTILS.NMToMeters(5) -- Not more than 2 NM port. - Bolter.Zmax= UTILS.NMToMeters(1) -- Not more than 1 NM starboard. - Bolter.LimitXmin= 100 -- Check that 100 meter ahead and port - Bolter.LimitXmax= nil - Bolter.LimitZmin= nil - Bolter.LimitZmax= nil + local Bolter = {} + Bolter.name = "Bolter Pattern" + Bolter.Xmin = -UTILS.NMToMeters( 5 ) -- Not more then 5 NM astern of boat. + Bolter.Xmax = UTILS.NMToMeters( 3 ) -- Not more then 3 NM ahead of boat. + Bolter.Zmin = -UTILS.NMToMeters( 5 ) -- Not more than 2 NM port. + Bolter.Zmax = UTILS.NMToMeters( 1 ) -- Not more than 1 NM starboard. + Bolter.LimitXmin = 100 -- Check that 100 meter ahead and port + Bolter.LimitXmax = nil + Bolter.LimitZmin = nil + Bolter.LimitZmax = nil -- Check if we are in front of the boat (diffX > 0). - if self:_CheckLimits(X, Z, Bolter) then + if self:_CheckLimits( X, Z, Bolter ) then local nextstep - if playerData.case<3 then - nextstep=AIRBOSS.PatternStep.ABEAM + if playerData.case < 3 then + nextstep = AIRBOSS.PatternStep.ABEAM else - nextstep=AIRBOSS.PatternStep.BULLSEYE + nextstep = AIRBOSS.PatternStep.BULLSEYE end - self:_SetPlayerStep(playerData, nextstep) + self:_SetPlayerStep( playerData, nextstep ) end end - - --- Break entry for case I/II recoveries. -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data table. -function AIRBOSS:_BreakEntry(playerData) +function AIRBOSS:_BreakEntry( playerData ) -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) - local X, Z=self:_GetDistances(playerData.unit) + local X, Z = self:_GetDistances( playerData.unit ) -- Abort condition check. - if self:_CheckAbort(X, Z, self.BreakEntry) then - self:_AbortPattern(playerData, X, Z, self.BreakEntry, true) + if self:_CheckAbort( X, Z, self.BreakEntry ) then + self:_AbortPattern( playerData, X, Z, self.BreakEntry, true ) return end -- Check if we are in front of the boat (diffX > 0). - if self:_CheckLimits(X, Z, self.BreakEntry) then + if self:_CheckLimits( X, Z, self.BreakEntry ) then -- Hint for player about altitude, AoA etc. - self:_PlayerHint(playerData) + self:_PlayerHint( playerData ) -- Next step: Early Break. - self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.EARLYBREAK) + self:_SetPlayerStep( playerData, AIRBOSS.PatternStep.EARLYBREAK ) end end - --- Break. -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data table. -- @param #string part Part of the break. -function AIRBOSS:_Break(playerData, part) +function AIRBOSS:_Break( playerData, part ) -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) - local X, Z=self:_GetDistances(playerData.unit) + local X, Z = self:_GetDistances( playerData.unit ) -- Early or late break. local breakpoint = self.BreakEarly - if part==AIRBOSS.PatternStep.LATEBREAK then + if part == AIRBOSS.PatternStep.LATEBREAK then breakpoint = self.BreakLate end -- Check abort conditions. - if self:_CheckAbort(X, Z, breakpoint) then - self:_AbortPattern(playerData, X, Z, breakpoint, true) + if self:_CheckAbort( X, Z, breakpoint ) then + self:_AbortPattern( playerData, X, Z, breakpoint, true ) return end -- Player made a very tight turn and did not trigger the latebreak threshold at 0.8 NM. - local tooclose=false - if part==AIRBOSS.PatternStep.LATEBREAK then - local close=0.8 - if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then - close=0.5 + 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 + elseif relheading > 90 and self:_CheckLimits( X, Z, self.Wake ) then -- Message to player. - 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: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 -- Debrief. - self:_AddToDebrief(playerData, "Overshoot at wake - Pattern Waveoff!") - self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.DEBRIEF) + self:_AddToDebrief( playerData, "Overshoot at wake - Pattern Waveoff!" ) + self:_SetPlayerStep( playerData, AIRBOSS.PatternStep.DEBRIEF ) end end --- At the Wake. -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data table. -function AIRBOSS:_Wake(playerData) +function AIRBOSS:_Wake( playerData ) -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) - local X, Z=self:_GetDistances(playerData.unit) + local X, Z = self:_GetDistances( playerData.unit ) -- Check abort conditions. - if self:_CheckAbort(X, Z, self.Wake) then - self:_AbortPattern(playerData, X, Z, self.Wake, true) + if self:_CheckAbort( X, Z, self.Wake ) then + self:_AbortPattern( playerData, X, Z, self.Wake, true ) return end -- Right behind the wake of the carrier dZ>0. - if self:_CheckLimits(X, Z, self.Wake) then + if self:_CheckLimits( X, Z, self.Wake ) then -- Hint for player about altitude, AoA etc. - self:_PlayerHint(playerData) + self:_PlayerHint( playerData ) -- Next step: Final. - self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.FINAL) + self:_SetPlayerStep( playerData, AIRBOSS.PatternStep.FINAL ) end end @@ -9955,53 +9256,53 @@ end -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data table. -- @return #AIRBOSS.GrooveData Groove data table. -function AIRBOSS:_GetGrooveData(playerData) +function AIRBOSS:_GetGrooveData( playerData ) -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier). - local X, Z=self:_GetDistances(playerData.unit) + local X, Z = self:_GetDistances( playerData.unit ) -- Stern position at the rundown. - local stern=self:_GetSternCoord() + local stern = self:_GetSternCoord() -- Distance from rundown to player aircraft. - local rho=stern:Get2DDistance(playerData.unit:GetCoordinate()) + local rho = stern:Get2DDistance( playerData.unit:GetCoordinate() ) -- Aircraft is behind the carrier. - local astern=X5. This would mean the player has not turned in correctly! -- Groove data. - playerData.groove.X0=UTILS.DeepCopy(groovedata) + playerData.groove.X0 = UTILS.DeepCopy( groovedata ) -- Set time stamp. Next call in 4 seconds. - playerData.Tlso=timer.getTime() + playerData.Tlso = timer.getTime() -- Next step: X start. - self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.GROOVE_XX) + self:_SetPlayerStep( playerData, AIRBOSS.PatternStep.GROOVE_XX ) end -- Groovedata step. - groovedata.Step=playerData.step + groovedata.Step = playerData.step end --- In the groove. -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data table. -function AIRBOSS:_Groove(playerData) +function AIRBOSS:_Groove( playerData ) -- Ranges in the groove. - local RX0=UTILS.NMToMeters(1.000) -- Everything before X 1.00 = 1852 m - local RXX=UTILS.NMToMeters(0.750) -- Start of groove. 0.75 = 1389 m - local RIM=UTILS.NMToMeters(0.500) -- In the Middle 0.50 = 926 m (middle one third of the glideslope) - local RIC=UTILS.NMToMeters(0.250) -- In Close 0.25 = 463 m (last one third of the glideslope) - local RAR=UTILS.NMToMeters(0.040) -- At the Ramp. 0.04 = 75 m + local RX0 = UTILS.NMToMeters( 1.000 ) -- Everything before X 1.00 = 1852 m + local RXX = UTILS.NMToMeters( 0.750 ) -- Start of groove. 0.75 = 1389 m + local RIM = UTILS.NMToMeters( 0.500 ) -- In the Middle 0.50 = 926 m (middle one third of the glideslope) + local RIC = UTILS.NMToMeters( 0.250 ) -- In Close 0.25 = 463 m (last one third of the glideslope) + local RAR = UTILS.NMToMeters( 0.040 ) -- At the Ramp. 0.04 = 75 m -- Groove data. - local groovedata=self:_GetGrooveData(playerData) + local groovedata = self:_GetGrooveData( playerData ) -- Add data to trapsheet. - table.insert(playerData.trapsheet, groovedata) + table.insert( playerData.trapsheet, groovedata ) -- Coords. - local X=groovedata.X - local Z=groovedata.Z + local X = groovedata.X + local Z = groovedata.Z -- Check abort conditions. - if self:_CheckAbort(groovedata.X, groovedata.Z, self.Groove) then - self:_AbortPattern(playerData, groovedata.X, groovedata.Z, self.Groove, true) + if self:_CheckAbort( groovedata.X, groovedata.Z, self.Groove ) then + self:_AbortPattern( playerData, groovedata.X, groovedata.Z, self.Groove, true ) return end -- Shortcuts. - local rho=groovedata.Rho - local lineupError=groovedata.LUE - local glideslopeError=groovedata.GSE - local AoA=groovedata.AoA + local rho = groovedata.Rho + local lineupError = groovedata.LUE + local glideslopeError = groovedata.GSE + local AoA = groovedata.AoA - - if rho<=RXX and playerData.step==AIRBOSS.PatternStep.GROOVE_XX and (math.abs(groovedata.Roll)<=4.0 or playerData.unit:IsInZone(self:_GetZoneLineup())) then + if rho <= RXX and playerData.step == AIRBOSS.PatternStep.GROOVE_XX and (math.abs( groovedata.Roll ) <= 4.0 or playerData.unit:IsInZone( self:_GetZoneLineup() )) then -- Start time in groove - playerData.TIG0=timer.getTime() + playerData.TIG0 = timer.getTime() -- LSO "Call the ball" call. - self:RadioTransmission(self.LSORadio, self.LSOCall.CALLTHEBALL, nil, nil, nil, true) - playerData.Tlso=timer.getTime() + self:RadioTransmission( self.LSORadio, self.LSOCall.CALLTHEBALL, nil, nil, nil, true ) + playerData.Tlso = timer.getTime() -- Pilot "405, Hornet Ball, 3.2". -- LSO "Roger ball" call in three seconds. - self:RadioTransmission(self.LSORadio, self.LSOCall.ROGERBALL, false, nil, 2, true) + self:RadioTransmission( self.LSORadio, self.LSOCall.ROGERBALL, false, nil, 2, true ) -- Store data. - playerData.groove.XX=UTILS.DeepCopy(groovedata) + playerData.groove.XX = UTILS.DeepCopy( groovedata ) -- This is a valid approach and player did not miss any important steps in the pattern. - playerData.valid=true + playerData.valid = true -- Next step: in the middle. - self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.GROOVE_IM) + self:_SetPlayerStep( playerData, AIRBOSS.PatternStep.GROOVE_IM ) - elseif rho<=RIM and playerData.step==AIRBOSS.PatternStep.GROOVE_IM then + elseif rho <= RIM and playerData.step == AIRBOSS.PatternStep.GROOVE_IM then -- Store data. - playerData.groove.IM=UTILS.DeepCopy(groovedata) + playerData.groove.IM = UTILS.DeepCopy( groovedata ) -- Next step: in close. - self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.GROOVE_IC) + self:_SetPlayerStep( playerData, AIRBOSS.PatternStep.GROOVE_IC ) - elseif rho<=RIC and playerData.step==AIRBOSS.PatternStep.GROOVE_IC then + elseif rho <= RIC and playerData.step == AIRBOSS.PatternStep.GROOVE_IC then -- Store data. - playerData.groove.IC=UTILS.DeepCopy(groovedata) + playerData.groove.IC = UTILS.DeepCopy( groovedata ) -- Next step: AR at the ramp. - self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.GROOVE_AR) + self:_SetPlayerStep( playerData, AIRBOSS.PatternStep.GROOVE_AR ) - elseif rho<=RAR and playerData.step==AIRBOSS.PatternStep.GROOVE_AR then + elseif rho <= RAR and playerData.step == AIRBOSS.PatternStep.GROOVE_AR then -- Store data. - playerData.groove.AR=UTILS.DeepCopy(groovedata) + playerData.groove.AR = UTILS.DeepCopy( groovedata ) -- Next step: in the wires. - if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then - self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.GROOVE_AL) + if playerData.actype == AIRBOSS.AircraftCarrier.AV8B then + self:_SetPlayerStep( playerData, AIRBOSS.PatternStep.GROOVE_AL ) else - self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.GROOVE_IW) + self:_SetPlayerStep( playerData, AIRBOSS.PatternStep.GROOVE_IW ) end - elseif rho<=RAR and playerData.step==AIRBOSS.PatternStep.GROOVE_AL then + elseif rho <= RAR and playerData.step == AIRBOSS.PatternStep.GROOVE_AL then -- Store data. - playerData.groove.AL=UTILS.DeepCopy(groovedata) + playerData.groove.AL = UTILS.DeepCopy( groovedata ) -- Get zone abeam LDG spot. - local ZoneALS=self:_GetZoneAbeamLandingSpot() + local ZoneALS = self:_GetZoneAbeamLandingSpot() -- Get player velocity in km/h. - local vplayer=playerData.unit:GetVelocityKMH() + local vplayer = playerData.unit:GetVelocityKMH() -- Get carrier velocity in km/h. - local vcarrier=self.carrier:GetVelocityKMH() + local vcarrier = self.carrier:GetVelocityKMH() -- Speed difference. - local dv=math.abs(vplayer-vcarrier) + local dv = math.abs( vplayer - vcarrier ) -- Stable when speed difference < 20 km/h. - local stable=dv<20 + local stable = dv < 20 -- Check if player is inside the zone. - if playerData.unit:IsInZone(ZoneALS) and stable then + if playerData.unit:IsInZone( ZoneALS ) and stable then -- Radio Transmission "Cleared to land" once the aircraft is inside the zone. - self:RadioTransmission(self.LSORadio, self.LSOCall.CLEAREDTOLAND, nil, nil, nil, true) + self:RadioTransmission( self.LSORadio, self.LSOCall.CLEAREDTOLAND, nil, nil, nil, true ) -- Next step: Level cross. - self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.GROOVE_LC) - -- Set Stable Hover - playerData.stable=true - playerData.hover=true + self:_SetPlayerStep( playerData, AIRBOSS.PatternStep.GROOVE_LC ) + -- Set Stable Hover + playerData.stable = true + playerData.hover = true end - elseif rho<=RAR and playerData.step==AIRBOSS.PatternStep.GROOVE_LC then + elseif rho <= RAR and playerData.step == AIRBOSS.PatternStep.GROOVE_LC then -- Store data. - playerData.groove.LC=UTILS.DeepCopy(groovedata) + playerData.groove.LC = UTILS.DeepCopy( groovedata ) -- Get zone primary LDG spot. - local ZoneLS=self:_GetZoneLandingSpot() + local ZoneLS = self:_GetZoneLandingSpot() -- Get player velocity in km/h. - local vplayer=playerData.unit:GetVelocityKMH() + local vplayer = playerData.unit:GetVelocityKMH() -- Get carrier velocity in km/h. - local vcarrier=self.carrier:GetVelocityKMH() + local vcarrier = self.carrier:GetVelocityKMH() -- Speed difference. - local dv=math.abs(vplayer-vcarrier) + local dv = math.abs( vplayer - vcarrier ) -- Stable when v<10 km/h. - local stable=dv<10 + local stable = dv < 10 -- Radio Transmission "Stabilized" once the aircraft has been cleared to cross and is over the Landing Spot and stable. - if playerData.unit:IsInZone(ZoneLS) and stable and playerData.stable==true then - self:RadioTransmission(self.LSORadio, self.LSOCall.STABILIZED, nil, nil, nil, false) - playerData.stable=false - playerData.warning=true + if playerData.unit:IsInZone( ZoneLS ) and stable and playerData.stable == true then + self:RadioTransmission( self.LSORadio, self.LSOCall.STABILIZED, nil, nil, nil, false ) + playerData.stable = false + playerData.warning = true end -- We keep it in this step until landed. @@ -10216,127 +9516,127 @@ function AIRBOSS:_Groove(playerData) -------------- -- Between IC and AR check for wave off. - if rho>=RAR and rho<=RIC and not playerData.waveoff then + if rho >= RAR and rho <= RIC and not playerData.waveoff then -- Check if player should wave off. - local waveoff=self:_CheckWaveOff(glideslopeError, lineupError, AoA, playerData) + local waveoff = self:_CheckWaveOff( glideslopeError, lineupError, AoA, playerData ) -- Let's see.. if waveoff then -- Debug info. - self:T3(self.lid..string.format("Waveoff distance rho=%.1f m", rho)) + self:T3( self.lid .. string.format( "Waveoff distance rho=%.1f m", rho ) ) -- LSO Wave off! - self:RadioTransmission(self.LSORadio, self.LSOCall.WAVEOFF, nil, nil, nil, true) - playerData.Tlso=timer.getTime() + self:RadioTransmission( self.LSORadio, self.LSOCall.WAVEOFF, nil, nil, nil, true ) + playerData.Tlso = timer.getTime() -- Player was waved off! - playerData.waveoff=true + playerData.waveoff = true -- Nothing else necessary. return end - end - - -- Long V/STOL groove time Wave Off over 75 seconds to IC - TOPGUN level Only. --pene testing (WIP) - - --if rho>=RAR and rho<=RIC and not playerData.waveoff and playerData.difficulty==AIRBOSS.Difficulty.HARD and playerData.actype== AIRBOSS.AircraftCarrier.AV8B then - -- Get groove time - --local vSlow=groovedata.time - -- If too slow wave off. - --if vSlow >75 then - - -- LSO Wave off! - --self:RadioTransmission(self.LSORadio, self.LSOCall.WAVEOFF, nil, nil, nil, true) - --playerData.Tlso=timer.getTime() - - -- Player was waved Off - --playerData.waveoff=true - --return - --end - --end + end + + -- Long V/STOL groove time Wave Off over 75 seconds to IC - TOPGUN level Only. --pene testing (WIP) + + -- if rho>=RAR and rho<=RIC and not playerData.waveoff and playerData.difficulty==AIRBOSS.Difficulty.HARD and playerData.actype== AIRBOSS.AircraftCarrier.AV8B then + -- Get groove time + -- local vSlow=groovedata.time + -- If too slow wave off. + -- if vSlow >75 then + + -- LSO Wave off! + -- self:RadioTransmission(self.LSORadio, self.LSOCall.WAVEOFF, nil, nil, nil, true) + -- playerData.Tlso=timer.getTime() + + -- Player was waved Off + -- playerData.waveoff=true + -- return + -- end + -- end -- Groovedata step. - groovedata.Step=playerData.step + groovedata.Step = playerData.step ----------------- -- Groove Data -- ----------------- -- Check if we are beween 3/4 NM and end of ship. - if rho>=RAR and rho= RAR and rho < RX0 and playerData.waveoff == false then -- Get groove step short hand of the previous step. - local gs=self:_GS(playerData.step, -1) + local gs = self:_GS( playerData.step, -1 ) -- Get current groove data. - local gd=playerData.groove[gs] --#AIRBOSS.GrooveData + local gd = playerData.groove[gs] -- #AIRBOSS.GrooveData if gd then - self:T3(gd) + self:T3( gd ) -- Distance in NM. - local d=UTILS.MetersToNM(rho) + local d = UTILS.MetersToNM( rho ) - -- Drift on lineup. - if rho>=RAR and rho<=RIM then - if gd.LUE>0.22 and lineupError<-0.22 then - env.info" Drift Right across centre ==> DR-" - gd.Drift=" DR" - self:T(self.lid..string.format("Got Drift Right across centre step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError)) - elseif gd.LUE<-0.22 and lineupError>0.22 then - env.info" Drift Left ==> DL-" - gd.Drift=" DL" - self:T(self.lid..string.format("Got Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError)) - elseif gd.LUE>0.13 and lineupError<-0.14 then - env.info" Little Drift Right across centre ==> (DR-)" - gd.Drift=" (DR)" - self:T(self.lid..string.format("Got Little Drift Right across centre at step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError)) - elseif gd.LUE<-0.13 and lineupError>0.14 then - env.info" Little Drift Left across centre ==> (DL-)" - gd.Drift=" (DL)" - self:E(self.lid..string.format("Got Little Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError)) - end + -- Drift on lineup. + if rho >= RAR and rho <= RIM then + if gd.LUE > 0.22 and lineupError < -0.22 then + env.info " Drift Right across centre ==> DR-" + gd.Drift = " DR" + self:T( self.lid .. string.format( "Got Drift Right across centre step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError ) ) + elseif gd.LUE < -0.22 and lineupError > 0.22 then + env.info " Drift Left ==> DL-" + gd.Drift = " DL" + self:T( self.lid .. string.format( "Got Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError ) ) + elseif gd.LUE > 0.13 and lineupError < -0.14 then + env.info " Little Drift Right across centre ==> (DR-)" + gd.Drift = " (DR)" + self:T( self.lid .. string.format( "Got Little Drift Right across centre at step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError ) ) + elseif gd.LUE < -0.13 and lineupError > 0.14 then + env.info " Little Drift Left across centre ==> (DL-)" + gd.Drift = " (DL)" + self:E( self.lid .. string.format( "Got Little Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError ) ) + end end -- Update max deviation of line up error. - 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 + 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 -- Fly through good window of glideslope. - if gd.GSE>0.4 and glideslopeError<-0.3 then + if gd.GSE > 0.4 and glideslopeError < -0.3 then -- Fly through down ==> "\" - 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: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 -- Fly through up ==> "/" - 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)) + 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 -- Update max deviation of glideslope error. - 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 + 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 -- Get aircraft AoA parameters. - local aircraftaoa=self:_GetAircraftAoA(playerData) + local aircraftaoa = self:_GetAircraftAoA( playerData ) -- On Speed AoA. - local aoaopt=aircraftaoa.OnSpeed + local aoaopt = aircraftaoa.OnSpeed -- Compare AoAs wrt on speed AoA and update max deviation. - 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 + 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 - --local gs2=self:_GS(groovedata.Step, -1) - --env.info(string.format("groovestep %s %s d=%.3f NM: GSE=%.3f %.3f, LUE=%.3f %.3f, AoA=%.3f %.3f", gs, gs2, d, groovedata.GSE, gd.GSE, groovedata.LUE, gd.LUE, groovedata.AoA, gd.AoA)) + -- local gs2=self:_GS(groovedata.Step, -1) + -- env.info(string.format("groovestep %s %s d=%.3f NM: GSE=%.3f %.3f, LUE=%.3f %.3f, AoA=%.3f %.3f", gs, gs2, d, groovedata.GSE, gd.GSE, groovedata.LUE, gd.LUE, groovedata.AoA, gd.AoA)) end @@ -10345,17 +9645,17 @@ function AIRBOSS:_Groove(playerData) --------------- -- Time since last LSO call. - local deltaT=timer.getTime()-playerData.Tlso + local deltaT = timer.getTime() - playerData.Tlso -- Wait until player passed the 0.75 NM distance. - local _advice=true - if playerData.TIG0==nil and playerData.difficulty~=AIRBOSS.Difficulty.EASY then --rho>RXX - _advice=false + local _advice = true + if playerData.TIG0 == nil and playerData.difficulty ~= AIRBOSS.Difficulty.EASY then -- rho>RXX + _advice = false end -- LSO call if necessary. - if deltaT>=self.LSOdT and _advice then - self:_LSOadvice(playerData, glideslopeError, lineupError) + if deltaT >= self.LSOdT and _advice then + self:_LSOadvice( playerData, glideslopeError, lineupError ) end end @@ -10365,37 +9665,37 @@ function AIRBOSS:_Groove(playerData) ---------------------------------------------------------- -- Player infront of the carrier X>~77 m. - if X>self.carrierparam.totlength+self.carrierparam.sterndist then + if X > self.carrierparam.totlength + self.carrierparam.sterndist then if playerData.waveoff then if playerData.landed then -- This should not happen because landing event was triggered. - self:_AddToDebrief(playerData, "You were waved off but landed anyway. Airboss wants to talk to you!") + self:_AddToDebrief( playerData, "You were waved off but landed anyway. Airboss wants to talk to you!" ) else - self:_AddToDebrief(playerData, "You were waved off.") + self:_AddToDebrief( playerData, "You were waved off." ) end elseif playerData.boltered then -- This should not happen because landing event was triggered. - self:_AddToDebrief(playerData, "You boltered.") + self:_AddToDebrief( playerData, "You boltered." ) else -- This should not happen. - self:T("Player was not waved off but flew past the carrier without landing ==> Own wave off!") + self:T( "Player was not waved off but flew past the carrier without landing ==> Own wave off!" ) -- We count this as OWO. - self:_AddToDebrief(playerData, "Own waveoff.") + self:_AddToDebrief( playerData, "Own waveoff." ) -- Set Owo - playerData.owo=true + playerData.owo = true end - -- Next step: debrief. - self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.DEBRIEF) + -- Next step: debrief. + self:_SetPlayerStep( playerData, AIRBOSS.PatternStep.DEBRIEF ) end @@ -10413,62 +9713,62 @@ end -- @param #number AoA Angle of attack of player aircraft. -- @param #AIRBOSS.PlayerData playerData Player data. -- @return #boolean If true, player should wave off! -function AIRBOSS:_CheckWaveOff(glideslopeError, lineupError, AoA, playerData) +function AIRBOSS:_CheckWaveOff( glideslopeError, lineupError, AoA, playerData ) -- Assume we're all good. - local waveoff=false + local waveoff = false -- Parameters - local glMax= 1.8 - local glMin=-1.2 - local luAbs= 3.0 + local glMax = 1.8 + local glMin = -1.2 + local luAbs = 3.0 -- For the harrier, we allow a bit more room. - if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then - glMax= 2.6 - glMin=-2.0 - luAbs= 4.1 -- Testing Pene (WIP) needs feedback to tighten up tolerences. - + if playerData.actype == AIRBOSS.AircraftCarrier.AV8B then + glMax = 2.6 + glMin = -2.0 + luAbs = 4.1 -- Testing Pene (WIP) needs feedback to tighten up tolerences. + end -- Too high or too low? - if glideslopeError>glMax then - local text=string.format("\n- Waveoff due to glideslope error %.2f > %.1f degrees!", glideslopeError, glMax) - self:T(self.lid..string.format("%s: %s", playerData.name, text)) - self:_AddToDebrief(playerData, text) - waveoff=true - elseif glideslopeError glMax then + local text = string.format( "\n- Waveoff due to glideslope error %.2f > %.1f degrees!", glideslopeError, glMax ) + self:T( self.lid .. string.format( "%s: %s", playerData.name, text ) ) + self:_AddToDebrief( playerData, text ) + waveoff = true + elseif glideslopeError < glMin then + local text = string.format( "\n- Waveoff due to glideslope error %.2f < %.1f degrees!", glideslopeError, glMin ) + self:T( self.lid .. string.format( "%s: %s", playerData.name, text ) ) + self:_AddToDebrief( playerData, text ) + waveoff = true end -- Too far from centerline? - if math.abs(lineupError)>luAbs then - local text=string.format("\n- Waveoff due to line up error |%.1f| > %.1f degrees!", lineupError, luAbs) - self:T(self.lid..string.format("%s: %s", playerData.name, text)) - self:_AddToDebrief(playerData, text) - waveoff=true + if math.abs( lineupError ) > luAbs then + local text = string.format( "\n- Waveoff due to line up error |%.1f| > %.1f degrees!", lineupError, luAbs ) + self:T( self.lid .. string.format( "%s: %s", playerData.name, text ) ) + self:_AddToDebrief( playerData, text ) + waveoff = true end -- Too slow or too fast? Only for pros. - - if playerData.difficulty==AIRBOSS.Difficulty.HARD and playerData.actype~=AIRBOSS.AircraftCarrier.AV8B then + + if playerData.difficulty == AIRBOSS.Difficulty.HARD and playerData.actype ~= AIRBOSS.AircraftCarrier.AV8B then -- Get aircraft specific AoA values. Not for AV-8B due to transition to Stable Hover. - local aoaac=self:_GetAircraftAoA(playerData) + local aoaac = self:_GetAircraftAoA( playerData ) -- Check too slow or too fast. - 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 - + if AoA < aoaac.FAST then + local text = string.format( "\n- Waveoff due to AoA %.1f < %.1f!", AoA, aoaac.FAST ) + self:T( self.lid .. string.format( "%s: %s", playerData.name, text ) ) + self:_AddToDebrief( playerData, text ) + waveoff = true + elseif AoA > aoaac.SLOW then + local text = string.format( "\n- Waveoff due to AoA %.1f > %.1f!", AoA, aoaac.SLOW ) + self:T( self.lid .. string.format( "%s: %s", playerData.name, text ) ) + self:_AddToDebrief( playerData, text ) + waveoff = true + end end @@ -10479,107 +9779,104 @@ end -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data. -- @return boolean If true, we have a foul deck. -function AIRBOSS:_CheckFoulDeck(playerData) +function AIRBOSS:_CheckFoulDeck( playerData ) -- Assume no check necessary. - local check=false + local check = false -- CVN: Check at IM and IC. - if playerData.step==AIRBOSS.PatternStep.GROOVE_IM or - playerData.step==AIRBOSS.PatternStep.GROOVE_IC then - check=true + if playerData.step == AIRBOSS.PatternStep.GROOVE_IM or playerData.step == AIRBOSS.PatternStep.GROOVE_IC then + check = true end -- AV-8B check until - if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then - if playerData.step==AIRBOSS.PatternStep.GROOVE_AR or - playerData.step==AIRBOSS.PatternStep.GROOVE_AL then - check=true + 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 -- Check if player was already waved off. Should not be necessary as player step is set to debrief afterwards! - if playerData.wofd==true or check==false then + if playerData.wofd == true or check == false then -- Player was already waved off. return end -- Landing runway zone. - local runway=self:_GetZoneRunwayBox() + local runway = self:_GetZoneRunwayBox() -- For AB-8B we just check the primary landing spot. - if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then - runway=self:_GetZoneLandingSpot() + if playerData.actype == AIRBOSS.AircraftCarrier.AV8B then + runway = self:_GetZoneLandingSpot() end -- Scan radius. - local R=250 + local R = 250 -- Debug info. - self:T(self.lid..string.format("Foul deck check: Scanning Carrier Runway Area. Radius=%.1f m.", R)) + self:T( self.lid .. string.format( "Foul deck check: Scanning Carrier Runway Area. Radius=%.1f m.", R ) ) -- Scan units in carrier zone. - local _,_,_,unitscan=self:GetCoordinate():ScanObjects(R, true, false, false) + local _, _, _, unitscan = self:GetCoordinate():ScanObjects( R, true, false, false ) -- Loop over all scanned units and check if they are on the runway. - local fouldeck=false - local foulunit=nil --Wrapper.Unit#UNIT - for _,_unit in pairs(unitscan) do - local unit=_unit --Wrapper.Unit#UNIT + local fouldeck = false + local foulunit = nil -- Wrapper.Unit#UNIT + for _, _unit in pairs( unitscan ) do + local unit = _unit -- Wrapper.Unit#UNIT -- Check if unit is in zone. - local inzone=unit:IsInZone(runway) + local inzone = unit:IsInZone( runway ) -- Check if aircraft and in air. - local isaircraft=unit:IsAir() - local isairborn =unit:InAir() + 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) + 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) + runway:FlareZone( FLARECOLOR.Red, 30 ) end - fouldeck=true - foulunit=unit + fouldeck = true + foulunit = unit end end - -- Add to debrief and if playerData and fouldeck then -- Debrief text. - 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) + 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 ) -- Foul deck + wave off radio message. - self:RadioTransmission(self.LSORadio, self.LSOCall.FOULDECK, false, 1) - self:RadioTransmission(self.LSORadio, self.LSOCall.WAVEOFF, false, 1.2, nil, true) + self:RadioTransmission( self.LSORadio, self.LSOCall.FOULDECK, false, 1 ) + self:RadioTransmission( self.LSORadio, self.LSOCall.WAVEOFF, false, 1.2, nil, true ) -- Player hint for flight students. if playerData.showhints then - local text=string.format("overfly landing area and enter bolter pattern.") - self:MessageToPlayer(playerData, text, "LSO", nil, nil, false, 3) + local text = string.format( "overfly landing area and enter bolter pattern." ) + self:MessageToPlayer( playerData, text, "LSO", nil, nil, false, 3 ) end -- Set player parameters for foul deck. - playerData.wofd=true + playerData.wofd = true -- Debrief. - playerData.step=AIRBOSS.PatternStep.DEBRIEF - playerData.warning=nil + playerData.step = AIRBOSS.PatternStep.DEBRIEF + playerData.warning = nil -- Pass would be invalid if the player lands. - playerData.valid=false + playerData.valid = false -- Send a message to the player that blocks the runway. if foulunit then - local foulflight=self:_GetFlightFromGroupInQueue(foulunit:GetGroup(), self.flights) + 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") + self:MessageToPlayer( foulflight, "move your ass from my runway. NOW!", "AIRBOSS" ) end end end @@ -10593,32 +9890,32 @@ end function AIRBOSS:_GetSternCoord() -- Heading of carrier (true). - local hdg=self.carrier:GetHeading() + local hdg = self.carrier:GetHeading() -- Final bearing (true). - local FB=self:GetFinalBearing() + local FB = self:GetFinalBearing() -- Stern coordinate (sterndist<0). Also translate 10 meters starboard wrt Final bearing. - self.sterncoord:UpdateFromCoordinate(self:GetCoordinate()) - --local stern=self:GetCoordinate() + self.sterncoord:UpdateFromCoordinate( self:GetCoordinate() ) + -- local stern=self:GetCoordinate() -- Stern coordinate (sterndist<0). - if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then -- Tarawa: Translate 8 meters port. - 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( 8, FB - 90, true, true ) + elseif self.carriertype == AIRBOSS.CarrierType.STENNIS then -- Stennis: translate 7 meters starboard wrt Final bearing. - self.sterncoord:Translate(self.carrierparam.sterndist, hdg, true, true):Translate(7, FB+90, true, true) - elseif self.carriertype==AIRBOSS.CarrierType.FORRESTAL then + self.sterncoord:Translate( self.carrierparam.sterndist, hdg, true, true ):Translate( 7, FB + 90, true, true ) + elseif self.carriertype == AIRBOSS.CarrierType.FORRESTAL then -- Forrestal - self.sterncoord:Translate(self.carrierparam.sterndist, hdg, true, true):Translate(7.5, FB+90, true, true) + self.sterncoord:Translate( self.carrierparam.sterndist, hdg, true, true ):Translate( 7.5, FB + 90, true, true ) else -- Nimitz SC: translate 8 meters starboard wrt Final bearing. - self.sterncoord:Translate(self.carrierparam.sterndist, hdg, true, true):Translate(9.5, FB+90, true, true) + self.sterncoord:Translate( self.carrierparam.sterndist, hdg, true, true ):Translate( 9.5, FB + 90, true, true ) end -- Set altitude. - self.sterncoord:SetAltitude(self.carrierparam.deckheight) + self.sterncoord:SetAltitude( self.carrierparam.deckheight ) return self.sterncoord end @@ -10628,73 +9925,73 @@ end -- @param Core.Point#COORDINATE Lcoord Landing position. -- @param #number dc Distance correction. Shift the landing coord back if dc>0 and forward if dc<0. -- @return #number Trapped wire (1-4) or 99 if no wire was trapped. -function AIRBOSS:_GetWire(Lcoord, dc) +function AIRBOSS:_GetWire( Lcoord, dc ) -- Final bearing (true). - local FB=self:GetFinalBearing() + local FB = self:GetFinalBearing() -- Stern coordinate (sterndist<0). Also translate 10 meters starboard wrt Final bearing. - local Scoord=self:_GetSternCoord() + local Scoord = self:_GetSternCoord() -- Distance to landing coord. - local Ldist=Lcoord:Get2DDistance(Scoord) + local Ldist = Lcoord:Get2DDistance( Scoord ) -- For human (not AI) the lading event is delayed unfortunately. Therefore, we need another correction factor. - dc= dc or 65 + dc = dc or 65 -- Corrected landing distance wrt to stern. Landing distance needs to be reduced due to delayed landing event for human players. - local d=Ldist-dc + local d = Ldist - dc -- Multiplayer wire correction. if self.mpWireCorrection then - d=d-self.mpWireCorrection + d = d - self.mpWireCorrection end -- Shift wires from stern to their correct position. - local w1=self.carrierparam.wire1 - local w2=self.carrierparam.wire2 - local w3=self.carrierparam.wire3 - local w4=self.carrierparam.wire4 + local w1 = self.carrierparam.wire1 + local w2 = self.carrierparam.wire2 + local w3 = self.carrierparam.wire3 + local w4 = self.carrierparam.wire4 -- Which wire was caught? local wire - if d wire=%d (dc=%.1f)", Ldist, Ldist-dc, wire, dc)) + self:T( string.format( "GetWire: L=%.1f, L-dc=%.1f ==> wire=%d (dc=%.1f)", Ldist, Ldist - dc, wire, dc ) ) return wire end @@ -10710,61 +10007,61 @@ end --- Trapped? Check if in air or not after landing event. -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data table. -function AIRBOSS:_Trapped(playerData) +function AIRBOSS:_Trapped( playerData ) - if playerData.unit:InAir()==false then + if playerData.unit:InAir() == false then -- Seems we have successfully landed. -- Lets see if we can get a good wire. - local unit=playerData.unit + local unit = playerData.unit -- Coordinate of player aircraft. - local coord=unit:GetCoordinate() + local coord = unit:GetCoordinate() -- Get velocity in km/h. We need to substrackt the carrier velocity. - local v=unit:GetVelocityKMH()-self.carrier:GetVelocityKMH() + local v = unit:GetVelocityKMH() - self.carrier:GetVelocityKMH() -- Stern coordinate. - local stern=self:_GetSternCoord() + local stern = self:_GetSternCoord() -- Distance to stern pos. - local s=stern:Get2DDistance(coord) + local s = stern:Get2DDistance( coord ) -- Get current wire (estimate). This now based on the position where the player comes to a standstill which should reflect the trapped wire better. - 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 + 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 -- TODO: Check Tomcat. - dcorr=100 - elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then + dcorr = 100 + elseif playerData.actype == AIRBOSS.AircraftCarrier.A4EC then -- A-4E gets slowed down much faster the the F/A-18C! - dcorr=56 - elseif playerData.actype==AIRBOSS.AircraftCarrier.T45C then - -- T-45 also gets slowed down much faster the the F/A-18C. - dcorr=56 + dcorr = 56 + elseif playerData.actype == AIRBOSS.AircraftCarrier.T45C then + -- T-45 also gets slowed down much faster the the F/A-18C. + dcorr = 56 end -- Get wire. - local wire=self:_GetWire(coord, dcorr) + local wire = self:_GetWire( coord, dcorr ) -- Debug. - 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) + 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 ) -- Call this function again until v < threshold. Player comes to a standstill ==> Get wire! - if v>5 then + if v > 5 then -- Check if we passed all wires. - if wire>4 and v>10 and not playerData.warning then + if wire > 4 and v > 10 and not playerData.warning then -- Looks like we missed the wires ==> Bolter! - self:RadioTransmission(self.LSORadio, self.LSOCall.BOLTER, nil, nil, nil, true) - playerData.warning=true + self:RadioTransmission( self.LSORadio, self.LSOCall.BOLTER, nil, nil, nil, true ) + playerData.warning = true end -- Call function again and check if converged or back in air. - --SCHEDULER:New(nil, self._Trapped, {self, playerData}, 0.1) - self:ScheduleOnce(0.1, self._Trapped, self, playerData) + -- SCHEDULER:New(nil, self._Trapped, {self, playerData}, 0.1) + self:ScheduleOnce( 0.1, self._Trapped, self, playerData ) return end @@ -10775,47 +10072,47 @@ function AIRBOSS:_Trapped(playerData) -- Put some smoke and a mark. if self.Debug then coord:SmokeBlue() - coord:MarkToAll(text) - stern:MarkToAll("Stern") + coord:MarkToAll( text ) + stern:MarkToAll( "Stern" ) end -- Set player wire. - playerData.wire=wire + playerData.wire = wire -- Message to player. - 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!" + 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 -- Message to player. - self:MessageToPlayer(playerData, text, "LSO", "") + self:MessageToPlayer( playerData, text, "LSO", "" ) -- Debrief. - local hint = string.format("Trapped %d-wire.", wire) - self:_AddToDebrief(playerData, hint, "Groove: IW") + local hint = string.format( "Trapped %d-wire.", wire ) + self:_AddToDebrief( playerData, hint, "Groove: IW" ) else - --Again in air ==> Boltered! - 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) + -- Again in air ==> Boltered! + 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 ) -- Bolter switch on. - playerData.boltered=true + playerData.boltered = true end -- Next step: debriefing. - playerData.step=AIRBOSS.PatternStep.DEBRIEF - playerData.warning=nil + playerData.step = AIRBOSS.PatternStep.DEBRIEF + playerData.warning = nil end ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -10826,53 +10123,53 @@ end -- @param #AIRBOSS self -- @param #number case Recovery Case. -- @return Core.Zone#ZONE_POLYGON_BASE Initial zone. -function AIRBOSS:_GetZoneInitial(case) +function AIRBOSS:_GetZoneInitial( case ) - self.zoneInitial=self.zoneInitial or ZONE_POLYGON_BASE:New("Zone CASE I/II Initial") + self.zoneInitial = self.zoneInitial or ZONE_POLYGON_BASE:New( "Zone CASE I/II Initial" ) -- Get radial, i.e. inverse of BRC. - local radial=self:GetRadial(2, false, false) + local radial = self:GetRadial( 2, false, false ) -- Carrier coordinate. - local cv=self:GetCoordinate() + local cv = self:GetCoordinate() -- Vec2 array. - local vec2={} + local vec2 = {} - if case==1 then + if case == 1 then -- Case I - local c1=cv:Translate(UTILS.NMToMeters(0.5), radial-90) -- 0.0 0.5 starboard - local c2=cv:Translate(UTILS.NMToMeters(1.3), radial-90):Translate(UTILS.NMToMeters(3), radial) -- -3.0 1.3 starboard, astern - local c3=cv:Translate(UTILS.NMToMeters(0.4), radial+90):Translate(UTILS.NMToMeters(3), radial) -- -3.0 -0.4 port, astern - local c4=cv:Translate(UTILS.NMToMeters(1.0), radial) - local c5=cv + local c1 = cv:Translate( UTILS.NMToMeters( 0.5 ), radial - 90 ) -- 0.0 0.5 starboard + local c2 = cv:Translate( UTILS.NMToMeters( 1.3 ), radial - 90 ):Translate( UTILS.NMToMeters( 3 ), radial ) -- -3.0 1.3 starboard, astern + local c3 = cv:Translate( UTILS.NMToMeters( 0.4 ), radial + 90 ):Translate( UTILS.NMToMeters( 3 ), radial ) -- -3.0 -0.4 port, astern + local c4 = cv:Translate( UTILS.NMToMeters( 1.0 ), radial ) + local c5 = cv -- Vec2 array. - vec2={c1:GetVec2(), c2:GetVec2(), c3:GetVec2(), c4:GetVec2(), c5:GetVec2()} + vec2 = { c1:GetVec2(), c2:GetVec2(), c3:GetVec2(), c4:GetVec2(), c5:GetVec2() } else -- Case II -- Funnel. - local c1=cv:Translate(UTILS.NMToMeters(0.5), radial-90) -- 0.0, 0.5 - local c2=c1:Translate(UTILS.NMToMeters(0.5), radial) -- 0.5, 0.5 - local c3=cv:Translate(UTILS.NMToMeters(1.2), radial-90):Translate(UTILS.NMToMeters(3), radial) -- 3.0, 1.2 - local c4=cv:Translate(UTILS.NMToMeters(1.2), radial+90):Translate(UTILS.NMToMeters(3), radial) -- 3.0,-1.2 - local c5=cv:Translate(UTILS.NMToMeters(0.5), radial) - local c6=cv + local c1 = cv:Translate( UTILS.NMToMeters( 0.5 ), radial - 90 ) -- 0.0, 0.5 + local c2 = c1:Translate( UTILS.NMToMeters( 0.5 ), radial ) -- 0.5, 0.5 + local c3 = cv:Translate( UTILS.NMToMeters( 1.2 ), radial - 90 ):Translate( UTILS.NMToMeters( 3 ), radial ) -- 3.0, 1.2 + local c4 = cv:Translate( UTILS.NMToMeters( 1.2 ), radial + 90 ):Translate( UTILS.NMToMeters( 3 ), radial ) -- 3.0,-1.2 + local c5 = cv:Translate( UTILS.NMToMeters( 0.5 ), radial ) + local c6 = cv -- Vec2 array. - vec2={c1:GetVec2(), c2:GetVec2(), c3:GetVec2(), c4:GetVec2(), c5:GetVec2(), c6:GetVec2()} + vec2 = { c1:GetVec2(), c2:GetVec2(), c3:GetVec2(), c4:GetVec2(), c5:GetVec2(), c6:GetVec2() } end -- Polygon zone. - --local zone=ZONE_POLYGON_BASE:New("Zone CASE I/II Initial", vec2) + -- local zone=ZONE_POLYGON_BASE:New("Zone CASE I/II Initial", vec2) - self.zoneInitial:UpdateFromVec2(vec2) + self.zoneInitial:UpdateFromVec2( vec2 ) - --return zone + -- return zone return self.zoneInitial end @@ -10881,70 +10178,69 @@ end -- @return Core.Zone#ZONE_POLYGON_BASE Lineup zone. function AIRBOSS:_GetZoneLineup() - self.zoneLineup=self.zoneLineup or ZONE_POLYGON_BASE:New("Zone Lineup") + self.zoneLineup = self.zoneLineup or ZONE_POLYGON_BASE:New( "Zone Lineup" ) -- Get radial, i.e. inverse of BRC. - local fbi=self:GetRadial(1, false, false) + local fbi = self:GetRadial( 1, false, false ) -- Stern coordinate. - local st=self:_GetOptLandingCoordinate() + local st = self:_GetOptLandingCoordinate() -- Zone points. - 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 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 ) -- Vec2 array. - local vec2={c1:GetVec2(), c2:GetVec2(), c3:GetVec2(), c4:GetVec2(), c5:GetVec2()} + local vec2 = { c1:GetVec2(), c2:GetVec2(), c3:GetVec2(), c4:GetVec2(), c5:GetVec2() } - self.zoneLineup:UpdateFromVec2(vec2) + self.zoneLineup:UpdateFromVec2( vec2 ) -- Polygon zone. - --local zone=ZONE_POLYGON_BASE:New("Zone Lineup", vec2) - --return zone + -- local zone=ZONE_POLYGON_BASE:New("Zone Lineup", vec2) + -- return zone return self.zoneLineup end - --- Get groove zone. -- @param #AIRBOSS self -- @param #number l Length of the groove in NM. Default 1.5 NM. -- @param #number w Width of the groove in NM. Default 0.25 NM. -- @param #number b Width of the beginning in NM. Default 0.10 NM. -- @return Core.Zone#ZONE_POLYGON_BASE Groove zone. -function AIRBOSS:_GetZoneGroove(l, w, b) +function AIRBOSS:_GetZoneGroove( l, w, b ) - self.zoneGroove=self.zoneGroove or ZONE_POLYGON_BASE:New("Zone Groove") + 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 + l = l or 1.50 + w = w or 0.25 + b = b or 0.10 -- Get radial, i.e. inverse of BRC. - local fbi=self:GetRadial(1, false, false) + local fbi = self:GetRadial( 1, false, false ) -- Stern coordinate. - local st=self:_GetSternCoord() + local st = self:_GetSternCoord() -- Zone points. - 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 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 ) -- Vec2 array. - local vec2={c1:GetVec2(), c2:GetVec2(), c3:GetVec2(), c4:GetVec2(), c5:GetVec2(), c6:GetVec2()} + local vec2 = { c1:GetVec2(), c2:GetVec2(), c3:GetVec2(), c4:GetVec2(), c5:GetVec2(), c6:GetVec2() } - self.zoneGroove:UpdateFromVec2(vec2) + self.zoneGroove:UpdateFromVec2( vec2 ) -- Polygon zone. - --local zone=ZONE_POLYGON_BASE:New("Zone Groove", vec2) - --return zone + -- local zone=ZONE_POLYGON_BASE:New("Zone Groove", vec2) + -- return zone return self.zoneGroove end @@ -10953,49 +10249,49 @@ end -- @param #AIRBOSS self -- @param #number case Recovery case. -- @return Core.Zone#ZONE_RADIUS Arc in zone. -function AIRBOSS:_GetZoneBullseye(case) +function AIRBOSS:_GetZoneBullseye( case ) -- Radius = 1 NM. - local radius=UTILS.NMToMeters(1) + local radius = UTILS.NMToMeters( 1 ) -- Distance = 3 NM - local distance=UTILS.NMToMeters(3) + local distance = UTILS.NMToMeters( 3 ) -- Zone depends on Case recovery. - local radial=self:GetRadial(case, false, false) + local radial = self:GetRadial( case, false, false ) -- Get coordinate and vec2. - local coord=self:GetCoordinate():Translate(distance, radial) - local vec2=coord:GetVec2() + local coord = self:GetCoordinate():Translate( distance, radial ) + local vec2 = coord:GetVec2() -- Create zone. - local zone=ZONE_RADIUS:New("Zone Bullseye", vec2, radius) + local zone = ZONE_RADIUS:New( "Zone Bullseye", vec2, radius ) return zone - --self.zoneBullseye=self.zoneBullseye or ZONE_RADIUS:New("Zone Bullseye", vec2, radius) + -- self.zoneBullseye=self.zoneBullseye or ZONE_RADIUS:New("Zone Bullseye", vec2, radius) end --- Get dirty up zone with radius 1 NM and DME 9 NM from the carrier. Radial depends on recovery case. -- @param #AIRBOSS self -- @param #number case Recovery case. -- @return Core.Zone#ZONE_RADIUS Dirty up zone. -function AIRBOSS:_GetZoneDirtyUp(case) +function AIRBOSS:_GetZoneDirtyUp( case ) -- Radius = 1 NM. - local radius=UTILS.NMToMeters(1) + local radius = UTILS.NMToMeters( 1 ) -- Distance = 9 NM - local distance=UTILS.NMToMeters(9) + local distance = UTILS.NMToMeters( 9 ) -- Zone depends on Case recovery. - local radial=self:GetRadial(case, false, false) + local radial = self:GetRadial( case, false, false ) -- Get coordinate and vec2. - local coord=self:GetCoordinate():Translate(distance, radial) - local vec2=coord:GetVec2() + local coord = self:GetCoordinate():Translate( distance, radial ) + local vec2 = coord:GetVec2() -- Create zone. - local zone=ZONE_RADIUS:New("Zone Dirty Up", vec2, radius) + local zone = ZONE_RADIUS:New( "Zone Dirty Up", vec2, radius ) return zone end @@ -11004,22 +10300,22 @@ end -- @param #AIRBOSS self -- @param #number case Recovery case. -- @return Core.Zone#ZONE_RADIUS Arc in zone. -function AIRBOSS:_GetZoneArcOut(case) +function AIRBOSS:_GetZoneArcOut( case ) -- Radius = 1.25 NM. - local radius=UTILS.NMToMeters(1.25) + local radius = UTILS.NMToMeters( 1.25 ) -- Distance = 12 NM - local distance=UTILS.NMToMeters(11.75) + local distance = UTILS.NMToMeters( 11.75 ) -- Zone depends on Case recovery. - local radial=self:GetRadial(case, false, false) + local radial = self:GetRadial( case, false, false ) -- Get coordinate of carrier and translate. - local coord=self:GetCoordinate():Translate(distance, radial) + local coord = self:GetCoordinate():Translate( distance, radial ) -- Create zone. - local zone=ZONE_RADIUS:New("Zone Arc Out", coord:GetVec2(), radius) + local zone = ZONE_RADIUS:New( "Zone Arc Out", coord:GetVec2(), radius ) return zone end @@ -11028,28 +10324,28 @@ end -- @param #AIRBOSS self -- @param #number case Recovery case. -- @return Core.Zone#ZONE_RADIUS Arc in zone. -function AIRBOSS:_GetZoneArcIn(case) +function AIRBOSS:_GetZoneArcIn( case ) -- Radius = 1.25 NM. - local radius=UTILS.NMToMeters(1.25) + local radius = UTILS.NMToMeters( 1.25 ) -- Zone depends on Case recovery. - local radial=self:GetRadial(case, false, true) + local radial = self:GetRadial( case, false, true ) -- Angle between FB/BRC and holding zone. - local alpha=math.rad(self.holdingoffset) + local alpha = math.rad( self.holdingoffset ) -- 14+x NM from carrier - local x=14 --/math.cos(alpha) + local x = 14 -- /math.cos(alpha) -- Distance = 14 NM - local distance=UTILS.NMToMeters(x) + local distance = UTILS.NMToMeters( x ) -- Get coordinate. - local coord=self:GetCoordinate():Translate(distance, radial) + local coord = self:GetCoordinate():Translate( distance, radial ) -- Create zone. - local zone=ZONE_RADIUS:New("Zone Arc In", coord:GetVec2(), radius) + local zone = ZONE_RADIUS:New( "Zone Arc In", coord:GetVec2(), radius ) return zone end @@ -11058,79 +10354,78 @@ end -- @param #AIRBOSS self -- @param #number case Recovery case. -- @return Core.Zone#ZONE_RADIUS Circular platform zone. -function AIRBOSS:_GetZonePlatform(case) +function AIRBOSS:_GetZonePlatform( case ) -- Radius = 1 NM. - local radius=UTILS.NMToMeters(1) + local radius = UTILS.NMToMeters( 1 ) -- Zone depends on Case recovery. - local radial=self:GetRadial(case, false, true) + local radial = self:GetRadial( case, false, true ) -- Angle between FB/BRC and holding zone. - local alpha=math.rad(self.holdingoffset) + local alpha = math.rad( self.holdingoffset ) -- Distance = 19 NM - local distance=UTILS.NMToMeters(19) --/math.cos(alpha) + local distance = UTILS.NMToMeters( 19 ) -- /math.cos(alpha) -- Get coordinate. - local coord=self:GetCoordinate():Translate(distance, radial) + local coord = self:GetCoordinate():Translate( distance, radial ) -- Create zone. - local zone=ZONE_RADIUS:New("Zone Platform", coord:GetVec2(), radius) + local zone = ZONE_RADIUS:New( "Zone Platform", coord:GetVec2(), radius ) return zone end - --- Get approach corridor zone. Shape depends on recovery case. -- @param #AIRBOSS self -- @param #number case Recovery case. -- @param #number l Length of the zone in NM. Default 31 (=21+10) NM. -- @return Core.Zone#ZONE_POLYGON_BASE Box zone. -function AIRBOSS:_GetZoneCorridor(case, l) +function AIRBOSS:_GetZoneCorridor( case, l ) -- Total length. - l=l or 31 + l = l or 31 -- Radial and offset. - local radial=self:GetRadial(case, false, false) - local offset=self:GetRadial(case, false, true) + local radial = self:GetRadial( case, false, false ) + local offset = self:GetRadial( case, false, true ) -- Distance shift ahead of carrier to allow for some space to bolter. - local dx=5 + local dx = 5 -- Width of the box in NM. - local w=2 - local w2=w/2 + local w = 2 + local w2 = w / 2 -- Distance from carrier to arc out zone. - local d=12 + local d = 12 -- Carrier position. - local cv=self:GetCoordinate() + local cv = self:GetCoordinate() -- Polygon points. - local c={} + local c = {} -- First point. Carrier coordinate translated 5 NM in direction of travel to allow for bolter space. - c[1]=cv:Translate(-UTILS.NMToMeters(dx), radial) + c[1] = cv:Translate( -UTILS.NMToMeters( dx ), radial ) - if math.abs(self.holdingoffset)>=5 then + if math.abs( self.holdingoffset ) >= 5 then ----------------- -- Angled Case -- ----------------- - c[2]=c[1]:Translate( UTILS.NMToMeters(w2), radial-90) -- 1 Right of carrier, dx ahead. - c[3]=c[2]:Translate( UTILS.NMToMeters(d+dx+w2), radial) -- 13 "south" @ 1 right + c[2] = c[1]:Translate( UTILS.NMToMeters( w2 ), radial - 90 ) -- 1 Right of carrier, dx ahead. + c[3] = c[2]:Translate( UTILS.NMToMeters( d + dx + w2 ), radial ) -- 13 "south" @ 1 right - 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[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) + c[9] = c[1]:Translate( UTILS.NMToMeters( w2 ), radial + 90 ) else @@ -11138,70 +10433,68 @@ function AIRBOSS:_GetZoneCorridor(case, l) -- Easy case of a long box -- ----------------------------- - c[2]=c[1]:Translate( UTILS.NMToMeters(w2), radial-90) - c[3]=c[2]:Translate( UTILS.NMToMeters(dx+l), radial) -- Stack 1 starts at 21 and is 7 NM. - c[4]=c[3]:Translate( UTILS.NMToMeters(w), radial+90) - c[5]=c[1]:Translate( UTILS.NMToMeters(w2), radial+90) + c[2] = c[1]:Translate( UTILS.NMToMeters( w2 ), radial - 90 ) + c[3] = c[2]:Translate( UTILS.NMToMeters( dx + l ), radial ) -- Stack 1 starts at 21 and is 7 NM. + c[4] = c[3]:Translate( UTILS.NMToMeters( w ), radial + 90 ) + c[5] = c[1]:Translate( UTILS.NMToMeters( w2 ), radial + 90 ) end - -- Create an array of a square! - local p={} - for _i,_c in ipairs(c) do + local p = {} + for _i, _c in ipairs( c ) do if self.Debug then - --_c:SmokeBlue() + -- _c:SmokeBlue() end - p[_i]=_c:GetVec2() + p[_i] = _c:GetVec2() end -- Square zone length=10NM width=6 NM behind the carrier starting at angels+15 NM behind the carrier. -- So stay 0-5 NM (+1 NM error margin) port of carrier. - local zone=ZONE_POLYGON_BASE:New("CASE II/III Approach Corridor", p) + local zone = ZONE_POLYGON_BASE:New( "CASE II/III Approach Corridor", p ) return zone end - --- Get zone of carrier. Carrier is approximated as rectangle. -- @param #AIRBOSS self -- @return Core.Zone#ZONE Zone surrounding the carrier. function AIRBOSS:_GetZoneCarrierBox() - self.zoneCarrierbox=self.zoneCarrierbox or ZONE_POLYGON_BASE:New("Carrier Box Zone") + self.zoneCarrierbox = self.zoneCarrierbox or ZONE_POLYGON_BASE:New( "Carrier Box Zone" ) -- Stern coordinate. - local S=self:_GetSternCoord() + local S = self:_GetSternCoord() -- Current carrier heading. - local hdg=self:GetHeading(false) + local hdg = self:GetHeading( false ) -- Coordinate array. - local p={} + local p = {} -- Starboard stern point. - p[1]=S:Translate(self.carrierparam.totwidthstarboard, hdg+90) + p[1] = S:Translate( self.carrierparam.totwidthstarboard, hdg + 90 ) -- Starboard bow point. - p[2]=p[1]:Translate(self.carrierparam.totlength, hdg) + p[2] = p[1]:Translate( self.carrierparam.totlength, hdg ) -- Port bow point. - p[3]=p[2]:Translate(self.carrierparam.totwidthstarboard+self.carrierparam.totwidthport, hdg-90) + p[3] = p[2]:Translate( self.carrierparam.totwidthstarboard + self.carrierparam.totwidthport, hdg - 90 ) -- Port stern point. - p[4]=p[3]:Translate(self.carrierparam.totlength, hdg-180) + p[4] = p[3]:Translate( self.carrierparam.totlength, hdg - 180 ) -- Convert to vec2. - local vec2={} - for _,coord in ipairs(p) do - table.insert(vec2, coord:GetVec2()) + local vec2 = {} + for _, coord in ipairs( p ) do + table.insert( vec2, coord:GetVec2() ) end -- Create polygon zone. - --local zone=ZONE_POLYGON_BASE:New("Carrier Box Zone", vec2) - --return zone + -- local zone=ZONE_POLYGON_BASE:New("Carrier Box Zone", vec2) + -- return zone - self.zoneCarrierbox:UpdateFromVec2(vec2) + self.zoneCarrierbox:UpdateFromVec2( vec2 ) return self.zoneCarrierbox end @@ -11211,39 +10504,38 @@ end -- @return Core.Zone#ZONE_POLYGON Zone surrounding landing runway. function AIRBOSS:_GetZoneRunwayBox() - self.zoneRunwaybox=self.zoneRunwaybox or ZONE_POLYGON_BASE:New("Landing Runway Zone") + self.zoneRunwaybox = self.zoneRunwaybox or ZONE_POLYGON_BASE:New( "Landing Runway Zone" ) -- Stern coordinate. - local S=self:_GetSternCoord() + local S = self:_GetSternCoord() -- Current carrier heading. - local FB=self:GetFinalBearing(false) + local FB = self:GetFinalBearing( false ) -- Coordinate array. - local p={} + local p = {} -- Points. - 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) + 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 ) -- Convert to vec2. - local vec2={} - for _,coord in ipairs(p) do - table.insert(vec2, coord:GetVec2()) + local vec2 = {} + for _, coord in ipairs( p ) do + table.insert( vec2, coord:GetVec2() ) end -- Create polygon zone. - --local zone=ZONE_POLYGON_BASE:New("Landing Runway Zone", vec2) - --return zone + -- local zone=ZONE_POLYGON_BASE:New("Landing Runway Zone", vec2) + -- return zone - self.zoneRunwaybox:UpdateFromVec2(vec2) + self.zoneRunwaybox:UpdateFromVec2( vec2 ) return self.zoneRunwaybox end - --- Get zone of primary abeam landing position of USS Tarawa, USS America and Juan Carlos. Box length 50 meters and width 30 meters. --- Allow for Clear to land call from LSO approaching abeam the landing spot if stable as per NATOPS 00-80T -- @param #AIRBOSS self @@ -11251,128 +10543,125 @@ end function AIRBOSS:_GetZoneAbeamLandingSpot() -- Primary landing Spot coordinate. - local S=self:_GetOptLandingCoordinate() + local S = self:_GetOptLandingCoordinate() -- Current carrier heading. - local FB=self:GetFinalBearing(false) + local FB = self:GetFinalBearing( false ) -- Coordinate array. - local p={} + local p = {} -- Points. - p[1]=S:Translate( 15, FB):Translate(15, FB+90) -- Top-Right - p[2]=S:Translate(-45, FB):Translate(15, FB+90) -- Bottom-Right - p[3]=S:Translate(-45, FB):Translate(15, FB-90) -- Bottom-Left - p[4]=S:Translate( 15, FB):Translate(15, FB-90) -- Top-Left + p[1] = S:Translate( 15, FB ):Translate( 15, FB + 90 ) -- Top-Right + p[2] = S:Translate( -45, FB ):Translate( 15, FB + 90 ) -- Bottom-Right + p[3] = S:Translate( -45, FB ):Translate( 15, FB - 90 ) -- Bottom-Left + p[4] = S:Translate( 15, FB ):Translate( 15, FB - 90 ) -- Top-Left -- Convert to vec2. - local vec2={} - for _,coord in ipairs(p) do - table.insert(vec2, coord:GetVec2()) + local vec2 = {} + for _, coord in ipairs( p ) do + table.insert( vec2, coord:GetVec2() ) end -- Create polygon zone. - local zone=ZONE_POLYGON_BASE:New("Abeam Landing Spot Zone", vec2) + local zone = ZONE_POLYGON_BASE:New( "Abeam Landing Spot Zone", vec2 ) return zone end - --- Get zone of the primary landing spot of the USS Tarawa. -- @param #AIRBOSS self -- @return Core.Zone#ZONE_POLYGON Zone surrounding landing runway. function AIRBOSS:_GetZoneLandingSpot() -- Primary landing Spot coordinate. - local S=self:_GetLandingSpotCoordinate() + local S = self:_GetLandingSpotCoordinate() -- Current carrier heading. - local FB=self:GetFinalBearing(false) + local FB = self:GetFinalBearing( false ) -- Coordinate array. - local p={} + local p = {} -- Points. - p[1]=S:Translate( 10, FB):Translate(10, FB+90) -- Top-Right - p[2]=S:Translate(-10, FB):Translate(10, FB+90) -- Bottom-Right - p[3]=S:Translate(-10, FB):Translate(10, FB-90) -- Bottom-Left - p[4]=S:Translate( 10, FB):Translate(10, FB-90) -- Top-left + p[1] = S:Translate( 10, FB ):Translate( 10, FB + 90 ) -- Top-Right + p[2] = S:Translate( -10, FB ):Translate( 10, FB + 90 ) -- Bottom-Right + p[3] = S:Translate( -10, FB ):Translate( 10, FB - 90 ) -- Bottom-Left + p[4] = S:Translate( 10, FB ):Translate( 10, FB - 90 ) -- Top-left -- Convert to vec2. - local vec2={} - for _,coord in ipairs(p) do - table.insert(vec2, coord:GetVec2()) + local vec2 = {} + for _, coord in ipairs( p ) do + table.insert( vec2, coord:GetVec2() ) end -- Create polygon zone. - local zone=ZONE_POLYGON_BASE:New("Landing Spot Zone", vec2) + local zone = ZONE_POLYGON_BASE:New( "Landing Spot Zone", vec2 ) return zone end - --- Get holding zone of player. -- @param #AIRBOSS self -- @param #number case Recovery case. -- @param #number stack Marshal stack number. -- @return Core.Zone#ZONE Holding zone. -function AIRBOSS:_GetZoneHolding(case, stack) +function AIRBOSS:_GetZoneHolding( case, stack ) -- Holding zone. - local zoneHolding=nil --Core.Zone#ZONE + local zoneHolding = nil -- Core.Zone#ZONE -- Stack is <= 0 ==> no marshal zone. - if stack<=0 then - self:E(self.lid.."ERROR: Stack <= 0 in _GetZoneHolding!") - self:E({case=case, stack=stack}) + if stack <= 0 then + self:E( self.lid .. "ERROR: Stack <= 0 in _GetZoneHolding!" ) + self:E( { case = case, stack = stack } ) return nil end -- Pattern altitude. - local patternalt, c1, c2=self:_GetMarshalAltitude(stack, case) + local patternalt, c1, c2 = self:_GetMarshalAltitude( stack, case ) -- Select case. - if case==1 then + if case == 1 then -- CASE I -- Get current carrier heading. - local hdg=self:GetHeading() + local hdg = self:GetHeading() -- Distance to the post. - local D=UTILS.NMToMeters(2.5) + local D = UTILS.NMToMeters( 2.5 ) -- Post 2.5 NM port of carrier. - local Post=self:GetCoordinate():Translate(D, hdg+270) + local Post = self:GetCoordinate():Translate( D, hdg + 270 ) - --TODO: update zone not creating a new one. + -- TODO: update zone not creating a new one. -- Create holding zone. - self.zoneHolding=ZONE_RADIUS:New("CASE I Holding Zone", Post:GetVec2(), self.marshalradius) + self.zoneHolding = ZONE_RADIUS:New( "CASE I Holding Zone", Post:GetVec2(), self.marshalradius ) -- Delta pattern. - if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then - self.zoneHolding=ZONE_RADIUS:New("CASE I Holding Zone", self.carrier:GetVec2(), UTILS.NMToMeters(5)) + if self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + self.zoneHolding = ZONE_RADIUS:New( "CASE I Holding Zone", self.carrier:GetVec2(), UTILS.NMToMeters( 5 ) ) end - else -- CASE II/II -- Get radial. - local radial=self:GetRadial(case, false, true) + local radial = self:GetRadial( case, false, true ) -- Create an array of a rectangle. Length is 7 NM, width is 8 NM. One NM starboard to line up with the approach corridor. - local p={} - p[1]=c2:Translate(UTILS.NMToMeters(1), radial-90):GetVec2() --c2 is at (angels+15) NM directly behind the carrier. We translate it 1 NM starboard. - p[2]=c1:Translate(UTILS.NMToMeters(1), radial-90):GetVec2() --c1 is 7 NM further behind. Also translated 1 NM starboard. - p[3]=c1:Translate(UTILS.NMToMeters(7), radial+90):GetVec2() --p3 7 NM port of carrier. - p[4]=c2:Translate(UTILS.NMToMeters(7), radial+90):GetVec2() --p4 7 NM port of carrier. + local p = {} + p[1] = c2:Translate( UTILS.NMToMeters( 1 ), radial - 90 ):GetVec2() -- c2 is at (angels+15) NM directly behind the carrier. We translate it 1 NM starboard. + p[2] = c1:Translate( UTILS.NMToMeters( 1 ), radial - 90 ):GetVec2() -- c1 is 7 NM further behind. Also translated 1 NM starboard. + p[3] = c1:Translate( UTILS.NMToMeters( 7 ), radial + 90 ):GetVec2() -- p3 7 NM port of carrier. + p[4] = c2:Translate( UTILS.NMToMeters( 7 ), radial + 90 ):GetVec2() -- p4 7 NM port of carrier. -- Square zone length=7NM width=6 NM behind the carrier starting at angels+15 NM behind the carrier. -- So stay 0-5 NM (+1 NM error margin) port of carrier. - self.zoneHolding=self.zoneHolding or ZONE_POLYGON_BASE:New("CASE II/III Holding Zone") + self.zoneHolding = self.zoneHolding or ZONE_POLYGON_BASE:New( "CASE II/III Holding Zone" ) - self.zoneHolding:UpdateFromVec2(p) + self.zoneHolding:UpdateFromVec2( p ) end return self.zoneHolding @@ -11383,75 +10672,75 @@ end -- @param #number case Recovery case. -- @param #number stack Stack for Case II/III as we commence from stack>=1. -- @return Core.Zone#ZONE Holding zone. -function AIRBOSS:_GetZoneCommence(case, stack) +function AIRBOSS:_GetZoneCommence( case, stack ) -- Commence zone. local zone - if case==1 then + if case == 1 then -- Case I -- Get current carrier heading. - local hdg=self:GetHeading() + local hdg = self:GetHeading() -- Distance to the zone. - local D=UTILS.NMToMeters(4.75) + local D = UTILS.NMToMeters( 4.75 ) -- Zone radius. - local R=UTILS.NMToMeters(1) + local R = UTILS.NMToMeters( 1 ) -- Three position - local Three=self:GetCoordinate():Translate(D, hdg+275) + local Three = self:GetCoordinate():Translate( D, hdg + 275 ) - if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then - local Dx=UTILS.NMToMeters(2.25) + local Dx = UTILS.NMToMeters( 2.25 ) - local Dz=UTILS.NMToMeters(2.25) + local Dz = UTILS.NMToMeters( 2.25 ) - R=UTILS.NMToMeters(1) + R = UTILS.NMToMeters( 1 ) - Three=self:GetCoordinate():Translate(Dz, hdg-90):Translate(Dx, hdg-180) + Three = self:GetCoordinate():Translate( Dz, hdg - 90 ):Translate( Dx, hdg - 180 ) end -- Create holding zone. - self.zoneCommence=self.zoneCommence or ZONE_RADIUS:New("CASE I Commence Zone") + self.zoneCommence = self.zoneCommence or ZONE_RADIUS:New( "CASE I Commence Zone" ) - self.zoneCommence:UpdateFromVec2(Three:GetVec2(), R) + self.zoneCommence:UpdateFromVec2( Three:GetVec2(), R ) else -- Case II/III - stack=stack or 1 + stack = stack or 1 - -- Start point at 21 NM for stack=1. - local l=20+stack + -- Start point at 21 NM for stack=1. + local l = 20 + stack -- Offset angle - local offset=self:GetRadial(case, false, true) + local offset = self:GetRadial( case, false, true ) -- Carrier position. - local cv=self:GetCoordinate() + local cv = self:GetCoordinate() -- Polygon points. - local c={} + 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) + 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 ) -- Create an array of a square! - local p={} - for _i,_c in ipairs(c) do - p[_i]=_c:GetVec2() + local p = {} + for _i, _c in ipairs( c ) do + p[_i] = _c:GetVec2() end -- Zone polygon. - self.zoneCommence=self.zoneCommence or ZONE_POLYGON_BASE:New("CASE II/III Commence Zone") + self.zoneCommence = self.zoneCommence or ZONE_POLYGON_BASE:New( "CASE II/III Commence Zone" ) - self.zoneCommence:UpdateFromVec2(p) + self.zoneCommence:UpdateFromVec2( p ) end @@ -11465,90 +10754,90 @@ end --- Provide info about player status on the fly. -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data. -function AIRBOSS:_AttitudeMonitor(playerData) +function AIRBOSS:_AttitudeMonitor( playerData ) -- Player unit. - local unit=playerData.unit + local unit = playerData.unit -- Aircraft attitude. - local aoa=unit:GetAoA() - local yaw=unit:GetYaw() - local roll=unit:GetRoll() - local pitch=unit:GetPitch() + local aoa = unit:GetAoA() + local yaw = unit:GetYaw() + local roll = unit:GetRoll() + local pitch = unit:GetPitch() -- Distance to the boat. - local dist=playerData.unit:GetCoordinate():Get2DDistance(self:GetCoordinate()) - local dx,dz,rho,phi=self:_GetDistances(unit) + local dist = playerData.unit:GetCoordinate():Get2DDistance( self:GetCoordinate() ) + local dx, dz, rho, phi = self:_GetDistances( unit ) -- Wind vector. - local wind=unit:GetCoordinate():GetWindWithTurbulenceVec3() + local wind = unit:GetCoordinate():GetWindWithTurbulenceVec3() -- Aircraft veloecity vector. - local velo=unit:GetVelocityVec3() - local vabs=UTILS.VecNorm(velo) + 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 + 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 -- Relative heading Aircraft to Carrier. - local relhead=self:_GetRelativeHeading(playerData.unit, rwy) + local relhead = self:_GetRelativeHeading( playerData.unit, rwy ) - --local lc=self:_GetOptLandingCoordinate() - --lc:FlareRed() + -- local lc=self:_GetOptLandingCoordinate() + -- lc:FlareRed() -- Output - 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)) + 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 -- Velocity vector. - text=text..string.format("\nVx=%.1f Vy=%.1f Vz=%.1f m/s", velo.x, velo.y, velo.z) - --Wind vector. - text=text..string.format("\nWind Vx=%.1f Vy=%.1f Vz=%.1f m/s", wind.x, wind.y, wind.z) + text = text .. string.format( "\nVx=%.1f Vy=%.1f Vz=%.1f m/s", velo.x, velo.y, velo.z ) + -- Wind vector. + 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) + 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 ) -- Get player velocity in km/h. - local vplayer=playerData.unit:GetVelocityKMH() + local vplayer = playerData.unit:GetVelocityKMH() -- Get carrier velocity in km/h. - local vcarrier=self.carrier:GetVelocityKMH() + local vcarrier = self.carrier:GetVelocityKMH() -- Speed difference. - 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) + 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 in the groove, provide line up and glide slope error. - 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) + 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) + 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) + MESSAGE:New( text, 1, nil, true ):ToClient( playerData.client ) end --- Get glide slope of aircraft unit. @@ -11556,34 +10845,34 @@ end -- @param Wrapper.Unit#UNIT unit Aircraft unit. -- @param #number optangle (Optional) Return glide slope relative to this angle, i.e. the error from the optimal glide slope ~3.5 degrees. -- @return #number Glide slope angle in degrees measured from the deck of the carrier and third wire. -function AIRBOSS:_Glideslope(unit, optangle) +function AIRBOSS:_Glideslope( unit, optangle ) - if optangle==nil then - if unit:GetTypeName()==AIRBOSS.AircraftCarrier.AV8B then - optangle=3.0 + if optangle == nil then + if unit:GetTypeName() == AIRBOSS.AircraftCarrier.AV8B then + optangle = 3.0 else - optangle=3.5 + optangle = 3.5 end end - -- Landing coordinate - local landingcoord=self:_GetOptLandingCoordinate() + -- Landing coordinate + local landingcoord = self:_GetOptLandingCoordinate() -- Distance from stern to aircraft. - local x=unit:GetCoordinate():Get2DDistance(landingcoord) + local x = unit:GetCoordinate():Get2DDistance( landingcoord ) -- Altitude of unit corrected by the deck height of the carrier. - local h=self:_GetAltCarrier(unit) + local h = self:_GetAltCarrier( unit ) -- Harrier should be 40-50 ft above the deck. - if unit:GetTypeName()==AIRBOSS.AircraftCarrier.AV8B then - h=unit:GetAltitude()-(UTILS.FeetToMeters(50)+self.carrierparam.deckheight+2) + if unit:GetTypeName() == AIRBOSS.AircraftCarrier.AV8B then + h = unit:GetAltitude() - (UTILS.FeetToMeters( 50 ) + self.carrierparam.deckheight + 2) end -- Glide slope. - local glideslope=math.atan(h/x) + local glideslope = math.atan( h / x ) -- Glide slope (error) in degrees. - local gs=math.deg(glideslope)-optangle + local gs = math.deg( glideslope ) - optangle return gs end @@ -11593,37 +10882,37 @@ end -- @param Wrapper.Unit#UNIT unit Aircraft unit. -- @param #number optangle (Optional) Return glide slope relative to this angle, i.e. the error from the optimal glide slope ~3.5 degrees. -- @return #number Glide slope angle in degrees measured from the deck of the carrier and third wire. -function AIRBOSS:_Glideslope2(unit, optangle) +function AIRBOSS:_Glideslope2( unit, optangle ) - if optangle==nil then - if unit:GetTypeName()==AIRBOSS.AircraftCarrier.AV8B then - optangle=3.0 + if optangle == nil then + if unit:GetTypeName() == AIRBOSS.AircraftCarrier.AV8B then + optangle = 3.0 else - optangle=3.5 + optangle = 3.5 end end - -- Landing coordinate - local landingcoord=self:_GetOptLandingCoordinate() + -- Landing coordinate + local landingcoord = self:_GetOptLandingCoordinate() -- Distance from stern to aircraft. - local x=unit:GetCoordinate():Get3DDistance(landingcoord) + local x = unit:GetCoordinate():Get3DDistance( landingcoord ) -- Altitude of unit corrected by the deck height of the carrier. - local h=self:_GetAltCarrier(unit) + local h = self:_GetAltCarrier( unit ) -- Harrier should be 40-50 ft above the deck. - if unit:GetTypeName()==AIRBOSS.AircraftCarrier.AV8B then - h=unit:GetAltitude()-(UTILS.FeetToMeters(50)+self.carrierparam.deckheight+2) + if unit:GetTypeName() == AIRBOSS.AircraftCarrier.AV8B then + h = unit:GetAltitude() - (UTILS.FeetToMeters( 50 ) + self.carrierparam.deckheight + 2) end -- Glide slope. - local glideslope=math.asin(h/x) + local glideslope = math.asin( h / x ) -- Glide slope (error) in degrees. - local gs=math.deg(glideslope)-optangle + local gs = math.deg( glideslope ) - optangle -- Debug. - self:T3(self.lid..string.format("Glide slope error = %.1f, x=%.1f h=%.1f", gs, x, h)) + self:T3( self.lid .. string.format( "Glide slope error = %.1f, x=%.1f h=%.1f", gs, x, h ) ) return gs end @@ -11633,49 +10922,49 @@ end -- @param Wrapper.Unit#UNIT unit Aircraft unit. -- @param #boolean runway If true, include angled runway. -- @return #number Line up with runway heading in degrees. 0 degrees = perfect line up. +1 too far left. -1 too far right. -function AIRBOSS:_Lineup(unit, runway) +function AIRBOSS:_Lineup( unit, runway ) -- Landing coordinate - local landingcoord=self:_GetOptLandingCoordinate() + local landingcoord = self:_GetOptLandingCoordinate() -- Vector to landing coord. - local A=landingcoord:GetVec3() + local A = landingcoord:GetVec3() -- Vector to player. - local B=unit:GetVec3() + local B = unit:GetVec3() -- Vector from player to carrier. - local C=UTILS.VecSubstract(A, B) + local C = UTILS.VecSubstract( A, B ) -- Only in 2D plane. - C.y=0.0 + C.y = 0.0 -- Orientation of carrier. - local X=self.carrier:GetOrientationX() - X.y=0.0 + local X = self.carrier:GetOrientationX() + X.y = 0.0 -- Rotate orientation to angled runway. if runway then - X=UTILS.Rotate2D(X, -self.carrierparam.rwyangle) + X = UTILS.Rotate2D( X, -self.carrierparam.rwyangle ) end -- Projection of player pos on x component. - local x=UTILS.VecDot(X, C) + local x = UTILS.VecDot( X, C ) -- Orientation of carrier. - local Z=self.carrier:GetOrientationZ() - Z.y=0.0 + local Z = self.carrier:GetOrientationZ() + Z.y = 0.0 -- Rotate orientation to angled runway. if runway then - Z=UTILS.Rotate2D(Z, -self.carrierparam.rwyangle) + Z = UTILS.Rotate2D( Z, -self.carrierparam.rwyangle ) end -- Projection of player pos on z component. - local z=UTILS.VecDot(Z, C) + local z = UTILS.VecDot( Z, C ) --- - local lineup=math.deg(math.atan2(z, x)) + local lineup = math.deg( math.atan2( z, x ) ) return lineup end @@ -11684,12 +10973,12 @@ end -- @param #AIRBOSS self -- @param Wrapper.Unit#UNIT unit Aircraft unit. -- @return #number Altitude in meters wrt carrier height. -function AIRBOSS:_GetAltCarrier(unit) +function AIRBOSS:_GetAltCarrier( unit ) -- TODO: Value 4 meters is for the Hornet. Adjust for Harrier, A4E and -- Altitude of unit corrected by the deck height of the carrier. - local h=unit:GetAltitude()-self.carrierparam.deckheight-2 + local h = unit:GetAltitude() - self.carrierparam.deckheight - 2 return h end @@ -11700,60 +10989,60 @@ end function AIRBOSS:_GetOptLandingCoordinate() -- Start with stern coordiante. - self.landingcoord:UpdateFromCoordinate(self:_GetSternCoord()) + self.landingcoord:UpdateFromCoordinate( self:_GetSternCoord() ) -- Stern coordinate. - --local stern=self:_GetSternCoord() + -- local stern=self:_GetSternCoord() -- Final bearing. - local FB=self:GetFinalBearing(false) + local FB = self:GetFinalBearing( false ) - if self.carriertype==AIRBOSS.CarrierType.TARAWA then + if self.carriertype == AIRBOSS.CarrierType.TARAWA then -- Landing 100 ft abeam, 120 ft alt. - self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate()):Translate(35, FB-90, true, true) - --stern=self:_GetLandingSpotCoordinate():Translate(35, FB-90) + self.landingcoord:UpdateFromCoordinate( self:_GetLandingSpotCoordinate() ):Translate( 35, FB - 90, true, true ) + -- stern=self:_GetLandingSpotCoordinate():Translate(35, FB-90) -- Alitude 120 ft. - self.landingcoord:SetAltitude(UTILS.FeetToMeters(120)) - elseif self.carriertype==AIRBOSS.CarrierType.AMERICA then + self.landingcoord:SetAltitude( UTILS.FeetToMeters( 120 ) ) + elseif self.carriertype == AIRBOSS.CarrierType.AMERICA then -- Landing 100 ft abeam, 120 ft alt. To allow adjustments to match different deck configurations. - self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate()):Translate(35, FB-90, true, true) - --stern=self:_GetLandingSpotCoordinate():Translate(35, FB-90) + self.landingcoord:UpdateFromCoordinate( self:_GetLandingSpotCoordinate() ):Translate( 35, FB - 90, true, true ) + -- stern=self:_GetLandingSpotCoordinate():Translate(35, FB-90) -- Alitude 120 ft. - self.landingcoord:SetAltitude(UTILS.FeetToMeters(120)) + self.landingcoord:SetAltitude( UTILS.FeetToMeters( 120 ) ) - elseif self.carriertype==AIRBOSS.CarrierType.JCARLOS then + elseif self.carriertype == AIRBOSS.CarrierType.JCARLOS then -- Landing 100 ft abeam, 120 ft alt. - self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate()):Translate(35, FB-90, true, true) - --stern=self:_GetLandingSpotCoordinate():Translate(35, FB-90) + self.landingcoord:UpdateFromCoordinate( self:_GetLandingSpotCoordinate() ):Translate( 35, FB - 90, true, true ) + -- stern=self:_GetLandingSpotCoordinate():Translate(35, FB-90) -- Alitude 120 ft. - self.landingcoord:SetAltitude(UTILS.FeetToMeters(120)) - - elseif self.carriertype==AIRBOSS.CarrierType.CANBERRA then + self.landingcoord:SetAltitude( UTILS.FeetToMeters( 120 ) ) + + elseif self.carriertype == AIRBOSS.CarrierType.CANBERRA then -- Landing 100 ft abeam, 120 ft alt. - self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate()):Translate(35, FB-90, true, true) - --stern=self:_GetLandingSpotCoordinate():Translate(35, FB-90) + self.landingcoord:UpdateFromCoordinate( self:_GetLandingSpotCoordinate() ):Translate( 35, FB - 90, true, true ) + -- stern=self:_GetLandingSpotCoordinate():Translate(35, FB-90) -- Alitude 120 ft. - self.landingcoord:SetAltitude(UTILS.FeetToMeters(120)) - + self.landingcoord:SetAltitude( UTILS.FeetToMeters( 120 ) ) + else -- Ideally we want to land between 2nd and 3rd wire. if self.carrierparam.wire3 then -- We take the position of the 3rd wire to approximately account for the length of the aircraft. - local w3=self.carrierparam.wire3 - self.landingcoord:Translate(w3, FB, true, true) + local w3 = self.carrierparam.wire3 + self.landingcoord:Translate( w3, FB, true, true ) end -- Add 2 meters to account for aircraft height. - self.landingcoord.y=self.landingcoord.y+2 + self.landingcoord.y = self.landingcoord.y + 2 end @@ -11765,41 +11054,41 @@ end -- @return Core.Point#COORDINATE Primary landing spot coordinate. function AIRBOSS:_GetLandingSpotCoordinate() - self.landingspotcoord:UpdateFromCoordinate(self:_GetSternCoord()) + self.landingspotcoord:UpdateFromCoordinate( self:_GetSternCoord() ) -- Stern coordinate. - --local stern=self:_GetSternCoord() + -- local stern=self:_GetSternCoord() - if self.carriertype==AIRBOSS.CarrierType.TARAWA then + if self.carriertype == AIRBOSS.CarrierType.TARAWA then -- Landing 100 ft abeam, 120 alt. - local hdg=self:GetHeading() + local hdg = self:GetHeading() -- Primary landing spot 7.5 - self.landingspotcoord:Translate(57, hdg, true, true):SetAltitude(self.carrierparam.deckheight) - elseif self.carriertype==AIRBOSS.CarrierType.AMERICA then + self.landingspotcoord:Translate( 57, hdg, true, true ):SetAltitude( self.carrierparam.deckheight ) + elseif self.carriertype == AIRBOSS.CarrierType.AMERICA then -- Landing 100 ft abeam, 120 alt. - local hdg=self:GetHeading() + local hdg = self:GetHeading() -- Primary landing spot 7.5 a little further forwad on the America - self.landingspotcoord:Translate(59, hdg, true, true):SetAltitude(self.carrierparam.deckheight) + self.landingspotcoord:Translate( 59, hdg, true, true ):SetAltitude( self.carrierparam.deckheight ) - elseif self.carriertype==AIRBOSS.CarrierType.JCARLOS then + elseif self.carriertype == AIRBOSS.CarrierType.JCARLOS then -- Landing 100 ft abeam, 120 alt. - local hdg=self:GetHeading() + local hdg = self:GetHeading() -- Primary landing spot 5.0 -- Done voice for different landing Spots. - self.landingspotcoord:Translate(89, hdg, true, true):SetAltitude(self.carrierparam.deckheight) - - elseif self.carriertype==AIRBOSS.CarrierType.CANBERRA then + self.landingspotcoord:Translate( 89, hdg, true, true ):SetAltitude( self.carrierparam.deckheight ) + + elseif self.carriertype == AIRBOSS.CarrierType.CANBERRA then -- Landing 100 ft abeam, 120 alt. - local hdg=self:GetHeading() + local hdg = self:GetHeading() -- Primary landing spot 5.0 -- Done voice for different landing Spots. - self.landingspotcoord:Translate(89, hdg, true, true):SetAltitude(self.carrierparam.deckheight) + self.landingspotcoord:Translate( 89, hdg, true, true ):SetAltitude( self.carrierparam.deckheight ) end return self.landingspotcoord @@ -11809,20 +11098,20 @@ end -- @param #AIRBOSS self -- @param #boolean magnetic If true, calculate magnetic heading. By default true heading is returned. -- @return #number Carrier heading in degrees. -function AIRBOSS:GetHeading(magnetic) - self:F3({magnetic=magnetic}) +function AIRBOSS:GetHeading( magnetic ) + self:F3( { magnetic = magnetic } ) -- Carrier heading - local hdg=self.carrier:GetHeading() + local hdg = self.carrier:GetHeading() -- Include magnetic declination. if magnetic then - hdg=hdg-self.magvar + hdg = hdg - self.magvar end -- Adjust negative values. - if hdg<0 then - hdg=hdg+360 + if hdg < 0 then + hdg = hdg + 360 end return hdg @@ -11833,7 +11122,7 @@ end -- @param #AIRBOSS self -- @return #number BRC in degrees. function AIRBOSS:GetBRC() - return self:GetHeading(true) + return self:GetHeading( true ) end --- Get wind direction and speed at carrier position. @@ -11843,20 +11132,20 @@ end -- @param Core.Point#COORDINATE coord (Optional) Coordinate at which to get the wind. Default is current carrier position. -- @return #number Direction the wind is blowing **from** in degrees. -- @return #number Wind speed in m/s. -function AIRBOSS:GetWind(alt, magnetic, coord) +function AIRBOSS:GetWind( alt, magnetic, coord ) -- Current position of the carrier or input. - local cv=coord or self:GetCoordinate() + local cv = coord or self:GetCoordinate() -- Wind direction and speed. By default at 50 meters ASL. - local Wdir, Wspeed=cv:GetWind(alt or 50) + local Wdir, Wspeed = cv:GetWind( alt or 50 ) -- Include magnetic declination. if magnetic then - Wdir=Wdir-self.magvar + Wdir = Wdir - self.magvar -- Adjust negative values. - if Wdir<0 then - Wdir=Wdir+360 + if Wdir < 0 then + Wdir = Wdir + 360 end end @@ -11869,72 +11158,71 @@ end -- @return #number Wind component parallel to runway im m/s. -- @return #number Wind component perpendicular to runway in m/s. -- @return #number Total wind strength in m/s. -function AIRBOSS:GetWindOnDeck(alt) +function AIRBOSS:GetWindOnDeck( alt ) -- Position of carrier. - local cv=self:GetCoordinate() + local cv = self:GetCoordinate() -- Velocity vector of carrier. - local vc=self.carrier:GetVelocityVec3() + local vc = self.carrier:GetVelocityVec3() -- Carrier orientation X. - local xc=self.carrier:GetOrientationX() + local xc = self.carrier:GetOrientationX() -- Carrier orientation Z. - local zc=self.carrier:GetOrientationZ() + local zc = self.carrier:GetOrientationZ() -- Rotate back so that angled deck points to wind. - xc=UTILS.Rotate2D(xc, -self.carrierparam.rwyangle) - zc=UTILS.Rotate2D(zc, -self.carrierparam.rwyangle) + xc = UTILS.Rotate2D( xc, -self.carrierparam.rwyangle ) + zc = UTILS.Rotate2D( zc, -self.carrierparam.rwyangle ) -- Wind (from) vector - local vw=cv:GetWindWithTurbulenceVec3(alt or 15) + local vw = cv:GetWindWithTurbulenceVec3( alt or 15 ) -- Total wind velocity vector. -- Carrier velocity has to be negative. If carrier drives in the direction the wind is blowing from, we have less wind in total. - local vT=UTILS.VecSubstract(vw, vc) + local vT = UTILS.VecSubstract( vw, vc ) -- || Parallel component. - local vpa=UTILS.VecDot(vT,xc) + local vpa = UTILS.VecDot( vT, xc ) -- == Perpendicular component. - local vpp=UTILS.VecDot(vT,zc) + local vpp = UTILS.VecDot( vT, zc ) -- Strength. - local vabs=UTILS.VecNorm(vT) + local vabs = UTILS.VecNorm( vT ) -- We return positive values as head wind and negative values as tail wind. - --TODO: Check minus sign. + -- TODO: Check minus sign. return -vpa, vpp, vabs end - --- Get true (or magnetic) heading of carrier into the wind. This accounts for the angled runway. -- @param #AIRBOSS self -- @param #boolean magnetic If true, calculate magnetic heading. By default true heading is returned. -- @param Core.Point#COORDINATE coord (Optional) Coodinate from which heading is calculated. Default is current carrier position. -- @return #number Carrier heading in degrees. -function AIRBOSS:GetHeadingIntoWind(magnetic, coord) +function AIRBOSS:GetHeadingIntoWind( magnetic, coord ) -- Get direction the wind is blowing from. This is where we want to go. - local windfrom, vwind=self:GetWind(nil, nil, coord) + local windfrom, vwind = self:GetWind( nil, nil, coord ) -- Actually, we want the runway in the wind. - local intowind=windfrom-self.carrierparam.rwyangle + local intowind = windfrom - self.carrierparam.rwyangle -- If no wind, take current heading. - if vwind<0.1 then - intowind=self:GetHeading() + if vwind < 0.1 then + intowind = self:GetHeading() end -- Magnetic heading. if magnetic then - intowind=intowind-self.magvar + intowind = intowind - self.magvar end -- Adjust negative values. - if intowind<0 then - intowind=intowind+360 + if intowind < 0 then + intowind = intowind + 360 end return intowind @@ -11946,27 +11234,26 @@ end -- @return #number BRC into the wind in degrees. function AIRBOSS:GetBRCintoWind() -- BRC is the magnetic heading. - return self:GetHeadingIntoWind(true) + return self:GetHeadingIntoWind( true ) end - --- Get final bearing (FB) of carrier. -- By default, the routine returns the magnetic FB depending on the current map (Caucasus, NTTR, Normandy, Persion Gulf etc). -- The true bearing can be obtained by setting the *TrueNorth* parameter to true. -- @param #AIRBOSS self -- @param #boolean magnetic If true, magnetic FB is returned. -- @return #number FB in degrees. -function AIRBOSS:GetFinalBearing(magnetic) +function AIRBOSS:GetFinalBearing( magnetic ) -- First get the heading. - local fb=self:GetHeading(magnetic) + local fb = self:GetHeading( magnetic ) -- Final baring = BRC including angled deck. - fb=fb+self.carrierparam.rwyangle + fb = fb + self.carrierparam.rwyangle -- Adjust negative values. - if fb<0 then - fb=fb+360 + if fb < 0 then + fb = fb + 360 end return fb @@ -11984,56 +11271,56 @@ end -- @param #boolean offset If true, inlcude holding offset. -- @param #boolean inverse Return inverse, i.e. radial-180 degrees. -- @return #number Radial in degrees. -function AIRBOSS:GetRadial(case, magnetic, offset, inverse) +function AIRBOSS:GetRadial( case, magnetic, offset, inverse ) -- Case or current case. - case=case or self.case + case = case or self.case -- Radial. local radial -- Select case. - if case==1 then + if case == 1 then -- Get radial. - radial=self:GetFinalBearing(magnetic)-180 + radial = self:GetFinalBearing( magnetic ) - 180 - elseif case==2 then + elseif case == 2 then -- Radial wrt to heading of carrier. - radial=self:GetHeading(magnetic)-180 + radial = self:GetHeading( magnetic ) - 180 -- Holding offset angle (+-15 or 30 degrees usually) if offset then - radial=radial+self.holdingoffset + radial = radial + self.holdingoffset end - elseif case==3 then + elseif case == 3 then -- Radial wrt angled runway. - radial=self:GetFinalBearing(magnetic)-180 + radial = self:GetFinalBearing( magnetic ) - 180 -- Holding offset angle (+-15 or 30 degrees usually) if offset then - radial=radial+self.holdingoffset + radial = radial + self.holdingoffset end end -- Adjust for negative values. - if radial<0 then - radial=radial+360 + if radial < 0 then + radial = radial + 360 end -- Inverse? if inverse then -- Inverse radial - radial=radial-180 + radial = radial - 180 -- Adjust for negative values. - if radial<0 then - radial=radial+360 + if radial < 0 then + radial = radial + 360 end end @@ -12046,19 +11333,19 @@ end -- @param #number hdg1 Heading one. -- @param #number hdg2 Heading two. -- @return #number Difference between the two headings in degrees. -function AIRBOSS:_GetDeltaHeading(hdg1, hdg2) +function AIRBOSS:_GetDeltaHeading( hdg1, hdg2 ) - local V={} --DCS#Vec3 - V.x=math.cos(math.rad(hdg1)) - V.y=0 - V.z=math.sin(math.rad(hdg1)) + local V = {} -- DCS#Vec3 + V.x = math.cos( math.rad( hdg1 ) ) + V.y = 0 + V.z = math.sin( math.rad( hdg1 ) ) - local W={} --DCS#Vec3 - W.x=math.cos(math.rad(hdg2)) - W.y=0 - W.z=math.sin(math.rad(hdg2)) + local W = {} -- DCS#Vec3 + W.x = math.cos( math.rad( hdg2 ) ) + W.y = 0 + W.z = math.sin( math.rad( hdg2 ) ) - local alpha=UTILS.VecAngle(V,W) + local alpha = UTILS.VecAngle( V, W ) return alpha end @@ -12070,24 +11357,25 @@ end -- @param Wrapper.Unit#UNIT unit Player unit. -- @param #boolean runway (Optional) If true, return relative heading of unit wrt to angled runway of the carrier. -- @return #number Relative heading in degrees. An angle of 0 means, unit fly parallel to carrier. An angle of + or - 90 degrees means, unit flies perpendicular to carrier. -function AIRBOSS:_GetRelativeHeading(unit, runway) +function AIRBOSS:_GetRelativeHeading( unit, runway ) -- Direction vector of the carrier. - local vC=self.carrier:GetOrientationX() + local vC = self.carrier:GetOrientationX() -- Include runway angle. if runway then - vC=UTILS.Rotate2D(vC, -self.carrierparam.rwyangle) + vC = UTILS.Rotate2D( vC, -self.carrierparam.rwyangle ) end -- Direction vector of the unit. - local vP=unit:GetOrientationX() + local vP = unit:GetOrientationX() -- We only want the X-Z plane. Aircraft could fly parallel but ballistic and we dont want the "pitch" angle. - vC.y=0 ; vP.y=0 + vC.y = 0; + vP.y = 0 -- Get angle between the two orientation vectors in degrees. - local rhdg=UTILS.VecAngle(vC,vP) + local rhdg = UTILS.VecAngle( vC, vP ) -- Return heading in degrees. return rhdg @@ -12097,20 +11385,20 @@ end -- @param #AIRBOSS self -- @param Wrapper.Unit#UNIT unit Player unit. -- @return #number Relative velocity in m/s. -function AIRBOSS:_GetRelativeVelocity(unit) +function AIRBOSS:_GetRelativeVelocity( unit ) - local vC=self.carrier:GetVelocityVec3() - local vP=unit:GetVelocityVec3() + local vC = self.carrier:GetVelocityVec3() + local vP = unit:GetVelocityVec3() -- Only X-Z plane is necessary here. - vC.y=0 ; vP.y=0 + vC.y = 0; + vP.y = 0 - local v=UTILS.VecSubstract(vP, vC) + local v = UTILS.VecSubstract( vP, vC ) - return UTILS.VecNorm(v),v + return UTILS.VecNorm( v ), v end - --- Calculate distances between carrier and aircraft unit. -- @param #AIRBOSS self -- @param Wrapper.Unit#UNIT unit Aircraft unit. @@ -12118,42 +11406,41 @@ end -- @return #number Distance [m] perpendicular to the orientation of the carrier. -- @return #number Distance [m] to the carrier. -- @return #number Angle [Deg] from carrier to plane. Phi=0 if the plane is directly behind the carrier, phi=90 if the plane is starboard, phi=180 if the plane is in front of the carrier. -function AIRBOSS:_GetDistances(unit) +function AIRBOSS:_GetDistances( unit ) -- Vector to carrier - local a=self.carrier:GetVec3() + local a = self.carrier:GetVec3() -- Vector to player - local b=unit:GetVec3() + local b = unit:GetVec3() -- Vector from carrier to player. - local c={x=b.x-a.x, y=0, z=b.z-a.z} + local c = { x = b.x - a.x, y = 0, z = b.z - a.z } -- Orientation of carrier. - local x=self.carrier:GetOrientationX() + local x = self.carrier:GetOrientationX() -- Projection of player pos on x component. - local dx=UTILS.VecDot(x,c) + local dx = UTILS.VecDot( x, c ) -- Orientation of carrier. - local z=self.carrier:GetOrientationZ() + local z = self.carrier:GetOrientationZ() -- Projection of player pos on z component. - local dz=UTILS.VecDot(z,c) + local dz = UTILS.VecDot( z, c ) -- Polar coordinates. - local rho=math.sqrt(dx*dx+dz*dz) - + local rho = math.sqrt( dx * dx + dz * dz ) -- Not exactly sure any more what I wanted to calculate here. - local phi=math.deg(math.atan2(dz,dx)) + local phi = math.deg( math.atan2( dz, dx ) ) -- Correct for negative values. - if phi<0 then - phi=phi+360 + if phi < 0 then + phi = phi + 360 end - return dx,dz,rho,phi + return dx, dz, rho, phi end --- Check limits for reaching next step. @@ -12162,26 +11449,24 @@ end -- @param #number Z Z position of player unit. -- @param #AIRBOSS.Checkpoint check Checkpoint. -- @return #boolean If true, checkpoint condition for next step was reached. -function AIRBOSS:_CheckLimits(X, Z, check) +function AIRBOSS:_CheckLimits( X, Z, check ) -- Limits - 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 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)) -- Proceed to next step if all conditions are fullfilled. - local next=nextXmin and nextXmax and nextZmin and nextZmax + local next = nextXmin and nextXmax and nextZmin and nextZmax -- Debug info. - 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) + 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 - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- LSO functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -12191,93 +11476,92 @@ end -- @param #AIRBOSS.PlayerData playerData Player data table. -- @param #number glideslopeError Error in degrees. -- @param #number lineupError Error in degrees. -function AIRBOSS:_LSOadvice(playerData, glideslopeError, lineupError) +function AIRBOSS:_LSOadvice( playerData, glideslopeError, lineupError ) -- Advice time. - local advice=0 + local advice = 0 -- Glideslope high/low calls. - if glideslopeError>self.gle.HIGH then --1.5 then + if glideslopeError > self.gle.HIGH then -- 1.5 then -- "You're high!" - self:RadioTransmission(self.LSORadio, self.LSOCall.HIGH, true, nil, nil, true) - advice=advice+self.LSOCall.HIGH.duration - elseif glideslopeError>self.gle.High then --0.8 then + self:RadioTransmission( self.LSORadio, self.LSOCall.HIGH, true, nil, nil, true ) + advice = advice + self.LSOCall.HIGH.duration + elseif glideslopeError > self.gle.High then -- 0.8 then -- "You're high." - self:RadioTransmission(self.LSORadio, self.LSOCall.HIGH, false, nil, nil, true) - advice=advice+self.LSOCall.HIGH.duration - elseif glideslopeErrorself.lue.RIGHT then --3 then + self:RadioTransmission( self.LSORadio, self.LSOCall.COMELEFT, false, nil, nil, true ) + advice = advice + self.LSOCall.COMELEFT.duration + elseif lineupError > self.lue.RIGHT then -- 3 then -- "Right for lineup!" - self:RadioTransmission(self.LSORadio, self.LSOCall.RIGHTFORLINEUP, true, nil, nil, true) - advice=advice+self.LSOCall.RIGHTFORLINEUP.duration - elseif lineupError>self.lue.Right then -- 1 then + self:RadioTransmission( self.LSORadio, self.LSOCall.RIGHTFORLINEUP, true, nil, nil, true ) + advice = advice + self.LSOCall.RIGHTFORLINEUP.duration + elseif lineupError > self.lue.Right then -- 1 then -- "Right for lineup." - self:RadioTransmission(self.LSORadio, self.LSOCall.RIGHTFORLINEUP, false, nil, nil, true) - advice=advice+self.LSOCall.RIGHTFORLINEUP.duration + self:RadioTransmission( self.LSORadio, self.LSOCall.RIGHTFORLINEUP, false, nil, nil, true ) + advice = advice + self.LSOCall.RIGHTFORLINEUP.duration else -- "Good lineup." end -- Get current AoA. - local AOA=playerData.unit:GetAoA() + local AOA = playerData.unit:GetAoA() -- Get aircraft AoA parameters. - local acaoa=self:_GetAircraftAoA(playerData) + local acaoa = self:_GetAircraftAoA( playerData ) -- Speed via AoA - not for the Harrier. - if playerData.actype~=AIRBOSS.AircraftCarrier.AV8B then - if AOA>acaoa.SLOW then + if playerData.actype ~= AIRBOSS.AircraftCarrier.AV8B then + if AOA > acaoa.SLOW then -- "Your're slow!" - self:RadioTransmission(self.LSORadio, self.LSOCall.SLOW, true, nil, nil, true) - advice=advice+self.LSOCall.SLOW.duration - --S=underline("SLO") - elseif AOA>acaoa.Slow then + self:RadioTransmission( self.LSORadio, self.LSOCall.SLOW, true, nil, nil, true ) + advice = advice + self.LSOCall.SLOW.duration + -- S=underline("SLO") + elseif AOA > acaoa.Slow then -- "Your're slow." - self:RadioTransmission(self.LSORadio, self.LSOCall.SLOW, false, nil, nil, true) - advice=advice+self.LSOCall.SLOW.duration - --S="SLO" - elseif AOA>acaoa.OnSpeedMax then + self:RadioTransmission( self.LSORadio, self.LSOCall.SLOW, false, nil, nil, true ) + advice = advice + self.LSOCall.SLOW.duration + -- S="SLO" + elseif AOA > acaoa.OnSpeedMax then -- No call. - --S=little("SLO") - elseif AOA=76 then -- VSTOL Early Hover stop selection slow to Abeam LDG Spot AV-8B. - grade="SLOW V/STOL Groove" + local grade = "" + if t < 9 then + grade = "_NESA_" + elseif t < 15 then + grade = "NESA" + elseif t < 19 then + grade = "OK Groove" + elseif t <= 24 then + grade = "(LIG)" + -- Time in groove for AV-8B + elseif playerData.actype == AIRBOSS.AircraftCarrier.AV8B and t < 55 then -- VSTOL Late Hover stop selection too fast to Abeam LDG Spot AV-8B. + grade = "FAST V/STOL Groove" + elseif playerData.actype == AIRBOSS.AircraftCarrier.AV8B and t < 75 then -- VSTOL Operations with AV-8B. + grade = "OK V/STOL Groove" + elseif playerData.actype == AIRBOSS.AircraftCarrier.AV8B and t >= 76 then -- VSTOL Early Hover stop selection slow to Abeam LDG Spot AV-8B. + grade = "SLOW V/STOL Groove" else - grade="LIG" + grade = "LIG" end -- The unicorn! - if t>=16.4 and t<=16.6 then - grade="_OK_" + if t >= 16.4 and t <= 16.6 then + grade = "_OK_" end -- V/STOL Unicorn! - if playerData.actype==AIRBOSS.AircraftCarrier.AV8B and (t>=60.0 and t<=65.0) then - grade="_OK_ V/STOL" + if playerData.actype == AIRBOSS.AircraftCarrier.AV8B and (t >= 60.0 and t <= 65.0) then + grade = "_OK_ V/STOL" end return grade @@ -12348,82 +11632,82 @@ end -- @return #string LSO grade, i.g. _OK_, OK, (OK), --, etc. -- @return #number Points. -- @return #string LSO analysis of flight path. -function AIRBOSS:_LSOgrade(playerData) +function AIRBOSS:_LSOgrade( playerData ) --- Count deviations. - local function count(base, pattern) - return select(2, string.gsub(base, pattern, "")) + local function count( base, pattern ) + return select( 2, string.gsub( base, pattern, "" ) ) end -- Analyse flight data and convert to LSO text. - 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 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 ) -- Put everything together. - local G=GXX.." "..GIM.." ".." "..GIC.." "..GAR + local G = GXX .. " " .. GIM .. " " .. " " .. GIC .. " " .. GAR -- Count number of minor, normal and major deviations. - local N=nXX+nIM+nIC+nAR - local nL=count(G, '_')/2 - local nS=count(G, '%(') - local nN=N-nS-nL + local N = nXX + nIM + nIC + nAR + local nL = count( G, '_' ) / 2 + local nS = count( G, '%(' ) + local nN = N - nS - nL -- Groove time 15-18.99 sec for a unicorn. Or 65-70 for V/STOL unicorn. - local Tgroove=playerData.Tgroove - local TgrooveUnicorn=Tgroove and (Tgroove>=15.0 and Tgroove<=18.99) or false - local TgrooveVstolUnicorn=Tgroove and (Tgroove>=60.0 and Tgroove<=65.0)and playerData.actype==AIRBOSS.AircraftCarrier.AV8B or false + local Tgroove = playerData.Tgroove + local TgrooveUnicorn = Tgroove and (Tgroove >= 15.0 and Tgroove <= 18.99) or false + local TgrooveVstolUnicorn = Tgroove and (Tgroove >= 60.0 and Tgroove <= 65.0) and playerData.actype == AIRBOSS.AircraftCarrier.AV8B or false local grade local points - if N==0 and (TgrooveUnicorn or TgrooveVstolUnicorn ) then + if N == 0 and (TgrooveUnicorn or TgrooveVstolUnicorn) then -- No deviations, should be REALLY RARE! - grade="_OK_" - points=5.0 - G="Unicorn" + grade = "_OK_" + points = 5.0 + G = "Unicorn" else -- Add AV-8B Harrier devation allowances due to lower groundspeed and 3x conventional groove time, this allows to maintain LSO tolerances while respecting the deviations are not unsafe. (WIP requires feedback) - -- Large devaitions still result in a No Grade, A Unicorn still requires a clean pass with no deviation. - if nL>3 and playerData.actype==AIRBOSS.AircraftCarrier.AV8B then + -- Large devaitions still result in a No Grade, A Unicorn still requires a clean pass with no deviation. + if nL > 3 and playerData.actype == AIRBOSS.AircraftCarrier.AV8B then -- Larger deviations ==> "No grade" 2.0 points. - grade="--" - points=2.0 - elseif nN>2 and playerData.actype==AIRBOSS.AircraftCarrier.AV8B then + grade = "--" + points = 2.0 + elseif nN > 2 and playerData.actype == AIRBOSS.AircraftCarrier.AV8B then -- Only average deviations ==> "Fair Pass" Pass with average deviations and corrections. - grade="(OK)" - points=3.0 - elseif nL>0 then + grade = "(OK)" + points = 3.0 + elseif nL > 0 then -- Larger deviations ==> "No grade" 2.0 points. - grade="--" - points=2.0 - elseif nN>0 then + grade = "--" + points = 2.0 + elseif nN > 0 then -- No larger but average deviations ==> "Fair Pass" Pass with average deviations and corrections. - grade="(OK)" - points=3.0 + grade = "(OK)" + points = 3.0 else -- Only minor corrections - grade="OK" - points=4.0 + grade = "OK" + points = 4.0 end -end + end -- Replace" )"( and "__" - G=G:gsub("%)%(", "") - G=G:gsub("__","") + G = G:gsub( "%)%(", "" ) + G = G:gsub( "__", "" ) -- Debug info - 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) - + 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 ) + -- Special cases. if playerData.wop then --------------------- @@ -12432,65 +11716,65 @@ end if playerData.lig then -- Long In the Groove (LIG). -- According to Stingers this is a CUT pass and gives 1.0 points. - grade="WO" - points=1.0 - G="LIG" + grade = "WO" + points = 1.0 + G = "LIG" else -- Other pattern WO - grade="WOP" - points=2.0 - G="n/a" + grade = "WOP" + points = 2.0 + G = "n/a" end elseif playerData.wofd then ----------------------- -- Foul Deck Waveoff -- ----------------------- if playerData.landed then - --AIRBOSS wants to talk to you! - grade="CUT" - points=0.0 + -- AIRBOSS wants to talk to you! + grade = "CUT" + points = 0.0 else - grade="WOFD" - points=-1.0 + grade = "WOFD" + points = -1.0 end - G="n/a" + G = "n/a" elseif playerData.owo then ----------------- -- Own Waveoff -- ----------------- - grade="OWO" - points=2.0 - if N==0 then - G="n/a" + grade = "OWO" + points = 2.0 + if N == 0 then + G = "n/a" end elseif playerData.waveoff then ------------- -- Waveoff -- ------------- if playerData.landed then - --AIRBOSS wants to talk to you! - grade="CUT" - points=0.0 + -- AIRBOSS wants to talk to you! + grade = "CUT" + points = 0.0 else - grade="WO" - points=1.0 + grade = "WO" + points = 1.0 end elseif playerData.boltered then -- Bolter - grade="-- (BOLTER)" - points=2.5 - - elseif not playerData.hover and playerData.actype==AIRBOSS.AircraftCarrier.AV8B then + grade = "-- (BOLTER)" + points = 2.5 + + elseif not playerData.hover and playerData.actype == AIRBOSS.AircraftCarrier.AV8B then ------------------------------- -- AV-8B not cleared to land -- -- Landing clearence is carrier from LC to Landing - ------------------------------- + ------------------------------- if playerData.landed then -- AIRBOSS wants your balls! - grade="CUT" - points=0.0 + grade = "CUT" + points = 0.0 end - - end + + end return grade, points, G end @@ -12501,175 +11785,171 @@ end -- @param #AIRBOSS.GrooveData fdata Flight data in the groove. -- @return #string LSO grade or empty string if flight data table is nil. -- @return #number Number of deviations from perfect flight path. -function AIRBOSS:_Flightdata2Text(playerData, groovestep) +function AIRBOSS:_Flightdata2Text( playerData, groovestep ) - local function little(text) - return string.format("(%s)",text) + local function little( text ) + return string.format( "(%s)", text ) end - local function underline(text) - return string.format("_%s_", text) + local function underline( text ) + return string.format( "_%s_", text ) end -- Groove Data. - local fdata=playerData.groove[groovestep] --#AIRBOSS.GrooveData + local fdata = playerData.groove[groovestep] -- #AIRBOSS.GrooveData -- No flight data ==> return empty string. - if fdata==nil then - self:T3(self.lid.."Flight data is nil.") + if fdata == nil then + self:T3( self.lid .. "Flight data is nil." ) return "", 0 end -- Flight data. - local step=fdata.Step - local AOA=fdata.AoA - local GSE=fdata.GSE - local LUE=fdata.LUE - local ROL=fdata.Roll + local step = fdata.Step + local AOA = fdata.AoA + local GSE = fdata.GSE + local LUE = fdata.LUE + local ROL = fdata.Roll -- Aircraft specific AoA values. - local acaoa=self:_GetAircraftAoA(playerData) + local acaoa = self:_GetAircraftAoA( playerData ) - --Angled Approach. - 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") + -- Angled Approach. + 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 - --Overshoot Start. - 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 AOA acaoa.SLOW then + S = underline( "SLO" ) + elseif AOA > acaoa.Slow then + S = "SLO" + elseif AOA > acaoa.OnSpeedMax then + S = little( "SLO" ) + elseif AOA < acaoa.FAST then + S = underline( "F" ) + elseif AOA < acaoa.Fast then + S = "F" + elseif AOA < acaoa.OnSpeedMin then + S = little( "F" ) end -- Glideslope/altitude. Good [-0.3, 0.4] asymmetric! - local A=nil - if GSE>self.gle.HIGH then - A=underline("H") - elseif GSE>self.gle.High then - A="H" - elseif GSE>self.gle._max then - A=little("H") - elseif GSE self.gle.HIGH then + A = underline( "H" ) + elseif GSE > self.gle.High then + A = "H" + elseif GSE > self.gle._max then + A = little( "H" ) + elseif GSE < self.gle.LOW then + A = underline( "LO" ) + elseif GSE < self.gle.Low then + A = "LO" + elseif GSE < self.gle._min then + A = little( "LO" ) end -- Line up. XX Step replaced by Overshoot start (OS). Good [-0.5, 0.5] - local D=nil - if LUE>self.lue.RIGHT then - D=underline("LUL") - elseif LUE>self.lue.Right then - D="LUL" - elseif LUE>self.lue._max then - D=little("LUL") - elseif playerData.case<3 then - if LUE self.lue.RIGHT then + D = underline( "LUL" ) + elseif LUE > self.lue.Right then + D = "LUL" + elseif LUE > self.lue._max then + D = little( "LUL" ) + elseif playerData.case < 3 then + if LUE < self.lue.LEFT and step ~= AIRBOSS.PatternStep.GROOVE_XX then + D = underline( "LUR" ) + elseif LUE < self.lue.Left and step ~= AIRBOSS.PatternStep.GROOVE_XX then + D = "LUR" + elseif LUE < self.lue._min and step ~= AIRBOSS.PatternStep.GROOVE_XX then + D = little( "LUR" ) + end + elseif playerData.case == 3 then + if LUE < self.lue.LEFT then + D = underline( "LUR" ) + elseif LUE < self.lue.Left then + D = "LUR" + elseif LUE < self.lue._min then + D = little( "LUR" ) + end end -- Compile. - local G="" - local n=0 + local G = "" + local n = 0 -- Fly trough. if fdata.FlyThrough then - G=G..fdata.FlyThrough + G = G .. fdata.FlyThrough end -- Angled Approach - doesn't affect score, advisory only. if P then - G=G..P - n=n + G = G .. P + n = n end -- Speed. if S then - G=G..S - n=n+1 + G = G .. S + n = n + 1 end -- Glide slope. if A then - G=G..A - n=n+1 + G = G .. A + n = n + 1 end -- Line up. if D then - G=G..D - n=n+1 + G = G .. D + n = n + 1 end - --Drift in Lineup + -- Drift in Lineup if fdata.Drift then - G=G..fdata.Drift - n=n -- Drift doesn't affect score, advisory only. + G = G .. fdata.Drift + n = n -- Drift doesn't affect score, advisory only. end -- Overshoot. if O then - G=G..O - n=n+1 + G = G .. O + n = n + 1 end -- Add current step. - local step=self:_GS(step) - step=step:gsub("XX","X") - if G~="" then - G=G..step + local step = self:_GS( step ) + step = step:gsub( "XX", "X" ) + if G ~= "" then + G = G .. step end -- Debug info. - local text=string.format("LSO Grade at %s:\n", step) - text=text..string.format("AOA=%.1f\n",AOA) - text=text..string.format("GSE=%.1f\n",GSE) - text=text..string.format("LUE=%.1f\n",LUE) - text=text..string.format("ROL=%.1f\n",ROL) - text=text..G - self:T3(self.lid..text) + local text = string.format( "LSO Grade at %s:\n", step ) + text = text .. string.format( "AOA=%.1f\n", AOA ) + text = text .. string.format( "GSE=%.1f\n", GSE ) + text = text .. string.format( "LUE=%.1f\n", LUE ) + text = text .. string.format( "ROL=%.1f\n", ROL ) + text = text .. G + self:T3( self.lid .. text ) - return G,n + return G, n end --- Get short name of the grove step. @@ -12677,69 +11957,69 @@ end -- @param #string step Player step. -- @param #number n Use -1 for previous or +1 for next. Default 0. -- @return #string Shortcut name "X", "RB", "IM", "AR", "IW". -function AIRBOSS:_GS(step, n) +function AIRBOSS:_GS( step, n ) local gp - n=n or 0 + n = n or 0 - if step==AIRBOSS.PatternStep.FINAL then - gp=AIRBOSS.GroovePos.X0 --"X0" -- Entering the groove. - if n==-1 then - gp=AIRBOSS.GroovePos.X0 -- There is no previous step. - elseif n==1 then - gp=AIRBOSS.GroovePos.XX + if step == AIRBOSS.PatternStep.FINAL then + gp = AIRBOSS.GroovePos.X0 -- "X0" -- Entering the groove. + if n == -1 then + gp = AIRBOSS.GroovePos.X0 -- There is no previous step. + elseif n == 1 then + gp = AIRBOSS.GroovePos.XX end - elseif step==AIRBOSS.PatternStep.GROOVE_XX then - gp=AIRBOSS.GroovePos.XX --"XX" -- Starting the groove. - if n==-1 then - gp=AIRBOSS.GroovePos.X0 - elseif n==1 then - gp=AIRBOSS.GroovePos.IM + elseif step == AIRBOSS.PatternStep.GROOVE_XX then + gp = AIRBOSS.GroovePos.XX -- "XX" -- Starting the groove. + if n == -1 then + gp = AIRBOSS.GroovePos.X0 + elseif n == 1 then + gp = AIRBOSS.GroovePos.IM end - elseif step==AIRBOSS.PatternStep.GROOVE_IM then - gp=AIRBOSS.GroovePos.IM --"IM" -- In the middle. - if n==-1 then - gp=AIRBOSS.GroovePos.XX - elseif n==1 then - gp=AIRBOSS.GroovePos.IC + elseif step == AIRBOSS.PatternStep.GROOVE_IM then + gp = AIRBOSS.GroovePos.IM -- "IM" -- In the middle. + if n == -1 then + gp = AIRBOSS.GroovePos.XX + elseif n == 1 then + gp = AIRBOSS.GroovePos.IC end - elseif step==AIRBOSS.PatternStep.GROOVE_IC then - gp=AIRBOSS.GroovePos.IC --"IC" -- In close. - if n==-1 then - gp=AIRBOSS.GroovePos.IM - elseif n==1 then - gp=AIRBOSS.GroovePos.AR + elseif step == AIRBOSS.PatternStep.GROOVE_IC then + gp = AIRBOSS.GroovePos.IC -- "IC" -- In close. + if n == -1 then + gp = AIRBOSS.GroovePos.IM + elseif n == 1 then + gp = AIRBOSS.GroovePos.AR end - elseif step==AIRBOSS.PatternStep.GROOVE_AR then - gp=AIRBOSS.GroovePos.AR --"AR" -- At the ramp. - if n==-1 then - gp=AIRBOSS.GroovePos.IC - elseif n==1 then - if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then - gp=AIRBOSS.GroovePos.AL + elseif step == AIRBOSS.PatternStep.GROOVE_AR then + gp = AIRBOSS.GroovePos.AR -- "AR" -- At the ramp. + if n == -1 then + gp = AIRBOSS.GroovePos.IC + elseif n == 1 then + if self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + gp = AIRBOSS.GroovePos.AL else - gp=AIRBOSS.GroovePos.IW + gp = AIRBOSS.GroovePos.IW end end - elseif step==AIRBOSS.PatternStep.GROOVE_AL then - gp=AIRBOSS.GroovePos.AL --"AL" -- Abeam landing spot. - if n==-1 then - gp=AIRBOSS.GroovePos.AR - elseif n==1 then - gp=AIRBOSS.GroovePos.LC + elseif step == AIRBOSS.PatternStep.GROOVE_AL then + gp = AIRBOSS.GroovePos.AL -- "AL" -- Abeam landing spot. + if n == -1 then + gp = AIRBOSS.GroovePos.AR + elseif n == 1 then + gp = AIRBOSS.GroovePos.LC end - elseif step==AIRBOSS.PatternStep.GROOVE_LC then - gp=AIRBOSS.GroovePos.LC --"LC" -- Level crossing. - if n==-1 then - gp=AIRBOSS.GroovePos.AL - elseif n==1 then - gp=AIRBOSS.GroovePos.LC + elseif step == AIRBOSS.PatternStep.GROOVE_LC then + gp = AIRBOSS.GroovePos.LC -- "LC" -- Level crossing. + if n == -1 then + gp = AIRBOSS.GroovePos.AL + elseif n == 1 then + gp = AIRBOSS.GroovePos.LC end - elseif step==AIRBOSS.PatternStep.GROOVE_IW then - gp=AIRBOSS.GroovePos.IW --"IW" -- In the wires. - if n==-1 then - gp=AIRBOSS.GroovePos.AR - elseif n==1 then - gp=AIRBOSS.GroovePos.IW -- There is no next step. + elseif step == AIRBOSS.PatternStep.GROOVE_IW then + gp = AIRBOSS.GroovePos.IW -- "IW" -- In the wires. + if n == -1 then + gp = AIRBOSS.GroovePos.AR + elseif n == 1 then + gp = AIRBOSS.GroovePos.IW -- There is no next step. end end return gp @@ -12751,21 +12031,21 @@ end -- @param #number Z Z distance player to carrier. -- @param #AIRBOSS.Checkpoint pos Position data limits. -- @return #boolean If true, approach should be aborted. -function AIRBOSS:_CheckAbort(X, Z, pos) +function AIRBOSS:_CheckAbort( X, Z, pos ) - local abort=false - if pos.Xmin and Xpos.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 + local abort = false + if pos.Xmin and X < pos.Xmin then + self:T( string.format( "Xmin: X=%d < %d=Xmin", X, pos.Xmin ) ) + abort = true + elseif pos.Xmax and X > pos.Xmax then + self:T( string.format( "Xmax: X=%d > %d=Xmax", X, pos.Xmax ) ) + abort = true + elseif pos.Zmin and Z < pos.Zmin then + self:T( string.format( "Zmin: Z=%d < %d=Zmin", Z, pos.Zmin ) ) + abort = true + elseif pos.Zmax and Z > pos.Zmax then + self:T( string.format( "Zmax: Z=%d > %d=Zmax", Z, pos.Zmax ) ) + abort = true end return abort @@ -12776,58 +12056,58 @@ end -- @param #number X X distance player to carrier. -- @param #number Z Z distance player to carrier. -- @param #AIRBOSS.Checkpoint posData Checkpoint data. -function AIRBOSS:_TooFarOutText(X, Z, posData) +function AIRBOSS:_TooFarOutText( X, Z, posData ) -- Intro. - local text="you are too " + local text = "you are too " -- X text. - local xtext=nil - if posData.Xmin and XposData.Xmax then - if posData.Xmax>=0 then - xtext="far ahead of " + elseif posData.Xmax and X > posData.Xmax then + if posData.Xmax >= 0 then + xtext = "far ahead of " else - xtext="close to " + xtext = "close to " end end -- Z text. - local ztext=nil - if posData.Zmin and ZposData.Zmax then - if posData.Zmax>=0 then - ztext="far starboard of " + elseif posData.Zmax and Z > posData.Zmax then + if posData.Zmax >= 0 then + ztext = "far starboard of " else - ztext="too close to " + ztext = "too close to " end end -- Combine X-Z text. if xtext and ztext then - text=text..xtext.." and "..ztext + text = text .. xtext .. " and " .. ztext elseif xtext then - text=text..xtext + text = text .. xtext elseif ztext then - text=text..ztext + text = text .. ztext end -- Complete the sentence - text=text.."the carrier." + text = text .. "the carrier." -- If no case could be identified. - if xtext==nil and ztext==nil then - text="you are too far from where you should be!" + if xtext == nil and ztext == nil then + text = "you are too far from where you should be!" end return text @@ -12840,33 +12120,33 @@ end -- @param #number Z Z distance player to carrier. -- @param #AIRBOSS.Checkpoint posData Checkpoint data. -- @param #boolean patternwo (Optional) Pattern wave off. -function AIRBOSS:_AbortPattern(playerData, X, Z, posData, patternwo) +function AIRBOSS:_AbortPattern( playerData, X, Z, posData, patternwo ) -- Text where we are wrong. - local text=self:_TooFarOutText(X, Z, posData) + local text = self:_TooFarOutText( X, Z, posData ) -- Debug. - 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) + 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 ) -- Message to player. - self:MessageToPlayer(playerData, text, "LSO") + self:MessageToPlayer( playerData, text, "LSO" ) if patternwo then -- Pattern wave off! - playerData.wop=true + playerData.wop = true -- Add to debrief. - self:_AddToDebrief(playerData, string.format("Pattern wave off: %s", text)) + self:_AddToDebrief( playerData, string.format( "Pattern wave off: %s", text ) ) -- Depart and re-enter radio message. -- TODO: Radio should depend on player step. - self:RadioTransmission(self.LSORadio, self.LSOCall.DEPARTANDREENTER, false, 3, nil, nil, true) + self:RadioTransmission( self.LSORadio, self.LSOCall.DEPARTANDREENTER, false, 3, nil, nil, true ) -- Next step debrief. - playerData.step=AIRBOSS.PatternStep.DEBRIEF - playerData.warning=nil + playerData.step = AIRBOSS.PatternStep.DEBRIEF + playerData.warning = nil end end @@ -12876,7 +12156,7 @@ end -- @param #AIRBOSS.PlayerData playerData Player data table. -- @param #number delay Delay before playing sound messages. Default 0 sec. -- @param #boolean soundoff If true, don't play and sound hint. -function AIRBOSS:_PlayerHint(playerData, delay, soundoff) +function AIRBOSS:_PlayerHint( playerData, delay, soundoff ) -- No hint for the pros. if not playerData.showhints then @@ -12884,195 +12164,193 @@ function AIRBOSS:_PlayerHint(playerData, delay, soundoff) end -- Get optimal altitude, distance and speed. - local alt, aoa, dist, speed=self:_GetAircraftParameters(playerData) + local alt, aoa, dist, speed = self:_GetAircraftParameters( playerData ) -- Get altitude hint. - local hintAlt,debriefAlt,callAlt=self:_AltitudeCheck(playerData, alt) + local hintAlt, debriefAlt, callAlt = self:_AltitudeCheck( playerData, alt ) -- Get speed hint. - local hintSpeed,debriefSpeed,callSpeed=self:_SpeedCheck(playerData, speed) + local hintSpeed, debriefSpeed, callSpeed = self:_SpeedCheck( playerData, speed ) -- Get AoA hint. - local hintAoA,debriefAoA,callAoA=self:_AoACheck(playerData, aoa) + local hintAoA, debriefAoA, callAoA = self:_AoACheck( playerData, aoa ) -- Get distance to the boat hint. - local hintDist,debriefDist,callDist=self:_DistanceCheck(playerData, dist) + local hintDist, debriefDist, callDist = self:_DistanceCheck( playerData, dist ) -- Message to player. - local hint="" - if hintAlt and hintAlt~="" then - hint=hint.."\n"..hintAlt + local hint = "" + if hintAlt and hintAlt ~= "" then + hint = hint .. "\n" .. hintAlt end - if hintSpeed and hintSpeed~="" then - hint=hint.."\n"..hintSpeed + if hintSpeed and hintSpeed ~= "" then + hint = hint .. "\n" .. hintSpeed end - if hintAoA and hintAoA~="" then - hint=hint.."\n"..hintAoA + if hintAoA and hintAoA ~= "" then + hint = hint .. "\n" .. hintAoA end - if hintDist and hintDist~="" then - hint=hint.."\n"..hintDist + if hintDist and hintDist ~= "" then + hint = hint .. "\n" .. hintDist end -- Debriefing text. - local debrief="" - if debriefAlt and debriefAlt~="" then - debrief=debrief.."\n- "..debriefAlt + local debrief = "" + if debriefAlt and debriefAlt ~= "" then + debrief = debrief .. "\n- " .. debriefAlt end - if debriefSpeed and debriefSpeed~="" then - debrief=debrief.."\n- "..debriefSpeed + if debriefSpeed and debriefSpeed ~= "" then + debrief = debrief .. "\n- " .. debriefSpeed end - if debriefAoA and debriefAoA~="" then - debrief=debrief.."\n- "..debriefAoA + if debriefAoA and debriefAoA ~= "" then + debrief = debrief .. "\n- " .. debriefAoA end - if debriefDist and debriefDist~="" then - debrief=debrief.."\n- "..debriefDist + if debriefDist and debriefDist ~= "" then + debrief = debrief .. "\n- " .. debriefDist end -- Add step to debriefing. - if debrief~="" then - self:_AddToDebrief(playerData, debrief) + if debrief ~= "" then + self:_AddToDebrief( playerData, debrief ) end -- Voice hint. - delay=delay or 0 + 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 + 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 + 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 + 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 + self:Sound2Player( playerData, self.LSORadio, callDist, false, delay ) + delay = delay + callDist.duration + 0.5 end end -- ARC IN info. - if playerData.step==AIRBOSS.PatternStep.ARCIN then + if playerData.step == AIRBOSS.PatternStep.ARCIN then -- Hint turn and set TACAN. - if playerData.difficulty==AIRBOSS.Difficulty.EASY then + if playerData.difficulty == AIRBOSS.Difficulty.EASY then -- Get inverse magnetic radial without offset ==> FB for Case II or BRC for Case III. - local radial=self:GetRadial(playerData.case, true, false, true) - local turn="right" - if self.holdingoffset<0 then - turn="left" + 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) + hint = hint .. string.format( "\nTurn %s and select TACAN %03d°.", turn, radial ) end end -- DIRTUP additonal info. - 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°." + 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 - --TODO: Tomcat? - hint=hint.."\nDirty up! Hook, gear and flaps down." + -- TODO: Tomcat? + hint = hint .. "\nDirty up! Hook, gear and flaps down." end end end -- BULLSEYE additonal info. - if playerData.step==AIRBOSS.PatternStep.BULLSEYE then + if playerData.step == AIRBOSS.PatternStep.BULLSEYE then -- Hint follow the needles. - if playerData.difficulty==AIRBOSS.Difficulty.EASY then - if playerData.actype==AIRBOSS.AircraftCarrier.HORNET then - hint=hint..string.format("\nIntercept glideslope and follow the needles.") + 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.") + hint = hint .. string.format( "\nIntercept glideslope." ) end end end -- Message to player. - if hint~="" then - local text=string.format("%s%s", playerData.step, hint) - self:MessageToPlayer(playerData, hint, "AIRBOSS", "") + if hint ~= "" then + local text = string.format( "%s%s", playerData.step, hint ) + self:MessageToPlayer( playerData, hint, "AIRBOSS", "" ) end end - --- Display hint for flight students about the (next) step. Message is displayed after one second. -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data. -- @param #string step Step for which hint is given. -function AIRBOSS:_StepHint(playerData, step) +function AIRBOSS:_StepHint( playerData, step ) -- Set step. - step=step or playerData.step + step = step or playerData.step -- Message is only for "Flight Students". - if playerData.difficulty==AIRBOSS.Difficulty.EASY and playerData.showhints then + if playerData.difficulty == AIRBOSS.Difficulty.EASY and playerData.showhints then -- Get optimal parameters at step. - local alt, aoa, dist, speed=self:_GetAircraftParameters(playerData, step) + local alt, aoa, dist, speed = self:_GetAircraftParameters( playerData, step ) -- Hint: - local hint="" + local hint = "" -- Altitude. if alt then - hint=hint..string.format("\nAltitude %d ft", UTILS.MetersToFeet(alt)) + hint = hint .. string.format( "\nAltitude %d ft", UTILS.MetersToFeet( alt ) ) end -- AoA. if aoa then - hint=hint..string.format("\nAoA %.1f", self:_AoADeg2Units(playerData, aoa)) + hint = hint .. string.format( "\nAoA %.1f", self:_AoADeg2Units( playerData, aoa ) ) end -- Speed. if speed then - hint=hint..string.format("\nSpeed %d knots", UTILS.MpsToKnots(speed)) + hint = hint .. string.format( "\nSpeed %d knots", UTILS.MpsToKnots( speed ) ) end -- Distance to the boat. if dist then - hint=hint..string.format("\nDistance to the boat %.1f NM", UTILS.MetersToNM(dist)) + hint = hint .. string.format( "\nDistance to the boat %.1f NM", UTILS.MetersToNM( dist ) ) end -- Late break. - 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." + 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 -- Abeam. - 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." + 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." + hint = hint .. "\nDirty up! Gear DOWN, flaps DOWN. Check hook down." end end -- Check if there was actually anything to tell. - if hint~="" then + if hint ~= "" then -- Compile text if any. - local text=string.format("Optimal setup at next step %s:%s", step, hint) + local text = string.format( "Optimal setup at next step %s:%s", step, hint ) -- Send hint to player. - self:MessageToPlayer(playerData, text, "AIRBOSS", "", nil, false, 1) + self:MessageToPlayer( playerData, text, "AIRBOSS", "", nil, false, 1 ) end end end - --- Evaluate player's altitude at checkpoint. -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data table. @@ -13080,57 +12358,57 @@ end -- @return #string Feedback text. -- @return #string Debriefing text. -- @return #AIRBOSS.RadioCall Radio call. -function AIRBOSS:_AltitudeCheck(playerData, altopt) +function AIRBOSS:_AltitudeCheck( playerData, altopt ) - if altopt==nil then + if altopt == nil then return nil, nil end -- Player altitude. - local altitude=playerData.unit:GetAltitude() + local altitude = playerData.unit:GetAltitude() -- Get relative score. - local lowscore, badscore=self:_GetGoodBadScore(playerData) + local lowscore, badscore = self:_GetGoodBadScore( playerData ) -- Altitude error +-X% - local _error=(altitude-altopt)/altopt*100 + local _error = (altitude - altopt) / altopt * 100 -- Radio call for flight students. - local radiocall=nil --#AIRBOSS.RadioCall + local radiocall = nil -- #AIRBOSS.RadioCall - local hint="" - if _error>badscore then - --hint=string.format("You're high.") - radiocall=self:_NewRadioCall(self.LSOCall.HIGH, "Paddles", "") - elseif _error>lowscore then - --hint= string.format("You're slightly high.") - radiocall=self:_NewRadioCall(self.LSOCall.HIGH, "Paddles", "") - elseif _error<-badscore then - --hint=string.format("You're low. ") - radiocall=self:_NewRadioCall(self.LSOCall.LOW, "Paddles", "") - elseif _error<-lowscore then - --hint=string.format("You're slightly low.") - radiocall=self:_NewRadioCall(self.LSOCall.LOW, "Paddles", "") + local hint = "" + if _error > badscore then + -- hint=string.format("You're high.") + radiocall = self:_NewRadioCall( self.LSOCall.HIGH, "Paddles", "" ) + elseif _error > lowscore then + -- hint= string.format("You're slightly high.") + radiocall = self:_NewRadioCall( self.LSOCall.HIGH, "Paddles", "" ) + elseif _error < -badscore then + -- hint=string.format("You're low. ") + radiocall = self:_NewRadioCall( self.LSOCall.LOW, "Paddles", "" ) + elseif _error < -lowscore then + -- hint=string.format("You're slightly low.") + radiocall = self:_NewRadioCall( self.LSOCall.LOW, "Paddles", "" ) else - hint=string.format("Good altitude. ") + hint = string.format( "Good altitude. " ) end -- Extend or decrease depending on skill. - if playerData.difficulty==AIRBOSS.Difficulty.EASY then + if playerData.difficulty == AIRBOSS.Difficulty.EASY then -- Also inform students about the optimal altitude. - hint=hint..string.format("Optimal altitude is %d ft.", UTILS.MetersToFeet(altopt)) - elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then + hint = hint .. string.format( "Optimal altitude is %d ft.", UTILS.MetersToFeet( altopt ) ) + elseif playerData.difficulty == AIRBOSS.Difficulty.NORMAL then -- We keep it short normally. - hint="" - elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then + hint = "" + elseif playerData.difficulty == AIRBOSS.Difficulty.HARD then -- No hint at all for the pros. - hint="" + hint = "" end -- Debrief text. - local debrief=string.format("Altitude %d ft = %d%% deviation from %d ft.", UTILS.MetersToFeet(altitude), _error, UTILS.MetersToFeet(altopt)) + local debrief = string.format( "Altitude %d ft = %d%% deviation from %d ft.", UTILS.MetersToFeet( altitude ), _error, UTILS.MetersToFeet( altopt ) ) - return hint, debrief,radiocall + return hint, debrief, radiocall end --- Score for correct AoA. @@ -13140,65 +12418,65 @@ end -- @return #string Feedback message text or easy and normal difficulty level or nil for hard. -- @return #string Debriefing text. -- @return #AIRBOSS.RadioCall Radio call. -function AIRBOSS:_AoACheck(playerData, optaoa) +function AIRBOSS:_AoACheck( playerData, optaoa ) - if optaoa==nil then + if optaoa == nil then return nil, nil end -- Get relative score. - local lowscore, badscore = self:_GetGoodBadScore(playerData) + local lowscore, badscore = self:_GetGoodBadScore( playerData ) -- Player AoA - local aoa=playerData.unit:GetAoA() + local aoa = playerData.unit:GetAoA() -- Altitude error +-X% - local _error=(aoa-optaoa)/optaoa*100 + local _error = (aoa - optaoa) / optaoa * 100 -- Get aircraft AoA parameters. - local aircraftaoa=self:_GetAircraftAoA(playerData) + local aircraftaoa = self:_GetAircraftAoA( playerData ) - -- Radio call for flight students. - local radiocall=nil --#AIRBOSS.RadioCall + -- Radio call for flight students. + local radiocall = nil -- #AIRBOSS.RadioCall -- Rate aoa. - local hint="" - if aoa>=aircraftaoa.SLOW then - --hint="Your're slow!" - radiocall=self:_NewRadioCall(self.LSOCall.SLOW, "Paddles", "") - elseif aoa>=aircraftaoa.Slow then - --hint="Your're slow." - 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 - --hint="Your're fast." - radiocall=self:_NewRadioCall(self.LSOCall.FAST, "Paddles", "") + local hint = "" + if aoa >= aircraftaoa.SLOW then + -- hint="Your're slow!" + radiocall = self:_NewRadioCall( self.LSOCall.SLOW, "Paddles", "" ) + elseif aoa >= aircraftaoa.Slow then + -- hint="Your're slow." + 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 + -- hint="Your're fast." + radiocall = self:_NewRadioCall( self.LSOCall.FAST, "Paddles", "" ) else - --hint="You're fast!" - radiocall=self:_NewRadioCall(self.LSOCall.FAST, "Paddles", "") + -- hint="You're fast!" + radiocall = self:_NewRadioCall( self.LSOCall.FAST, "Paddles", "" ) end -- Extend or decrease depending on skill. - if playerData.difficulty==AIRBOSS.Difficulty.EASY then + if playerData.difficulty == AIRBOSS.Difficulty.EASY then -- Also inform students about optimal value. - hint=hint..string.format("Optimal AoA is %.1f.", self:_AoADeg2Units(playerData, optaoa)) - elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then + hint = hint .. string.format( "Optimal AoA is %.1f.", self:_AoADeg2Units( playerData, optaoa ) ) + elseif playerData.difficulty == AIRBOSS.Difficulty.NORMAL then -- We keep is short normally. - hint="" - elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then + hint = "" + elseif playerData.difficulty == AIRBOSS.Difficulty.HARD then -- No hint at all for the pros. - hint="" + hint = "" end -- Debriefing text. - local debrief=string.format("AoA %.1f = %d%% deviation from %.1f.", self:_AoADeg2Units(playerData, aoa), _error, self:_AoADeg2Units(playerData, optaoa)) + local debrief = string.format( "AoA %.1f = %d%% deviation from %.1f.", self:_AoADeg2Units( playerData, aoa ), _error, self:_AoADeg2Units( playerData, optaoa ) ) - return hint, debrief,radiocall + return hint, debrief, radiocall end --- Evaluate player's speed. @@ -13208,54 +12486,54 @@ end -- @return #string Feedback text. -- @return #string Debriefing text. -- @return #AIRBOSS.RadioCall Radio call. -function AIRBOSS:_SpeedCheck(playerData, speedopt) +function AIRBOSS:_SpeedCheck( playerData, speedopt ) - if speedopt==nil then + if speedopt == nil then return nil, nil end -- Player altitude. - local speed=playerData.unit:GetVelocityMPS() + local speed = playerData.unit:GetVelocityMPS() -- Get relative score. - local lowscore, badscore=self:_GetGoodBadScore(playerData) + local lowscore, badscore = self:_GetGoodBadScore( playerData ) -- Altitude error +-X% - local _error=(speed-speedopt)/speedopt*100 + local _error = (speed - speedopt) / speedopt * 100 - -- Radio call for flight students. - local radiocall=nil --#AIRBOSS.RadioCall + -- Radio call for flight students. + local radiocall = nil -- #AIRBOSS.RadioCall - local hint="" - if _error>badscore then - --hint=string.format("You're fast.") - radiocall=self:_NewRadioCall(self.LSOCall.FAST, "AIRBOSS", "") - elseif _error>lowscore then - --hint= string.format("You're slightly fast.") - radiocall=self:_NewRadioCall(self.LSOCall.FAST, "AIRBOSS", "") - elseif _error<-badscore then - --hint=string.format("You're slow.") - radiocall=self:_NewRadioCall(self.LSOCall.SLOW, "AIRBOSS", "") - elseif _error<-lowscore then - --hint=string.format("You're slightly slow.") - radiocall=self:_NewRadioCall(self.LSOCall.SLOW, "AIRBOSS", "") + local hint = "" + if _error > badscore then + -- hint=string.format("You're fast.") + radiocall = self:_NewRadioCall( self.LSOCall.FAST, "AIRBOSS", "" ) + elseif _error > lowscore then + -- hint= string.format("You're slightly fast.") + radiocall = self:_NewRadioCall( self.LSOCall.FAST, "AIRBOSS", "" ) + elseif _error < -badscore then + -- hint=string.format("You're slow.") + radiocall = self:_NewRadioCall( self.LSOCall.SLOW, "AIRBOSS", "" ) + elseif _error < -lowscore then + -- hint=string.format("You're slightly slow.") + radiocall = self:_NewRadioCall( self.LSOCall.SLOW, "AIRBOSS", "" ) else - hint=string.format("Good speed. ") + hint = string.format( "Good speed. " ) end -- Extend or decrease depending on skill. - 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 + 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 -- We keep is short normally. - hint="" - elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then + hint = "" + elseif playerData.difficulty == AIRBOSS.Difficulty.HARD then -- No hint at all for pros. - hint="" + hint = "" end -- Debrief text. - local debrief=string.format("Speed %d knots = %d%% deviation from %d knots.", UTILS.MpsToKnots(speed), _error, UTILS.MpsToKnots(speedopt)) + local debrief = string.format( "Speed %d knots = %d%% deviation from %d knots.", UTILS.MpsToKnots( speed ), _error, UTILS.MpsToKnots( speedopt ) ) return hint, debrief, radiocall end @@ -13267,48 +12545,48 @@ end -- @return #string Feedback message text. -- @return #string Debriefing text. -- @return #AIRBOSS.RadioCall Distance radio call. Not implemented yet. -function AIRBOSS:_DistanceCheck(playerData, optdist) +function AIRBOSS:_DistanceCheck( playerData, optdist ) - if optdist==nil then + if optdist == nil then return nil, nil end -- Distance to carrier. - local distance=playerData.unit:GetCoordinate():Get2DDistance(self:GetCoordinate()) + local distance = playerData.unit:GetCoordinate():Get2DDistance( self:GetCoordinate() ) -- Get relative score. - local lowscore, badscore = self:_GetGoodBadScore(playerData) + local lowscore, badscore = self:_GetGoodBadScore( playerData ) -- Altitude error +-X% - local _error=(distance-optdist)/optdist*100 + 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.") + 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.") + hint = string.format( "Good distance to the boat." ) end -- Extend or decrease depending on skill. - if playerData.difficulty==AIRBOSS.Difficulty.EASY then + if playerData.difficulty == AIRBOSS.Difficulty.EASY then -- Also inform students about optimal value. - hint=hint..string.format(" Optimal distance is %.1f NM.", UTILS.MetersToNM(optdist)) - elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then + hint = hint .. string.format( " Optimal distance is %.1f NM.", UTILS.MetersToNM( optdist ) ) + elseif playerData.difficulty == AIRBOSS.Difficulty.NORMAL then -- We keep it short normally. - hint="" - elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then + hint = "" + elseif playerData.difficulty == AIRBOSS.Difficulty.HARD then -- No hint at all for the pros. - hint="" + hint = "" end -- Debriefing text. - local debrief=string.format("Distance %.1f NM = %d%% deviation from %.1f NM.",UTILS.MetersToNM(distance), _error, UTILS.MetersToNM(optdist)) + local debrief = string.format( "Distance %.1f NM = %d%% deviation from %.1f NM.", UTILS.MetersToNM( distance ), _error, UTILS.MetersToNM( optdist ) ) return hint, debrief, nil end @@ -13322,122 +12600,120 @@ end -- @param #AIRBOSS.PlayerData playerData Player data. -- @param #string hint Debrief text of this step. -- @param #string step (Optional) Current step in the pattern. Default from playerData. -function AIRBOSS:_AddToDebrief(playerData, hint, step) - step=step or playerData.step - table.insert(playerData.debrief, {step=step, hint=hint}) +function AIRBOSS:_AddToDebrief( playerData, hint, step ) + step = step or playerData.step + table.insert( playerData.debrief, { step = step, hint = hint } ) end --- Debrief player and set next step. -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data. -function AIRBOSS:_Debrief(playerData) - self:F(self.lid..string.format("Debriefing of player %s.", playerData.name)) +function AIRBOSS:_Debrief( playerData ) + self:F( self.lid .. string.format( "Debriefing of player %s.", playerData.name ) ) -- Delete scheduler ID. - playerData.debriefschedulerID=nil + playerData.debriefschedulerID = nil -- Switch attitude monitor off if on. - playerData.attitudemonitor=false + playerData.attitudemonitor = false -- LSO grade, points, and flight data analyis. - local grade, points, analysis=self:_LSOgrade(playerData) + local grade, points, analysis = self:_LSOgrade( playerData ) -- Insert points to table of all points until player landed. - if points and points>=0 then - table.insert(playerData.points, points) + if points and points >= 0 then + table.insert( playerData.points, points ) end -- Player has landed and is not airborne any more. - local Points=0 + local Points = 0 if playerData.landed and not playerData.unit:InAir() then -- Average over all points received so far. - for _,_points in pairs(playerData.points) do - Points=Points+_points + for _, _points in pairs( playerData.points ) do + Points = Points + _points end -- This is the final points. - Points=Points/#playerData.points + Points = Points / #playerData.points -- Reset points array. - playerData.points={} + playerData.points = {} else -- Player boltered or was waved off ==> We display the normal points. - Points=points + Points = points end -- My LSO grade. - local mygrade={} --#AIRBOSS.LSOgrade - mygrade.grade=grade - mygrade.points=points - mygrade.details=analysis - mygrade.wire=playerData.wire - mygrade.Tgroove=playerData.Tgroove + local mygrade = {} -- #AIRBOSS.LSOgrade + 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 + 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" + 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() --os.date("%d.%m.%Y") + mygrade.osdate = os.date() -- os.date("%d.%m.%Y") end -- Save trap sheet. if playerData.trapon and self.trapsheet then - self:_SaveTrapSheet(playerData, mygrade) + self:_SaveTrapSheet( playerData, mygrade ) end -- Add LSO grade to player grades table. - table.insert(self.playerscores[playerData.name], mygrade) + table.insert( self.playerscores[playerData.name], mygrade ) -- Trigger grading event. - self:LSOGrade(playerData, mygrade) + self:LSOGrade( playerData, mygrade ) -- LSO grade: (OK) 3.0 PT - LURIM - 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) + 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 -- Wire and Groove time only if not pattern WO. if not (playerData.wop or playerData.wofd) then -- Wire trapped. Not if pattern WI. - if playerData.wire and playerData.wire<=4 then - text=text..string.format(" %d-wire", playerData.wire) + if playerData.wire and playerData.wire <= 4 then + text = text .. string.format( " %d-wire", playerData.wire ) end -- Time in the groove. Only Case I/II and not pattern WO. - 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)) + 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 -- Copy debriefing text. - playerData.lastdebrief=UTILS.DeepCopy(playerData.debrief) + playerData.lastdebrief = UTILS.DeepCopy( playerData.debrief ) -- Info text. - if playerData.difficulty==AIRBOSS.Difficulty.EASY then - text=text..string.format("\nYour detailed debriefing can be found via the F10 radio menu.") + if playerData.difficulty == AIRBOSS.Difficulty.EASY then + text = text .. string.format( "\nYour detailed debriefing can be found via the F10 radio menu." ) end -- Message. - self:MessageToPlayer(playerData, text, "LSO", "", 30, true) - + self:MessageToPlayer( playerData, text, "LSO", "", 30, true ) -- Set step to undefined and check if other cases apply. - playerData.step=AIRBOSS.PatternStep.UNDEFINED - + playerData.step = AIRBOSS.PatternStep.UNDEFINED -- Check what happened? if playerData.wop then @@ -13458,41 +12734,41 @@ function AIRBOSS:_Debrief(playerData) -- Heading and distance tip. local heading, distance - if playerData.case==1 or playerData.case==2 then + if playerData.case == 1 or playerData.case == 2 then -- Next step: Initial again. - playerData.step=AIRBOSS.PatternStep.INITIAL + playerData.step = AIRBOSS.PatternStep.INITIAL -- Create a point 3.0 NM astern for re-entry. - local initial=self:GetCoordinate():Translate(UTILS.NMToMeters(3.5), self:GetRadial(2, false, false, false)) + local initial = self:GetCoordinate():Translate( UTILS.NMToMeters( 3.5 ), self:GetRadial( 2, false, false, false ) ) -- Get heading and distance to initial zone ~3 NM astern. - heading=playerData.unit:GetCoordinate():HeadingTo(initial) - distance=playerData.unit:GetCoordinate():Get2DDistance(initial) + heading = playerData.unit:GetCoordinate():HeadingTo( initial ) + distance = playerData.unit:GetCoordinate():Get2DDistance( initial ) - elseif playerData.case==3 then + elseif playerData.case == 3 then -- Next step? Bullseye for now. -- TODO: Could be DIRTY UP or PLATFORM or even back to MARSHAL STACK? - playerData.step=AIRBOSS.PatternStep.BULLSEYE + playerData.step = AIRBOSS.PatternStep.BULLSEYE -- Get heading and distance to bullseye zone ~3 NM astern. - local zone=self:_GetZoneBullseye(playerData.case) + local zone = self:_GetZoneBullseye( playerData.case ) - heading=playerData.unit:GetCoordinate():HeadingTo(zone:GetCoordinate()) - distance=playerData.unit:GetCoordinate():Get2DDistance(zone:GetCoordinate()) + heading = playerData.unit:GetCoordinate():HeadingTo( zone:GetCoordinate() ) + distance = playerData.unit:GetCoordinate():Get2DDistance( zone:GetCoordinate() ) end -- Re-enter message. - 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) + 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 -- Unit does not seem to be alive! -- TODO: What now? - self:E(self.lid..string.format("ERROR: Player unit not alive!")) + self:E( self.lid .. string.format( "ERROR: Player unit not alive!" ) ) end @@ -13505,16 +12781,16 @@ function AIRBOSS:_Debrief(playerData) if playerData.unit:InAir() then -- Bolter pattern. Then Abeam or bullseye. - playerData.step=AIRBOSS.PatternStep.BOLTER + playerData.step = AIRBOSS.PatternStep.BOLTER else -- Welcome aboard! - self:Sound2Player(playerData, self.LSORadio, self.LSOCall.WELCOMEABOARD) + self:Sound2Player( playerData, self.LSORadio, self.LSOCall.WELCOMEABOARD ) -- Airboss talkto! - 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) + 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 @@ -13527,18 +12803,17 @@ function AIRBOSS:_Debrief(playerData) if playerData.unit:InAir() then -- Bolter pattern. Then Abeam or bullseye. - playerData.step=AIRBOSS.PatternStep.BOLTER + playerData.step = AIRBOSS.PatternStep.BOLTER else -- Welcome aboard! -- NOTE: This should not happen as owo is only triggered if player flew past the carrier. - 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) + 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 -------------- @@ -13548,16 +12823,16 @@ function AIRBOSS:_Debrief(playerData) if playerData.unit:InAir() then -- Bolter pattern. Then Abeam or bullseye. - playerData.step=AIRBOSS.PatternStep.BOLTER + playerData.step = AIRBOSS.PatternStep.BOLTER else -- Welcome aboard! - self:Sound2Player(playerData, self.LSORadio, self.LSOCall.WELCOMEABOARD) + self:Sound2Player( playerData, self.LSORadio, self.LSOCall.WELCOMEABOARD ) -- Airboss talkto! - 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) + 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 @@ -13570,7 +12845,7 @@ function AIRBOSS:_Debrief(playerData) if playerData.unit:InAir() then -- Bolter pattern. Then Abeam or bullseye. - playerData.step=AIRBOSS.PatternStep.BOLTER + playerData.step = AIRBOSS.PatternStep.BOLTER end @@ -13580,47 +12855,47 @@ function AIRBOSS:_Debrief(playerData) -- Landed -- ------------ - if not playerData.unit:InAir() then + if not playerData.unit:InAir() then -- Welcome aboard! - self:Sound2Player(playerData, self.LSORadio, self.LSOCall.WELCOMEABOARD) + self:Sound2Player( playerData, self.LSORadio, self.LSOCall.WELCOMEABOARD ) end else -- Message to player. - self:MessageToPlayer(playerData, "Undefined state after landing! Please report.", "ERROR", nil, 20) + self:MessageToPlayer( playerData, "Undefined state after landing! Please report.", "ERROR", nil, 20 ) -- Next step. - playerData.step=AIRBOSS.PatternStep.UNDEFINED + playerData.step = AIRBOSS.PatternStep.UNDEFINED end -- Player landed and is not in air anymore. if playerData.landed and not playerData.unit:InAir() then -- Set recovered flag. - self:_RecoveredElement(playerData.unit) + self:_RecoveredElement( playerData.unit ) -- Check if all elements - self:_CheckSectionRecovered(playerData) + self:_CheckSectionRecovered( playerData ) end -- Increase number of passes. - playerData.passes=playerData.passes+1 + playerData.passes = playerData.passes + 1 -- Next step hint for students if any. - self:_StepHint(playerData) + self:_StepHint( playerData ) -- Reinitialize player data for new approach. - self:_InitPlayer(playerData, playerData.step) + self:_InitPlayer( playerData, playerData.step ) -- Debug message. - MESSAGE:New(string.format("Player step %s.", playerData.step), 5, "DEBUG"):ToAllIf(self.Debug) + MESSAGE:New( string.format( "Player step %s.", playerData.step ), 5, "DEBUG" ):ToAllIf( self.Debug ) -- Auto save player results. if self.autosave and mygrade.finalscore then - self:Save(self.autosavepath, self.autosavefile) + self:Save( self.autosavepath, self.autosavefile ) end end @@ -13634,80 +12909,79 @@ end -- @param Core.Point#COORDINATE coordfrom Coordinate from which the collision is check. -- @return #boolean If true, surface type ahead is not deep water. -- @return #number Max free distance in meters. -function AIRBOSS:_CheckCollisionCoord(coordto, coordfrom) +function AIRBOSS:_CheckCollisionCoord( coordto, coordfrom ) -- Increment in meters. - local dx=100 + local dx = 100 -- From coordinate. Default 500 in front of the carrier. - local d=0 + local d = 0 if coordfrom then - d=0 + d = 0 else - d=250 - coordfrom=self:GetCoordinate():Translate(d, self:GetHeading()) + d = 250 + coordfrom = self:GetCoordinate():Translate( d, self:GetHeading() ) end -- Distance between the two coordinates. - local dmax=coordfrom:Get2DDistance(coordto) + local dmax = coordfrom:Get2DDistance( coordto ) -- Direction. - local direction=coordfrom:HeadingTo(coordto) + local direction = coordfrom:HeadingTo( coordto ) -- Scan path between the two coordinates. - local clear=true - while d<=dmax do + local clear = true + while d <= dmax do -- Check point. - local cp=coordfrom:Translate(d, direction) + local cp = coordfrom:Translate( d, direction ) -- Check if surface type is water. if not cp:IsSurfaceTypeWater() then -- Debug mark points. if self.Debug then - local st=cp:GetSurfaceType() - cp:MarkToAll(string.format("Collision check surface type %d", st)) + local st = cp:GetSurfaceType() + cp:MarkToAll( string.format( "Collision check surface type %d", st ) ) end -- Collision WARNING! - clear=false + clear = false break end -- Increase distance. - d=d+dx + d = d + dx end - local text="" + local text = "" if clear then - text=string.format("Path into direction %03d° is clear for the next %.1f NM.", direction, UTILS.MetersToNM(d)) + 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) + text = string.format( "Detected obstacle at distance %.1f NM into direction %03d°.", UTILS.MetersToNM( d ), direction ) end - self:T2(self.lid..text) + self:T2( self.lid .. text ) return not clear, d end - --- Check Collision. -- @param #AIRBOSS self -- @param Core.Point#COORDINATE fromcoord Coordinate from which the path to the next WP is calculated. Default current carrier position. -- @return #boolean If true, surface type ahead is not deep water. -function AIRBOSS:_CheckFreePathToNextWP(fromcoord) +function AIRBOSS:_CheckFreePathToNextWP( fromcoord ) -- Position. - fromcoord=fromcoord or self:GetCoordinate():Translate(250, self:GetHeading()) + fromcoord = fromcoord or self:GetCoordinate():Translate( 250, self:GetHeading() ) -- Next wp = current+1 (or last) - local Nnextwp=math.min(self.currentwp+1, #self.waypoints) + local Nnextwp = math.min( self.currentwp + 1, #self.waypoints ) -- Next waypoint. - local nextwp=self.waypoints[Nnextwp] --Core.Point#COORDINATE + local nextwp = self.waypoints[Nnextwp] -- Core.Point#COORDINATE -- Check for collision. - local collision=self:_CheckCollisionCoord(nextwp, fromcoord) + local collision = self:_CheckCollisionCoord( nextwp, fromcoord ) return collision end @@ -13717,59 +12991,57 @@ end function AIRBOSS:_Pathfinder() -- Heading and current coordiante. - local hdg=self:GetHeading() - local cv=self:GetCoordinate() + local hdg = self:GetHeading() + local cv = self:GetCoordinate() -- Possible directions. - local directions={-20, 20, -30, 30, -40, 40, -50, 50, -60, 60, -70, 70, -80, 80, -90, 90, -100, 100} + local directions = { -20, 20, -30, 30, -40, 40, -50, 50, -60, 60, -70, 70, -80, 80, -90, 90, -100, 100 } -- Starboard turns up to 90 degrees. - for _,_direction in pairs(directions) do + for _, _direction in pairs( directions ) do -- New direction. - local direction=hdg+_direction + local direction = hdg + _direction -- Check for collisions in the next 20 NM of the current direction. - local _, dfree=self:_CheckCollisionCoord(cv:Translate(UTILS.NMToMeters(20), direction), cv) + local _, dfree = self:_CheckCollisionCoord( cv:Translate( UTILS.NMToMeters( 20 ), direction ), cv ) -- Loop over distances and find the first one which gives a clear path to the next waypoint. - local distance=500 - while distance<=dfree do + local distance = 500 + while distance <= dfree do -- Coordinate from which we calculate the path. - local fromcoord=cv:Translate(distance, direction) + local fromcoord = cv:Translate( distance, direction ) -- Check for collision between point and next waypoint. - local collision=self:_CheckFreePathToNextWP(fromcoord) + local collision = self:_CheckFreePathToNextWP( fromcoord ) -- Debug info. - self:T2(self.lid..string.format("Pathfinder d=%.1f m, direction=%03d°, collision=%s", distance, direction, tostring(collision))) + self:T2( self.lid .. string.format( "Pathfinder d=%.1f m, direction=%03d°, collision=%s", distance, direction, tostring( collision ) ) ) -- If path is clear, we start a little detour. if not collision then - self:CarrierDetour(fromcoord) + self:CarrierDetour( fromcoord ) return end - distance=distance+500 + distance = distance + 500 end end end - --- Carrier resumes the route at its next waypoint. ---@param #AIRBOSS self ---@param Core.Point#COORDINATE gotocoord (Optional) First goto this coordinate before resuming route. ---@return #AIRBOSS self -function AIRBOSS:CarrierResumeRoute(gotocoord) +-- @param #AIRBOSS self +-- @param Core.Point#COORDINATE gotocoord (Optional) First goto this coordinate before resuming route. +-- @return #AIRBOSS self +function AIRBOSS:CarrierResumeRoute( gotocoord ) -- Make carrier resume its route. - AIRBOSS._ResumeRoute(self.carrier:GetGroup(), self, gotocoord) + AIRBOSS._ResumeRoute( self.carrier:GetGroup(), self, gotocoord ) return self end - --- Let the carrier make a detour to a given point. When it reaches the point, it will resume its normal route. -- @param #AIRBOSS self -- @param Core.Point#COORDINATE coord Coordinate of the detour. @@ -13778,70 +13050,70 @@ end -- @param #number uspeed Speed in knots after U-turn. Default is same as before. -- @param Core.Point#COORDINATE tcoord Additional coordinate to make turn smoother. -- @return #AIRBOSS self -function AIRBOSS:CarrierDetour(coord, speed, uturn, uspeed, tcoord) +function AIRBOSS:CarrierDetour( coord, speed, uturn, uspeed, tcoord ) -- Current coordinate of the carrier. - local pos0=self:GetCoordinate() + local pos0 = self:GetCoordinate() -- Current speed in knots. - local vel0=self.carrier:GetVelocityKNOTS() + local vel0 = self.carrier:GetVelocityKNOTS() -- Default. If speed is not given we take the current speed but at least 5 knots. - speed=speed or math.max(vel0, 5) + speed = speed or math.max( vel0, 5 ) -- Speed in km/h. At least 2 knots. - local speedkmh=math.max(UTILS.KnotsToKmph(speed), UTILS.KnotsToKmph(2)) + local speedkmh = math.max( UTILS.KnotsToKmph( speed ), UTILS.KnotsToKmph( 2 ) ) -- Turn speed in km/h. At least 10 knots. - local cspeedkmh=math.max(self.carrier:GetVelocityKMH(), UTILS.KnotsToKmph(10)) + local cspeedkmh = math.max( self.carrier:GetVelocityKMH(), UTILS.KnotsToKmph( 10 ) ) -- U-turn speed in km/h. - local uspeedkmh=UTILS.KnotsToKmph(uspeed or speed) + local uspeedkmh = UTILS.KnotsToKmph( uspeed or speed ) -- Waypoint table. - local wp={} + local wp = {} -- Waypoint at current position. - table.insert(wp, pos0:WaypointGround(cspeedkmh)) + table.insert( wp, pos0:WaypointGround( cspeedkmh ) ) -- Waypooint to help the turn. if tcoord then - table.insert(wp, tcoord:WaypointGround(cspeedkmh)) + table.insert( wp, tcoord:WaypointGround( cspeedkmh ) ) end -- Detour waypoint. - table.insert(wp, coord:WaypointGround(speedkmh)) + table.insert( wp, coord:WaypointGround( speedkmh ) ) -- U-turn waypoint. If enabled, go back to where you came from. if uturn then - table.insert(wp, pos0:WaypointGround(uspeedkmh)) + table.insert( wp, pos0:WaypointGround( uspeedkmh ) ) end -- Get carrier group. - local group=self.carrier:GetGroup() + local group = self.carrier:GetGroup() -- Passing waypoint taskfunction - local TaskResumeRoute=group:TaskFunction("AIRBOSS._ResumeRoute", self) + local TaskResumeRoute = group:TaskFunction( "AIRBOSS._ResumeRoute", self ) -- Set task to restart route at the last point. - group:SetTaskWaypoint(wp[#wp], TaskResumeRoute) + group:SetTaskWaypoint( wp[#wp], TaskResumeRoute ) -- Debug mark. if self.Debug then if tcoord then - tcoord:MarkToAll(string.format("Detour Turn Help WP. Speed %.1f knots", UTILS.KmphToKnots(cspeedkmh))) + 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))) + 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))) + pos0:MarkToAll( string.format( "Detour U-turn WP. Speed %.1f knots", UTILS.KmphToKnots( uspeedkmh ) ) ) end end -- Detour switch true. - self.detour=true + self.detour = true -- Route carrier into the wind. - self.carrier:Route(wp) + self.carrier:Route( wp ) end --- Let the carrier turn into the wind. @@ -13850,109 +13122,108 @@ end -- @param #number vdeck Speed on deck m/s. Carrier will -- @param #boolean uturn Make U-turn and go back to initial after downwind leg. -- @return #AIRBOSS self -function AIRBOSS:CarrierTurnIntoWind(time, vdeck, uturn) +function AIRBOSS:CarrierTurnIntoWind( time, vdeck, uturn ) -- Wind speed. - local _,vwind=self:GetWind() + local _, vwind = self:GetWind() -- Speed of carrier in m/s but at least 2 knots. - local vtot=math.max(vdeck-vwind, UTILS.KnotsToMps(2)) + local vtot = math.max( vdeck - vwind, UTILS.KnotsToMps( 2 ) ) -- Distance to travel - local dist=vtot*time + local dist = vtot * time -- Speed in knots - local speedknots=UTILS.MpsToKnots(vtot) - local distNM=UTILS.MetersToNM(dist) + local speedknots = UTILS.MpsToKnots( vtot ) + local distNM = UTILS.MetersToNM( dist ) -- Debug output - 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)) + 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 ) ) -- Get heading into the wind accounting for angled runway. - local hiw=self:GetHeadingIntoWind() + local hiw = self:GetHeadingIntoWind() -- Current heading. - local hdg=self:GetHeading() + local hdg = self:GetHeading() -- Heading difference. - local deltaH=self:_GetDeltaHeading(hdg, hiw) + local deltaH = self:_GetDeltaHeading( hdg, hiw ) - local Cv=self:GetCoordinate() + local Cv = self:GetCoordinate() - local Ctiw=nil --Core.Point#COORDINATE - local Csoo=nil --Core.Point#COORDINATE + local Ctiw = nil -- Core.Point#COORDINATE + local Csoo = nil -- Core.Point#COORDINATE -- Define path depending on turn angle. - if deltaH<45 then + if deltaH < 45 then -- Small turn. -- Point in the right direction to help turning. - Csoo=Cv:Translate(750, hdg):Translate(750, hiw) + Csoo = Cv:Translate( 750, hdg ):Translate( 750, hiw ) -- Heading into wind from Csoo. - local hsw=self:GetHeadingIntoWind(false, Csoo) + local hsw = self:GetHeadingIntoWind( false, Csoo ) -- Into the wind coord. - Ctiw=Csoo:Translate(dist, hsw) + Ctiw = Csoo:Translate( dist, hsw ) - elseif deltaH<90 then + elseif deltaH < 90 then -- Medium turn. - -- Point in the right direction to help turning. - Csoo=Cv:Translate(900, hdg):Translate(900, hiw) + -- Point in the right direction to help turning. + Csoo = Cv:Translate( 900, hdg ):Translate( 900, hiw ) -- Heading into wind from Csoo. - local hsw=self:GetHeadingIntoWind(false, Csoo) + local hsw = self:GetHeadingIntoWind( false, Csoo ) -- Into the wind coord. - Ctiw=Csoo:Translate(dist, hsw) + Ctiw = Csoo:Translate( dist, hsw ) - elseif deltaH<135 then + elseif deltaH < 135 then -- Large turn backwards. -- Point in the right direction to help turning. - Csoo=Cv:Translate(1100, hdg-90):Translate(1000, hiw) + Csoo = Cv:Translate( 1100, hdg - 90 ):Translate( 1000, hiw ) -- Heading into wind from Csoo. - local hsw=self:GetHeadingIntoWind(false, Csoo) + local hsw = self:GetHeadingIntoWind( false, Csoo ) -- Into the wind coord. - Ctiw=Csoo:Translate(dist, hsw) + Ctiw = Csoo:Translate( dist, hsw ) else -- Huge turn backwards. -- Point in the right direction to help turning. - Csoo=Cv:Translate(1200, hdg-90):Translate(1000, hiw) + Csoo = Cv:Translate( 1200, hdg - 90 ):Translate( 1000, hiw ) -- Heading into wind from Csoo. - local hsw=self:GetHeadingIntoWind(false, Csoo) + local hsw = self:GetHeadingIntoWind( false, Csoo ) -- Into the wind coord. - Ctiw=Csoo:Translate(dist, hsw) + Ctiw = Csoo:Translate( dist, hsw ) end - -- Return to coordinate if collision is detected. - self.Creturnto=self:GetCoordinate() + self.Creturnto = self:GetCoordinate() -- Next waypoint. - local nextwp=self:_GetNextWaypoint() + local nextwp = self:_GetNextWaypoint() -- For downwind, we take the velocity at the next WP. - local vdownwind=UTILS.MpsToKnots(nextwp:GetVelocity()) + local vdownwind = UTILS.MpsToKnots( nextwp:GetVelocity() ) -- Make sure we move at all in case the speed at the waypoint is zero. - if vdownwind<1 then - vdownwind=10 + if vdownwind < 1 then + vdownwind = 10 end -- Let the carrier make a detour from its route but return to its current position. - self:CarrierDetour(Ctiw, speedknots, uturn, vdownwind, Csoo) + self:CarrierDetour( Ctiw, speedknots, uturn, vdownwind, Csoo ) -- Set switch that we are currently turning into the wind. - self.turnintowind=true + self.turnintowind = true return self end @@ -13964,50 +13235,49 @@ end function AIRBOSS:_GetNextWaypoint() -- Next waypoint. - local Nextwp=nil - if self.currentwp==#self.waypoints then - Nextwp=1 + local Nextwp = nil + if self.currentwp == #self.waypoints then + Nextwp = 1 else - Nextwp=self.currentwp+1 + Nextwp = self.currentwp + 1 end -- Debug output - local text=string.format("Current WP=%d/%d, next WP=%d", self.currentwp, #self.waypoints, Nextwp) - self:T2(self.lid..text) + local text = string.format( "Current WP=%d/%d, next WP=%d", self.currentwp, #self.waypoints, Nextwp ) + self:T2( self.lid .. text ) -- Next waypoint. - local nextwp=self.waypoints[Nextwp] --Core.Point#COORDINATE + local nextwp = self.waypoints[Nextwp] -- Core.Point#COORDINATE - return nextwp,Nextwp + return nextwp, Nextwp end - --- Initialize Mission Editor waypoints. -- @param #AIRBOSS self -- @return #AIRBOSS self function AIRBOSS:_InitWaypoints() -- Waypoints of group as defined in the ME. - local Waypoints=self.carrier:GetGroup():GetTemplateRoutePoints() + local Waypoints = self.carrier:GetGroup():GetTemplateRoutePoints() -- Init array. - self.waypoints={} + self.waypoints = {} -- Set waypoint table. - for i,point in ipairs(Waypoints) do + for i, point in ipairs( Waypoints ) do -- Coordinate of the waypoint - local coord=COORDINATE:New(point.x, point.alt, point.y) + local coord = COORDINATE:New( point.x, point.alt, point.y ) -- Set velocity of the coordinate. - coord:SetVelocity(point.speed) + coord:SetVelocity( point.speed ) -- Add to table. - table.insert(self.waypoints, coord) + table.insert( self.waypoints, coord ) -- Debug info. if self.Debug then - coord:MarkToAll(string.format("Carrier Waypoint %d, Speed=%.1f knots", i, UTILS.MpsToKnots(point.speed))) + coord:MarkToAll( string.format( "Carrier Waypoint %d, Speed=%.1f knots", i, UTILS.MpsToKnots( point.speed ) ) ) end end @@ -14019,86 +13289,83 @@ end -- @param #AIRBOSS self -- @param #number n Next waypoint number. -- @return #AIRBOSS self -function AIRBOSS:_PatrolRoute(n) +function AIRBOSS:_PatrolRoute( n ) -- Get next waypoint coordinate and number. - local nextWP, N=self:_GetNextWaypoint() + local nextWP, N = self:_GetNextWaypoint() -- Default resume is to next waypoint. - n=n or N + n = n or N -- Get carrier group. - local CarrierGroup=self.carrier:GetGroup() + local CarrierGroup = self.carrier:GetGroup() -- Waypoints table. - local Waypoints={} + local Waypoints = {} -- Create a waypoint from the current coordinate. - local wp=self:GetCoordinate():WaypointGround(CarrierGroup:GetVelocityKMH()) + local wp = self:GetCoordinate():WaypointGround( CarrierGroup:GetVelocityKMH() ) -- Add current position as first waypoint. - table.insert(Waypoints, wp) + table.insert( Waypoints, wp ) -- Loop over waypoints. - for i=n,#self.waypoints do - local coord=self.waypoints[i] --Core.Point#COORDINATE + for i = n, #self.waypoints do + local coord = self.waypoints[i] -- Core.Point#COORDINATE -- Create a waypoint from the coordinate. - local wp=coord:WaypointGround(UTILS.MpsToKmph(coord.Velocity)) + local wp = coord:WaypointGround( UTILS.MpsToKmph( coord.Velocity ) ) -- Passing waypoint taskfunction - local TaskPassingWP=CarrierGroup:TaskFunction("AIRBOSS._PassingWaypoint", self, i, #self.waypoints) + local TaskPassingWP = CarrierGroup:TaskFunction( "AIRBOSS._PassingWaypoint", self, i, #self.waypoints ) -- Call task function when carrier arrives at waypoint. - CarrierGroup:SetTaskWaypoint(wp, TaskPassingWP) + CarrierGroup:SetTaskWaypoint( wp, TaskPassingWP ) -- Add waypoint to table. - table.insert(Waypoints, wp) + table.insert( Waypoints, wp ) end -- Route carrier group. - CarrierGroup:Route(Waypoints) + CarrierGroup:Route( Waypoints ) return self end - - - --- Estimated the carrier position at some point in the future given the current waypoints and speeds. -- @param #AIRBOSS self -- @return DCS#time ETA abs. time in seconds. function AIRBOSS:_GetETAatNextWP() -- Current waypoint - local cwp=self.currentwp + local cwp = self.currentwp -- Current abs. time. - local tnow=timer.getAbsTime() + local tnow = timer.getAbsTime() -- Current position. - local p=self:GetCoordinate() + local p = self:GetCoordinate() -- Current velocity [m/s]. - local v=self.carrier:GetVelocityMPS() + local v = self.carrier:GetVelocityMPS() -- Next waypoint. - local nextWP=self:_GetNextWaypoint() + local nextWP = self:_GetNextWaypoint() -- Distance to next waypoint. - local s=p:Get2DDistance(nextWP) + local s = p:Get2DDistance( nextWP ) -- Distance to next waypoint. - --local s=0 - --if #self.waypoints>cwp then + -- local s=0 + -- if #self.waypoints>cwp then -- s=p:Get2DDistance(self.waypoints[cwp+1]) - --end + -- end -- v=s/t <==> t=s/v - local t=s/v + local t = s / v -- ETA - local eta=t+tnow + local eta = t + tnow return eta end @@ -14108,31 +13375,32 @@ end function AIRBOSS:_CheckCarrierTurning() -- Current orientation of carrier. - local vNew=self.carrier:GetOrientationX() + local vNew = self.carrier:GetOrientationX() -- Last orientation from 30 seconds ago. - local vLast=self.Corientlast + local vLast = self.Corientlast -- We only need the X-Z plane. - vNew.y=0 ; vLast.y=0 + vNew.y = 0; + vLast.y = 0 -- Angle between current heading and last time we checked ~30 seconds ago. - local deltaLast=math.deg(math.acos(UTILS.VecDot(vNew,vLast)/UTILS.VecNorm(vNew)/UTILS.VecNorm(vLast))) + local deltaLast = math.deg( math.acos( UTILS.VecDot( vNew, vLast ) / UTILS.VecNorm( vNew ) / UTILS.VecNorm( vLast ) ) ) -- Last orientation becomes new orientation - self.Corientlast=vNew + self.Corientlast = vNew -- Carrier is turning when its heading changed by at least one degree since last check. - local turning=math.abs(deltaLast)>=1 + local turning = math.abs( deltaLast ) >= 1 -- Check if turning stopped. (Carrier was turning but is not any more.) if self.turning and not turning then -- Get final bearing. - local FB=self:GetFinalBearing(true) + local FB = self:GetFinalBearing( true ) -- Marshal radio call: "99, new final bearing XYZ degrees." - self:_MarshalCallNewFinalBearing(FB) + self:_MarshalCallNewFinalBearing( FB ) end @@ -14143,24 +13411,24 @@ function AIRBOSS:_CheckCarrierTurning() local hdg if self.turnintowind then -- We are now steaming into the wind. - hdg=self:GetHeadingIntoWind(false) + hdg = self:GetHeadingIntoWind( false ) else -- We turn towards the next waypoint. - hdg=self:GetCoordinate():HeadingTo(self:_GetNextWaypoint()) + hdg = self:GetCoordinate():HeadingTo( self:_GetNextWaypoint() ) end -- Magnetic! - hdg=hdg-self.magvar - if hdg<0 then - hdg=360+hdg + hdg = hdg - self.magvar + if hdg < 0 then + hdg = 360 + hdg end -- Radio call: "99, Carrier starting turn to heading XYZ degrees". - self:_MarshalCallCarrierTurnTo(hdg) + self:_MarshalCallCarrierTurnTo( hdg ) end -- Update turning. - self.turning=turning + self.turning = turning end --- Check if heading or position of carrier have changed significantly. @@ -14172,23 +13440,23 @@ function AIRBOSS:_CheckPatternUpdate() ---------------------------------------- -- Min 10 min between pattern updates. - local dTPupdate=10*60 + local dTPupdate = 10 * 60 -- Update if carrier moves by more than 2.5 NM. - local Dupdate=UTILS.NMToMeters(2.5) + local Dupdate = UTILS.NMToMeters( 2.5 ) -- Update if carrier turned by more than 5°. - local Hupdate=5 + local Hupdate = 5 ----------------------- -- Time Update Check -- ----------------------- -- Time since last pattern update - local dt=timer.getTime()-self.Tpupdate + local dt = timer.getTime() - self.Tpupdate -- Check whether at least 10 min between updates and not turning currently. - if dt=Hupdate then - self:T(self.lid..string.format("Carrier heading changed by %d°.", deltaHeading)) - Hchange=true + local Hchange = false + if math.abs( deltaHeading ) >= Hupdate then + self:T( self.lid .. string.format( "Carrier heading changed by %d°.", deltaHeading ) ) + Hchange = true end --------------------------- @@ -14220,16 +13489,16 @@ function AIRBOSS:_CheckPatternUpdate() --------------------------- -- Get current position and orientation of carrier. - local pos=self:GetCoordinate() + local pos = self:GetCoordinate() -- Get distance to saved position. - local dist=pos:Get2DDistance(self.Cposition) + local dist = pos:Get2DDistance( self.Cposition ) -- Check if carrier moved more than ~10 km. - local Dchange=false - if dist>=Dupdate then - self:T(self.lid..string.format("Carrier position changed by %.1f NM.", UTILS.MetersToNM(dist))) - Dchange=true + 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 ---------------------------- @@ -14240,228 +13509,227 @@ function AIRBOSS:_CheckPatternUpdate() if Hchange or Dchange then -- Loop over all marshal flights - for _,_flight in pairs(self.Qmarshal) do - local flight=_flight --#AIRBOSS.FlightGroup + for _, _flight in pairs( self.Qmarshal ) do + local flight = _flight -- #AIRBOSS.FlightGroup -- Update marshal pattern of AI keeping the same stack. if flight.ai then - self:_MarshalAI(flight, flight.flag) + self:_MarshalAI( flight, flight.flag ) end end -- Reset parameters for next update check. - self.Corientation=vNew - self.Cposition=pos - self.Tpupdate=timer.getTime() + self.Corientation = vNew + self.Cposition = pos + self.Tpupdate = timer.getTime() end end --- Function called when a group is passing a waypoint. ---@param Wrapper.Group#GROUP group Group that passed the waypoint ---@param #AIRBOSS airboss Airboss object. ---@param #number i Waypoint number that has been reached. ---@param #number final Final waypoint number. -function AIRBOSS._PassingWaypoint(group, airboss, i, final) +-- @param Wrapper.Group#GROUP group Group that passed the waypoint +-- @param #AIRBOSS airboss Airboss object. +-- @param #number i Waypoint number that has been reached. +-- @param #number final Final waypoint number. +function AIRBOSS._PassingWaypoint( group, airboss, i, final ) -- Debug message. - local text=string.format("Group %s passing waypoint %d of %d.", group:GetName(), i, final) + local text = string.format( "Group %s passing waypoint %d of %d.", group:GetName(), i, final ) -- Debug smoke and marker. if airboss.Debug and false then - local pos=group:GetCoordinate() + local pos = group:GetCoordinate() pos:SmokeRed() - local MarkerID=pos:MarkToAll(string.format("Group %s reached waypoint %d", group:GetName(), i)) + local MarkerID = pos:MarkToAll( string.format( "Group %s reached waypoint %d", group:GetName(), i ) ) end -- Debug message. - MESSAGE:New(text,10):ToAllIf(airboss.Debug) - airboss:T(airboss.lid..text) + MESSAGE:New( text, 10 ):ToAllIf( airboss.Debug ) + airboss:T( airboss.lid .. text ) -- Set current waypoint. - airboss.currentwp=i + airboss.currentwp = i -- Passing Waypoint event. - airboss:PassingWaypoint(i) + airboss:PassingWaypoint( i ) -- Reactivate beacons. - --airboss:_ActivateBeacons() + -- airboss:_ActivateBeacons() -- If final waypoint reached, do route all over again. - if i==final and final>1 and airboss.adinfinitum then + if i == final and final > 1 and airboss.adinfinitum then airboss:_PatrolRoute() end end --- Carrier Strike Group resumes the route of the waypoints defined in the mission editor. ---@param Wrapper.Group#GROUP group Carrier Strike Group that passed the waypoint. ---@param #AIRBOSS airboss Airboss object. ---@param Core.Point#COORDINATE gotocoord Go to coordinate before route is resumed. -function AIRBOSS._ResumeRoute(group, airboss, gotocoord) +-- @param Wrapper.Group#GROUP group Carrier Strike Group that passed the waypoint. +-- @param #AIRBOSS airboss Airboss object. +-- @param Core.Point#COORDINATE gotocoord Go to coordinate before route is resumed. +function AIRBOSS._ResumeRoute( group, airboss, gotocoord ) -- Get next waypoint - local nextwp,Nextwp=airboss:_GetNextWaypoint() + local nextwp, Nextwp = airboss:_GetNextWaypoint() -- Speed set at waypoint. - local speedkmh=nextwp.Velocity*3.6 + local speedkmh = nextwp.Velocity * 3.6 -- If speed at waypoint is zero, we set it to 10 knots. - if speedkmh<1 then - speedkmh=UTILS.KnotsToKmph(10) + if speedkmh < 1 then + speedkmh = UTILS.KnotsToKmph( 10 ) end -- Waypoints array. - local waypoints={} + local waypoints = {} -- Current position. - local c0=group:GetCoordinate() + local c0 = group:GetCoordinate() -- Current positon as first waypoint. - local wp0=c0:WaypointGround(speedkmh) - table.insert(waypoints, wp0) + local wp0 = c0:WaypointGround( speedkmh ) + table.insert( waypoints, wp0 ) -- First goto this coordinate. if gotocoord then - --gotocoord:MarkToAll(string.format("Goto waypoint speed=%.1f km/h", speedkmh)) + -- gotocoord:MarkToAll(string.format("Goto waypoint speed=%.1f km/h", speedkmh)) - local headingto=c0:HeadingTo(gotocoord) + local headingto = c0:HeadingTo( gotocoord ) - local hdg1=airboss:GetHeading() - local hdg2=c0:HeadingTo(gotocoord) - local delta=airboss:_GetDeltaHeading(hdg1, hdg2) - - --env.info(string.format("FF hdg1=%d, hdg2=%d, delta=%d", hdg1, hdg2, delta)) + local hdg1 = airboss:GetHeading() + local hdg2 = c0:HeadingTo( gotocoord ) + local delta = airboss:_GetDeltaHeading( hdg1, hdg2 ) + -- env.info(string.format("FF hdg1=%d, hdg2=%d, delta=%d", hdg1, hdg2, delta)) -- Add additional turn points - if delta>90 then + if delta > 90 then -- Turn radius 3 NM. - local turnradius=UTILS.NMToMeters(3) + local turnradius = UTILS.NMToMeters( 3 ) - local gotocoordh=c0:Translate(turnradius, hdg1+45) - --gotocoordh:MarkToAll(string.format("Goto help waypoint 1 speed=%.1f km/h", speedkmh)) + local gotocoordh = c0:Translate( turnradius, hdg1 + 45 ) + -- gotocoordh:MarkToAll(string.format("Goto help waypoint 1 speed=%.1f km/h", speedkmh)) - local wp=gotocoordh:WaypointGround(speedkmh) - table.insert(waypoints, wp) + local wp = gotocoordh:WaypointGround( speedkmh ) + table.insert( waypoints, wp ) - gotocoordh=c0:Translate(turnradius, hdg1+90) - --gotocoordh:MarkToAll(string.format("Goto help waypoint 2 speed=%.1f km/h", speedkmh)) + gotocoordh = c0:Translate( turnradius, hdg1 + 90 ) + -- gotocoordh:MarkToAll(string.format("Goto help waypoint 2 speed=%.1f km/h", speedkmh)) - wp=gotocoordh:WaypointGround(speedkmh) - table.insert(waypoints, wp) + wp = gotocoordh:WaypointGround( speedkmh ) + table.insert( waypoints, wp ) end - local wp1=gotocoord:WaypointGround(speedkmh) - table.insert(waypoints, wp1) + local wp1 = gotocoord:WaypointGround( speedkmh ) + table.insert( waypoints, wp1 ) end -- Debug message. - local text=string.format("Carrier is resuming route. Next waypoint %d, Speed=%.1f knots.", Nextwp, UTILS.KmphToKnots(speedkmh)) + local text = string.format( "Carrier is resuming route. Next waypoint %d, Speed=%.1f knots.", Nextwp, UTILS.KmphToKnots( speedkmh ) ) -- Debug message. - MESSAGE:New(text,10):ToAllIf(airboss.Debug) - airboss:I(airboss.lid..text) + MESSAGE:New( text, 10 ):ToAllIf( airboss.Debug ) + airboss:I( airboss.lid .. text ) -- Loop over all remaining waypoints. - for i=Nextwp, #airboss.waypoints do + for i = Nextwp, #airboss.waypoints do -- Coordinate of the next WP. - local coord=airboss.waypoints[i] --Core.Point#COORDINATE + local coord = airboss.waypoints[i] -- Core.Point#COORDINATE -- Speed in km/h of that WP. Velocity is in m/s. - local speed=coord.Velocity*3.6 + local speed = coord.Velocity * 3.6 -- If speed is zero we set it to 10 knots. - if speed<1 then - speed=UTILS.KnotsToKmph(10) + if speed < 1 then + speed = UTILS.KnotsToKmph( 10 ) end - --coord:MarkToAll(string.format("Resume route WP %d, speed=%.1f km/h", i, speed)) + -- coord:MarkToAll(string.format("Resume route WP %d, speed=%.1f km/h", i, speed)) -- Create waypoint. - local wp=coord:WaypointGround(speed) + local wp = coord:WaypointGround( speed ) -- Passing waypoint task function. - local TaskPassingWP=group:TaskFunction("AIRBOSS._PassingWaypoint", airboss, i, #airboss.waypoints) + local TaskPassingWP = group:TaskFunction( "AIRBOSS._PassingWaypoint", airboss, i, #airboss.waypoints ) -- Call task function when carrier arrives at waypoint. - group:SetTaskWaypoint(wp, TaskPassingWP) + group:SetTaskWaypoint( wp, TaskPassingWP ) -- Add waypoints to table. - table.insert(waypoints, wp) + table.insert( waypoints, wp ) end -- Set turn into wind switch false. - airboss.turnintowind=false - airboss.detour=false + airboss.turnintowind = false + airboss.detour = false -- Route group. - group:Route(waypoints) + group:Route( waypoints ) end --- Function called when a group has reached the holding zone. ---@param Wrapper.Group#GROUP group Group that reached the holding zone. ---@param #AIRBOSS airboss Airboss object. ---@param #AIRBOSS.FlightGroup flight Flight group that has reached the holding zone. -function AIRBOSS._ReachedHoldingZone(group, airboss, flight) +-- @param Wrapper.Group#GROUP group Group that reached the holding zone. +-- @param #AIRBOSS airboss Airboss object. +-- @param #AIRBOSS.FlightGroup flight Flight group that has reached the holding zone. +function AIRBOSS._ReachedHoldingZone( group, airboss, flight ) -- Debug message. - local text=string.format("Flight %s reached holding zone.", group:GetName()) - MESSAGE:New(text,10):ToAllIf(airboss.Debug) - airboss:T(airboss.lid..text) + local text = string.format( "Flight %s reached holding zone.", group:GetName() ) + MESSAGE:New( text, 10 ):ToAllIf( airboss.Debug ) + airboss:T( airboss.lid .. text ) -- Debug mark. if airboss.Debug then - group:GetCoordinate():MarkToAll(text) + group:GetCoordinate():MarkToAll( text ) end -- Set holding flag true and set timestamp for marshal time check. if flight then - flight.holding=true - flight.time=timer.getAbsTime() + flight.holding = true + flight.time = timer.getAbsTime() end end --- Function called when a group should be send to the Marshal stack. If stack is full, it is send to wait. ---@param Wrapper.Group#GROUP group Group that reached the holding zone. ---@param #AIRBOSS airboss Airboss object. ---@param #AIRBOSS.FlightGroup flight Flight group that has reached the holding zone. -function AIRBOSS._TaskFunctionMarshalAI(group, airboss, flight) +-- @param Wrapper.Group#GROUP group Group that reached the holding zone. +-- @param #AIRBOSS airboss Airboss object. +-- @param #AIRBOSS.FlightGroup flight Flight group that has reached the holding zone. +function AIRBOSS._TaskFunctionMarshalAI( group, airboss, flight ) -- Debug message. - 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 text = string.format( "Flight %s is send to marshal.", group:GetName() ) + MESSAGE:New( text, 10 ):ToAllIf( airboss.Debug ) + airboss:T( airboss.lid .. text ) -- Get the next free stack for current recovery case. - local stack=airboss:_GetFreeStack(flight.ai) + local stack = airboss:_GetFreeStack( flight.ai ) if stack then -- Send AI to marshal stack. - airboss:_MarshalAI(flight, stack) + airboss:_MarshalAI( flight, stack ) else -- Send AI to orbit outside 10 NM zone and wait until the next Marshal stack is available. - if not airboss:_InQueue(airboss.Qwaiting, flight.group) then - airboss:_WaitAI(flight) + if not airboss:_InQueue( airboss.Qwaiting, flight.group ) then + airboss:_WaitAI( flight ) end end -- If it came from refueling. - if flight.refueling==true then - airboss:I(airboss.lid..string.format("Flight group %s finished refueling task.", flight.groupname)) + if flight.refueling == true then + airboss:I( airboss.lid .. string.format( "Flight group %s finished refueling task.", flight.groupname ) ) end -- Not refueling any more in case it was. - flight.refueling=false + flight.refueling = false end @@ -14473,23 +13741,23 @@ end -- @param #AIRBOSS self -- @param #string actype Aircraft type name. -- @return #string Aircraft nickname. E.g. "Hornet" for the F/A-18C or "Tomcat" For the F-14A. -function AIRBOSS:_GetACNickname(actype) +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" + 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 @@ -14499,8 +13767,8 @@ end -- @param #AIRBOSS self -- @param Wrapper.Group#GROUP group Aircraft group. -- @return #string Onboard number as string. -function AIRBOSS:_GetOnboardNumberPlayer(group) - return self:_GetOnboardNumbers(group, true) +function AIRBOSS:_GetOnboardNumberPlayer( group ) + return self:_GetOnboardNumbers( group, true ) end --- Get onboard numbers of all units in a group. @@ -14508,60 +13776,59 @@ end -- @param Wrapper.Group#GROUP group Aircraft group. -- @param #boolean playeronly If true, return the onboard number for player or client skill units. -- @return #table Table of onboard numbers. -function AIRBOSS:_GetOnboardNumbers(group, playeronly) - --self:F({groupname=group:GetName}) +function AIRBOSS:_GetOnboardNumbers( group, playeronly ) + -- self:F({groupname=group:GetName}) -- Get group name. - local groupname=group:GetName() + local groupname = group:GetName() -- Debug text. - local text=string.format("Onboard numbers of group %s:", groupname) + local text = string.format( "Onboard numbers of group %s:", groupname ) -- Units of template group. - local units=group:GetTemplate().units + local units = group:GetTemplate().units -- Get numbers. - local numbers={} - for _,unit in pairs(units) do + local numbers = {} + for _, unit in pairs( units ) do -- Onboard number and unit name. - local n=tostring(unit.onboard_num) - local name=unit.name - local skill=unit.skill or "Unknown" + local n = tostring( unit.onboard_num ) + local name = unit.name + local skill = unit.skill or "Unknown" -- Debug text. - text=text..string.format("\n- unit %s: onboard #=%s skill=%s", name, n, tostring(skill)) + text = text .. string.format( "\n- unit %s: onboard #=%s skill=%s", name, n, tostring( skill ) ) - if playeronly and skill=="Client" or skill=="Player" then + if playeronly and skill == "Client" or skill == "Player" then -- There can be only one player in the group, so we skip everything else. return n end -- Table entry. - numbers[name]=n + numbers[name] = n end -- Debug info. - self:T2(self.lid..text) + self:T2( self.lid .. text ) return numbers end - --- Get Tower frequency of carrier. -- @param #AIRBOSS self function AIRBOSS:_GetTowerFrequency() -- Tower frequency in MHz - self.TowerFreq=0 + self.TowerFreq = 0 -- Get Template of Strike Group - local striketemplate=self.carrier:GetGroup():GetTemplate() + local striketemplate = self.carrier:GetGroup():GetTemplate() -- Find the carrier unit. - for _,unit in pairs(striketemplate.units) do - if self.carrier:GetName()==unit.name then - self.TowerFreq=unit.frequency/1000000 + for _, unit in pairs( striketemplate.units ) do + if self.carrier:GetName() == unit.name then + self.TowerFreq = unit.frequency / 1000000 return end end @@ -14577,19 +13844,19 @@ end -- @param #AIRBOSS.PlayerData playerData Player data table. -- @return #number Error margin for still being okay. -- @return #number Error margin for really sucking. -function AIRBOSS:_GetGoodBadScore(playerData) +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 + 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 @@ -14599,14 +13866,14 @@ end -- @param #AIRBOSS self -- @param Wrapper.Unit#UNIT unit Aircraft unit. (Will also work with groups as given parameter.) -- @return #boolean If true, aircraft can land on a carrier. -function AIRBOSS:_IsCarrierAircraft(unit) +function AIRBOSS:_IsCarrierAircraft( unit ) -- Get aircraft type name - local aircrafttype=unit:GetTypeName() + local aircrafttype = unit:GetTypeName() -- Special case for Harrier which can only land on Tarawa, LHA and LHD. - if aircrafttype==AIRBOSS.AircraftCarrier.AV8B then - if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then + if aircrafttype == AIRBOSS.AircraftCarrier.AV8B then + if self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then return true else return false @@ -14614,17 +13881,17 @@ function AIRBOSS:_IsCarrierAircraft(unit) end -- Also only Harriers can land on the Tarawa, LHA and LHD. - if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then - if aircrafttype~=AIRBOSS.AircraftCarrier.AV8B then + if self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if aircrafttype ~= AIRBOSS.AircraftCarrier.AV8B then return false end end -- Loop over all other known carrier capable aircraft. - for _,actype in pairs(AIRBOSS.AircraftCarrier) do + for _, actype in pairs( AIRBOSS.AircraftCarrier ) do -- Check if this is a carrier capable aircraft type. - if actype==aircrafttype then + if actype == aircrafttype then return true end end @@ -14637,10 +13904,10 @@ end -- @param #AIRBOSS self -- @param Wrapper.Unit#UNIT unit Aircraft unit. -- @return #boolean If true, human player inside the unit. -function AIRBOSS:_IsHumanUnit(unit) +function AIRBOSS:_IsHumanUnit( unit ) -- Get player unit or nil if no player unit. - local playerunit=self:_GetPlayerUnitAndName(unit:GetName()) + local playerunit = self:_GetPlayerUnitAndName( unit:GetName() ) if playerunit then return true @@ -14653,15 +13920,15 @@ end -- @param #AIRBOSS self -- @param Wrapper.Group#GROUP group Aircraft group. -- @return #boolean If true, human player inside group. -function AIRBOSS:_IsHuman(group) +function AIRBOSS:_IsHuman( group ) -- Get all units of the group. - local units=group:GetUnits() + local units = group:GetUnits() -- Loop over all units. - for _,_unit in pairs(units) do + for _, _unit in pairs( units ) do -- Check if unit is human. - local human=self:_IsHumanUnit(_unit) + local human = self:_IsHumanUnit( _unit ) if human then return true end @@ -14674,31 +13941,31 @@ end -- @param #AIRBOSS self -- @param Wrapper.Unit#UNIT unit The unit for which the mass is determined. -- @return #number Fuel state in pounds. -function AIRBOSS:_GetFuelState(unit) +function AIRBOSS:_GetFuelState( unit ) -- Get relative fuel [0,1]. - local fuel=unit:GetFuel() + local fuel = unit:GetFuel() -- Get max weight of fuel in kg. - local maxfuel=self:_GetUnitMasses(unit) + local maxfuel = self:_GetUnitMasses( unit ) -- Fuel state, i.e. what let's - local fuelstate=fuel*maxfuel + local fuelstate = fuel * maxfuel -- Debug info. - self:T2(self.lid..string.format("Unit %s fuel state = %.1f kg = %.1f lbs", unit:GetName(), fuelstate, UTILS.kg2lbs(fuelstate))) + self:T2( self.lid .. string.format( "Unit %s fuel state = %.1f kg = %.1f lbs", unit:GetName(), fuelstate, UTILS.kg2lbs( fuelstate ) ) ) - return UTILS.kg2lbs(fuelstate) + return UTILS.kg2lbs( fuelstate ) end --- Convert altitude from meters to angels (thousands of feet). -- @param #AIRBOSS self -- @param alt Alitude in meters. -- @return #number Altitude in Anglels = thousands of feet using math.floor(). -function AIRBOSS:_GetAngels(alt) +function AIRBOSS:_GetAngels( alt ) if alt then - local angels=UTILS.Round(UTILS.MetersToFeet(alt)/1000, 0) + local angels = UTILS.Round( UTILS.MetersToFeet( alt ) / 1000, 0 ) return angels else return 0 @@ -14713,25 +13980,25 @@ end -- @return #number Empty weight of unit in kg. -- @return #number Max weight of unit in kg. -- @return #number Max cargo weight in kg. -function AIRBOSS:_GetUnitMasses(unit) +function AIRBOSS:_GetUnitMasses( unit ) -- Get DCS descriptors table. - local Desc=unit:GetDesc() + local Desc = unit:GetDesc() -- Mass of fuel in kg. - local massfuel=Desc.fuelMassMax or 0 + local massfuel = Desc.fuelMassMax or 0 -- Mass of empty unit in km. - local massempty=Desc.massEmpty or 0 + local massempty = Desc.massEmpty or 0 -- Max weight of unit in kg. - local massmax=Desc.massMax or 0 + local massmax = Desc.massMax or 0 -- Rest is cargo. - local masscargo=massmax-massfuel-massempty + local masscargo = massmax - massfuel - massempty -- Debug info. - 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)) + 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 @@ -14740,10 +14007,10 @@ end -- @param #AIRBOSS self -- @param Wrapper.Unit#UNIT unit Unit in question. -- @return #AIRBOSS.PlayerData Player data or nil if not player with this name or unit exists. -function AIRBOSS:_GetPlayerDataUnit(unit) +function AIRBOSS:_GetPlayerDataUnit( unit ) if unit:IsAlive() then - local unitname=unit:GetName() - local playerunit,playername=self:_GetPlayerUnitAndName(unitname) + local unitname = unit:GetName() + local playerunit, playername = self:_GetPlayerUnitAndName( unitname ) if playerunit and playername then return self.players[playername] end @@ -14751,15 +14018,14 @@ function AIRBOSS:_GetPlayerDataUnit(unit) return nil end - --- Get player data from group object. -- @param #AIRBOSS self -- @param Wrapper.Group#GROUP group Group in question. -- @return #AIRBOSS.PlayerData Player data or nil if not player with this name or unit exists. -function AIRBOSS:_GetPlayerDataGroup(group) - local units=group:GetUnits() - for _,unit in pairs(units) do - local playerdata=self:_GetPlayerDataUnit(unit) +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 @@ -14772,20 +14038,20 @@ end -- @param #string _unitName Name of the player unit. -- @return Wrapper.Unit#UNIT Unit of player or nil. -- @return #string Name of player or nil. -function AIRBOSS:_GetPlayerUnit(_unitName) +function AIRBOSS:_GetPlayerUnit( _unitName ) - for _,_player in pairs(self.players) do + for _, _player in pairs( self.players ) do - local player=_player --#AIRBOSS.PlayerData + local player = _player -- #AIRBOSS.PlayerData - 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))) + 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 + return nil, nil end --- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned. @@ -14793,13 +14059,13 @@ end -- @param #string _unitName Name of the player unit. -- @return Wrapper.Unit#UNIT Unit of player or nil. -- @return #string Name of the player or nil. -function AIRBOSS:_GetPlayerUnitAndName(_unitName) - self:F2(_unitName) +function AIRBOSS:_GetPlayerUnitAndName( _unitName ) + self:F2( _unitName ) if _unitName ~= nil then -- First, let's look up all current players. - local u,pn=self:_GetPlayerUnit(_unitName) + local u, pn = self:_GetPlayerUnit( _unitName ) -- Return if u and pn then @@ -14807,22 +14073,22 @@ function AIRBOSS:_GetPlayerUnitAndName(_unitName) end -- Get DCS unit from its name. - local DCSunit=Unit.getByName(_unitName) + local DCSunit = Unit.getByName( _unitName ) if DCSunit then -- Get player name if any. - local playername=DCSunit:getPlayerName() + local playername = DCSunit:getPlayerName() -- Unit object. - local unit=UNIT:Find(DCSunit) + local unit = UNIT:Find( DCSunit ) -- Debug. - self:T2({DCSunit=DCSunit, unit=unit, playername=playername}) + self:T2( { DCSunit = DCSunit, unit = unit, playername = playername } ) -- Check if enverything is there. if DCSunit and unit and playername then - self:T(self.lid..string.format("Found DCS unit %s with player %s.", tostring(_unitName), tostring(playername))) + self:T( self.lid .. string.format( "Found DCS unit %s with player %s.", tostring( _unitName ), tostring( playername ) ) ) return unit, playername end @@ -14831,7 +14097,7 @@ function AIRBOSS:_GetPlayerUnitAndName(_unitName) end -- Return nil if we could not find a player. - return nil,nil + return nil, nil end --- Get carrier coalition. @@ -14864,7 +14130,7 @@ end function AIRBOSS:_GetStaticWeather() -- Weather data from mission file. - local weather=env.mission.weather + local weather = env.mission.weather -- Clouds --[[ @@ -14876,19 +14142,19 @@ function AIRBOSS:_GetStaticWeather() ["iprecptns"] = 1, }, -- end of ["clouds"] ]] - local clouds=weather.clouds + local clouds = weather.clouds -- Visibilty distance in meters. - local visibility=weather.visibility.distance + local visibility = weather.visibility.distance -- Dust --[[ ["enable_dust"] = false, ["dust_density"] = 0, ]] - local dust=nil - if weather.enable_dust==true then - dust=weather.dust_density + local dust = nil + if weather.enable_dust == true then + dust = weather.dust_density end -- Fog @@ -14900,12 +14166,11 @@ function AIRBOSS:_GetStaticWeather() ["visibility"] = 25, }, -- end of ["fog"] ]] - local fog=nil - if weather.enable_fog==true then - fog=weather.fog + local fog = nil + if weather.enable_fog == true then + fog = weather.fog end - return clouds, visibility, fog, dust end @@ -14916,9 +14181,9 @@ end --- Function called by DCS timer. Unused. -- @param #table param Parameters. -- @param #number time Time. -function AIRBOSS._CheckRadioQueueT(param, time) - AIRBOSS._CheckRadioQueue(param.airboss, param.radioqueue, param.name) - return time+0.05 +function AIRBOSS._CheckRadioQueueT( param, time ) + AIRBOSS._CheckRadioQueue( param.airboss, param.radioqueue, param.name ) + return time + 0.05 end --- Radio queue item. @@ -14935,83 +14200,83 @@ end -- @param #AIRBOSS self -- @param #table radioqueue The radio queue. -- @param #string name Name of the queue. -function AIRBOSS:_CheckRadioQueue(radioqueue, name) +function AIRBOSS:_CheckRadioQueue( radioqueue, name ) - --env.info(string.format("FF %s #radioqueue %d", name, #radioqueue)) + -- env.info(string.format("FF %s #radioqueue %d", name, #radioqueue)) -- Check if queue is empty. - if #radioqueue==0 then + 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 + 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 -- Get current abs time. - local _time=timer.getAbsTime() + local _time = timer.getAbsTime() - local playing=false - local next=nil --#AIRBOSS.Radioitem - local _remove=nil - for i,_transmission in ipairs(radioqueue) do - local transmission=_transmission --#AIRBOSS.Radioitem + local playing = false + local next = nil -- #AIRBOSS.Radioitem + local _remove = nil + for i, _transmission in ipairs( radioqueue ) do + local transmission = _transmission -- #AIRBOSS.Radioitem -- Check if transmission time has passed. - if _time>=transmission.Tplay then + if _time >= transmission.Tplay then -- Check if transmission is currently playing. if transmission.isplaying then -- Check if transmission is finished. - if _time>=transmission.Tstarted+transmission.call.duration then + if _time >= transmission.Tstarted + transmission.call.duration then -- Transmission over. - transmission.isplaying=false - _remove=i + transmission.isplaying = false + _remove = i - if transmission.radio.alias=="LSO" then - self.TQLSO=_time - elseif transmission.radio.alias=="MARSHAL" then - self.TQMarshal=_time + if transmission.radio.alias == "LSO" then + self.TQLSO = _time + elseif transmission.radio.alias == "MARSHAL" then + self.TQMarshal = _time end else -- still playing -- Transmission is still playing. - playing=true + playing = true end else -- not playing yet - local Tlast=nil + 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 + 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 transmission.interval == nil then -- Not playing ==> this will be next. - if next==nil then - next=transmission + if next == nil then + next = transmission end else - if _time-Tlast>=transmission.interval then - next=transmission + if _time - Tlast >= transmission.interval then + next = transmission else end @@ -15026,21 +14291,21 @@ function AIRBOSS:_CheckRadioQueue(radioqueue, name) else - -- Transmission not due yet. + -- Transmission not due yet. end end -- Found a new transmission. - if next~=nil and not playing then - self:Broadcast(next.radio, next.call, next.loud) - next.isplaying=true - next.Tstarted=_time + if next ~= nil and not playing then + self:Broadcast( next.radio, next.call, next.loud ) + next.isplaying = true + next.Tstarted = _time end -- Remove completed calls from queue. if _remove then - table.remove(radioqueue, _remove) + table.remove( radioqueue, _remove ) end return @@ -15055,76 +14320,75 @@ end -- @param #number interval Interval in seconds after the last sound has been played. -- @param #boolean click If true, play radio click at the end. -- @param #boolean pilotcall If true, it's a pilot call. -function AIRBOSS:RadioTransmission(radio, call, loud, delay, interval, click, pilotcall) - self:F2({radio=radio, call=call, loud=loud, delay=delay, interval=interval, click=click}) +function AIRBOSS:RadioTransmission( radio, call, loud, delay, interval, click, pilotcall ) + self:F2( { radio = radio, call = call, loud = loud, delay = delay, interval = interval, click = click } ) -- Nil check. - if radio==nil or call==nil then + if radio == nil or call == nil then return end -- Create a new radio transmission item. - local transmission={} --#AIRBOSS.Radioitem + local transmission = {} -- #AIRBOSS.Radioitem - 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 + 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 -- Player onboard number if sender has one. - if self:_IsOnboard(call.modexsender) then - self:_Number2Radio(radio, call.modexsender, delay, 0.3, pilotcall) + if self:_IsOnboard( call.modexsender ) then + self:_Number2Radio( radio, call.modexsender, delay, 0.3, pilotcall ) end -- Play onboard number if receiver has one. - if self:_IsOnboard(call.modexreceiver) then - self:_Number2Radio(radio, call.modexreceiver, delay, 0.3, pilotcall) + if self:_IsOnboard( call.modexreceiver ) then + self:_Number2Radio( radio, call.modexreceiver, delay, 0.3, pilotcall ) end -- Add transmission to the right queue. - local caller="" - if radio.alias=="LSO" then + local caller = "" + if radio.alias == "LSO" then - table.insert(self.RQLSO, transmission) + table.insert( self.RQLSO, transmission ) - caller="LSOCall" + caller = "LSOCall" -- Schedule radio queue checks. 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) + 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 + elseif radio.alias == "MARSHAL" then - table.insert(self.RQMarshal, transmission) + table.insert( self.RQMarshal, transmission ) - caller="MarshalCall" + 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) + 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 -- Append radio click sound at the end of the transmission. if click then - self:RadioTransmission(radio, self[caller].CLICK, false, delay) + self:RadioTransmission( radio, self[caller].CLICK, false, delay ) end end - --- Check if a call needs a subtitle because the complete voice overs are not available. -- @param #AIRBOSS self -- @param #AIRBOSS.RadioCall call Radio sound files and subtitles. -- @return #boolean If true, call needs a subtitle. -function AIRBOSS:_NeedsSubtitle(call) +function AIRBOSS:_NeedsSubtitle( call ) -- Currently we play the noise file. - if call.file==self.MarshalCall.NOISE.file or call.file==self.LSOCall.NOISE.file then + if call.file == self.MarshalCall.NOISE.file or call.file == self.LSOCall.NOISE.file then return true else return false @@ -15136,8 +14400,8 @@ end -- @param #AIRBOSS.Radio radio Radio sending transmission. -- @param #AIRBOSS.RadioCall call Radio sound files and subtitles. -- @param #boolean loud Play loud version of file. -function AIRBOSS:Broadcast(radio, call, loud) - self:F(call) +function AIRBOSS:Broadcast( radio, call, loud ) + self:F( call ) -- Check which sound output method to use. if not self.usersoundradio then @@ -15147,72 +14411,74 @@ function AIRBOSS:Broadcast(radio, call, loud) ---------------------------- -- Get unit sending the transmission. - local sender=self:_GetRadioSender(radio) + local sender = self:_GetRadioSender( radio ) -- Construct file name and subtitle. - local filename=self:_RadioFilename(call, loud, radio.alias) + local filename = self:_RadioFilename( call, loud, radio.alias ) -- Create subtitle for transmission. - local subtitle=self:_RadioSubtitle(radio, call, loud) + local subtitle = self:_RadioSubtitle( radio, call, loud ) -- Debug. - self:T({filename=filename, subtitle=subtitle}) + self:T( { filename = filename, subtitle = subtitle } ) if sender then -- Broadcasting from aircraft. Only players tuned in to the right frequency will see the message. - self:T(self.lid..string.format("Broadcasting from aircraft %s", sender:GetName())) + self:T( self.lid .. string.format( "Broadcasting from aircraft %s", sender:GetName() ) ) -- Command to set the Frequency for the transmission. - local commandFrequency={ - id="SetFrequency", - params={ - frequency=radio.frequency*1000000, -- Frequency in Hz. - modulation=radio.modulation, - }} + local commandFrequency = { + id = "SetFrequency", + params = { + frequency = radio.frequency * 1000000, -- Frequency in Hz. + modulation = radio.modulation, + }, + } -- Command to tranmit the call. - local commandTransmit={ + local commandTransmit = { id = "TransmitMessage", params = { - file=filename, - duration=call.subduration or 5, - subtitle=subtitle, - loop=false, - }} + file = filename, + duration = call.subduration or 5, + subtitle = subtitle, + loop = false, + }, + } -- Set commend for frequency - sender:SetCommand(commandFrequency) + sender:SetCommand( commandFrequency ) -- Set command for radio transmission. - sender:SetCommand(commandTransmit) + sender:SetCommand( commandTransmit ) else -- Broadcasting from carrier. No subtitle possible. Need to send messages to players. - self:T(self.lid..string.format("Broadcasting from carrier via trigger.action.radioTransmission().")) + self:T( self.lid .. string.format( "Broadcasting from carrier via trigger.action.radioTransmission()." ) ) -- Transmit from carrier position. - local vec3=self.carrier:GetPositionVec3() + local vec3 = self.carrier:GetPositionVec3() -- Transmit via trigger. - trigger.action.radioTransmission(filename, vec3, radio.modulation, false, radio.frequency*1000000, 100) + trigger.action.radioTransmission( filename, vec3, radio.modulation, false, radio.frequency * 1000000, 100 ) -- Display subtitle of message to players. - for _,_player in pairs(self.players) do - local playerData=_player --#AIRBOSS.PlayerData + for _, _player in pairs( self.players ) do + local playerData = _player -- #AIRBOSS.PlayerData -- Message to all players in CCA that have subtites on. - if playerData.unit:IsInZone(self.zoneCCA) and playerData.actype~=AIRBOSS.AircraftCarrier.A4EC then + if playerData.unit:IsInZone( self.zoneCCA ) and playerData.actype ~= AIRBOSS.AircraftCarrier.A4EC then -- Only to players with subtitle on or if noise is played. - if playerData.subtitles or self:_NeedsSubtitle(call) then + if playerData.subtitles or self:_NeedsSubtitle( call ) then -- Messages to marshal to everyone. Messages on LSO radio only to those in the pattern. - if radio.alias=="MARSHAL" or (radio.alias=="LSO" and self:_InQueue(self.Qpattern, playerData.group)) then + if radio.alias == "MARSHAL" or (radio.alias == "LSO" and self:_InQueue( self.Qpattern, playerData.group )) then -- Message to player. - self:MessageToPlayer(playerData, subtitle, nil, "", call.subduration or 5) + self:MessageToPlayer( playerData, subtitle, nil, "", call.subduration or 5 ) end @@ -15228,17 +14494,17 @@ function AIRBOSS:Broadcast(radio, call, loud) ---------------- -- Workaround for the community A-4E-C as long as their radios are not functioning properly. - for _,_player in pairs(self.players) do - local playerData=_player --#AIRBOSS.PlayerData + for _, _player in pairs( self.players ) do + local playerData = _player -- #AIRBOSS.PlayerData -- Easy comms if globally activated but definitly for all player in the community A-4E. - if self.usersoundradio or playerData.actype==AIRBOSS.AircraftCarrier.A4EC then + if self.usersoundradio or playerData.actype == AIRBOSS.AircraftCarrier.A4EC then -- Messages to marshal to everyone. Messages on LSO radio only to those in the pattern. - if radio.alias=="MARSHAL" or (radio.alias=="LSO" and self:_InQueue(self.Qpattern, playerData.group)) then + if radio.alias == "MARSHAL" or (radio.alias == "LSO" and self:_InQueue( self.Qpattern, playerData.group )) then -- User sound to players (inside CCA). - self:Sound2Player(playerData, radio, call, loud) + self:Sound2Player( playerData, radio, call, loud ) end end @@ -15253,23 +14519,23 @@ end -- @param #AIRBOSS.RadioCall call Radio sound files and subtitles. -- @param #boolean loud If true, play loud sound file version. -- @param #number delay Delay in seconds, before the message is broadcasted. -function AIRBOSS:Sound2Player(playerData, radio, call, loud, delay) +function AIRBOSS:Sound2Player( playerData, radio, call, loud, delay ) -- Only to players inside the CCA. - if playerData.unit:IsInZone(self.zoneCCA) and call then + if playerData.unit:IsInZone( self.zoneCCA ) and call then -- Construct file name. - local filename=self:_RadioFilename(call, loud, radio.alias) + local filename = self:_RadioFilename( call, loud, radio.alias ) -- Get Subtitle - local subtitle=self:_RadioSubtitle(radio, call, loud) + local subtitle = self:_RadioSubtitle( radio, call, loud ) -- Play sound file via usersound trigger. - USERSOUND:New(filename):ToGroup(playerData.group, delay) + USERSOUND:New( filename ):ToGroup( playerData.group, delay ) -- Only to players with subtitle on or if noise is played. - if playerData.subtitles or self:_NeedsSubtitle(call) then - self:MessageToPlayer(playerData, subtitle, nil, "", call.subduration, false, delay) + if playerData.subtitles or self:_NeedsSubtitle( call ) then + self:MessageToPlayer( playerData, subtitle, nil, "", call.subduration, false, delay ) end end @@ -15281,44 +14547,44 @@ end -- @param #AIRBOSS.RadioCall call Radio sound files and subtitles. -- @param #boolean loud If true, append "!" else ".". -- @return #string Subtitle to be displayed. -function AIRBOSS:_RadioSubtitle(radio, call, loud) +function AIRBOSS:_RadioSubtitle( radio, call, loud ) -- No subtitle if call is nil, or subtitle is nil or subtitle is empty. - if call==nil or call.subtitle==nil or call.subtitle=="" then + if call == nil or call.subtitle == nil or call.subtitle == "" then return "" end -- Sender - local sender=call.sender or radio.alias + local sender = call.sender or radio.alias if call.modexsender then - sender=call.modexsender + sender = call.modexsender end -- Modex of receiver. - local receiver=call.modexreceiver or "" + local receiver = call.modexreceiver or "" -- Init subtitle. - local subtitle=string.format("%s: %s", sender, call.subtitle) - if receiver and receiver~="" then - subtitle=string.format("%s: %s, %s", sender, receiver, call.subtitle) + 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 -- Last character of the string. - local lastchar=string.sub(subtitle, -1) + local lastchar = string.sub( subtitle, -1 ) -- Append ! or . if loud then - if lastchar=="." or lastchar=="!" then - subtitle=string.sub(subtitle, 1,-1) + if lastchar == "." or lastchar == "!" then + subtitle = string.sub( subtitle, 1, -1 ) end - subtitle=subtitle.."!" + subtitle = subtitle .. "!" else - if lastchar=="!" then + if lastchar == "!" then -- This also okay. - elseif lastchar=="." then + elseif lastchar == "." then -- Nothing to do. else - subtitle=subtitle.."." + subtitle = subtitle .. "." end end @@ -15331,30 +14597,30 @@ end -- @param #boolean loud Use loud version of file if available. -- @param #string channel Radio channel alias "LSO" or "LSOCall", "MARSHAL" or "MarshalCall". -- @return #string The file name of the radio sound. -function AIRBOSS:_RadioFilename(call, loud, channel) +function AIRBOSS:_RadioFilename( call, loud, channel ) -- Construct file name and subtitle. - local prefix=call.file or "" - local suffix=call.suffix or "ogg" + local prefix = call.file or "" + local suffix = call.suffix or "ogg" -- Path to sound files. Default is in the ME - local path=self.soundfolder or "l10n/DEFAULT/" + local path = self.soundfolder or "l10n/DEFAULT/" -- Check for special LSO and Marshal sound folders. - if string.find(call.file, "LSO-") and channel and (channel=="LSO" or channel=="LSOCall") then - path=self.soundfolderLSO or path + 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 + if string.find( call.file, "MARSHAL-" ) and channel and (channel == "MARSHAL" or channel == "MarshalCall") then + path = self.soundfolderMSH or path end -- Loud version. if loud then - prefix=prefix.."_Loud" + prefix = prefix .. "_Loud" end -- File name inclusing path in miz file. - local filename=string.format("%s%s.%s", path, prefix, suffix) + local filename = string.format( "%s%s.%s", path, prefix, suffix ) return filename end @@ -15369,76 +14635,76 @@ end -- @param #number duration Display message duration. Default 10 seconds. -- @param #boolean clear If true, clear screen from previous messages. -- @param #number delay Delay in seconds, before the message is displayed. -function AIRBOSS:MessageToPlayer(playerData, message, sender, receiver, duration, clear, delay) +function AIRBOSS:MessageToPlayer( playerData, message, sender, receiver, duration, clear, delay ) - if playerData and message and message~="" then + if playerData and message and message ~= "" then -- Default duration. - duration=duration or self.Tmessage + duration = duration or self.Tmessage -- Format message. local text - if receiver and receiver=="" then + if receiver and receiver == "" then -- No (blank) receiver. - text=string.format("%s", message) + text = string.format( "%s", message ) else -- Default "receiver" is onboard number of player. - receiver=receiver or playerData.onboard - text=string.format("%s, %s", receiver, message) + receiver = receiver or playerData.onboard + text = string.format( "%s, %s", receiver, message ) end - self:T(self.lid..text) + self:T( self.lid .. text ) - if delay and delay>0 then + if delay and delay > 0 then -- Delayed call. - --SCHEDULER:New(nil, self.MessageToPlayer, {self, playerData, message, sender, receiver, duration, clear}, delay) - self:ScheduleOnce(delay, self.MessageToPlayer, self, playerData, message, sender, receiver, duration, clear) + -- SCHEDULER:New(nil, self.MessageToPlayer, {self, playerData, message, sender, receiver, duration, clear}, delay) + self:ScheduleOnce( delay, self.MessageToPlayer, self, playerData, message, sender, receiver, duration, clear ) else -- Wait until previous sound finished. - local wait=0 + local wait = 0 -- Onboard number to get the attention. - if receiver==playerData.onboard then + if receiver == playerData.onboard then -- Which voice over number to use. - if sender and (sender=="LSO" or sender=="MARSHAL" or sender=="AIRBOSS") then + if sender and (sender == "LSO" or sender == "MARSHAL" or sender == "AIRBOSS") then -- User sound of board number. - wait=wait+self:_Number2Sound(playerData, sender, receiver) + wait = wait + self:_Number2Sound( playerData, sender, receiver ) end end -- Negative. - 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 + 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 -- Affirm. - 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 + 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 -- Roger. - 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 + 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 -- Play click sound to end message. - if wait>0 then - local filename=self:_RadioFilename(self.MarshalCall.CLICK) - USERSOUND:New(filename):ToGroup(playerData.group, wait) + if wait > 0 then + local filename = self:_RadioFilename( self.MarshalCall.CLICK ) + USERSOUND:New( filename ):ToGroup( playerData.group, wait ) end -- Text message to player client. if playerData.client then - MESSAGE:New(text, duration, sender, clear):ToClient(playerData.client) + MESSAGE:New( text, duration, sender, clear ):ToClient( playerData.client ) end end @@ -15446,7 +14712,6 @@ function AIRBOSS:MessageToPlayer(playerData, message, sender, receiver, duration end end - --- Send text message to all players in the pattern queue. -- Message format will be "SENDER: RECCEIVER, MESSAGE". -- @param #AIRBOSS self @@ -15456,13 +14721,13 @@ end -- @param #number duration Display message duration. Default 10 seconds. -- @param #boolean clear If true, clear screen from previous messages. -- @param #number delay Delay in seconds, before the message is displayed. -function AIRBOSS:MessageToPattern(message, sender, receiver, duration, clear, delay) +function AIRBOSS:MessageToPattern( message, sender, receiver, duration, clear, delay ) -- Create new (fake) radio call to show the subtitile. - local call=self:_NewRadioCall(self.LSOCall.NOISE, sender or "LSO", message, duration, receiver, sender) + local call = self:_NewRadioCall( self.LSOCall.NOISE, sender or "LSO", message, duration, receiver, sender ) -- Dummy radio transmission to display subtitle only to those who tuned in. - self:RadioTransmission(self.LSORadio, call, false, delay, nil, true) + self:RadioTransmission( self.LSORadio, call, false, delay, nil, true ) end @@ -15475,13 +14740,13 @@ end -- @param #number duration Display message duration. Default 10 seconds. -- @param #boolean clear If true, clear screen from previous messages. -- @param #number delay Delay in seconds, before the message is displayed. -function AIRBOSS:MessageToMarshal(message, sender, receiver, duration, clear, delay) +function AIRBOSS:MessageToMarshal( message, sender, receiver, duration, clear, delay ) -- Create new (fake) radio call to show the subtitile. - local call=self:_NewRadioCall(self.MarshalCall.NOISE, sender or "MARSHAL", message, duration, receiver, sender) + local call = self:_NewRadioCall( self.MarshalCall.NOISE, sender or "MARSHAL", message, duration, receiver, sender ) -- Dummy radio transmission to display subtitle only to those who tuned in. - self:RadioTransmission(self.MarshalRadio, call, false, delay, nil, true) + self:RadioTransmission( self.MarshalRadio, call, false, delay, nil, true ) end @@ -15493,28 +14758,28 @@ end -- @param #number subduration Time in seconds the subtitle is displayed. Default 10 seconds. -- @param #string modexreceiver Onboard number of the receiver or nil. -- @param #string modexsender Onboard number of the sender or nil. -function AIRBOSS:_NewRadioCall(call, sender, subtitle, subduration, modexreceiver, modexsender) +function AIRBOSS:_NewRadioCall( call, sender, subtitle, subduration, modexreceiver, modexsender ) -- Create a new call - local newcall=UTILS.DeepCopy(call) --#AIRBOSS.RadioCall + local newcall = UTILS.DeepCopy( call ) -- #AIRBOSS.RadioCall -- Sender for displaying the subtitle. - newcall.sender=sender + newcall.sender = sender -- Subtitle of the message. - newcall.subtitle=subtitle or call.subtitle + newcall.subtitle = subtitle or call.subtitle -- Duration of subtitle display. - newcall.subduration=subduration or self.Tmessage + newcall.subduration = subduration or self.Tmessage -- Tail number of the receiver. - if self:_IsOnboard(modexreceiver) then - newcall.modexreceiver=modexreceiver + if self:_IsOnboard( modexreceiver ) then + newcall.modexreceiver = modexreceiver end -- Tail number of the sender. - if self:_IsOnboard(modexsender) then - newcall.modexsender=modexsender + if self:_IsOnboard( modexsender ) then + newcall.modexsender = modexsender end return newcall @@ -15524,27 +14789,27 @@ end -- @param #AIRBOSS self -- @param #AIRBOSS.Radio radio Airboss radio data. -- @return Wrapper.Unit#UNIT Sending aircraft unit or nil if was not setup, is not an aircraft or is not alive. -function AIRBOSS:_GetRadioSender(radio) +function AIRBOSS:_GetRadioSender( radio ) -- Check if we have a sending aircraft. - local sender=nil --Wrapper.Unit#UNIT + local sender = nil -- Wrapper.Unit#UNIT -- Try the general default. if self.senderac then - sender=UNIT:FindByName(self.senderac) + sender = UNIT:FindByName( self.senderac ) end -- Try the specific marshal unit. - if radio.alias=="MARSHAL" then + if radio.alias == "MARSHAL" then if self.radiorelayMSH then - sender=UNIT:FindByName(self.radiorelayMSH) + sender = UNIT:FindByName( self.radiorelayMSH ) end end -- Try the specific LSO unit. - if radio.alias=="LSO" then + if radio.alias == "LSO" then if self.radiorelayLSO then - sender=UNIT:FindByName(self.radiorelayLSO) + sender = UNIT:FindByName( self.radiorelayLSO ) end end @@ -15560,25 +14825,25 @@ end -- @param #AIRBOSS self -- @param #string text Text to check. -- @return #boolean If true, text is an onboard number of a flight. -function AIRBOSS:_IsOnboard(text) +function AIRBOSS:_IsOnboard( text ) -- Nil check. - if text==nil then + if text == nil then return false end -- Message to all. - if text=="99" then + if text == "99" then return true end -- Loop over all flights. - for _,_flight in pairs(self.flights) do - local flight=_flight --#AIRBOSS.FlightGroup + for _, _flight in pairs( self.flights ) do + local flight = _flight -- #AIRBOSS.FlightGroup -- Loop over all onboard number of that flight. - for _,onboard in pairs(flight.onboardnumbers) do - if text==onboard then + for _, onboard in pairs( flight.onboardnumbers ) do + if text == onboard then return true end end @@ -15595,55 +14860,55 @@ end -- @param #string number Number string, e.g. "032" or "183". -- @param #number delay Delay before transmission in seconds. -- @return #number Duration of the call in seconds. -function AIRBOSS:_Number2Sound(playerData, sender, number, delay) +function AIRBOSS:_Number2Sound( playerData, sender, number, delay ) -- Default. - delay=delay or 0 + delay = delay or 0 --- Split string into characters. - local function _split(str) - local chars={} - for i=1,#str do - local c=str:sub(i,i) - table.insert(chars, c) + 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 -- Sender local Sender - if sender=="LSO" then - Sender="LSOCall" - elseif sender=="MARSHAL" or sender=="AIRBOSS" then - Sender="MarshalCall" + 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))) + self:E( self.lid .. string.format( "ERROR: Unknown radio sender %s!", tostring( sender ) ) ) return end -- Split string into characters. - local numbers=_split(number) + local numbers = _split( number ) - local wait=0 - for i=1,#numbers do + local wait = 0 + for i = 1, #numbers do -- Current number - local n=numbers[i] + local n = numbers[i] -- Convert to N0, N1, ... - local N=string.format("N%s", n) + local N = string.format( "N%s", n ) -- Radio call. - local call=self[Sender][N] --#AIRBOSS.RadioCall + local call = self[Sender][N] -- #AIRBOSS.RadioCall -- Create file name. - local filename=self:_RadioFilename(call, false, Sender) + local filename = self:_RadioFilename( call, false, Sender ) -- Play sound. - USERSOUND:New(filename):ToGroup(playerData.group, delay+wait) + USERSOUND:New( filename ):ToGroup( playerData.group, delay + wait ) -- Wait until this call is over before playing the next. - wait=wait+call.duration + wait = wait + call.duration end return wait @@ -15658,120 +14923,119 @@ end -- @param #number interval Interval between the next call. -- @param #boolean pilotcall If true, use pilot sound files. -- @return #number Duration of the call in seconds. -function AIRBOSS:_Number2Radio(radio, number, delay, interval, pilotcall) +function AIRBOSS:_Number2Radio( radio, number, delay, interval, pilotcall ) --- Split string into characters. - local function _split(str) - local chars={} - for i=1,#str do - local c=str:sub(i,i) - table.insert(chars, c) + 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 -- Sender. - local Sender="" - if radio.alias=="LSO" then - Sender="LSOCall" - elseif radio.alias=="MARSHAL" then - Sender="MarshalCall" + 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))) + self:E( self.lid .. string.format( "ERROR: Unknown radio alias %s!", tostring( radio.alias ) ) ) end if pilotcall then - Sender="PilotCall" + Sender = "PilotCall" end -- Split string into characters. - local numbers=_split(number) + local numbers = _split( number ) - local wait=0 - for i=1,#numbers do + local wait = 0 + for i = 1, #numbers do -- Current number - local n=numbers[i] + local n = numbers[i] -- Convert to N0, N1, ... - local N=string.format("N%s", n) + local N = string.format( "N%s", n ) -- Radio call. - local call=self[Sender][N] --#AIRBOSS.RadioCall + local call = self[Sender][N] -- #AIRBOSS.RadioCall - if interval and i==1 then + if interval and i == 1 then -- Transmit. - self:RadioTransmission(radio, call, false, delay, interval) + self:RadioTransmission( radio, call, false, delay, interval ) else - self:RadioTransmission(radio, call, false, delay) + self:RadioTransmission( radio, call, false, delay ) end -- Add up duration of the number. - wait=wait+call.duration + wait = wait + call.duration end -- Return the total duration of the call. return wait end - --- AI aircraft calls the ball. -- @param #AIRBOSS self -- @param #string modex Tail number. -- @param #string nickname Aircraft nickname. -- @param #number fuelstate Aircraft fuel state in thouthands of pounds. -function AIRBOSS:_LSOCallAircraftBall(modex, nickname, fuelstate) +function AIRBOSS:_LSOCallAircraftBall( modex, nickname, fuelstate ) -- Pilot: "405, Hornet Ball, 3.2" - local text=string.format("%s Ball, %.1f.", nickname, fuelstate) + local text = string.format( "%s Ball, %.1f.", nickname, fuelstate ) -- Debug message. - self:I(self.lid..text) + self:I( self.lid .. text ) -- Nickname UPPERCASE. - local NICKNAME=nickname:upper() + local NICKNAME = nickname:upper() -- Fuel state. - local FS=UTILS.Split(string.format("%.1f", fuelstate), ".") + local FS = UTILS.Split( string.format( "%.1f", fuelstate ), "." ) -- Create new call to display complete subtitle. - local call=self:_NewRadioCall(self.PilotCall[NICKNAME], modex, text, self.Tmessage, nil, modex) + local call = self:_NewRadioCall( self.PilotCall[NICKNAME], modex, text, self.Tmessage, nil, modex ) -- Hornet .. - self:RadioTransmission(self.LSORadio, call, nil, nil, nil, nil, true) + self:RadioTransmission( self.LSORadio, call, nil, nil, nil, nil, true ) -- Ball, - self:RadioTransmission(self.LSORadio, self.PilotCall.BALL, nil, nil, nil, nil, true) + self:RadioTransmission( self.LSORadio, self.PilotCall.BALL, nil, nil, nil, nil, true ) -- X.. - self:_Number2Radio(self.LSORadio, FS[1], nil, nil, true) + self:_Number2Radio( self.LSORadio, FS[1], nil, nil, true ) -- Point.. - self:RadioTransmission(self.LSORadio, self.PilotCall.POINT, nil, nil, nil, nil, true) + self:RadioTransmission( self.LSORadio, self.PilotCall.POINT, nil, nil, nil, nil, true ) -- Y. - self:_Number2Radio(self.LSORadio, FS[2], nil, nil, true) + self:_Number2Radio( self.LSORadio, FS[2], nil, nil, true ) -- CLICK! - self:RadioTransmission(self.LSORadio, self.LSOCall.CLICK) + self:RadioTransmission( self.LSORadio, self.LSOCall.CLICK ) end --- AI is bingo and goes to the recovery tanker. -- @param #AIRBOSS self -- @param #string modex Tail number. -function AIRBOSS:_MarshalCallGasAtTanker(modex) +function AIRBOSS:_MarshalCallGasAtTanker( modex ) -- Subtitle. - local text=string.format("Bingo fuel! Going for gas at the recovery tanker.") + local text = string.format( "Bingo fuel! Going for gas at the recovery tanker." ) -- Debug message. - self:I(self.lid..text) + self:I( self.lid .. text ) -- Create new call to display complete subtitle. - local call=self:_NewRadioCall(self.PilotCall.BINGOFUEL, modex, text, self.Tmessage, nil, modex) + local call = self:_NewRadioCall( self.PilotCall.BINGOFUEL, modex, text, self.Tmessage, nil, modex ) -- MODEX, bingo fuel! - self:RadioTransmission(self.MarshalRadio, call, nil, nil, nil, nil, true) + self:RadioTransmission( self.MarshalRadio, call, nil, nil, nil, nil, true ) -- Going for fuel at the recovery tanker. Click! - self:RadioTransmission(self.MarshalRadio, self.PilotCall.GASATTANKER, nil, nil, nil, true, true) + self:RadioTransmission( self.MarshalRadio, self.PilotCall.GASATTANKER, nil, nil, nil, true, true ) end @@ -15779,48 +15043,47 @@ end -- @param #AIRBOSS self -- @param #string modex Tail number. -- @param #string divertname Name of the divert field. -function AIRBOSS:_MarshalCallGasAtDivert(modex, divertname) +function AIRBOSS:_MarshalCallGasAtDivert( modex, divertname ) -- Subtitle. - local text=string.format("Bingo fuel! Going for gas at divert field %s.", divertname) + local text = string.format( "Bingo fuel! Going for gas at divert field %s.", divertname ) -- Debug message. - self:I(self.lid..text) + self:I( self.lid .. text ) -- Create new call to display complete subtitle. - local call=self:_NewRadioCall(self.PilotCall.BINGOFUEL, modex, text, self.Tmessage, nil, modex) + local call = self:_NewRadioCall( self.PilotCall.BINGOFUEL, modex, text, self.Tmessage, nil, modex ) -- MODEX, bingo fuel! - self:RadioTransmission(self.MarshalRadio, call, nil, nil, nil, nil, true) + self:RadioTransmission( self.MarshalRadio, call, nil, nil, nil, nil, true ) -- Going for fuel at the divert field. Click! - self:RadioTransmission(self.MarshalRadio, self.PilotCall.GASATDIVERT, nil, nil, nil, true, true) + self:RadioTransmission( self.MarshalRadio, self.PilotCall.GASATDIVERT, nil, nil, nil, true, true ) end - --- Inform everyone that recovery ops are stopped and deck is closed. -- @param #AIRBOSS self -- @param #number case Recovery case. -function AIRBOSS:_MarshalCallRecoveryStopped(case) +function AIRBOSS:_MarshalCallRecoveryStopped( case ) -- Subtitle. - local text=string.format("Case %d recovery ops are stopped. Deck is closed.", case) + local text = string.format( "Case %d recovery ops are stopped. Deck is closed.", case ) -- Debug message. - self:I(self.lid..text) + self:I( self.lid .. text ) -- Create new call to display complete subtitle. - local call=self:_NewRadioCall(self.MarshalCall.CASE, "AIRBOSS", text, self.Tmessage, "99") + local call = self:_NewRadioCall( self.MarshalCall.CASE, "AIRBOSS", text, self.Tmessage, "99" ) -- 99, Case.. - self:RadioTransmission(self.MarshalRadio, call) + self:RadioTransmission( self.MarshalRadio, call ) -- X. - self:_Number2Radio(self.MarshalRadio, tostring(case)) + self:_Number2Radio( self.MarshalRadio, tostring( case ) ) -- recovery ops are stopped. - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.RECOVERYOPSSTOPPED, nil, nil, 0.2) + self:RadioTransmission( self.MarshalRadio, self.MarshalCall.RECOVERYOPSSTOPPED, nil, nil, 0.2 ) -- Deck is closed. Click! - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.DECKCLOSED, nil, nil, nil, true) + self:RadioTransmission( self.MarshalRadio, self.MarshalCall.DECKCLOSED, nil, nil, nil, true ) end @@ -15829,68 +15092,67 @@ end function AIRBOSS:_MarshalCallRecoveryPausedUntilFurtherNotice() -- Create new call. Subtitle already set. - local call=self:_NewRadioCall(self.MarshalCall.RECOVERYPAUSEDNOTICE, "AIRBOSS", nil, self.Tmessage, "99") + local call = self:_NewRadioCall( self.MarshalCall.RECOVERYPAUSEDNOTICE, "AIRBOSS", nil, self.Tmessage, "99" ) -- 99, aircraft recovery is paused until further notice. - self:RadioTransmission(self.MarshalRadio, call, nil, nil, nil, true) + self:RadioTransmission( self.MarshalRadio, call, nil, nil, nil, true ) end --- Inform everyone that recovery is paused and will resume at a certain time. -- @param #AIRBOSS self -- @param #string clock Time. -function AIRBOSS:_MarshalCallRecoveryPausedResumedAt(clock) +function AIRBOSS:_MarshalCallRecoveryPausedResumedAt( clock ) -- Get relevant part of clock. - local _clock=UTILS.Split(clock, "+") - local CT=UTILS.Split(_clock[1], ":") + local _clock = UTILS.Split( clock, "+" ) + local CT = UTILS.Split( _clock[1], ":" ) -- Subtitle. - local text=string.format("aircraft recovery is paused and will be resumed at %s.", clock) + local text = string.format( "aircraft recovery is paused and will be resumed at %s.", clock ) -- Debug message. - self:I(self.lid..text) + self:I( self.lid .. text ) -- Create new call with full subtitle. - local call=self:_NewRadioCall(self.MarshalCall.RECOVERYPAUSEDRESUMED, "AIRBOSS", text, self.Tmessage, "99") + local call = self:_NewRadioCall( self.MarshalCall.RECOVERYPAUSEDRESUMED, "AIRBOSS", text, self.Tmessage, "99" ) -- 99, aircraft recovery is paused and will resume at... - self:RadioTransmission(self.MarshalRadio, call) + self:RadioTransmission( self.MarshalRadio, call ) -- XY.. (hours) - self:_Number2Radio(self.MarshalRadio, CT[1]) + self:_Number2Radio( self.MarshalRadio, CT[1] ) -- XY (minutes).. - self:_Number2Radio(self.MarshalRadio, CT[2]) + self:_Number2Radio( self.MarshalRadio, CT[2] ) -- hours. Click! - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.HOURS, nil, nil, nil, true) + self:RadioTransmission( self.MarshalRadio, self.MarshalCall.HOURS, nil, nil, nil, true ) end - --- Inform flight that he is cleared for recovery. -- @param #AIRBOSS self -- @param #string modex Tail number. -- @param #number case Recovery case. -function AIRBOSS:_MarshalCallClearedForRecovery(modex, case) +function AIRBOSS:_MarshalCallClearedForRecovery( modex, case ) -- Subtitle. - local text=string.format("you're cleared for Case %d recovery.", case) + local text = string.format( "you're cleared for Case %d recovery.", case ) -- Debug message. - self:I(self.lid..text) + self:I( self.lid .. text ) -- Create new call with full subtitle. - local call=self:_NewRadioCall(self.MarshalCall.CLEAREDFORRECOVERY, "MARSHAL", text, self.Tmessage, modex) + local call = self:_NewRadioCall( self.MarshalCall.CLEAREDFORRECOVERY, "MARSHAL", text, self.Tmessage, modex ) -- Two second delay. - local delay=2 + local delay = 2 -- XYZ, you're cleared for case.. - self:RadioTransmission(self.MarshalRadio, call, nil, delay) + self:RadioTransmission( self.MarshalRadio, call, nil, delay ) -- X.. - self:_Number2Radio(self.MarshalRadio, tostring(case), delay) + self:_Number2Radio( self.MarshalRadio, tostring( case ), delay ) -- recovery. Click! - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.RECOVERY, nil, delay, nil, true) + self:RadioTransmission( self.MarshalRadio, self.MarshalCall.RECOVERY, nil, delay, nil, true ) end @@ -15899,56 +15161,56 @@ end function AIRBOSS:_MarshalCallResumeRecovery() -- Create new call with full subtitle. - local call=self:_NewRadioCall(self.MarshalCall.RESUMERECOVERY, "AIRBOSS", nil, self.Tmessage, "99") + local call = self:_NewRadioCall( self.MarshalCall.RESUMERECOVERY, "AIRBOSS", nil, self.Tmessage, "99" ) -- 99, aircraft recovery resumed. Click! - self:RadioTransmission(self.MarshalRadio, call, nil, nil, nil, true) + self:RadioTransmission( self.MarshalRadio, call, nil, nil, nil, true ) end --- Inform everyone about new final bearing. -- @param #AIRBOSS self -- @param #number FB Final Bearing in degrees. -function AIRBOSS:_MarshalCallNewFinalBearing(FB) +function AIRBOSS:_MarshalCallNewFinalBearing( FB ) -- Subtitle. - local text=string.format("new final bearing %03d°.", FB) + local text = string.format( "new final bearing %03d°.", FB ) -- Debug message. - self:I(self.lid..text) + self:I( self.lid .. text ) -- Create new call with full subtitle. - local call=self:_NewRadioCall(self.MarshalCall.NEWFB, "AIRBOSS", text, self.Tmessage, "99") + local call = self:_NewRadioCall( self.MarshalCall.NEWFB, "AIRBOSS", text, self.Tmessage, "99" ) -- 99, new final bearing.. - self:RadioTransmission(self.MarshalRadio, call) + self:RadioTransmission( self.MarshalRadio, call ) -- XYZ.. - self:_Number2Radio(self.MarshalRadio, string.format("%03d", FB), nil, 0.2) + self:_Number2Radio( self.MarshalRadio, string.format( "%03d", FB ), nil, 0.2 ) -- Degrees. Click! - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.DEGREES, nil, nil, nil, true) + self:RadioTransmission( self.MarshalRadio, self.MarshalCall.DEGREES, nil, nil, nil, true ) end --- Compile a radio call when Marshal tells a flight the holding alitude. -- @param #AIRBOSS self -- @param #number hdg Heading in degrees. -function AIRBOSS:_MarshalCallCarrierTurnTo(hdg) +function AIRBOSS:_MarshalCallCarrierTurnTo( hdg ) -- Subtitle. - local text=string.format("carrier is now starting turn to heading %03d°.", hdg) + local text = string.format( "carrier is now starting turn to heading %03d°.", hdg ) -- Debug message. - self:I(self.lid..text) + self:I( self.lid .. text ) -- Create new call with full subtitle. - local call=self:_NewRadioCall(self.MarshalCall.CARRIERTURNTOHEADING, "AIRBOSS", text, self.Tmessage, "99") + local call = self:_NewRadioCall( self.MarshalCall.CARRIERTURNTOHEADING, "AIRBOSS", text, self.Tmessage, "99" ) -- 99, turning to heading... - self:RadioTransmission(self.MarshalRadio, call) + self:RadioTransmission( self.MarshalRadio, call ) -- XYZ.. - self:_Number2Radio(self.MarshalRadio, string.format("%03d", hdg), nil, 0.2) + self:_Number2Radio( self.MarshalRadio, string.format( "%03d", hdg ), nil, 0.2 ) -- Degrees. Click! - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.DEGREES, nil, nil, nil, true) + self:RadioTransmission( self.MarshalRadio, self.MarshalCall.DEGREES, nil, nil, nil, true ) end @@ -15956,64 +15218,64 @@ end -- @param #AIRBOSS self -- @param #string modex Tail number. -- @param #number nwaiting Number of flights already waiting. -function AIRBOSS:_MarshalCallStackFull(modex, nwaiting) +function AIRBOSS:_MarshalCallStackFull( modex, nwaiting ) -- Subtitle. - 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) + 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.") + text = text .. string.format( "You are next in line." ) end -- Debug message. - self:I(self.lid..text) + self:I( self.lid .. text ) -- Create new call with full subtitle. - local call=self:_NewRadioCall(self.MarshalCall.STACKFULL, "AIRBOSS", text, self.Tmessage, modex) + local call = self:_NewRadioCall( self.MarshalCall.STACKFULL, "AIRBOSS", text, self.Tmessage, modex ) -- XYZ, Marshal stack is currently full. - self:RadioTransmission(self.MarshalRadio, call, nil, nil, nil, true) + self:RadioTransmission( self.MarshalRadio, call, nil, nil, nil, true ) end --- Compile a radio call when Marshal tells a flight the holding alitude. -- @param #AIRBOSS self -function AIRBOSS:_MarshalCallRecoveryStart(case) +function AIRBOSS:_MarshalCallRecoveryStart( case ) -- Marshal radial. - local radial=self:GetRadial(case, true, true, false) + local radial = self:GetRadial( case, true, true, false ) -- Debug output. - local text=string.format("Starting aircraft recovery Case %d ops.", case) - if case==1 then - text=text..string.format(" BRC %03d°.", self:GetBRC()) - elseif case==2 then - text=text..string.format(" Marshal radial %03d°. BRC %03d°.", radial, self:GetBRC()) - elseif case==3 then - text=text..string.format(" Marshal radial %03d°. Final heading %03d°.", radial, self:GetFinalBearing(false)) + local text = string.format( "Starting aircraft recovery Case %d ops.", case ) + if case == 1 then + text = text .. string.format( " BRC %03d°.", self:GetBRC() ) + elseif case == 2 then + text = text .. string.format( " Marshal radial %03d°. BRC %03d°.", radial, self:GetBRC() ) + elseif case == 3 then + text = text .. string.format( " Marshal radial %03d°. Final heading %03d°.", radial, self:GetFinalBearing( false ) ) end - self:T(self.lid..text) + self:T( self.lid .. text ) -- New call including the subtitle. - local call=self:_NewRadioCall(self.MarshalCall.STARTINGRECOVERY, "AIRBOSS", text, self.Tmessage, "99") + local call = self:_NewRadioCall( self.MarshalCall.STARTINGRECOVERY, "AIRBOSS", text, self.Tmessage, "99" ) -- 99, Starting aircraft recovery case.. - self:RadioTransmission(self.MarshalRadio, call) + self:RadioTransmission( self.MarshalRadio, call ) -- X.. - self:_Number2Radio(self.MarshalRadio,tostring(case), nil, 0.1) + self:_Number2Radio( self.MarshalRadio, tostring( case ), nil, 0.1 ) -- ops. - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.OPS) + self:RadioTransmission( self.MarshalRadio, self.MarshalCall.OPS ) - --Marshal Radial - if case>1 then + -- Marshal Radial + if case > 1 then -- Marshal radial.. - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.MARSHALRADIAL) + self:RadioTransmission( self.MarshalRadio, self.MarshalCall.MARSHALRADIAL ) -- XYZ.. - self:_Number2Radio(self.MarshalRadio, string.format("%03d", radial), nil, 0.2) + self:_Number2Radio( self.MarshalRadio, string.format( "%03d", radial ), nil, 0.2 ) -- Degrees. - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.DEGREES, nil, nil, nil, true) + self:RadioTransmission( self.MarshalRadio, self.MarshalCall.DEGREES, nil, nil, nil, true ) end end @@ -16026,68 +15288,66 @@ end -- @param #number altitude Holding alitude. -- @param #string charlie Charlie Time estimate. -- @param #number qfe Alitmeter inHg. -function AIRBOSS:_MarshalCallArrived(modex, case, brc, altitude, charlie, qfe) - self:F({modex=modex,case=case,brc=brc,altitude=altitude,charlie=charlie,qfe=qfe}) +function AIRBOSS:_MarshalCallArrived( modex, case, brc, altitude, charlie, qfe ) + self:F( { modex = modex, case = case, brc = brc, altitude = altitude, charlie = charlie, qfe = qfe } ) -- Split strings etc. - local angels=self:_GetAngels(altitude) - --local QFE=UTILS.Split(tostring(UTILS.Round(qfe,2)), ".") - local QFE=UTILS.Split(string.format("%.2f", qfe), ".") - local clock=UTILS.Split(charlie, "+") - local CT=UTILS.Split(clock[1], ":") + local angels = self:_GetAngels( altitude ) + -- local QFE=UTILS.Split(tostring(UTILS.Round(qfe,2)), ".") + local QFE = UTILS.Split( string.format( "%.2f", qfe ), "." ) + local clock = UTILS.Split( charlie, "+" ) + local CT = UTILS.Split( clock[1], ":" ) -- Subtitle text. - 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) + 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 ) -- Debug message. - self:I(self.lid..text) + self:I( self.lid .. text ) -- Create new call to display complete subtitle. - local casecall=self:_NewRadioCall(self.MarshalCall.CASE, "MARSHAL", text, self.Tmessage, modex) + local casecall = self:_NewRadioCall( self.MarshalCall.CASE, "MARSHAL", text, self.Tmessage, modex ) -- Case.. - self:RadioTransmission(self.MarshalRadio, casecall) + self:RadioTransmission( self.MarshalRadio, casecall ) -- X. - self:_Number2Radio(self.MarshalRadio, tostring(case)) + self:_Number2Radio( self.MarshalRadio, tostring( case ) ) -- Expected.. - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.EXPECTED, nil, nil, 0.5) + self:RadioTransmission( self.MarshalRadio, self.MarshalCall.EXPECTED, nil, nil, 0.5 ) -- BRC.. - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.BRC) + self:RadioTransmission( self.MarshalRadio, self.MarshalCall.BRC ) -- XYZ... - self:_Number2Radio(self.MarshalRadio, string.format("%03d", brc)) + self:_Number2Radio( self.MarshalRadio, string.format( "%03d", brc ) ) -- Degrees. - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.DEGREES) - + self:RadioTransmission( self.MarshalRadio, self.MarshalCall.DEGREES ) -- Hold at.. - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.HOLDATANGELS, nil, nil, 0.5) + self:RadioTransmission( self.MarshalRadio, self.MarshalCall.HOLDATANGELS, nil, nil, 0.5 ) -- X. - self:_Number2Radio(self.MarshalRadio, tostring(angels)) + self:_Number2Radio( self.MarshalRadio, tostring( angels ) ) -- Expected.. - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.EXPECTED, nil, nil, 0.5) + self:RadioTransmission( self.MarshalRadio, self.MarshalCall.EXPECTED, nil, nil, 0.5 ) -- Charlie time.. - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.CHARLIETIME) + self:RadioTransmission( self.MarshalRadio, self.MarshalCall.CHARLIETIME ) -- XY.. (hours) - self:_Number2Radio(self.MarshalRadio, CT[1]) + self:_Number2Radio( self.MarshalRadio, CT[1] ) -- XY (minutes). - self:_Number2Radio(self.MarshalRadio, CT[2]) + self:_Number2Radio( self.MarshalRadio, CT[2] ) -- hours. - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.HOURS) - + self:RadioTransmission( self.MarshalRadio, self.MarshalCall.HOURS ) -- Altimeter.. - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.ALTIMETER, nil, nil, 0.5) + self:RadioTransmission( self.MarshalRadio, self.MarshalCall.ALTIMETER, nil, nil, 0.5 ) -- XY.. - self:_Number2Radio(self.MarshalRadio, QFE[1]) + self:_Number2Radio( self.MarshalRadio, QFE[1] ) -- Point.. - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.POINT) + self:RadioTransmission( self.MarshalRadio, self.MarshalCall.POINT ) -- XY. - self:_Number2Radio(self.MarshalRadio, QFE[2]) + self:_Number2Radio( self.MarshalRadio, QFE[2] ) -- Report see me. Click! - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.REPORTSEEME, nil, nil, 0.5, true) + self:RadioTransmission( self.MarshalRadio, self.MarshalCall.REPORTSEEME, nil, nil, 0.5, true ) end @@ -16098,28 +15358,28 @@ end --- Add menu commands for player. -- @param #AIRBOSS self -- @param #string _unitName Name of player unit. -function AIRBOSS:_AddF10Commands(_unitName) - self:F(_unitName) +function AIRBOSS:_AddF10Commands( _unitName ) + self:F( _unitName ) -- Get player unit and name. - local _unit, playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, playername = self:_GetPlayerUnitAndName( _unitName ) -- Check for player unit. if _unit and playername then -- Get group and ID. - local group=_unit:GetGroup() - local gid=group:GetID() + local group = _unit:GetGroup() + local gid = group:GetID() if group and gid then if not self.menuadded[gid] then -- Enable switch so we don't do this twice. - self.menuadded[gid]=true + self.menuadded[gid] = true -- Set menu root path. - local _rootPath=nil + local _rootPath = nil if AIRBOSS.MenuF10Root then ------------------------ -- MISSON LEVEL MENUE -- @@ -16127,10 +15387,10 @@ function AIRBOSS:_AddF10Commands(_unitName) if self.menusingle then -- F10/Airboss/... - _rootPath=AIRBOSS.MenuF10Root + _rootPath = AIRBOSS.MenuF10Root else -- F10/Airboss//... - _rootPath=missionCommands.addSubMenuForGroup(gid, self.alias, AIRBOSS.MenuF10Root) + _rootPath = missionCommands.addSubMenuForGroup( gid, self.alias, AIRBOSS.MenuF10Root ) end else @@ -16139,116 +15399,114 @@ function AIRBOSS:_AddF10Commands(_unitName) ------------------------ -- Main F10 menu: F10/Airboss/ - if AIRBOSS.MenuF10[gid]==nil then - AIRBOSS.MenuF10[gid]=missionCommands.addSubMenuForGroup(gid, "Airboss") + if AIRBOSS.MenuF10[gid] == nil then + AIRBOSS.MenuF10[gid] = missionCommands.addSubMenuForGroup( gid, "Airboss" ) end - if self.menusingle then -- F10/Airboss/... - _rootPath=AIRBOSS.MenuF10[gid] + _rootPath = AIRBOSS.MenuF10[gid] else -- F10/Airboss//... - _rootPath=missionCommands.addSubMenuForGroup(gid, self.alias, AIRBOSS.MenuF10[gid]) + _rootPath = missionCommands.addSubMenuForGroup( gid, self.alias, AIRBOSS.MenuF10[gid] ) end end - -------------------------------- -- F10/Airboss//F1 Help -------------------------------- - local _helpPath=missionCommands.addSubMenuForGroup(gid, "Help", _rootPath) + local _helpPath = missionCommands.addSubMenuForGroup( gid, "Help", _rootPath ) -- F10/Airboss//F1 Help/F1 Mark Zones if self.menumarkzones then - local _markPath=missionCommands.addSubMenuForGroup(gid, "Mark Zones", _helpPath) + local _markPath = missionCommands.addSubMenuForGroup( gid, "Mark Zones", _helpPath ) -- F10/Airboss//F1 Help/F1 Mark Zones/ if self.menusmokezones then - missionCommands.addCommandForGroup(gid, "Smoke Pattern Zones", _markPath, self._MarkCaseZones, self, _unitName, false) -- F1 + missionCommands.addCommandForGroup( gid, "Smoke Pattern Zones", _markPath, self._MarkCaseZones, self, _unitName, false ) -- F1 end - missionCommands.addCommandForGroup(gid, "Flare Pattern Zones", _markPath, self._MarkCaseZones, self, _unitName, true) -- F2 + missionCommands.addCommandForGroup( gid, "Flare Pattern Zones", _markPath, self._MarkCaseZones, self, _unitName, true ) -- F2 if self.menusmokezones then - missionCommands.addCommandForGroup(gid, "Smoke Marshal Zone", _markPath, self._MarkMarshalZone, self, _unitName, false) -- F3 + missionCommands.addCommandForGroup( gid, "Smoke Marshal Zone", _markPath, self._MarkMarshalZone, self, _unitName, false ) -- F3 end - missionCommands.addCommandForGroup(gid, "Flare Marshal Zone", _markPath, self._MarkMarshalZone, self, _unitName, true) -- F4 + missionCommands.addCommandForGroup( gid, "Flare Marshal Zone", _markPath, self._MarkMarshalZone, self, _unitName, true ) -- F4 end -- F10/Airboss//F1 Help/F2 Skill Level - local _skillPath=missionCommands.addSubMenuForGroup(gid, "Skill Level", _helpPath) + local _skillPath = missionCommands.addSubMenuForGroup( gid, "Skill Level", _helpPath ) -- F10/Airboss//F1 Help/F2 Skill Level/ - missionCommands.addCommandForGroup(gid, "Flight Student", _skillPath, self._SetDifficulty, self, _unitName, AIRBOSS.Difficulty.EASY) -- F1 - missionCommands.addCommandForGroup(gid, "Naval Aviator", _skillPath, self._SetDifficulty, self, _unitName, AIRBOSS.Difficulty.NORMAL) -- F2 - missionCommands.addCommandForGroup(gid, "TOPGUN Graduate", _skillPath, self._SetDifficulty, self, _unitName, AIRBOSS.Difficulty.HARD) -- F3 - missionCommands.addCommandForGroup(gid, "Hints On/Off", _skillPath, self._SetHintsOnOff, self, _unitName) -- F4 + missionCommands.addCommandForGroup( gid, "Flight Student", _skillPath, self._SetDifficulty, self, _unitName, AIRBOSS.Difficulty.EASY ) -- F1 + missionCommands.addCommandForGroup( gid, "Naval Aviator", _skillPath, self._SetDifficulty, self, _unitName, AIRBOSS.Difficulty.NORMAL ) -- F2 + missionCommands.addCommandForGroup( gid, "TOPGUN Graduate", _skillPath, self._SetDifficulty, self, _unitName, AIRBOSS.Difficulty.HARD ) -- F3 + missionCommands.addCommandForGroup( gid, "Hints On/Off", _skillPath, self._SetHintsOnOff, self, _unitName ) -- F4 -- F10/Airboss//F1 Help/ - missionCommands.addCommandForGroup(gid, "My Status", _helpPath, self._DisplayPlayerStatus, self, _unitName) -- F3 - missionCommands.addCommandForGroup(gid, "Attitude Monitor", _helpPath, self._DisplayAttitude, self, _unitName) -- F4 - missionCommands.addCommandForGroup(gid, "Radio Check LSO", _helpPath, self._LSORadioCheck, self, _unitName) -- F5 - missionCommands.addCommandForGroup(gid, "Radio Check Marshal", _helpPath, self._MarshalRadioCheck, self, _unitName) -- F6 - missionCommands.addCommandForGroup(gid, "Subtitles On/Off", _helpPath, self._SubtitlesOnOff, self, _unitName) -- F7 - missionCommands.addCommandForGroup(gid, "Trapsheet On/Off", _helpPath, self._TrapsheetOnOff, self, _unitName) -- F8 + missionCommands.addCommandForGroup( gid, "My Status", _helpPath, self._DisplayPlayerStatus, self, _unitName ) -- F3 + missionCommands.addCommandForGroup( gid, "Attitude Monitor", _helpPath, self._DisplayAttitude, self, _unitName ) -- F4 + missionCommands.addCommandForGroup( gid, "Radio Check LSO", _helpPath, self._LSORadioCheck, self, _unitName ) -- F5 + missionCommands.addCommandForGroup( gid, "Radio Check Marshal", _helpPath, self._MarshalRadioCheck, self, _unitName ) -- F6 + missionCommands.addCommandForGroup( gid, "Subtitles On/Off", _helpPath, self._SubtitlesOnOff, self, _unitName ) -- F7 + missionCommands.addCommandForGroup( gid, "Trapsheet On/Off", _helpPath, self._TrapsheetOnOff, self, _unitName ) -- F8 ------------------------------------- -- F10/Airboss//F2 Kneeboard ------------------------------------- - local _kneeboardPath=missionCommands.addSubMenuForGroup(gid, "Kneeboard", _rootPath) + local _kneeboardPath = missionCommands.addSubMenuForGroup( gid, "Kneeboard", _rootPath ) -- F10/Airboss//F2 Kneeboard/F1 Results - local _resultsPath=missionCommands.addSubMenuForGroup(gid, "Results", _kneeboardPath) + local _resultsPath = missionCommands.addSubMenuForGroup( gid, "Results", _kneeboardPath ) -- F10/Airboss//F2 Kneeboard/F1 Results/ - missionCommands.addCommandForGroup(gid, "Greenie Board", _resultsPath, self._DisplayScoreBoard, self, _unitName) -- F1 - missionCommands.addCommandForGroup(gid, "My LSO Grades", _resultsPath, self._DisplayPlayerGrades, self, _unitName) -- F2 - missionCommands.addCommandForGroup(gid, "Last Debrief", _resultsPath, self._DisplayDebriefing, self, _unitName) -- F3 + missionCommands.addCommandForGroup( gid, "Greenie Board", _resultsPath, self._DisplayScoreBoard, self, _unitName ) -- F1 + missionCommands.addCommandForGroup( gid, "My LSO Grades", _resultsPath, self._DisplayPlayerGrades, self, _unitName ) -- F2 + missionCommands.addCommandForGroup( gid, "Last Debrief", _resultsPath, self._DisplayDebriefing, self, _unitName ) -- F3 -- F10/Airboss//F2 Kneeboard/F2 Skipper/ 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) + 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 -- F10/Airboss// ------------------------- - missionCommands.addCommandForGroup(gid, "Request Marshal", _rootPath, self._RequestMarshal, self, _unitName) -- F3 - missionCommands.addCommandForGroup(gid, "Request Commence", _rootPath, self._RequestCommence, self, _unitName) -- F4 - missionCommands.addCommandForGroup(gid, "Request Refueling", _rootPath, self._RequestRefueling, self, _unitName) -- F5 - missionCommands.addCommandForGroup(gid, "Spinning", _rootPath, self._RequestSpinning, self, _unitName) -- F6 - missionCommands.addCommandForGroup(gid, "Emergency Landing", _rootPath, self._RequestEmergency, self, _unitName) -- F7 - missionCommands.addCommandForGroup(gid, "[Reset My Status]", _rootPath, self._ResetPlayerStatus, self, _unitName) -- F8 + missionCommands.addCommandForGroup( gid, "Request Marshal", _rootPath, self._RequestMarshal, self, _unitName ) -- F3 + missionCommands.addCommandForGroup( gid, "Request Commence", _rootPath, self._RequestCommence, self, _unitName ) -- F4 + missionCommands.addCommandForGroup( gid, "Request Refueling", _rootPath, self._RequestRefueling, self, _unitName ) -- F5 + missionCommands.addCommandForGroup( gid, "Spinning", _rootPath, self._RequestSpinning, self, _unitName ) -- F6 + missionCommands.addCommandForGroup( gid, "Emergency Landing", _rootPath, self._RequestEmergency, self, _unitName ) -- F7 + missionCommands.addCommandForGroup( gid, "[Reset My Status]", _rootPath, self._ResetPlayerStatus, self, _unitName ) -- F8 end else - self:E(self.lid..string.format("ERROR: Could not find group or group ID in AddF10Menu() function. Unit name: %s.", _unitName)) + 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)) + self:E( self.lid .. string.format( "ERROR: Player unit does not exist in AddF10Menu() function. Unit name: %s.", _unitName ) ) end end @@ -16261,37 +15519,37 @@ end -- @param #AIRBOSS self -- @param #string _unitName Name fo the player unit. -- @param #number case Recovery case. -function AIRBOSS:_SkipperStartRecovery(_unitName, case) +function AIRBOSS:_SkipperStartRecovery( _unitName, case ) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) -- Check if we have a unit which is a player. if _unit and _playername then - local playerData=self.players[_playername] --#AIRBOSS.PlayerData + local playerData = self.players[_playername] -- #AIRBOSS.PlayerData if playerData then -- Inform player. - 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 + 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") + text = "negative, carrier is already recovering." + self:MessageToPlayer( playerData, text, "AIRBOSS" ) return end - self:MessageToPlayer(playerData, text, "AIRBOSS") + self:MessageToPlayer( playerData, text, "AIRBOSS" ) -- Recovery staring in 5 min for 30 min. - local t0=timer.getAbsTime()+5*60 - local t9=t0+self.skipperTime*60 - local C0=UTILS.SecondsToClock(t0) - local C9=UTILS.SecondsToClock(t9) + local t0 = timer.getAbsTime() + 5 * 60 + local t9 = t0 + self.skipperTime * 60 + local C0 = UTILS.SecondsToClock( t0 ) + local C9 = UTILS.SecondsToClock( t9 ) -- Carrier will turn into the wind. Wind on deck 25 knots. U-turn on. - self:AddRecoveryWindow(C0, C9, case, self.skipperOffset, true, self.skipperSpeed, self.skipperUturn) + self:AddRecoveryWindow( C0, C9, case, self.skipperOffset, true, self.skipperSpeed, self.skipperUturn ) end end @@ -16300,25 +15558,25 @@ end --- Skipper Stop recovery function. -- @param #AIRBOSS self -- @param #string _unitName Name fo the player unit. -function AIRBOSS:_SkipperStopRecovery(_unitName) +function AIRBOSS:_SkipperStopRecovery( _unitName ) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) -- Check if we have a unit which is a player. if _unit and _playername then - local playerData=self.players[_playername] --#AIRBOSS.PlayerData + local playerData = self.players[_playername] -- #AIRBOSS.PlayerData if playerData then -- Inform player. - local text="roger, stopping recovery right away." + local text = "roger, stopping recovery right away." if not self:IsRecovering() then - text="negative, carrier is currently not recovering." - self:MessageToPlayer(playerData, text, "AIRBOSS") + text = "negative, carrier is currently not recovering." + self:MessageToPlayer( playerData, text, "AIRBOSS" ) return end - self:MessageToPlayer(playerData, text, "AIRBOSS") + self:MessageToPlayer( playerData, text, "AIRBOSS" ) self:RecoveryStop() end @@ -16329,22 +15587,22 @@ end -- @param #AIRBOSS self -- @param #string _unitName Name fo the player unit. -- @param #number offset Recovery holding offset angle in degrees for Case II/III. -function AIRBOSS:_SkipperRecoveryOffset(_unitName, offset) +function AIRBOSS:_SkipperRecoveryOffset( _unitName, offset ) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) -- Check if we have a unit which is a player. if _unit and _playername then - local playerData=self.players[_playername] --#AIRBOSS.PlayerData + local playerData = self.players[_playername] -- #AIRBOSS.PlayerData if playerData then -- Inform player. - local text=string.format("roger, relative CASE II/III Marshal radial set to %d°.", offset) - self:MessageToPlayer(playerData, text, "AIRBOSS") + local text = string.format( "roger, relative CASE II/III Marshal radial set to %d°.", offset ) + self:MessageToPlayer( playerData, text, "AIRBOSS" ) - self.skipperOffset=offset + self.skipperOffset = offset end end end @@ -16353,22 +15611,22 @@ end -- @param #AIRBOSS self -- @param #string _unitName Name fo the player unit. -- @param #number time Recovery time in minutes. -function AIRBOSS:_SkipperRecoveryTime(_unitName, time) +function AIRBOSS:_SkipperRecoveryTime( _unitName, time ) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) -- Check if we have a unit which is a player. if _unit and _playername then - local playerData=self.players[_playername] --#AIRBOSS.PlayerData + local playerData = self.players[_playername] -- #AIRBOSS.PlayerData if playerData then -- Inform player. - local text=string.format("roger, manual recovery time set to %d min.", time) - self:MessageToPlayer(playerData, text, "AIRBOSS") + local text = string.format( "roger, manual recovery time set to %d min.", time ) + self:MessageToPlayer( playerData, text, "AIRBOSS" ) - self.skipperTime=time + self.skipperTime = time end end @@ -16378,22 +15636,22 @@ end -- @param #AIRBOSS self -- @param #string _unitName Name fo the player unit. -- @param #number speed Recovery speed in knots. -function AIRBOSS:_SkipperRecoverySpeed(_unitName, speed) +function AIRBOSS:_SkipperRecoverySpeed( _unitName, speed ) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) -- Check if we have a unit which is a player. if _unit and _playername then - local playerData=self.players[_playername] --#AIRBOSS.PlayerData + local playerData = self.players[_playername] -- #AIRBOSS.PlayerData if playerData then -- Inform player. - local text=string.format("roger, wind on deck set to %d knots.", speed) - self:MessageToPlayer(playerData, text, "AIRBOSS") + local text = string.format( "roger, wind on deck set to %d knots.", speed ) + self:MessageToPlayer( playerData, text, "AIRBOSS" ) - self.skipperSpeed=speed + self.skipperSpeed = speed end end end @@ -16401,28 +15659,27 @@ end --- Skipper set recovery speed. -- @param #AIRBOSS self -- @param #string _unitName Name fo the player unit. -function AIRBOSS:_SkipperRecoveryUturn(_unitName) +function AIRBOSS:_SkipperRecoveryUturn( _unitName ) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) -- Check if we have a unit which is a player. if _unit and _playername then - local playerData=self.players[_playername] --#AIRBOSS.PlayerData + local playerData = self.players[_playername] -- #AIRBOSS.PlayerData if playerData then - self.skipperUturn=not self.skipperUturn + self.skipperUturn = not self.skipperUturn -- Inform player. - local text=string.format("roger, U-turn is now %s.", tostring(self.skipperUturn)) - self:MessageToPlayer(playerData, text, "AIRBOSS") + local text = string.format( "roger, U-turn is now %s.", tostring( self.skipperUturn ) ) + self:MessageToPlayer( playerData, text, "AIRBOSS" ) end end end - ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ROOT MENU ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -16430,33 +15687,33 @@ end --- Reset player status. Player is removed from all queues and its status is set to undefined. -- @param #AIRBOSS self -- @param #string _unitName Name fo the player unit. -function AIRBOSS:_ResetPlayerStatus(_unitName) - self:F(_unitName) +function AIRBOSS:_ResetPlayerStatus( _unitName ) + self:F( _unitName ) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) -- Check if we have a unit which is a player. if _unit and _playername then - local playerData=self.players[_playername] --#AIRBOSS.PlayerData + local playerData = self.players[_playername] -- #AIRBOSS.PlayerData if playerData then -- Inform player. - local text="roger, status reset executed! You have been removed from all queues." - self:MessageToPlayer(playerData, text, "AIRBOSS") + local text = "roger, status reset executed! You have been removed from all queues." + self:MessageToPlayer( playerData, text, "AIRBOSS" ) -- Remove flight from queues. Collapse marshal stack if necessary. -- Section members are removed from the Spinning queue. If flight is member, he is removed from the section. - self:_RemoveFlight(playerData) + self:_RemoveFlight( playerData ) -- Stop pending debrief scheduler. if playerData.debriefschedulerID and self.Scheduler then - self.Scheduler:Stop(playerData.debriefschedulerID) + self.Scheduler:Stop( playerData.debriefschedulerID ) end -- Initialize player data. - self:_InitPlayer(playerData) + self:_InitPlayer( playerData ) end end @@ -16465,68 +15722,68 @@ end --- Request marshal. -- @param #AIRBOSS self -- @param #string _unitName Name fo the player unit. -function AIRBOSS:_RequestMarshal(_unitName) - self:F(_unitName) +function AIRBOSS:_RequestMarshal( _unitName ) + self:F( _unitName ) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) -- Check if we have a unit which is a player. if _unit and _playername then - local playerData=self.players[_playername] --#AIRBOSS.PlayerData + local playerData = self.players[_playername] -- #AIRBOSS.PlayerData if playerData then -- Check if player is in CCA - local inCCA=playerData.unit:IsInZone(self.zoneCCA) + local inCCA = playerData.unit:IsInZone( self.zoneCCA ) if inCCA then - if self:_InQueue(self.Qmarshal, playerData.group) then + if self:_InQueue( self.Qmarshal, playerData.group ) then -- Flight group is already in marhal queue. - local text=string.format("negative, you are already in the Marshal queue. New marshal request denied!") - self:MessageToPlayer(playerData, text, "MARSHAL") + 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 + elseif self:_InQueue( self.Qpattern, playerData.group ) then -- Flight group is already in pattern queue. - local text=string.format("negative, you are already in the Pattern queue. Marshal request denied!") - self:MessageToPlayer(playerData, text, "MARSHAL") + 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 + elseif self:_InQueue( self.Qwaiting, playerData.group ) then -- Flight group is already in pattern queue. - 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") + 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 -- Flight group is already in pattern queue. - local text=string.format("negative, you are not airborne. Marshal request denied!") - self:MessageToPlayer(playerData, text, "MARSHAL") + local text = string.format( "negative, you are not airborne. Marshal request denied!" ) + self:MessageToPlayer( playerData, text, "MARSHAL" ) - elseif playerData.name~=playerData.seclead then + elseif playerData.name ~= playerData.seclead then -- Flight group is already in pattern queue. - local text=string.format("negative, your section lead %s needs to request Marshal.", playerData.seclead) - self:MessageToPlayer(playerData, text, "MARSHAL") + local text = string.format( "negative, your section lead %s needs to request Marshal.", playerData.seclead ) + self:MessageToPlayer( playerData, text, "MARSHAL" ) else -- Get next free Marshal stack. - local freestack=self:_GetFreeStack(playerData.ai) + local freestack = self:_GetFreeStack( playerData.ai ) -- Check if stack is available. For Case I the number is limited. if freestack then -- Add flight to marshal stack. - self:_MarshalPlayer(playerData, freestack) + self:_MarshalPlayer( playerData, freestack ) else -- Add flight to waiting queue. - self:_WaitPlayer(playerData) + self:_WaitPlayer( playerData ) end @@ -16535,8 +15792,8 @@ function AIRBOSS:_RequestMarshal(_unitName) else -- Flight group is not in CCA yet. - local text=string.format("negative, you are not inside CCA. Marshal request denied!") - self:MessageToPlayer(playerData, text, "MARSHAL") + local text = string.format( "negative, you are not inside CCA. Marshal request denied!" ) + self:MessageToPlayer( playerData, text, "MARSHAL" ) end end @@ -16546,102 +15803,102 @@ end --- Request emergency landing. -- @param #AIRBOSS self -- @param #string _unitName Name fo the player unit. -function AIRBOSS:_RequestEmergency(_unitName) - self:F(_unitName) +function AIRBOSS:_RequestEmergency( _unitName ) + self:F( _unitName ) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) -- Check if we have a unit which is a player. if _unit and _playername then - local playerData=self.players[_playername] --#AIRBOSS.PlayerData + local playerData = self.players[_playername] -- #AIRBOSS.PlayerData if playerData then - local text="" + local text = "" if not self.emergency then -- Mission designer did not allow emergency landing. - text="negative, no emergency landings on my carrier. We are currently busy. See how you get along!" + text = "negative, no emergency landings on my carrier. We are currently busy. See how you get along!" elseif not _unit:InAir() then -- Carrier zone. - local zone=self:_GetZoneCarrierBox() + local zone = self:_GetZoneCarrierBox() -- Check if player is on the carrier. - if playerData.unit:IsInZone(zone) then + if playerData.unit:IsInZone( zone ) then -- Bolter pattern. - text="roger, you are now technically in the bolter pattern. Your next step after takeoff is abeam!" + text = "roger, you are now technically in the bolter pattern. Your next step after takeoff is abeam!" -- Get flight lead. - local lead=self:_GetFlightLead(playerData) + local lead = self:_GetFlightLead( playerData ) -- Set set for lead. - self:_SetPlayerStep(lead, AIRBOSS.PatternStep.BOLTER) + self:_SetPlayerStep( lead, AIRBOSS.PatternStep.BOLTER ) -- Also set bolter pattern for all members. - for _,sec in pairs(lead.section) do - local sectionmember=sec --#AIRBOSS.PlayerData - self:_SetPlayerStep(sectionmember, AIRBOSS.PatternStep.BOLTER) + for _, sec in pairs( lead.section ) do + local sectionmember = sec -- #AIRBOSS.PlayerData + self:_SetPlayerStep( sectionmember, AIRBOSS.PatternStep.BOLTER ) end -- Remove flight from waiting queue just in case. - self:_RemoveFlightFromQueue(self.Qwaiting, lead) + self:_RemoveFlightFromQueue( self.Qwaiting, lead ) - if self:_InQueue(self.Qmarshal, lead.group) then + if self:_InQueue( self.Qmarshal, lead.group ) then -- Remove flight from Marshal queue and add to pattern. - self:_RemoveFlightFromMarshalQueue(lead) + self:_RemoveFlightFromMarshalQueue( lead ) else -- Add flight to pattern if he was not. - if not self:_InQueue(self.Qpattern, lead.group) then - self:_AddFlightToPatternQueue(lead) + if not self:_InQueue( self.Qpattern, lead.group ) then + self:_AddFlightToPatternQueue( lead ) end end else -- Flight group is not in air. - text=string.format("negative, you are not airborne. Request denied!") + text = string.format( "negative, you are not airborne. Request denied!" ) end else -- Cleared. - text="affirmative, you can bypass the pattern and are cleared for final approach!" + text = "affirmative, you can bypass the pattern and are cleared for final approach!" -- Now, if player is in the marshal or waiting queue he will be removed. But the new leader should stay in or not. - local lead=self:_GetFlightLead(playerData) + local lead = self:_GetFlightLead( playerData ) -- Set set for lead. - self:_SetPlayerStep(lead, AIRBOSS.PatternStep.EMERGENCY) + self:_SetPlayerStep( lead, AIRBOSS.PatternStep.EMERGENCY ) -- Also set emergency landing for all members. - for _,sec in pairs(lead.section) do - local sectionmember=sec --#AIRBOSS.PlayerData - self:_SetPlayerStep(sectionmember, AIRBOSS.PatternStep.EMERGENCY) + for _, sec in pairs( lead.section ) do + local sectionmember = sec -- #AIRBOSS.PlayerData + self:_SetPlayerStep( sectionmember, AIRBOSS.PatternStep.EMERGENCY ) -- Remove flight from spinning queue just in case (everone can spin on his own). - self:_RemoveFlightFromQueue(self.Qspinning, sectionmember) + self:_RemoveFlightFromQueue( self.Qspinning, sectionmember ) end -- Remove flight from waiting queue just in case. - self:_RemoveFlightFromQueue(self.Qwaiting, lead) + self:_RemoveFlightFromQueue( self.Qwaiting, lead ) - if self:_InQueue(self.Qmarshal, lead.group) then + if self:_InQueue( self.Qmarshal, lead.group ) then -- Remove flight from Marshal queue and add to pattern. - self:_RemoveFlightFromMarshalQueue(lead) + self:_RemoveFlightFromMarshalQueue( lead ) else -- Add flight to pattern if he was not. - if not self:_InQueue(self.Qpattern, lead.group) then - self:_AddFlightToPatternQueue(lead) + if not self:_InQueue( self.Qpattern, lead.group ) then + self:_AddFlightToPatternQueue( lead ) end end end -- Send message. - self:MessageToPlayer(playerData, text, "AIRBOSS") + self:MessageToPlayer( playerData, text, "AIRBOSS" ) end @@ -16651,60 +15908,60 @@ end --- Request spinning. -- @param #AIRBOSS self -- @param #string _unitName Name fo the player unit. -function AIRBOSS:_RequestSpinning(_unitName) - self:F(_unitName) +function AIRBOSS:_RequestSpinning( _unitName ) + self:F( _unitName ) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) -- Check if we have a unit which is a player. if _unit and _playername then - local playerData=self.players[_playername] --#AIRBOSS.PlayerData + local playerData = self.players[_playername] -- #AIRBOSS.PlayerData if playerData then - local text="" - if not self:_InQueue(self.Qpattern, playerData.group) then + local text = "" + if not self:_InQueue( self.Qpattern, playerData.group ) then -- Player not in pattern queue. - text="negative, you have to be in the pattern to spin it!" + text = "negative, you have to be in the pattern to spin it!" - elseif playerData.step==AIRBOSS.PatternStep.SPINNING then + elseif playerData.step == AIRBOSS.PatternStep.SPINNING then -- Player is already spinning. - text="negative, you are already spinning." + text = "negative, you are already spinning." - -- Check if player is in the right step. - elseif not (playerData.step==AIRBOSS.PatternStep.BREAKENTRY or - playerData.step==AIRBOSS.PatternStep.EARLYBREAK or - playerData.step==AIRBOSS.PatternStep.LATEBREAK) then + -- Check if player is in the right step. + elseif not (playerData.step == AIRBOSS.PatternStep.BREAKENTRY or + playerData.step == AIRBOSS.PatternStep.EARLYBREAK or + playerData.step == AIRBOSS.PatternStep.LATEBREAK) then -- Player is not in the right step. - text="negative, you have to be in the right step to spin it!" + text = "negative, you have to be in the right step to spin it!" else -- Set player step. - self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.SPINNING) + self:_SetPlayerStep( playerData, AIRBOSS.PatternStep.SPINNING ) -- Add player to spinning queue. - table.insert(self.Qspinning, playerData) + table.insert( self.Qspinning, playerData ) -- 405, Spin it! Click. - local call=self:_NewRadioCall(self.LSOCall.SPINIT, "AIRBOSS", "Spin it!", self.Tmessage, playerData.onboard) - self:RadioTransmission(self.LSORadio, call, nil, nil, nil, true) + local call = self:_NewRadioCall( self.LSOCall.SPINIT, "AIRBOSS", "Spin it!", self.Tmessage, playerData.onboard ) + self:RadioTransmission( self.LSORadio, call, nil, nil, nil, true ) -- Some advice. - if playerData.difficulty==AIRBOSS.Difficulty.EASY then - local text="Climb to 1200 feet and proceed to the initial again." - self:MessageToPlayer(playerData, text, "INSTRUCTOR", "") + 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 -- Send message. - self:MessageToPlayer(playerData, text, "AIRBOSS") + self:MessageToPlayer( playerData, text, "AIRBOSS" ) end end @@ -16713,69 +15970,69 @@ end --- Request to commence landing approach. -- @param #AIRBOSS self -- @param #string _unitName Name fo the player unit. -function AIRBOSS:_RequestCommence(_unitName) - self:F(_unitName) +function AIRBOSS:_RequestCommence( _unitName ) + self:F( _unitName ) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) -- Check if we have a unit which is a player. if _unit and _playername then - local playerData=self.players[_playername] --#AIRBOSS.PlayerData + local playerData = self.players[_playername] -- #AIRBOSS.PlayerData if playerData then -- Check if unit is in CCA. - local text="" - local cleared=false - if _unit:IsInZone(self.zoneCCA) then + local text = "" + local cleared = false + if _unit:IsInZone( self.zoneCCA ) then -- Get stack value. - local stack=playerData.flag + local stack = playerData.flag -- Number of airborne aircraft currently in pattern. - local _,npattern=self:_GetQueueInfo(self.Qpattern) + local _, npattern = self:_GetQueueInfo( self.Qpattern ) -- TODO: Check distance to initial or platform. Only allow commence if < max distance. Otherwise say bearing. - if self:_InQueue(self.Qpattern, playerData.group) then + if self:_InQueue( self.Qpattern, playerData.group ) then -- Flight group is already in pattern queue. - text=string.format("negative, %s, you are already in the Pattern queue.", playerData.name) + text = string.format( "negative, %s, you are already in the Pattern queue.", playerData.name ) elseif not _unit:InAir() then -- Flight group is already in pattern queue. - text=string.format("negative, %s, you are not airborne.", playerData.name) + text = string.format( "negative, %s, you are not airborne.", playerData.name ) - elseif playerData.seclead~=playerData.name then + elseif playerData.seclead ~= playerData.name then -- Flight group is already in pattern queue. - text=string.format("negative, %s, your section leader %s has to request commence!", playerData.name, playerData.seclead) + text = string.format( "negative, %s, your section leader %s has to request commence!", playerData.name, playerData.seclead ) - elseif stack>1 then + elseif stack > 1 then -- We are in a higher stack. - text=string.format("negative, %s, it's not your turn yet! You are in stack no. %s.", playerData.name, stack) + 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 + elseif npattern >= self.Nmaxpattern then -- Patern is full! - text=string.format("negative ghostrider, pattern is full!\nThere are %d aircraft currently in the pattern.", npattern) + 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 + elseif self:IsRecovering() == false and not self.airbossnice then -- Carrier is not recovering right now. 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) + 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.") + 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 + elseif not self:_InQueue( self.Qmarshal, playerData.group ) and not self.airbossnice then - text="negative, you have to request Marshal before you can commence." + text = "negative, you have to request Marshal before you can commence." else @@ -16783,60 +16040,60 @@ function AIRBOSS:_RequestCommence(_unitName) -- Positive Response -- ----------------------- - text=text.."roger." + text = text .. "roger." -- Carrier is not recovering but Airboss has a good day. if not self:IsRecovering() then - text=text.." Carrier is not recovering currently! However, you are cleared anyway as I have a nice day." + text = text .. " Carrier is not recovering currently! However, you are cleared anyway as I have a nice day." end -- If player is not in the Marshal queue set player case to current case. - if not self:_InQueue(self.Qmarshal, playerData.group) then + if not self:_InQueue( self.Qmarshal, playerData.group ) then -- Set current case. - playerData.case=self.case + playerData.case = self.case -- Hint about TACAN bearing. - if self.TACANon and playerData.difficulty~=AIRBOSS.Difficulty.HARD then + if self.TACANon and playerData.difficulty ~= AIRBOSS.Difficulty.HARD then -- Get inverse magnetic radial potential offset. - local radial=self:GetRadial(playerData.case, true, true, true) - if playerData.case==1 then + local radial = self:GetRadial( playerData.case, true, true, true ) + if playerData.case == 1 then -- For case 1 we want the BRC but above routine return FB. - radial=self:GetBRC() + radial = self:GetBRC() end - text=text..string.format("\nSelect TACAN %03d°, Channel %d%s (%s).\n", radial, self.TACANchannel,self.TACANmode, self.TACANmorse) + text = text .. string.format( "\nSelect TACAN %03d°, Channel %d%s (%s).\n", radial, self.TACANchannel, self.TACANmode, self.TACANmorse ) end -- TODO: Inform section members. -- Set case of section members as well. Not sure if necessary any more since it is set as soon as the recovery case is changed. - for _,flight in pairs(playerData.section) do - flight.case=playerData.case + for _, flight in pairs( playerData.section ) do + flight.case = playerData.case end -- Add player to pattern queue. Usually this is done when the stack is collapsed but this player is not in the Marshal queue. - self:_AddFlightToPatternQueue(playerData) + self:_AddFlightToPatternQueue( playerData ) end -- Clear player for commence. - cleared=true + cleared = true end else -- This flight is not yet registered! - text=string.format("negative, %s, you are not inside the CCA!", playerData.name) + text = string.format( "negative, %s, you are not inside the CCA!", playerData.name ) end -- Debug - self:T(self.lid..text) + self:T( self.lid .. text ) -- Send message. - self:MessageToPlayer(playerData, text, "MARSHAL") + self:MessageToPlayer( playerData, text, "MARSHAL" ) -- Check if player was cleard. Need to do this after the message above is displayed. if cleared then -- Call commence routine. No zone check. NOTE: Commencing will set step for all section members as well. - self:_Commencing(playerData, false) + self:_Commencing( playerData, false ) end end end @@ -16845,14 +16102,14 @@ end --- Player requests refueling. -- @param #AIRBOSS self -- @param #string _unitName Name of the player unit. -function AIRBOSS:_RequestRefueling(_unitName) +function AIRBOSS:_RequestRefueling( _unitName ) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) -- Check if we have a unit which is a player. if _unit and _playername then - local playerData=self.players[_playername] --#AIRBOSS.PlayerData + local playerData = self.players[_playername] -- #AIRBOSS.PlayerData if playerData then @@ -16861,72 +16118,71 @@ function AIRBOSS:_RequestRefueling(_unitName) if self.tanker then -- Check if player is in CCA. - if _unit:IsInZone(self.zoneCCA) then + if _unit:IsInZone( self.zoneCCA ) then -- Check if tanker is running or refueling or returning. if self.tanker:IsRunning() or self.tanker:IsRefueling() then -- Get alt of tanker in angels. - --local angels=UTILS.Round(UTILS.MetersToFeet(self.tanker.altitude)/1000, 0) - local angels=self:_GetAngels(self.tanker.altitude) + -- local angels=UTILS.Round(UTILS.MetersToFeet(self.tanker.altitude)/1000, 0) + local angels = self:_GetAngels( self.tanker.altitude ) -- Tanker is up and running. - text=string.format("affirmative, proceed to tanker at angels %d.", angels) + text = string.format( "affirmative, proceed to tanker at angels %d.", angels ) -- State TACAN channel of tanker if defined. 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) + 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 -- Tanker is currently refueling. Inform player. if self.tanker:IsRefueling() then - text=text.."\nTanker is currently refueling. You might have to queue up." + text = text .. "\nTanker is currently refueling. You might have to queue up." end -- Collapse marshal stack if player is in queue. - self:_RemoveFlightFromMarshalQueue(playerData, true) + self:_RemoveFlightFromMarshalQueue( playerData, true ) -- Set step to refueling. - self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.REFUELING) + self:_SetPlayerStep( playerData, AIRBOSS.PatternStep.REFUELING ) -- Inform section and set step. - 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) + 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 -- Tanker is RTB. - text="negative, tanker is RTB. Request denied!\nWait for the tanker to be back on station if you can." + 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." + text = "negative, you are not inside the CCA yet." end else - text="negative, no refueling tanker available." + text = "negative, no refueling tanker available." end -- Send message. - self:MessageToPlayer(playerData, text, "MARSHAL") + self:MessageToPlayer( playerData, text, "MARSHAL" ) end end end - --- Remove a member from the player's section. -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player -- @param #AIRBOSS.PlayerData sectionmember The section member to be removed. -- @return #boolean If true, flight was a section member and could be removed. False otherwise. -function AIRBOSS:_RemoveSectionMember(playerData, sectionmember) +function AIRBOSS:_RemoveSectionMember( playerData, sectionmember ) -- Loop over all flights in player's section - for i,_flight in pairs(playerData.section) do - local flight=_flight --#AIRBOSS.PlayerData - if flight.name==sectionmember.name then - table.remove(playerData.section, i) + for i, _flight in pairs( playerData.section ) do + local flight = _flight -- #AIRBOSS.PlayerData + if flight.name == sectionmember.name then + table.remove( playerData.section, i ) return true end end @@ -16936,148 +16192,150 @@ end --- Set all flights within 100 meters to be part of my section. -- @param #AIRBOSS self -- @param #string _unitName Name of the player unit. -function AIRBOSS:_SetSection(_unitName) +function AIRBOSS:_SetSection( _unitName ) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) -- Check if we have a unit which is a player. if _unit and _playername then - local playerData=self.players[_playername] --#AIRBOSS.PlayerData + local playerData = self.players[_playername] -- #AIRBOSS.PlayerData if playerData then -- Coordinate of flight lead. - local mycoord=_unit:GetCoordinate() + local mycoord = _unit:GetCoordinate() -- Max distance up to which section members are allowed. - local dmax=100 + local dmax = 100 -- Check if player is in Marshal or pattern queue already. 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!") + 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 -- Check if player is member of another section already. If so, remove him from his current section. - if playerData.seclead~=playerData.name then - local lead=self.players[playerData.seclead] --#AIRBOSS.PlayerData + if playerData.seclead ~= playerData.name then + local lead = self.players[playerData.seclead] -- #AIRBOSS.PlayerData if lead then -- Remove player from his old section lead. - local removed=self:_RemoveSectionMember(lead, playerData) + 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) + 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 -- Potential section members. - local section={} + local section = {} -- Loop over all registered flights. - for _,_flight in pairs(self.flights) do - local flight=_flight --#AIRBOSS.FlightGroup + for _, _flight in pairs( self.flights ) do + local flight = _flight -- #AIRBOSS.FlightGroup -- Only human flight groups excluding myself. Also only flights that dont have a section itself (would get messy) or are part of another section (no double membership). - if flight.ai==false and flight.groupname~=playerData.groupname and #flight.section==0 and flight.seclead==flight.name then + if flight.ai == false and flight.groupname ~= playerData.groupname and #flight.section == 0 and flight.seclead == flight.name then -- Distance (3D) to other flight group. - local distance=flight.group:GetCoordinate():Get3DDistance(mycoord) + local distance = flight.group:GetCoordinate():Get3DDistance( mycoord ) -- Check distance. - if distance remove it. if not gotit then - self:MessageToPlayer(flight, string.format("you were removed from %s's section and are on your own now.", playerData.name), "AIRBOSS", "", 5) - flight.seclead=flight.name - self:_RemoveSectionMember(playerData, flight) + self:MessageToPlayer( flight, string.format( "you were removed from %s's section and are on your own now.", playerData.name ), "AIRBOSS", "", 5 ) + flight.seclead = flight.name + self:_RemoveSectionMember( playerData, flight ) end end -- Remove all flights that are currently in the player's section already from scanned potential new section members. - for i,_new in pairs(section) do - local newflight=_new.flight --#AIRBOSS.PlayerData - for _,_flight in pairs(playerData.section) do - local currentflight=_flight --#AIRBOSS.PlayerData - if newflight.name==currentflight.name then - table.remove(section, i) + for i, _new in pairs( section ) do + local newflight = _new.flight -- #AIRBOSS.PlayerData + for _, _flight in pairs( playerData.section ) do + local currentflight = _flight -- #AIRBOSS.PlayerData + if newflight.name == currentflight.name then + table.remove( section, i ) end end end -- Init section table. Should not be necessary as all members are removed anyhow above. - --playerData.section={} + -- playerData.section={} -- Output text. - text=string.format("Registered flight section:") - text=text..string.format("\n- %s (lead)", playerData.seclead) + text = string.format( "Registered flight section:" ) + text = text .. string.format( "\n- %s (lead)", playerData.seclead ) -- Old members that stay (if any). - for _,_flight in pairs(playerData.section) do - local flight=_flight --#AIRBOSS.PlayerData - text=text..string.format("\n- %s", flight.name) + for _, _flight in pairs( playerData.section ) do + local flight = _flight -- #AIRBOSS.PlayerData + text = text .. string.format( "\n- %s", flight.name ) end -- New members (if any). - for i=1,math.min(self.NmaxSection-#playerData.section, #section) do - local flight=section[i].flight --#AIRBOSS.PlayerData + for i = 1, math.min( self.NmaxSection - #playerData.section, #section ) do + local flight = section[i].flight -- #AIRBOSS.PlayerData -- New flight members. - text=text..string.format("\n- %s", flight.name) + text = text .. string.format( "\n- %s", flight.name ) -- Set section lead of player flight. - flight.seclead=playerData.name + flight.seclead = playerData.name -- Set case of f - flight.case=playerData.case + flight.case = playerData.case -- Inform player that he is now part of a section. - self:MessageToPlayer(flight, string.format("your section lead is now %s.", playerData.name), "AIRBOSS") + self:MessageToPlayer( flight, string.format( "your section lead is now %s.", playerData.name ), "AIRBOSS" ) -- Add flight to section table. - table.insert(playerData.section, flight) + table.insert( playerData.section, flight ) end -- Section is empty. - if #playerData.section==0 then - text=text..string.format("\n- No other human flights found within radius of %.1f meters!", dmax) + if #playerData.section == 0 then + text = text .. string.format( "\n- No other human flights found within radius of %.1f meters!", dmax ) end end -- Message to section lead. - self:MessageToPlayer(playerData, text, "MARSHAL") + self:MessageToPlayer( playerData, text, "MARSHAL" ) end end end @@ -17089,33 +16347,33 @@ end --- Display top 10 player scores. -- @param #AIRBOSS self -- @param #string _unitName Name fo the player unit. -function AIRBOSS:_DisplayScoreBoard(_unitName) - self:F(_unitName) +function AIRBOSS:_DisplayScoreBoard( _unitName ) + self:F( _unitName ) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) -- Check if we have a unit which is a player. if _unit and _playername then -- Results table. - local _playerResults={} + local _playerResults = {} -- Calculate average points for all players. - for playerName,playerGrades in pairs(self.playerscores) do + for playerName, playerGrades in pairs( self.playerscores ) do if playerGrades then -- Loop over all grades - local Paverage=0 - local n=0 - for _,_grade in pairs(playerGrades) do - local grade=_grade --#AIRBOSS.LSOgrade + local Paverage = 0 + local n = 0 + for _, _grade in pairs( playerGrades ) do + local grade = _grade -- #AIRBOSS.LSOgrade -- Add up only final scores for the average. - if grade.finalscore then --grade.points>=0 then - Paverage=Paverage+grade.finalscore - n=n+1 + if grade.finalscore then -- grade.points>=0 then + Paverage = Paverage + grade.finalscore + n = n + 1 else -- Case when the player just leaves after an unfinished pass, e.g bolter, without landing. -- But this should now be solved by deleteing all unfinished results. @@ -17123,50 +16381,52 @@ function AIRBOSS:_DisplayScoreBoard(_unitName) end -- We dont want to devide by zero. - if n>0 then - _playerResults[playerName]=Paverage/n + if n > 0 then + _playerResults[playerName] = Paverage / n end end end -- Message text. - local text = string.format("Greenie Board (top ten):") - local i=1 - for _playerName,_points in UTILS.spairs(_playerResults, function(t, a, b) return t[b] < t[a] end) do + local text = string.format( "Greenie Board (top ten):" ) + local i = 1 + for _playerName, _points in UTILS.spairs( _playerResults, function( t, a, b ) + return t[b] < t[a] + end ) do -- Text. - text=text..string.format("\n[%d] %s %.1f||", i,_playerName, _points) + text = text .. string.format( "\n[%d] %s %.1f||", i, _playerName, _points ) -- All player grades. - local playerGrades=self.playerscores[_playerName] + local playerGrades = self.playerscores[_playerName] -- Add grades of passes. We use the actual grade of each pass here and not the average after player has landed. - for _,_grade in pairs(playerGrades) do - local grade=_grade --#AIRBOSS.LSOgrade + for _, _grade in pairs( playerGrades ) do + local grade = _grade -- #AIRBOSS.LSOgrade if grade.finalscore then - text=text..string.format("%.1f|", grade.points) - elseif grade.points>=0 then -- Only points >=0 as foul deck gives -1. - text=text..string.format("(%.1f)", grade.points) + text = text .. string.format( "%.1f|", grade.points ) + elseif grade.points >= 0 then -- Only points >=0 as foul deck gives -1. + text = text .. string.format( "(%.1f)", grade.points ) end end -- Display only the top ten. - i=i+1 - if i>10 then + i = i + 1 + if i > 10 then break end end -- If no results yet. - if i==1 then - text=text.."\nNo results yet." + if i == 1 then + text = text .. "\nNo results yet." end -- Send message. - local playerData=self.players[_playername] --#AIRBOSS.PlayerData + local playerData = self.players[_playername] -- #AIRBOSS.PlayerData if playerData.client then - MESSAGE:New(text, 30, nil, true):ToClient(playerData.client) + MESSAGE:New( text, 30, nil, true ):ToClient( playerData.client ) end end @@ -17175,74 +16435,73 @@ end --- Display top 10 player scores. -- @param #AIRBOSS self -- @param #string _unitName Name fo the player unit. -function AIRBOSS:_DisplayPlayerGrades(_unitName) - self:F(_unitName) +function AIRBOSS:_DisplayPlayerGrades( _unitName ) + self:F( _unitName ) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) -- Check if we have a unit which is a player. if _unit and _playername then - local playerData=self.players[_playername] --#AIRBOSS.PlayerData + local playerData = self.players[_playername] -- #AIRBOSS.PlayerData if playerData then -- Grades of player: - local text=string.format("Your last 10 grades, %s:", _playername) + local text = string.format( "Your last 10 grades, %s:", _playername ) -- All player grades. - local playerGrades=self.playerscores[_playername] or {} + local playerGrades = self.playerscores[_playername] or {} - local p=0 -- Average points. - local n=0 -- Number of final passes. - local m=0 -- Number of total passes. - --for i,_grade in pairs(playerGrades) do - for i=#playerGrades,1,-1 do - --local grade=_grade --#AIRBOSS.LSOgrade - local grade=playerGrades[i] --#AIRBOSS.LSOgrade + local p = 0 -- Average points. + local n = 0 -- Number of final passes. + local m = 0 -- Number of total passes. + -- for i,_grade in pairs(playerGrades) do + for i = #playerGrades, 1, -1 do + -- local grade=_grade --#AIRBOSS.LSOgrade + local grade = playerGrades[i] -- #AIRBOSS.LSOgrade -- Check if points >=0. For foul deck WO we give -1 and pass is not counted. - if grade.points>=0 then + if grade.points >= 0 then -- Show final points or points of pass. - local points=grade.finalscore or grade.points + local points = grade.finalscore or grade.points -- Display max 10 results. - if m<10 then - text=text..string.format("\n[%d] %s %.1f PT - %s", i, grade.grade, points, grade.details) + if m < 10 then + text = text .. string.format( "\n[%d] %s %.1f PT - %s", i, grade.grade, points, grade.details ) -- Wire trapped if any. - if grade.wire and grade.wire<=4 then - text=text..string.format(" %d-wire", grade.wire) + if grade.wire and grade.wire <= 4 then + text = text .. string.format( " %d-wire", grade.wire ) end -- Time in the groove if any. - if grade.Tgroove and grade.Tgroove<=360 then - text=text..string.format(" Tgroove=%.1f s", grade.Tgroove) + if grade.Tgroove and grade.Tgroove <= 360 then + text = text .. string.format( " Tgroove=%.1f s", grade.Tgroove ) end end -- Add up final points. if grade.finalscore then - p=p+grade.finalscore - n=n+1 + p = p + grade.finalscore + n = n + 1 end -- Total passes - m=m+1 + m = m + 1 end end - - if n>0 then - text=text..string.format("\nAverage points = %.1f", p/n) + if n > 0 then + text = text .. string.format( "\nAverage points = %.1f", p / n ) else - text=text..string.format("\nNo data available.") + text = text .. string.format( "\nNo data available." ) end -- Send message. if playerData.client then - MESSAGE:New(text, 30, nil, true):ToClient(playerData.client) + MESSAGE:New( text, 30, nil, true ):ToClient( playerData.client ) end end end @@ -17251,36 +16510,36 @@ end --- Display last debriefing. -- @param #AIRBOSS self -- @param #string _unitName Name fo the player unit. -function AIRBOSS:_DisplayDebriefing(_unitName) - self:F(_unitName) +function AIRBOSS:_DisplayDebriefing( _unitName ) + self:F( _unitName ) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) -- Check if we have a unit which is a player. if _unit and _playername then - local playerData=self.players[_playername] --#AIRBOSS.PlayerData + local playerData = self.players[_playername] -- #AIRBOSS.PlayerData if playerData then -- Debriefing text. - local text=string.format("Debriefing:") + local text = string.format( "Debriefing:" ) -- Check if data is present. - 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) + 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." + text = text .. " Nothing to show yet." end -- Send debrief message to player - self:MessageToPlayer(playerData, text, nil , "", 30, true) + self:MessageToPlayer( playerData, text, nil, "", 30, true ) end end @@ -17294,134 +16553,133 @@ end -- @param #AIRBOSS self -- @param #string _unitname Name of the player unit. -- @param #string qname Name of the queue. -function AIRBOSS:_DisplayQueue(_unitname, qname) +function AIRBOSS:_DisplayQueue( _unitname, qname ) -- Get player unit and player name. - local unit, playername = self:_GetPlayerUnitAndName(_unitname) + local unit, playername = self:_GetPlayerUnitAndName( _unitname ) -- Check if we have a player. if unit and playername then -- Player data. - local playerData=self.players[playername] --#AIRBOSS.PlayerData + local playerData = self.players[playername] -- #AIRBOSS.PlayerData if playerData then -- Queue to display. - local queue=nil - if qname=="Marshal" then - queue=self.Qmarshal - elseif qname=="Pattern" then - queue=self.Qpattern - elseif qname=="Waiting" then - queue=self.Qwaiting + 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 -- Number of group and units in queue - local Nqueue,nqueue=self:_GetQueueInfo(queue, playerData.case) + local Nqueue, nqueue = self:_GetQueueInfo( queue, playerData.case ) - local text=string.format("%s Queue:", qname) - if #queue==0 then - text=text.." empty" + 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 --#AIRBOSS.FlightGroup - 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)) + local N = 0 + if qname == "Marshal" then + for i, _flight in pairs( queue ) do + local flight = _flight -- #AIRBOSS.FlightGroup + 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 --#AIRBOSS.FlightGroup - 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) + elseif qname == "Pattern" or qname == "Waiting" then + for i, _flight in pairs( queue ) do + local flight = _flight -- #AIRBOSS.FlightGroup + 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) + text = text .. string.format( "\nTotal AC: %d (airborne %d)", N, nqueue ) end -- Send message. - self:MessageToPlayer(playerData, text, nil, "", nil, true) + self:MessageToPlayer( playerData, text, nil, "", nil, true ) end end end - --- Report information about carrier. -- @param #AIRBOSS self -- @param #string _unitname Name of the player unit. -function AIRBOSS:_DisplayCarrierInfo(_unitname) - self:F2(_unitname) +function AIRBOSS:_DisplayCarrierInfo( _unitname ) + self:F2( _unitname ) -- Get player unit and player name. - local unit, playername = self:_GetPlayerUnitAndName(_unitname) + local unit, playername = self:_GetPlayerUnitAndName( _unitname ) -- Check if we have a player. if unit and playername then -- Player data. - local playerData=self.players[playername] --#AIRBOSS.PlayerData + local playerData = self.players[playername] -- #AIRBOSS.PlayerData if playerData then -- Current coordinates. - local coord=self:GetCoordinate() + local coord = self:GetCoordinate() -- Carrier speed and heading. - local carrierheading=self.carrier:GetHeading() - local carrierspeed=UTILS.MpsToKnots(self.carrier:GetVelocityMPS()) + local carrierheading = self.carrier:GetHeading() + local carrierspeed = UTILS.MpsToKnots( self.carrier:GetVelocityMPS() ) -- TACAN/ICLS. - 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) + 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) + if self.ICLSon and self.ICLSchannel ~= nil then + icls = string.format( "%d (%s)", self.ICLSchannel, self.ICLSmorse ) end -- Wind on flight deck - local wind=UTILS.MpsToKnots(select(1, self:GetWindOnDeck())) + local wind = UTILS.MpsToKnots( select( 1, self:GetWindOnDeck() ) ) -- Get groups, units in queues. - 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 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 ) -- Current abs time. - local Tabs=timer.getAbsTime() + local Tabs = timer.getAbsTime() -- Get recovery times of carrier. - local recoverytext="Recovery time windows (max 5):" - if #self.recoverytimes==0 then - recoverytext=recoverytext.." none." + local recoverytext = "Recovery time windows (max 5):" + if #self.recoverytimes == 0 then + recoverytext = recoverytext .. " none." else -- Loop over recovery windows. - local rw=0 - for _,_recovery in pairs(self.recoverytimes) do - local recovery=_recovery --#AIRBOSS.Recovery + local rw = 0 + for _, _recovery in pairs( self.recoverytimes ) do + local recovery = _recovery -- #AIRBOSS.Recovery -- Only include current and future recovery windows. - if Tabs=5 then + rw = rw + 1 + if rw >= 5 then -- Break the loop after 5 recovery times. break end @@ -17430,140 +16688,139 @@ function AIRBOSS:_DisplayCarrierInfo(_unitname) end -- Recovery tanker TACAN text. - local tankertext=nil + local tankertext = nil if self.tanker then - tankertext=string.format("Recovery tanker frequency %.3f MHz\n", self.tanker.RadioFreq) + 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) + 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" + tankertext = tankertext .. "Recovery tanker TACAN n/a" end end -- Carrier FSM state. Idle is not clear enough. - local state=self:GetState() - if state=="Idle" then - state="Deck closed" + local state = self:GetState() + if state == "Idle" then + state = "Deck closed" end if self.turning then - state=state.." (turning currently)" + state = state .. " (turning currently)" end -- Message text. - 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) + 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) + 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) + 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" + 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) + 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 ) -- Send message. - self:MessageToPlayer(playerData, text, nil, "", 30, true) + self:MessageToPlayer( playerData, text, nil, "", 30, true ) else - self:E(self.lid..string.format("ERROR: Could not get player data for player %s.", playername)) + self:E( self.lid .. string.format( "ERROR: Could not get player data for player %s.", playername ) ) end end end - --- Report weather conditions at the carrier location. Temperature, QFE pressure and wind data. -- @param #AIRBOSS self -- @param #string _unitname Name of the player unit. -function AIRBOSS:_DisplayCarrierWeather(_unitname) - self:F2(_unitname) +function AIRBOSS:_DisplayCarrierWeather( _unitname ) + self:F2( _unitname ) -- Get player unit and player name. - local unit, playername = self:_GetPlayerUnitAndName(_unitname) + local unit, playername = self:_GetPlayerUnitAndName( _unitname ) -- Check if we have a player. if unit and playername then -- Message text. - local text="" + local text = "" -- Current coordinates. - local coord=self:GetCoordinate() + local coord = self:GetCoordinate() -- Get atmospheric data at carrier location. - local T=coord:GetTemperature() - local P=coord:GetPressure() + local T = coord:GetTemperature() + local P = coord:GetPressure() -- Get wind direction (magnetic) and strength. - local Wd,Ws=self:GetWind(nil, true) + local Wd, Ws = self:GetWind( nil, true ) -- Get Beaufort wind scale. - local Bn,Bd=UTILS.BeaufortScale(Ws) + local Bn, Bd = UTILS.BeaufortScale( Ws ) -- Wind on flight deck. - local WodPA,WodPP=self:GetWindOnDeck() - local WodPA=UTILS.MpsToKnots(WodPA) - local WodPP=UTILS.MpsToKnots(WodPP) + 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 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)) + 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 ) ) -- Report text. - 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) + 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 ) -- More info only reliable if Mission uses static weather. 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) + 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)) + 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") + text = text .. string.format( "\nNo fog" ) end if dust then - text=text..string.format("\nDust density %d", dust) + text = text .. string.format( "\nDust density %d", dust ) else - text=text..string.format("\nNo dust") + text = text .. string.format( "\nNo dust" ) end end -- Debug output. - self:T2(self.lid..text) + self:T2( self.lid .. text ) -- Send message to player group. - self:MessageToPlayer(self.players[playername], text, nil, "", 30, true) + 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)) + self:E( self.lid .. string.format( "ERROR! Could not find player unit in CarrierWeather! Unit name = %s", _unitname ) ) end end @@ -17575,31 +16832,31 @@ end -- @param #AIRBOSS self -- @param #string _unitname Name of the player unit. -- @param #AIRBOSS.Difficulty difficulty Difficulty level. -function AIRBOSS:_SetDifficulty(_unitname, difficulty) - self:T2({difficulty=difficulty, unitname=_unitname}) +function AIRBOSS:_SetDifficulty( _unitname, difficulty ) + self:T2( { difficulty = difficulty, unitname = _unitname } ) -- Get player unit and player name. - local unit, playername = self:_GetPlayerUnitAndName(_unitname) + local unit, playername = self:_GetPlayerUnitAndName( _unitname ) -- Check if we have a player. if unit and playername then -- Player data. - local playerData=self.players[playername] --#AIRBOSS.PlayerData + local playerData = self.players[playername] -- #AIRBOSS.PlayerData 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) + 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)) + self:E( self.lid .. string.format( "ERROR: Could not get player data for player %s.", playername ) ) end -- Set hints as well. - if playerData.difficulty==AIRBOSS.Difficulty.HARD then - playerData.showhints=false + if playerData.difficulty == AIRBOSS.Difficulty.HARD then + playerData.showhints = false else - playerData.showhints=true + playerData.showhints = true end end @@ -17608,31 +16865,31 @@ end --- Turn player's aircraft attitude display on or off. -- @param #AIRBOSS self -- @param #string _unitname Name of the player unit. -function AIRBOSS:_SetHintsOnOff(_unitname) - self:F2(_unitname) +function AIRBOSS:_SetHintsOnOff( _unitname ) + self:F2( _unitname ) -- Get player unit and player name. - local unit, playername = self:_GetPlayerUnitAndName(_unitname) + local unit, playername = self:_GetPlayerUnitAndName( _unitname ) -- Check if we have a player. if unit and playername then -- Player data. - local playerData=self.players[playername] --#AIRBOSS.PlayerData + local playerData = self.players[playername] -- #AIRBOSS.PlayerData if playerData then -- Invert hints. - playerData.showhints=not playerData.showhints + playerData.showhints = not playerData.showhints -- Inform player. - local text="" - if playerData.showhints==true then - text=string.format("roger, hints are now ON.") + local text = "" + if playerData.showhints == true then + text = string.format( "roger, hints are now ON." ) else - text=string.format("affirm, hints are now OFF.") + text = string.format( "affirm, hints are now OFF." ) end - self:MessageToPlayer(playerData, text, nil, playerData.name, 5) + self:MessageToPlayer( playerData, text, nil, playerData.name, 5 ) end end @@ -17641,20 +16898,20 @@ end --- Turn player's aircraft attitude display on or off. -- @param #AIRBOSS self -- @param #string _unitname Name of the player unit. -function AIRBOSS:_DisplayAttitude(_unitname) - self:F2(_unitname) +function AIRBOSS:_DisplayAttitude( _unitname ) + self:F2( _unitname ) -- Get player unit and player name. - local unit, playername = self:_GetPlayerUnitAndName(_unitname) + local unit, playername = self:_GetPlayerUnitAndName( _unitname ) -- Check if we have a player. if unit and playername then -- Player data. - local playerData=self.players[playername] --#AIRBOSS.PlayerData + local playerData = self.players[playername] -- #AIRBOSS.PlayerData if playerData then - playerData.attitudemonitor=not playerData.attitudemonitor + playerData.attitudemonitor = not playerData.attitudemonitor end end @@ -17663,28 +16920,28 @@ end --- Turn radio subtitles of player on or off. -- @param #AIRBOSS self -- @param #string _unitname Name of the player unit. -function AIRBOSS:_SubtitlesOnOff(_unitname) - self:F2(_unitname) +function AIRBOSS:_SubtitlesOnOff( _unitname ) + self:F2( _unitname ) -- Get player unit and player name. - local unit, playername = self:_GetPlayerUnitAndName(_unitname) + local unit, playername = self:_GetPlayerUnitAndName( _unitname ) -- Check if we have a player. if unit and playername then -- Player data. - local playerData=self.players[playername] --#AIRBOSS.PlayerData + local playerData = self.players[playername] -- #AIRBOSS.PlayerData if playerData then - playerData.subtitles=not playerData.subtitles + playerData.subtitles = not playerData.subtitles -- Inform player. - 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.") + 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) + self:MessageToPlayer( playerData, text, nil, playerData.name, 5 ) end end @@ -17693,154 +16950,152 @@ end --- Turn radio subtitles of player on or off. -- @param #AIRBOSS self -- @param #string _unitname Name of the player unit. -function AIRBOSS:_TrapsheetOnOff(_unitname) - self:F2(_unitname) +function AIRBOSS:_TrapsheetOnOff( _unitname ) + self:F2( _unitname ) -- Get player unit and player name. - local unit, playername = self:_GetPlayerUnitAndName(_unitname) + local unit, playername = self:_GetPlayerUnitAndName( _unitname ) -- Check if we have a player. if unit and playername then -- Player data. - local playerData=self.players[playername] --#AIRBOSS.PlayerData + local playerData = self.players[playername] -- #AIRBOSS.PlayerData if playerData then -- Check if option is enabled at all. - local text="" + local text = "" if self.trapsheet then -- Invert current setting. - playerData.trapon=not playerData.trapon + playerData.trapon = not playerData.trapon -- Inform player. - if playerData.trapon==true then - text=string.format("roger, your trapsheets are now SAVED.") + if playerData.trapon == true then + text = string.format( "roger, your trapsheets are now SAVED." ) else - text=string.format("affirm, your trapsheets are NOT SAVED.") + text = string.format( "affirm, your trapsheets are NOT SAVED." ) end else - text="negative, trap sheet data recorder is broken on this carrier." + text = "negative, trap sheet data recorder is broken on this carrier." end -- Message to player. - self:MessageToPlayer(playerData, text, nil, playerData.name, 5) + self:MessageToPlayer( playerData, text, nil, playerData.name, 5 ) end end end - --- Display player status. -- @param #AIRBOSS self -- @param #string _unitName Name of the player unit. -function AIRBOSS:_DisplayPlayerStatus(_unitName) +function AIRBOSS:_DisplayPlayerStatus( _unitName ) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) -- Check if we have a unit which is a player. if _unit and _playername then - local playerData=self.players[_playername] --#AIRBOSS.PlayerData + local playerData = self.players[_playername] -- #AIRBOSS.PlayerData if playerData then -- Pattern step text. - 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" + 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 -- Stack. - local stack=playerData.flag + local stack = playerData.flag -- Stack text. - 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) - + 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 ) -- Hint about TACAN bearing. - if playerData.step==AIRBOSS.PatternStep.HOLDING and playerData.case>1 then + if playerData.step == AIRBOSS.PatternStep.HOLDING and playerData.case > 1 then -- Get inverse magnetic radial potential offset. - local radial=self:GetRadial(playerData.case, true, true, true) - stacktext=stacktext..string.format("Select TACAN %03d°, %d DME\n", radial, angels+15) + local radial = self:GetRadial( playerData.case, true, true, true ) + stacktext = stacktext .. string.format( "Select TACAN %03d°, %d DME\n", radial, angels + 15 ) end end -- Fuel and fuel state. - local fuel=playerData.unit:GetFuel()*100 - local fuelstate=self:_GetFuelState(playerData.unit) + local fuel = playerData.unit:GetFuel() * 100 + local fuelstate = self:_GetFuelState( playerData.unit ) -- Number of units in group. - local _,nunitsGround=self:_GetFlightUnits(playerData, true) - local _,nunitsAirborne=self:_GetFlightUnits(playerData, false) + local _, nunitsGround = self:_GetFlightUnits( playerData, true ) + local _, nunitsAirborne = self:_GetFlightUnits( playerData, false ) -- Player data. - 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) + 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 + 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 --#AIRBOSS.PlayerData - text=text..string.format("\n- %s", sec.name) + 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 -- #AIRBOSS.PlayerData + text = text .. string.format( "\n- %s", sec.name ) end - if playerData.step==AIRBOSS.PatternStep.INITIAL then + if playerData.step == AIRBOSS.PatternStep.INITIAL then -- Create a point 3.0 NM astern for re-entry. - local zoneinitial=self:GetCoordinate():Translate(UTILS.NMToMeters(3.5), self:GetRadial(2, false, false, false)) + local zoneinitial = self:GetCoordinate():Translate( UTILS.NMToMeters( 3.5 ), self:GetRadial( 2, false, false, false ) ) -- Heading and distance to initial zone. - local flyhdg=playerData.unit:GetCoordinate():HeadingTo(zoneinitial) - local flydist=UTILS.MetersToNM(playerData.unit:GetCoordinate():Get2DDistance(zoneinitial)) - local brc=self:GetBRC() + local flyhdg = playerData.unit:GetCoordinate():HeadingTo( zoneinitial ) + local flydist = UTILS.MetersToNM( playerData.unit:GetCoordinate():Get2DDistance( zoneinitial ) ) + local brc = self:GetBRC() -- Help player to find its way to the initial zone. - text=text..string.format("\nTo Initial: Fly heading %03d° for %.1f NM and turn to BRC %03d°", flyhdg, flydist, brc) + 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 + elseif playerData.step == AIRBOSS.PatternStep.PLATFORM then -- Coordinate of the platform zone. - local zoneplatform=self:_GetZonePlatform(playerData.case):GetCoordinate() + local zoneplatform = self:_GetZonePlatform( playerData.case ):GetCoordinate() -- Heading and distance to platform zone. - local flyhdg=playerData.unit:GetCoordinate():HeadingTo(zoneplatform) - local flydist=UTILS.MetersToNM(playerData.unit:GetCoordinate():Get2DDistance(zoneplatform)) + local flyhdg = playerData.unit:GetCoordinate():HeadingTo( zoneplatform ) + local flydist = UTILS.MetersToNM( playerData.unit:GetCoordinate():Get2DDistance( zoneplatform ) ) -- Get heading. - local hdg=self:GetRadial(playerData.case, true, true, true) + local hdg = self:GetRadial( playerData.case, true, true, true ) -- Help player to find its way to the initial zone. - text=text..string.format("\nTo Platform: Fly heading %03d° for %.1f NM and turn to %03d°", flyhdg, flydist, hdg) + text = text .. string.format( "\nTo Platform: Fly heading %03d° for %.1f NM and turn to %03d°", flyhdg, flydist, hdg ) end -- Send message. - self:MessageToPlayer(playerData, text, nil, "", 30, true) + 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)) + 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)) + self:E( self.lid .. string.format( "ERROR: could not find player for unit %s", _unitName ) ) end end @@ -17849,92 +17104,91 @@ end -- @param #AIRBOSS self -- @param #string _unitName Name of the player unit. -- @param #boolean flare If true, flare the zone. If false, smoke the zone. -function AIRBOSS:_MarkMarshalZone(_unitName, flare) +function AIRBOSS:_MarkMarshalZone( _unitName, flare ) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) -- Check if we have a unit which is a player. if _unit and _playername then - local playerData=self.players[_playername] --#AIRBOSS.PlayerData + local playerData = self.players[_playername] -- #AIRBOSS.PlayerData if playerData then -- Get player stack and recovery case. - local stack=playerData.flag - local case=playerData.case + local stack = playerData.flag + local case = playerData.case - local text="" - if stack>0 then + local text = "" + if stack > 0 then -- Get current holding zone. - local zoneHolding=self:_GetZoneHolding(case, stack) + local zoneHolding = self:_GetZoneHolding( case, stack ) -- Get Case I commence zone at three position. - local zoneThree=self:_GetZoneCommence(case, stack) + local zoneThree = self:_GetZoneCommence( case, stack ) -- Pattern alitude. - local patternalt=self:_GetMarshalAltitude(stack, case) + local patternalt = self:_GetMarshalAltitude( stack, case ) -- Flare and smoke at the ground. - patternalt=5 + patternalt = 5 -- Roger! - text="roger, marking" + text = "roger, marking" if flare then -- Marshal WHITE flares. - text=text..string.format("\n* Marshal zone stack %d with WHITE flares.", stack) - zoneHolding:FlareZone(FLARECOLOR.White, 45, nil, patternalt) + text = text .. string.format( "\n* Marshal zone stack %d with WHITE flares.", stack ) + zoneHolding:FlareZone( FLARECOLOR.White, 45, nil, patternalt ) -- Commence RED flares. - text=text.."\n* Commence zone with RED flares." - zoneThree:FlareZone(FLARECOLOR.Red, 45, nil, patternalt) + text = text .. "\n* Commence zone with RED flares." + zoneThree:FlareZone( FLARECOLOR.Red, 45, nil, patternalt ) else -- Marshal WHITE smoke. - text=text..string.format("\n* Marshal zone stack %d with WHITE smoke.", stack) - zoneHolding:SmokeZone(SMOKECOLOR.White, 45, patternalt) + text = text .. string.format( "\n* Marshal zone stack %d with WHITE smoke.", stack ) + zoneHolding:SmokeZone( SMOKECOLOR.White, 45, patternalt ) -- Commence RED smoke - text=text.."\n* Commence zone with RED smoke." - zoneThree:SmokeZone(SMOKECOLOR.Red, 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!" + text = "negative, you are currently not in a Marshal stack. No zones will be marked!" end -- Send message to player. - self:MessageToPlayer(playerData, text, "MARSHAL", playerData.name) + self:MessageToPlayer( playerData, text, "MARSHAL", playerData.name ) end end end - --- Mark CASE I or II/II zones by either smoke or flares. -- @param #AIRBOSS self -- @param #string _unitName Name of the player unit. -- @param #boolean flare If true, flare the zone. If false, smoke the zone. -function AIRBOSS:_MarkCaseZones(_unitName, flare) +function AIRBOSS:_MarkCaseZones( _unitName, flare ) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) -- Check if we have a unit which is a player. if _unit and _playername then - local playerData=self.players[_playername] --#AIRBOSS.PlayerData + local playerData = self.players[_playername] -- #AIRBOSS.PlayerData if playerData then -- Player's recovery case. - local case=playerData.case + local case = playerData.case -- Initial - local text=string.format("affirm, marking CASE %d zones", case) + local text = string.format( "affirm, marking CASE %d zones", case ) -- Flare or smoke? if flare then @@ -17944,55 +17198,55 @@ function AIRBOSS:_MarkCaseZones(_unitName, flare) ----------- -- Case I/II: Initial - if case==1 or case==2 then - text=text.."\n* initial with GREEN flares" - self:_GetZoneInitial(case):FlareZone(FLARECOLOR.Green, 45) + if case == 1 or case == 2 then + text = text .. "\n* initial with GREEN flares" + self:_GetZoneInitial( case ):FlareZone( FLARECOLOR.Green, 45 ) end -- Case II/III: approach corridor - if case==2 or case==3 then - text=text.."\n* approach corridor with GREEN flares" - self:_GetZoneCorridor(case):FlareZone(FLARECOLOR.Green, 45) + if case == 2 or case == 3 then + text = text .. "\n* approach corridor with GREEN flares" + self:_GetZoneCorridor( case ):FlareZone( FLARECOLOR.Green, 45 ) end -- Case II/III: platform - if case==2 or case==3 then - text=text.."\n* platform with RED flares" - self:_GetZonePlatform(case):FlareZone(FLARECOLOR.Red, 45) + if case == 2 or case == 3 then + text = text .. "\n* platform with RED flares" + self:_GetZonePlatform( case ):FlareZone( FLARECOLOR.Red, 45 ) end -- Case III: dirty up - if case==3 then - text=text.."\n* dirty up with YELLOW flares" - self:_GetZoneDirtyUp(case):FlareZone(FLARECOLOR.Yellow, 45) + if case == 3 then + text = text .. "\n* dirty up with YELLOW flares" + self:_GetZoneDirtyUp( case ):FlareZone( FLARECOLOR.Yellow, 45 ) end -- Case II/III: arc in/out - 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" + 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 -- Case III: bullseye - if case==3 then - text=text.."\n* bullseye with GREEN flares" - self:_GetZoneBullseye(case):FlareZone(FLARECOLOR.Green, 45) + if case == 3 then + text = text .. "\n* bullseye with GREEN flares" + self:_GetZoneBullseye( case ):FlareZone( FLARECOLOR.Green, 45 ) end -- Tarawa, LHA and LHD landing spots. - if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then - text=text.."\n* abeam landing stop with RED flares" + if self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + text = text .. "\n* abeam landing stop with RED flares" -- Abeam landing spot zone. - local ALSPT=self:_GetZoneAbeamLandingSpot() - ALSPT:FlareZone(FLARECOLOR.Red, 5, nil, UTILS.FeetToMeters(110)) + local ALSPT = self:_GetZoneAbeamLandingSpot() + ALSPT:FlareZone( FLARECOLOR.Red, 5, nil, UTILS.FeetToMeters( 110 ) ) -- Primary landing spot zone. - text=text.."\n* primary landing spot with GREEN flares" - local LSPT=self:_GetZoneLandingSpot() - LSPT:FlareZone(FLARECOLOR.Green, 5, nil, self.carrierparam.deckheight) + text = text .. "\n* primary landing spot with GREEN flares" + local LSPT = self:_GetZoneLandingSpot() + LSPT:FlareZone( FLARECOLOR.Green, 5, nil, self.carrierparam.deckheight ) end else @@ -18002,49 +17256,49 @@ function AIRBOSS:_MarkCaseZones(_unitName, flare) ----------- -- Case I/II: Initial - if case==1 or case==2 then - text=text.."\n* initial with GREEN smoke" - self:_GetZoneInitial(case):SmokeZone(SMOKECOLOR.Green, 45) + if case == 1 or case == 2 then + text = text .. "\n* initial with GREEN smoke" + self:_GetZoneInitial( case ):SmokeZone( SMOKECOLOR.Green, 45 ) end -- Case II/III: Approach Corridor - if case==2 or case==3 then - text=text.."\n* approach corridor with GREEN smoke" - self:_GetZoneCorridor(case):SmokeZone(SMOKECOLOR.Green, 45) + if case == 2 or case == 3 then + text = text .. "\n* approach corridor with GREEN smoke" + self:_GetZoneCorridor( case ):SmokeZone( SMOKECOLOR.Green, 45 ) end -- Case II/III: platform - if case==2 or case==3 then - text=text.."\n* platform with RED smoke" - self:_GetZonePlatform(case):SmokeZone(SMOKECOLOR.Red, 45) + if case == 2 or case == 3 then + text = text .. "\n* platform with RED smoke" + self:_GetZonePlatform( case ):SmokeZone( SMOKECOLOR.Red, 45 ) end -- Case II/III: arc in/out if offset>0. - 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" + 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 -- Case III: dirty up - if case==3 then - text=text.."\n* dirty up with ORANGE smoke" - self:_GetZoneDirtyUp(case):SmokeZone(SMOKECOLOR.Orange, 45) + if case == 3 then + text = text .. "\n* dirty up with ORANGE smoke" + self:_GetZoneDirtyUp( case ):SmokeZone( SMOKECOLOR.Orange, 45 ) end -- Case III: bullseye - if case==3 then - text=text.."\n* bullseye with GREEN smoke" - self:_GetZoneBullseye(case):SmokeZone(SMOKECOLOR.Green, 45) + if case == 3 then + text = text .. "\n* bullseye with GREEN smoke" + self:_GetZoneBullseye( case ):SmokeZone( SMOKECOLOR.Green, 45 ) end end -- Send message to player. - self:MessageToPlayer(playerData, text, "MARSHAL", playerData.name) + self:MessageToPlayer( playerData, text, "MARSHAL", playerData.name ) end end @@ -18053,18 +17307,18 @@ end --- LSO radio check. Will broadcase LSO message at given LSO frequency. -- @param #AIRBOSS self -- @param #string _unitName Name fo the player unit. -function AIRBOSS:_LSORadioCheck(_unitName) - self:F(_unitName) +function AIRBOSS:_LSORadioCheck( _unitName ) + self:F( _unitName ) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) -- Check if we have a unit which is a player. if _unit and _playername then - local playerData=self.players[_playername] --#AIRBOSS.PlayerData + local playerData = self.players[_playername] -- #AIRBOSS.PlayerData if playerData then -- Broadcase LSO radio check message on LSO radio. - self:RadioTransmission(self.LSORadio, self.LSOCall.RADIOCHECK, nil, nil, nil, true) + self:RadioTransmission( self.LSORadio, self.LSOCall.RADIOCHECK, nil, nil, nil, true ) end end end @@ -18072,23 +17326,22 @@ end --- Marshal radio check. Will broadcase Marshal message at given Marshal frequency. -- @param #AIRBOSS self -- @param #string _unitName Name fo the player unit. -function AIRBOSS:_MarshalRadioCheck(_unitName) - self:F(_unitName) +function AIRBOSS:_MarshalRadioCheck( _unitName ) + self:F( _unitName ) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) -- Check if we have a unit which is a player. if _unit and _playername then - local playerData=self.players[_playername] --#AIRBOSS.PlayerData + local playerData = self.players[_playername] -- #AIRBOSS.PlayerData if playerData then -- Broadcase Marshal radio check message on Marshal radio. - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.RADIOCHECK, nil, nil, nil, true) + self:RadioTransmission( self.MarshalRadio, self.MarshalCall.RADIOCHECK, nil, nil, nil, true ) end end end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ -- Persistence Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ @@ -18097,94 +17350,92 @@ end -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data table. -- @param #AIRBOSS.LSOgrade grade LSO grad data. -function AIRBOSS:_SaveTrapSheet(playerData, grade) +function AIRBOSS:_SaveTrapSheet( playerData, grade ) -- Nothing to save. - if playerData.trapsheet==nil or #playerData.trapsheet==0 or not io then + if playerData.trapsheet == nil or #playerData.trapsheet == 0 or not io then return end --- Function that saves data to file - local function _savefile(filename, data) - local f = io.open(filename, "wb") + local function _savefile( filename, data ) + local f = io.open( filename, "wb" ) if f then - f:write(data) + 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))) + self:E( self.lid .. string.format( "ERROR: could not save trap sheet to file %s.\nFile may contain invalid characters.", tostring( filename ) ) ) end end -- Set path or default. - local path=self.trappath + local path = self.trappath if lfs then - path=path or lfs.writedir() + path = path or lfs.writedir() end - -- Create unused file name. - local filename=nil - for i=1,9999 do + local filename = nil + for i = 1, 9999 do -- Create file name - 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 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 -- Set path. - if path~=nil then - filename=path.."\\"..filename + if path ~= nil then + filename = path .. "\\" .. filename end -- Check if file exists. - local _exists=UTILS.FileExists(filename) + local _exists = UTILS.FileExists( filename ) if not _exists then break end end - -- Info - local text=string.format("Saving player %s trapsheet to file %s", playerData.name, filename) - self:I(self.lid..text) + local text = string.format( "Saving player %s trapsheet to file %s", playerData.name, filename ) + self:I( self.lid .. text ) -- Header line - local data="#Time,Rho,X,Z,Alt,AoA,GSE,LUE,Vtot,Vy,Gamma,Pitch,Roll,Yaw,Step,Grade,Points,Details\n" + 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] --#AIRBOSS.GrooveData - local T0=g0.Time + local g0 = playerData.trapsheet[1] -- #AIRBOSS.GrooveData + local T0 = g0.Time - --for _,_groove in ipairs(playerData.trapsheet) do - for i=1,#playerData.trapsheet do - --local groove=_groove --#AIRBOSS.GrooveData - 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" - -- t a b c d e f g h i j k l m n o p q - 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) + -- for _,_groove in ipairs(playerData.trapsheet) do + for i = 1, #playerData.trapsheet do + -- local groove=_groove --#AIRBOSS.GrooveData + 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" + -- t a b c d e f g h i j k l m n o p q + 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 -- Save file. - _savefile(filename, data) + _savefile( filename, data ) end --- On before "Save" event. Checks if io and lfs are available. @@ -18194,17 +17445,17 @@ end -- @param #string To To state. -- @param #string path (Optional) Path where the file is saved. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized. -- @param #string filename (Optional) File name for saving the player grades. Default is "AIRBOSS-_LSOgrades.csv". -function AIRBOSS:onbeforeSave(From, Event, To, path, filename) +function AIRBOSS:onbeforeSave( From, Event, To, path, filename ) -- Check io module is available. if not io then - self:E(self.lid.."ERROR: io not desanitized. Can't save player grades.") + self:E( self.lid .. "ERROR: io not desanitized. Can't save player grades." ) return false end -- Check default path. - 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.") + 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 @@ -18217,72 +17468,69 @@ end -- @param #string To To state. -- @param #string path Path where the file is saved. If nil, file is saved in the DCS root installtion directory or your "Saved Games" folder if lfs was desanitized. -- @param #string filename (Optional) File name for saving the player grades. Default is "AIRBOSS-_LSOgrades.csv". -function AIRBOSS:onafterSave(From, Event, To, path, filename) +function AIRBOSS:onafterSave( From, Event, To, path, filename ) --- Function that saves data to file - local function _savefile(filename, data) - local f = assert(io.open(filename, "wb")) - f:write(data) + local function _savefile( filename, data ) + local f = assert( io.open( filename, "wb" ) ) + f:write( data ) f:close() end -- Set path or default. if lfs then - path=path or lfs.writedir() + path = path or lfs.writedir() end -- Set file name. - filename=filename or string.format("AIRBOSS-%s_LSOgrades.csv", self.alias) + filename = filename or string.format( "AIRBOSS-%s_LSOgrades.csv", self.alias ) -- Set path. - if path~=nil then - filename=path.."\\"..filename + if path ~= nil then + filename = path .. "\\" .. filename end -- Header line - 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 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" -- Loop over all players. - local n=0 - for playername,grades in pairs(self.playerscores) do + local n = 0 + for playername, grades in pairs( self.playerscores ) do -- Loop over player grades table. - for i,_grade in pairs(grades) do - local grade=_grade --#AIRBOSS.LSOgrade + for i, _grade in pairs( grades ) do + local grade = _grade -- #AIRBOSS.LSOgrade -- Check some stuff that could be nil. - local wire="n/a" - if grade.wire and grade.wire<=4 then - wire=tostring(grade.wire) + 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)) + 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" + local finalscore = "n/a" if grade.finalscore then - finalscore=tostring(UTILS.Round(grade.finalscore, 1)) + finalscore = tostring( UTILS.Round( grade.finalscore, 1 ) ) end -- Compile grade line. - 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 + 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 -- Info - local text=string.format("Saving %d player LSO grades to file %s", n, filename) - self:I(self.lid..text) + local text = string.format( "Saving %d player LSO grades to file %s", n, filename ) + self:I( self.lid .. text ) -- Save file. - _savefile(filename, scores) + _savefile( filename, scores ) end - --- On before "Load" event. Checks if the file that the player grades from exists. -- @param #AIRBOSS self -- @param #string From From state. @@ -18290,13 +17538,13 @@ end -- @param #string To To state. -- @param #string path (Optional) Path where the file is loaded from. Default is the DCS installation root directory or your "Saved Games\\DCS" folder if lfs was desanizized. -- @param #string filename (Optional) File name for saving the player grades. Default is "AIRBOSS-_LSOgrades.csv". -function AIRBOSS:onbeforeLoad(From, Event, To, path, filename) +function AIRBOSS:onbeforeLoad( From, Event, To, path, filename ) --- Function that check if a file exists. - local function _fileexists(name) - local f=io.open(name,"r") - if f~=nil then - io.close(f) + local function _fileexists( name ) + local f = io.open( name, "r" ) + if f ~= nil then + io.close( f ) return true else return false @@ -18305,41 +17553,40 @@ function AIRBOSS:onbeforeLoad(From, Event, To, path, filename) -- Check io module is available. if not io then - self:E(self.lid.."WARNING: io not desanitized. Can't load player grades.") + self:E( self.lid .. "WARNING: io not desanitized. Can't load player grades." ) return false end -- Check default path. - 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.") + 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 -- Set path or default. if lfs then - path=path or lfs.writedir() + path = path or lfs.writedir() end -- Set file name. - filename=filename or string.format("AIRBOSS-%s_LSOgrades.csv", self.alias) + filename = filename or string.format( "AIRBOSS-%s_LSOgrades.csv", self.alias ) -- Set path. - if path~=nil then - filename=path.."\\"..filename + if path ~= nil then + filename = path .. "\\" .. filename end -- Check if file exists. - local exists=_fileexists(filename) + 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)) + self:E( self.lid .. string.format( "WARNING: Player LSO grades file %s does not exist.", filename ) ) return false end end - --- On after "Load" event. Loads grades of all players from file. -- @param #AIRBOSS self -- @param #string From From state. @@ -18347,102 +17594,102 @@ end -- @param #string To To state. -- @param #string path Path where the file is loaded from. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if lfs was desanizied. -- @param #string filename (Optional) File name for saving the player grades. Default is "AIRBOSS-_LSOgrades.csv". -function AIRBOSS:onafterLoad(From, Event, To, path, filename) +function AIRBOSS:onafterLoad( From, Event, To, path, filename ) --- Function that load data from a file. - local function _loadfile(filename) - local f=assert(io.open(filename, "rb")) - local data=f:read("*all") + local function _loadfile( filename ) + local f = assert( io.open( filename, "rb" ) ) + local data = f:read( "*all" ) f:close() return data end -- Set path or default. if lfs then - path=path or lfs.writedir() + path = path or lfs.writedir() end -- Set file name. - filename=filename or string.format("AIRBOSS-%s_LSOgrades.csv", self.alias) + filename = filename or string.format( "AIRBOSS-%s_LSOgrades.csv", self.alias ) -- Set path. - if path~=nil then - filename=path.."\\"..filename + if path ~= nil then + filename = path .. "\\" .. filename end -- Info message. - 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 text = string.format( "Loading player LSO grades from file %s", filename ) + MESSAGE:New( text, 10 ):ToAllIf( self.Debug ) + self:I( self.lid .. text ) -- Load asset data from file. - local data=_loadfile(filename) + local data = _loadfile( filename ) -- Split by line break. - local playergrades=UTILS.Split(data,"\n") + local playergrades = UTILS.Split( data, "\n" ) -- Remove first header line. - table.remove(playergrades, 1) + table.remove( playergrades, 1 ) -- Init player scores table. - self.playerscores={} + self.playerscores = {} -- Loop over all lines. - local n=0 - for _,gradeline in pairs(playergrades) do + local n = 0 + for _, gradeline in pairs( playergrades ) do -- Parameters are separated by commata. - local gradedata=UTILS.Split(gradeline, ",") + local gradedata = UTILS.Split( gradeline, "," ) -- Debug info. - self:T2(gradedata) + self:T2( gradedata ) -- Grade table - local grade={} --#AIRBOSS.LSOgrade + local grade = {} -- #AIRBOSS.LSOgrade --- Line format: -- playername, i, grade.finalscore, grade.points, grade.grade, grade.details, wire, Tgroove, case, -- time, wind, airframe, modex, carriertype, carriername, theatre, date - local playername=gradedata[1] - if gradedata[3]~=nil and gradedata[3]~="n/a" then - grade.finalscore=tonumber(gradedata[3]) + 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]) + 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]) + if gradedata[8] ~= nil and gradedata[8] ~= "n/a" then + grade.Tgroove = tonumber( gradedata[8] ) end - grade.case=tonumber(gradedata[9]) + grade.case = tonumber( gradedata[9] ) -- new - 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" + 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" -- Init player table if necessary. - self.playerscores[playername]=self.playerscores[playername] or {} + self.playerscores[playername] = self.playerscores[playername] or {} -- Add grade to table. - table.insert(self.playerscores[playername], grade) + table.insert( self.playerscores[playername], grade ) - n=n+1 + n = n + 1 -- Debug info. - self:T2({playername, self.playerscores[playername]}) + self:T2( { playername, self.playerscores[playername] } ) end -- Info message. - local text=string.format("Loaded %d player LSO grades from file %s", n, filename) - self:I(self.lid..text) + local text = string.format( "Loaded %d player LSO grades from file %s", n, filename ) + self:I( self.lid .. text ) end From 624a7c70c9282f409c655c0325b9edda8f0ce486 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 12 Dec 2021 19:30:27 +0100 Subject: [PATCH 034/200] CTLD - corrected landheight of dropped smoke --- Moose Development/Moose/Ops/CTLD.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 532256489..37e203ec7 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -990,7 +990,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="1.0.0" +CTLD.version="1.0.1" --- Instantiate a new CTLD. -- @param #CTLD self @@ -3396,6 +3396,8 @@ function CTLD:SmokePositionNow(Unit, Flare) if Flare then unitcoord:Flare(FlareColor, 90) else + local height = unitcoord:GetLandHeight() + 2 + unitcoord.y = height unitcoord:Smoke(SmokeColor) end return self From f29da39dffb67d9ade41befd10dc254667352ca5 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 12 Dec 2021 19:48:36 +0100 Subject: [PATCH 035/200] CSAR - override suppressmessages for menu driven information --- Moose Development/Moose/Ops/CSAR.lua | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index efb04badc..6e8cfe25e 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -247,7 +247,7 @@ CSAR.AircraftType["Bell-47"] = 2 --- CSAR class version. -- @field #string version -CSAR.version="0.1.12r5" +CSAR.version="0.1.12r6" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -1456,12 +1456,13 @@ end -- @param #number _time Message show duration. -- @param #boolean _clear (optional) Clear screen. -- @param #boolean _speak (optional) Speak message via SRS. -function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear, _speak) +-- @param #boolean _override (optional) Override message suppression +function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear, _speak, _override) self:T(self.lid .. " _DisplayMessageToSAR") local group = _unit:GetGroup() local _clear = _clear or nil local _time = _time or self.messageTime - if not self.suppressmessages then + if _override or not self.suppressmessages then local m = MESSAGE:New(_text,_time,"Info",_clear):ToGroup(group) end -- integrate SRS @@ -1549,7 +1550,7 @@ function CSAR:_DisplayActiveSAR(_unitName) _msg = _msg .. "\n" .. _line.msg end - self:_DisplayMessageToSAR(_heli, _msg, self.messageTime*2) + self:_DisplayMessageToSAR(_heli, _msg, self.messageTime*2, false, false, true) return self end @@ -1617,7 +1618,7 @@ function CSAR:_SignalFlare(_unitName) _distance = string.format("%.1fkm",_closest.distance) end local _msg = string.format("%s - Popping signal flare at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance) - self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true) + self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true) local _coord = _closest.pilot:GetCoordinate() _coord:FlareRed(_clockDir) @@ -1628,7 +1629,7 @@ function CSAR:_SignalFlare(_unitName) else _distance = string.format("%.1fkm",smokedist/1000) end - self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",_distance), self.messageTime) + self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",_distance), self.messageTime, false, false, true) end return self end @@ -1671,7 +1672,7 @@ function CSAR:_Reqsmoke( _unitName ) _distance = string.format("%.1fkm",_closest.distance/1000) end local _msg = string.format("%s - Popping smoke at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance) - self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true) + self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true) local _coord = _closest.pilot:GetCoordinate() local color = self.smokecolor _coord:Smoke(color) @@ -1682,7 +1683,7 @@ function CSAR:_Reqsmoke( _unitName ) else _distance = string.format("%.1fkm",smokedist/1000) end - self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",_distance), self.messageTime) + self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",_distance), self.messageTime, false, false, true) end return self end @@ -1754,13 +1755,13 @@ function CSAR:_CheckOnboard(_unitName) --list onboard pilots local _inTransit = self.inTransitGroups[_unitName] if _inTransit == nil then - self:_DisplayMessageToSAR(_unit, "No Rescued Pilots onboard", self.messageTime) + self:_DisplayMessageToSAR(_unit, "No Rescued Pilots onboard", self.messageTime, false, false, true) else local _text = "Onboard - RTB to FARP/Airfield or MASH: " for _, _onboard in pairs(self.inTransitGroups[_unitName]) do _text = _text .. "\n" .. _onboard.desc end - self:_DisplayMessageToSAR(_unit, _text, self.messageTime*2) + self:_DisplayMessageToSAR(_unit, _text, self.messageTime*2, false, false, true) end return self end From 058c750bc6987890b47e5b556f1b893dea003f23 Mon Sep 17 00:00:00 2001 From: TommyC81 Date: Mon, 13 Dec 2021 19:52:01 +0400 Subject: [PATCH 036/200] Update Set.lua (#1663) Additional minor code formatting and typo fixes. --- Moose Development/Moose/Core/Set.lua | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 35f3c3b35..ea5e9f04c 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -1258,7 +1258,7 @@ do -- SET_GROUP end --- Builds a set of groups that contain the given string in their group name. - -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all groups that **contain** the string. + -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all groups that **contain** the string. -- @param #SET_GROUP self -- @param #string Prefixes The string pattern(s) that needs to be contained in the group name. Can also be passed as a `#table` of strings. -- @return #SET_GROUP self @@ -1981,7 +1981,6 @@ do -- SET_UNIT }, } - --- Get the first unit from the set. -- @function [parent=#SET_UNIT] GetFirst -- @param #SET_UNIT self @@ -2555,9 +2554,9 @@ do -- SET_UNIT return UnitThreatLevels end - --- Calculate the maxium A2G threat level of the SET_UNIT. + --- Calculate the maximum A2G threat level of the SET_UNIT. -- @param #SET_UNIT self - -- @return #number The maximum threatlevel + -- @return #number The maximum threat level function SET_UNIT:CalculateThreatLevelA2G() local MaxThreatLevelA2G = 0 @@ -3000,7 +2999,7 @@ do -- SET_STATIC -- * @{#SET_STATIC.FilterCountries}: Builds the SET_STATIC with the units belonging to the country(ies). -- * @{#SET_STATIC.FilterPrefixes}: Builds the SET_STATIC with the units containing the same string(s) in their name. **ATTENTION** bad naming convention as this *does not** only filter *prefixes*. -- * @{#SET_STATIC.FilterZones}: Builds the SET_STATIC with the units within a @{Core.Zone#ZONE}. - -- + -- -- Once the filter criteria have been set for the SET_STATIC, you can start filtering using: -- -- * @{#SET_STATIC.FilterStart}: Starts the filtering of the units within the SET_STATIC. @@ -3558,7 +3557,7 @@ do -- SET_STATIC end - --- Calculate the maxium A2G threat level of the SET_STATIC. + --- Calculate the maximum A2G threat level of the SET_STATIC. -- @param #SET_STATIC self -- @return #number The maximum threatlevel function SET_STATIC:CalculateThreatLevelA2G() @@ -3762,7 +3761,6 @@ do -- SET_CLIENT }, } - --- Creates a new SET_CLIENT object, building a set of clients belonging to a coalitions, categories, countries, types or with defined prefix names. -- @param #SET_CLIENT self -- @return #SET_CLIENT @@ -4986,7 +4984,6 @@ do -- SET_CARGO -- * @{#SET_CARGO.ForEachCargo}: Calls a function for each cargo it finds within the SET_CARGO. -- -- @field #SET_CARGO SET_CARGO - -- SET_CARGO = { ClassName = "SET_CARGO", Cargos = {}, @@ -5413,7 +5410,8 @@ do -- SET_ZONE Filter = { Prefixes = nil, }, - FilterMeta = {}, + FilterMeta = { + }, } --- Creates a new SET_ZONE object, building a set of zones. @@ -5674,7 +5672,7 @@ do -- SET_ZONE end --- Validate if a coordinate is in one of the zones in the set. - -- Returns the ZONE object where the coordiante is located. + -- Returns the ZONE object where the coordinate is located. -- If zones overlap, the first zone that validates the test is returned. -- @param #SET_ZONE self -- @param Core.Point#COORDINATE Coordinate The coordinate to be searched. @@ -6302,14 +6300,14 @@ do -- SET_OPSGROUP end --- Builds a set of groups out of categories. - -- + -- -- Possible current categories are: - -- + -- -- * "plane" for fixed wing groups -- * "helicopter" for rotary wing groups -- * "ground" for ground groups -- * "ship" for naval groups - -- + -- -- @param #SET_OPSGROUP self -- @param #string Categories Can take the following values: "plane", "helicopter", "ground", "ship" or combinations as a table, for example `{"plane", "helicopter"}`. -- @return #SET_OPSGROUP self @@ -6346,7 +6344,7 @@ do -- SET_OPSGROUP return self end - --- Builds a set of groups out of aicraft category (planes and helicopters). + --- Builds a set of groups out of aircraft category (planes and helicopters). -- @param #SET_OPSGROUP self -- @return #SET_OPSGROUP self function SET_OPSGROUP:FilterCategoryAircraft() @@ -6395,7 +6393,7 @@ do -- SET_OPSGROUP end --- Builds a set of groups that contain the given string in their group name. - -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all groups that **contain** the string. + -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all groups that **contain** the string. -- @param #SET_OPSGROUP self -- @param #string Prefixes The string pattern(s) that needs to be contained in the group name. Can also be passed as a `#table` of strings. -- @return #SET_OPSGROUP self From 78fab9ab0c75e5c4a04d58eae8aefcf637f3f611 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 14 Dec 2021 09:50:27 +0100 Subject: [PATCH 037/200] CSAR - make beacon length configureable --- Moose Development/Moose/Ops/CSAR.lua | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 6e8cfe25e..0ca2f1058 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -247,7 +247,7 @@ CSAR.AircraftType["Bell-47"] = 2 --- CSAR class version. -- @field #string version -CSAR.version="0.1.12r6" +CSAR.version="1.0.1r1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -356,6 +356,7 @@ function CSAR:New(Coalition, Template, Alias) self.extractDistance = 500 -- Distance the Downed pilot will run to the rescue helicopter self.loadtimemax = 135 -- seconds self.radioSound = "beacon.ogg" -- the name of the sound file to use for the Pilot radio beacons. If this isnt added to the mission BEACONS WONT WORK! + self.beaconRefresher = 29 -- seconds self.allowFARPRescue = true --allows pilot to be rescued by landing at a FARP or Airbase self.FARPRescueDistance = 1000 -- you need to be this close to a FARP or Airport for the pilot to be rescued. self.max_units = 6 --max number of pilots that can be carried @@ -2021,7 +2022,12 @@ function CSAR:onbeforeStatus(From, Event, To) self:T({From, Event, To}) -- housekeeping self:_AddMedevacMenuItem() - self:_RefreshRadioBeacons() + + if not self.BeaconTimer or (self.BeaconTimer and not self.BeaconTimer:IsRunning()) then + self.BeaconTimer = TIMER:New(self._RefreshRadioBeacons,self) + self.BeaconTimer:Start(2,self.beaconRefresher) + end + self:_CheckDownedPilotTable() for _,_sar in pairs (self.csarUnits) do local PilotTable = self.downedPilots From 9c5561921bebed89194997bb327fb63eb97437fa Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 15 Dec 2021 13:46:07 +0100 Subject: [PATCH 038/200] Noise reducing measures --- Moose Development/Moose/AI/AI_Air.lua | 2 +- Moose Development/Moose/Ops/RescueHelo.lua | 2 +- Moose Development/Moose/Utilities/Utils.lua | 6 +++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Air.lua b/Moose Development/Moose/AI/AI_Air.lua index 6c8c9579c..3c0724585 100644 --- a/Moose Development/Moose/AI/AI_Air.lua +++ b/Moose Development/Moose/AI/AI_Air.lua @@ -585,7 +585,7 @@ function AI_AIR:onafterRTB( AIGroup, From, Event, To ) if AIGroup and AIGroup:IsAlive() then - self:I( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" ) + self:T( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" ) self:ClearTargetDistance() --AIGroup:ClearTasks() diff --git a/Moose Development/Moose/Ops/RescueHelo.lua b/Moose Development/Moose/Ops/RescueHelo.lua index 6aaf8d69b..4820b6501 100644 --- a/Moose Development/Moose/Ops/RescueHelo.lua +++ b/Moose Development/Moose/Ops/RescueHelo.lua @@ -802,7 +802,7 @@ function RESCUEHELO:_OnEventCrashOrEject(EventData) -- Debug. local text=string.format("Unit %s crashed or ejected.", unitname) MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) - self:I(self.lid..text) + self:T(self.lid..text) -- Get coordinate of unit. local coord=unit:GetCoordinate() diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index cac4f9af7..671fbb347 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -404,7 +404,11 @@ end -- @param #number knots Speed in knots. -- @return #number Speed in m/s. UTILS.KnotsToMps = function( knots ) - return knots / 1.94384 -- * 1852 / 3600 + if type(knots) == "number" then + return knots / 1.94384 --* 1852 / 3600 + else + return 0 + end end --- Convert temperature from Celsius to Fahrenheit. From e8e790102af422b4c8f000431a750058d24913c0 Mon Sep 17 00:00:00 2001 From: TommyC81 Date: Fri, 17 Dec 2021 12:07:13 +0400 Subject: [PATCH 039/200] Update Base.lua (#1667) Code Formatting. General whitespace and spelling. --- Moose Development/Moose/Core/Base.lua | 719 +++++++++++++------------- 1 file changed, 349 insertions(+), 370 deletions(-) diff --git a/Moose Development/Moose/Core/Base.lua b/Moose Development/Moose/Core/Base.lua index 1b809252a..ccb8c8755 100644 --- a/Moose Development/Moose/Core/Base.lua +++ b/Moose Development/Moose/Core/Base.lua @@ -1,28 +1,28 @@ --- **Core** - The base class within the framework. --- +-- -- === --- +-- -- ## Features: --- +-- -- * The construction and inheritance of MOOSE classes. -- * The class naming and numbering system. -- * The class hierarchy search system. --- * The tracing of information or objects during mission execution for debuggin purposes. +-- * The tracing of information or objects during mission execution for debugging purposes. -- * The subscription to DCS events for event handling in MOOSE objects. -- * Object inspection. --- +-- -- === --- +-- -- All classes within the MOOSE framework are derived from the BASE class. -- Note: The BASE class is an abstract class and is not meant to be used directly. --- +-- -- === --- +-- -- ### Author: **FlightControl** --- ### Contributions: --- +-- ### Contributions: +-- -- === --- +-- -- @module Core.Base -- @image Core_Base.JPG @@ -42,107 +42,107 @@ local _ClassID = 0 --- BASE class -- -- # 1. BASE constructor. --- --- Any class derived from BASE, will use the @{Core.Base#BASE.New} constructor embedded in the @{Core.Base#BASE.Inherit} method. +-- +-- Any class derived from BASE, will use the @{Core.Base#BASE.New} constructor embedded in the @{Core.Base#BASE.Inherit} method. -- See an example at the @{Core.Base#BASE.New} method how this is done. --- +-- -- # 2. Trace information for debugging. --- +-- -- The BASE class contains trace methods to trace progress within a mission execution of a certain object. --- These trace methods are inherited by each MOOSE class interiting BASE, soeach object created from derived class from BASE can use the tracing methods to trace its execution. --- +-- These trace methods are inherited by each MOOSE class inheriting BASE, thus all objects created from +-- a class derived from BASE can use the tracing methods to trace its execution. +-- -- Any type of information can be passed to these tracing methods. See the following examples: --- +-- -- self:E( "Hello" ) --- +-- -- Result in the word "Hello" in the dcs.log. --- +-- -- local Array = { 1, nil, "h", { "a","b" }, "x" } -- self:E( Array ) --- --- Results with the text [1]=1,[3]="h",[4]={[1]="a",[2]="b"},[5]="x"} in the dcs.log. --- +-- +-- Results with the text [1]=1,[3]="h",[4]={[1]="a",[2]="b"},[5]="x"} in the dcs.log. +-- -- local Object1 = "Object1" -- local Object2 = 3 -- local Object3 = { Object 1, Object 2 } -- self:E( { Object1, Object2, Object3 } ) --- +-- -- Results with the text [1]={[1]="Object",[2]=3,[3]={[1]="Object",[2]=3}} in the dcs.log. --- +-- -- local SpawnObject = SPAWN:New( "Plane" ) -- local GroupObject = GROUP:FindByName( "Group" ) -- self:E( { Spawn = SpawnObject, Group = GroupObject } ) --- --- Results with the text [1]={Spawn={....),Group={...}} in the dcs.log. --- +-- +-- Results with the text [1]={Spawn={....),Group={...}} in the dcs.log. +-- -- Below a more detailed explanation of the different method types for tracing. --- +-- -- ## 2.1. Tracing methods categories. -- -- There are basically 3 types of tracing methods available: --- +-- -- * @{#BASE.F}: Used to trace the entrance of a function and its given parameters. An F is indicated at column 44 in the DCS.log file. -- * @{#BASE.T}: Used to trace further logic within a function giving optional variables or parameters. A T is indicated at column 44 in the DCS.log file. -- * @{#BASE.E}: Used to always trace information giving optional variables or parameters. An E is indicated at column 44 in the DCS.log file. --- +-- -- ## 2.2 Tracing levels. -- --- There are 3 tracing levels within MOOSE. +-- There are 3 tracing levels within MOOSE. -- These tracing levels were defined to avoid bulks of tracing to be generated by lots of objects. --- +-- -- As such, the F and T methods have additional variants to trace level 2 and 3 respectively: -- -- * @{#BASE.F2}: Trace the beginning of a function and its given parameters with tracing level 2. -- * @{#BASE.F3}: Trace the beginning of a function and its given parameters with tracing level 3. -- * @{#BASE.T2}: Trace further logic within a function giving optional variables or parameters with tracing level 2. -- * @{#BASE.T3}: Trace further logic within a function giving optional variables or parameters with tracing level 3. --- +-- -- ## 2.3. Trace activation. --- +-- -- Tracing can be activated in several ways: --- +-- -- * Switch tracing on or off through the @{#BASE.TraceOnOff}() method. -- * Activate all tracing through the @{#BASE.TraceAll}() method. -- * Activate only the tracing of a certain class (name) through the @{#BASE.TraceClass}() method. -- * Activate only the tracing of a certain method of a certain class through the @{#BASE.TraceClassMethod}() method. -- * Activate only the tracing of a certain level through the @{#BASE.TraceLevel}() method. --- +-- -- ## 2.4. Check if tracing is on. --- +-- -- The method @{#BASE.IsTrace}() will validate if tracing is activated or not. --- --- +-- -- # 3. DCS simulator Event Handling. --- --- The BASE class provides methods to catch DCS Events. These are events that are triggered from within the DCS simulator, +-- +-- The BASE class provides methods to catch DCS Events. These are events that are triggered from within the DCS simulator, -- and handled through lua scripting. MOOSE provides an encapsulation to handle these events more efficiently. --- +-- -- ## 3.1. Subscribe / Unsubscribe to DCS Events. --- +-- -- At first, the mission designer will need to **Subscribe** to a specific DCS event for the class. -- So, when the DCS event occurs, the class will be notified of that event. -- There are two methods which you use to subscribe to or unsubscribe from an event. --- +-- -- * @{#BASE.HandleEvent}(): Subscribe to a DCS Event. -- * @{#BASE.UnHandleEvent}(): Unsubscribe from a DCS Event. --- +-- -- ## 3.2. Event Handling of DCS Events. --- +-- -- Once the class is subscribed to the event, an **Event Handling** method on the object or class needs to be written that will be called -- when the DCS event occurs. The Event Handling method receives an @{Core.Event#EVENTDATA} structure, which contains a lot of information -- about the event that occurred. --- --- Find below an example of the prototype how to write an event handling function for two units: +-- +-- Find below an example of the prototype how to write an event handling function for two units: -- -- local Tank1 = UNIT:FindByName( "Tank A" ) -- local Tank2 = UNIT:FindByName( "Tank B" ) --- +-- -- -- Here we subscribe to the Dead events. So, if one of these tanks dies, the Tank1 or Tank2 objects will be notified. -- Tank1:HandleEvent( EVENTS.Dead ) -- Tank2:HandleEvent( EVENTS.Dead ) --- +-- -- --- This function is an Event Handling function that will be called when Tank1 is Dead. --- -- @param Wrapper.Unit#UNIT self +-- -- @param Wrapper.Unit#UNIT self -- -- @param Core.Event#EVENTDATA EventData -- function Tank1:OnEventDead( EventData ) -- @@ -150,49 +150,47 @@ local _ClassID = 0 -- end -- -- --- This function is an Event Handling function that will be called when Tank2 is Dead. --- -- @param Wrapper.Unit#UNIT self +-- -- @param Wrapper.Unit#UNIT self -- -- @param Core.Event#EVENTDATA EventData -- function Tank2:OnEventDead( EventData ) -- -- self:SmokeBlue() -- end --- --- --- +-- -- See the @{Event} module for more information about event handling. --- +-- -- # 4. Class identification methods. --- +-- -- BASE provides methods to get more information of each object: --- +-- -- * @{#BASE.GetClassID}(): Gets the ID (number) of the object. Each object created is assigned a number, that is incremented by one. -- * @{#BASE.GetClassName}(): Gets the name of the object, which is the name of the class the object was instantiated from. -- * @{#BASE.GetClassNameAndID}(): Gets the name and ID of the object. --- +-- -- # 5. All objects derived from BASE can have "States". --- --- A mechanism is in place in MOOSE, that allows to let the objects administer **states**. --- States are essentially properties of objects, which are identified by a **Key** and a **Value**. --- --- The method @{#BASE.SetState}() can be used to set a Value with a reference Key to the object. --- To **read or retrieve** a state Value based on a Key, use the @{#BASE.GetState} method. --- +-- +-- A mechanism is in place in MOOSE, that allows to let the objects administer **states**. +-- States are essentially properties of objects, which are identified by a **Key** and a **Value**. +-- +-- The method @{#BASE.SetState}() can be used to set a Value with a reference Key to the object. +-- To **read or retrieve** a state Value based on a Key, use the @{#BASE.GetState} method. +-- -- These two methods provide a very handy way to keep state at long lasting processes. -- Values can be stored within the objects, and later retrieved or changed when needed. -- There is one other important thing to note, the @{#BASE.SetState}() and @{#BASE.GetState} methods -- receive as the **first parameter the object for which the state needs to be set**. -- Thus, if the state is to be set for the same object as the object for which the method is used, then provide the same -- object name to the method. --- +-- -- # 6. Inheritance. --- +-- -- The following methods are available to implement inheritance --- +-- -- * @{#BASE.Inherit}: Inherits from a class. -- * @{#BASE.GetParent}: Returns the parent object from the object it is handling, or nil if there is no parent object. --- +-- -- === --- +-- -- @field #BASE BASE = { ClassName = "BASE", @@ -203,13 +201,12 @@ BASE = { Scheduler = nil, } - --- @field #BASE.__ BASE.__ = {} --- @field #BASE._ BASE._ = { - Schedules = {} --- Contains the Schedulers Active + Schedules = {}, --- Contains the Schedulers Active } --- The Formation Class @@ -217,35 +214,33 @@ BASE._ = { -- @field Cone A cone formation. FORMATION = { Cone = "Cone", - Vee = "Vee" + Vee = "Vee", } - - ---- BASE constructor. --- +--- BASE constructor. +-- -- This is an example how to use the BASE:New() constructor in a new class definition when inheriting from BASE. --- +-- -- function EVENT:New() -- local self = BASE:Inherit( self, BASE:New() ) -- #EVENT -- return self -- end --- +-- -- @param #BASE self -- @return #BASE function BASE:New() local self = routines.utils.deepCopy( self ) -- Create a new self instance - _ClassID = _ClassID + 1 - self.ClassID = _ClassID - - -- This is for "private" methods... - -- When a __ is passed to a method as "self", the __index will search for the method on the public method list too! --- if rawget( self, "__" ) then - --setmetatable( self, { __index = self.__ } ) --- end - - return self + _ClassID = _ClassID + 1 + self.ClassID = _ClassID + + -- This is for "private" methods... + -- When a __ is passed to a method as "self", the __index will search for the method on the public method list too! + -- if rawget( self, "__" ) then + -- setmetatable( self, { __index = self.__ } ) + -- end + + return self end --- This is the worker method to inherit from a parent class. @@ -256,29 +251,28 @@ end function BASE:Inherit( Child, Parent ) -- Create child. - local Child = routines.utils.deepCopy( Child ) + local Child = routines.utils.deepCopy( Child ) - if Child ~= nil then + if Child ~= nil then - -- This is for "private" methods... - -- When a __ is passed to a method as "self", the __index will search for the method on the public method list of the same object too! + -- This is for "private" methods... + -- When a __ is passed to a method as "self", the __index will search for the method on the public method list of the same object too! if rawget( Child, "__" ) then - setmetatable( Child, { __index = Child.__ } ) + setmetatable( Child, { __index = Child.__ } ) setmetatable( Child.__, { __index = Parent } ) else setmetatable( Child, { __index = Parent } ) end - - --Child:_SetDestructor() - end - - return Child -end + -- Child:_SetDestructor() + end + + return Child +end local function getParent( Child ) local Parent = nil - + if Child.ClassName == 'BASE' then Parent = nil else @@ -286,46 +280,44 @@ local function getParent( Child ) Parent = getmetatable( Child.__ ).__index else Parent = getmetatable( Child ).__index - end + end end return Parent end - ---- This is the worker method to retrieve the Parent class. +--- This is the worker method to retrieve the Parent class. -- Note that the Parent class must be passed to call the parent class method. --- +-- -- self:GetParent(self):ParentMethod() --- --- +-- +-- -- @param #BASE self -- @param #BASE Child This is the Child class from which the Parent class needs to be retrieved. -- @param #BASE FromClass (Optional) The class from which to get the parent. -- @return #BASE function BASE:GetParent( Child, FromClass ) - local Parent -- BASE class has no parent if Child.ClassName == 'BASE' then Parent = nil else - - --self:E({FromClass = FromClass}) - --self:E({Child = Child.ClassName}) + + -- self:E({FromClass = FromClass}) + -- self:E({Child = Child.ClassName}) if FromClass then - while( Child.ClassName ~= "BASE" and Child.ClassName ~= FromClass.ClassName ) do + while (Child.ClassName ~= "BASE" and Child.ClassName ~= FromClass.ClassName) do Child = getParent( Child ) - --self:E({Child.ClassName}) + -- self:E({Child.ClassName}) end - end + end if Child.ClassName == 'BASE' then Parent = nil else Parent = getParent( Child ) end end - --self:E({Parent.ClassName}) + -- self:E({Parent.ClassName}) return Parent end @@ -339,7 +331,7 @@ end -- * ZONE:New( 'some zone' ):IsInstanceOf( 'BASE' ) will return true -- -- * ZONE:New( 'some zone' ):IsInstanceOf( 'GROUP' ) will return false --- +-- -- @param #BASE self -- @param ClassName is the name of the class or the class itself to run the check against -- @return #boolean @@ -347,32 +339,32 @@ function BASE:IsInstanceOf( ClassName ) -- Is className NOT a string ? if type( ClassName ) ~= 'string' then - + -- Is className a Moose class ? if type( ClassName ) == 'table' and ClassName.ClassName ~= nil then - + -- Get the name of the Moose class as a string ClassName = ClassName.ClassName - - -- className is neither a string nor a Moose class, throw an error + + -- className is neither a string nor a Moose class, throw an error else - + -- I'm not sure if this should take advantage of MOOSE logging function, or throw an error for pcall - local err_str = 'className parameter should be a string; parameter received: '..type( ClassName ) + local err_str = 'className parameter should be a string; parameter received: ' .. type( ClassName ) self:E( err_str ) -- error( err_str ) return false - + end end - + ClassName = string.upper( ClassName ) if string.upper( self.ClassName ) == ClassName then return true end - local Parent = getParent(self) + local Parent = getParent( self ) while Parent do @@ -388,7 +380,7 @@ function BASE:IsInstanceOf( ClassName ) end --- Get the ClassName + ClassID of the class instance. --- The ClassName + ClassID is formatted as '%s#%09d'. +-- The ClassName + ClassID is formatted as '%s#%09d'. -- @param #BASE self -- @return #string The ClassName + ClassID of the class instance. function BASE:GetClassNameAndID() @@ -415,22 +407,21 @@ do -- Event Handling -- @param #BASE self -- @return Core.Event#EVENT function BASE:EventDispatcher() - + return _EVENTDISPATCHER end - - + --- Get the Class @{Event} processing Priority. - -- The Event processing Priority is a number from 1 to 10, + -- The Event processing Priority is a number from 1 to 10, -- reflecting the order of the classes subscribed to the Event to be processed. -- @param #BASE self -- @return #number The @{Event} processing Priority. function BASE:GetEventPriority() return self._.EventPriority or 5 end - + --- Set the Class @{Event} processing Priority. - -- The Event processing Priority is a number from 1 to 10, + -- The Event processing Priority is a number from 1 to 10, -- reflecting the order of the classes subscribed to the Event to be processed. -- @param #BASE self -- @param #number EventPriority The @{Event} processing Priority. @@ -438,240 +429,239 @@ do -- Event Handling function BASE:SetEventPriority( EventPriority ) self._.EventPriority = EventPriority end - + --- Remove all subscribed events -- @param #BASE self -- @return #BASE function BASE:EventRemoveAll() - + self:EventDispatcher():RemoveAll( self ) - + return self end - + --- Subscribe to a DCS Event. -- @param #BASE self -- @param Core.Event#EVENTS EventID Event ID. -- @param #function EventFunction (optional) The function to be called when the event occurs for the unit. -- @return #BASE function BASE:HandleEvent( EventID, EventFunction ) - + self:EventDispatcher():OnEventGeneric( EventFunction, self, EventID ) - + return self end - + --- UnSubscribe to a DCS event. -- @param #BASE self -- @param Core.Event#EVENTS EventID Event ID. -- @return #BASE function BASE:UnHandleEvent( EventID ) - + self:EventDispatcher():RemoveEvent( self, EventID ) - + return self end - + -- Event handling function prototypes - - --- Occurs whenever any unit in a mission fires a weapon. But not any machine gun or autocannon based weapon, those are handled by EVENT.ShootingStart. + + --- Occurs whenever any unit in a mission fires a weapon. But not any machine gun or auto cannon based weapon, those are handled by EVENT.ShootingStart. -- @function [parent=#BASE] OnEventShot -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. --- Occurs whenever an object is hit by a weapon. - -- initiator : The unit object the fired the weapon - -- weapon: Weapon object that hit the target - -- target: The Object that was hit. + -- initiator : The unit object the fired the weapon. + -- weapon: Weapon object that hit the target. + -- target: The Object that was hit. -- @function [parent=#BASE] OnEventHit -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. --- Occurs when an aircraft takes off from an airbase, farp, or ship. - -- initiator : The unit that tookoff - -- place: Object from where the AI took-off from. Can be an Airbase Object, FARP, or Ships + -- initiator : The unit that took off. + -- place: Object from where the AI took-off from. Can be an Airbase Object, FARP, or Ships. -- @function [parent=#BASE] OnEventTakeoff -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. --- Occurs when an aircraft lands at an airbase, farp or ship - -- initiator : The unit that has landed - -- place: Object that the unit landed on. Can be an Airbase Object, FARP, or Ships + -- initiator : The unit that has landed. + -- place: Object that the unit landed on. Can be an Airbase Object, FARP, or Ships. -- @function [parent=#BASE] OnEventLand -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. --- Occurs when any aircraft crashes into the ground and is completely destroyed. - -- initiator : The unit that has crashed + -- initiator : The unit that has crashed. -- @function [parent=#BASE] OnEventCrash -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. --- Occurs when a pilot ejects from an aircraft - -- initiator : The unit that has ejected + -- initiator : The unit that has ejected -- @function [parent=#BASE] OnEventEjection -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. --- Occurs when an aircraft connects with a tanker and begins taking on fuel. - -- initiator : The unit that is receiving fuel. + -- initiator : The unit that is receiving fuel. -- @function [parent=#BASE] OnEventRefueling -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. --- Occurs when an object is dead. - -- initiator : The unit that is dead. + -- initiator : The unit that is dead. -- @function [parent=#BASE] OnEventDead -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. --- Occurs when an object is completely destroyed. - -- initiator : The unit that is was destroyed. + -- initiator : The unit that is was destroyed. -- @function [parent=#BASE] OnEvent -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. --- Occurs when the pilot of an aircraft is killed. Can occur either if the player is alive and crashes or if a weapon kills the pilot without completely destroying the plane. - -- initiator : The unit that the pilot has died in. + -- initiator : The unit that the pilot has died in. -- @function [parent=#BASE] OnEventPilotDead -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. --- Occurs when a ground unit captures either an airbase or a farp. - -- initiator : The unit that captured the base - -- place: The airbase that was captured, can be a FARP or Airbase. When calling place:getCoalition() the faction will already be the new owning faction. + -- initiator : The unit that captured the base. + -- place: The airbase that was captured, can be a FARP or Airbase. When calling place:getCoalition() the faction will already be the new owning faction. -- @function [parent=#BASE] OnEventBaseCaptured -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. - --- Occurs when a mission starts + --- Occurs when a mission starts. -- @function [parent=#BASE] OnEventMissionStart -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. - --- Occurs when a mission ends + --- Occurs when a mission ends. -- @function [parent=#BASE] OnEventMissionEnd -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. --- Occurs when an aircraft is finished taking fuel. - -- initiator : The unit that was receiving fuel. + -- initiator : The unit that was receiving fuel. -- @function [parent=#BASE] OnEventRefuelingStop -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. --- Occurs when any object is spawned into the mission. - -- initiator : The unit that was spawned + -- initiator : The unit that was spawned. -- @function [parent=#BASE] OnEventBirth -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. --- Occurs when any system fails on a human controlled aircraft. - -- initiator : The unit that had the failure + -- initiator : The unit that had the failure. -- @function [parent=#BASE] OnEventHumanFailure -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. --- Occurs when any aircraft starts its engines. - -- initiator : The unit that is starting its engines. + -- initiator : The unit that is starting its engines.. -- @function [parent=#BASE] OnEventEngineStartup -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. --- Occurs when any aircraft shuts down its engines. - -- initiator : The unit that is stopping its engines. + -- initiator : The unit that is stopping its engines.. -- @function [parent=#BASE] OnEventEngineShutdown -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. --- Occurs when any player assumes direct control of a unit. - -- initiator : The unit that is being taken control of. + -- initiator : The unit that is being taken control of. -- @function [parent=#BASE] OnEventPlayerEnterUnit -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. --- Occurs when any player relieves control of a unit to the AI. - -- initiator : The unit that the player left. + -- initiator : The unit that the player left. -- @function [parent=#BASE] OnEventPlayerLeaveUnit -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. - --- Occurs when any unit begins firing a weapon that has a high rate of fire. Most common with aircraft cannons (GAU-8), autocannons, and machine guns. + --- Occurs when any unit begins firing a weapon that has a high rate of fire. Most common with aircraft cannons (GAU-8), auto cannons, and machine guns. -- initiator : The unit that is doing the shooting. - -- target: The unit that is being targeted. + -- target: The unit that is being targeted. -- @function [parent=#BASE] OnEventShootingStart -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. --- Occurs when any unit stops firing its weapon. Event will always correspond with a shooting start event. - -- initiator : The unit that was doing the shooting. + -- initiator : The unit that was doing the shooting. -- @function [parent=#BASE] OnEventShootingEnd -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. --- Occurs when a new mark was added. - -- MarkID: ID of the mark. + -- MarkID: ID of the mark. -- @function [parent=#BASE] OnEventMarkAdded -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. --- Occurs when a mark was removed. - -- MarkID: ID of the mark. + -- MarkID: ID of the mark. -- @function [parent=#BASE] OnEventMarkRemoved -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. --- Occurs when a mark text was changed. - -- MarkID: ID of the mark. + -- MarkID: ID of the mark. -- @function [parent=#BASE] OnEventMarkChange -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. - --- Unknown precisely what creates this event, likely tied into newer damage model. Will update this page when new information become available. - -- + -- -- * initiator: The unit that had the failure. - -- + -- -- @function [parent=#BASE] OnEventDetailedFailure -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. - --- Occurs when any modification to the "Score" as seen on the debrief menu would occur. + --- Occurs when any modification to the "Score" as seen on the debrief menu would occur. -- There is no information on what values the score was changed to. Event is likely similar to player_comment in this regard. -- @function [parent=#BASE] OnEventScore -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. --- Occurs on the death of a unit. Contains more and different information. Similar to unit_lost it will occur for aircraft before the aircraft crash event occurs. - -- - -- * initiator: The unit that killed the target + -- + -- * initiator: The unit that killed the target. -- * target: Target Object -- * weapon: Weapon Object - -- + -- -- @function [parent=#BASE] OnEventKill -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. - --- Occurs when any modification to the "Score" as seen on the debrief menu would occur. + --- Occurs when any modification to the "Score" as seen on the debrief menu would occur. -- There is no information on what values the score was changed to. Event is likely similar to player_comment in this regard. -- @function [parent=#BASE] OnEventScore -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. --- Occurs when the game thinks an object is destroyed. - -- + -- -- * initiator: The unit that is was destroyed. - -- + -- -- @function [parent=#BASE] OnEventUnitLost -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. --- Occurs shortly after the landing animation of an ejected pilot touching the ground and standing up. Event does not occur if the pilot lands in the water and sub combs to Davey Jones Locker. - -- + -- -- * initiator: Static object representing the ejected pilot. Place : Aircraft that the pilot ejected from. -- * place: may not return as a valid object if the aircraft has crashed into the ground and no longer exists. -- * subplace: is always 0 for unknown reasons. - -- + -- -- @function [parent=#BASE] OnEventLandingAfterEjection -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. @@ -696,7 +686,7 @@ do -- Event Handling -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. - --- Landing quality mark. + --- Landing quality mark. -- @function [parent=#BASE] OnEventLandingQualityMark -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. @@ -706,16 +696,14 @@ do -- Event Handling -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. - --- Occurs when a player enters a slot and takes control of an aircraft. - -- **NOTE**: This is a workaround of a long standing DCS bug with the PLAYER_ENTER_UNIT event. - -- initiator : The unit that is being taken control of. + -- **NOTE**: This is a workaround of a long standing DCS bug with the PLAYER_ENTER_UNIT event. + -- initiator : The unit that is being taken control of. -- @function [parent=#BASE] OnEventPlayerEnterAircraft -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. end - --- Creation of a Birth Event. -- @param #BASE self @@ -725,18 +713,18 @@ end -- @param place -- @param subplace function BASE:CreateEventBirth( EventTime, Initiator, IniUnitName, place, subplace ) - self:F( { 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 - } + local Event = { + id = world.event.S_EVENT_BIRTH, + time = EventTime, + initiator = Initiator, + IniUnitName = IniUnitName, + place = place, + subplace = subplace, + } - world.onEvent( Event ) + world.onEvent( Event ) end --- Creation of a Crash Event. @@ -744,15 +732,15 @@ end -- @param DCS#Time EventTime The time stamp of the event. -- @param DCS#Object Initiator The initiating object of the event. function BASE:CreateEventCrash( EventTime, Initiator ) - self:F( { EventTime, Initiator } ) + self:F( { EventTime, Initiator } ) - local Event = { - id = world.event.S_EVENT_CRASH, - time = EventTime, - initiator = Initiator, - } + local Event = { + id = world.event.S_EVENT_CRASH, + time = EventTime, + initiator = Initiator, + } - world.onEvent( Event ) + world.onEvent( Event ) end --- Creation of a Dead Event. @@ -766,7 +754,7 @@ function BASE:CreateEventDead( EventTime, Initiator ) id = world.event.S_EVENT_DEAD, time = EventTime, initiator = Initiator, - } + } world.onEvent( Event ) end @@ -782,7 +770,7 @@ function BASE:CreateEventRemoveUnit( EventTime, Initiator ) id = EVENTS.RemoveUnit, time = EventTime, initiator = Initiator, - } + } world.onEvent( Event ) end @@ -798,56 +786,56 @@ function BASE:CreateEventTakeoff( EventTime, Initiator ) id = world.event.S_EVENT_TAKEOFF, time = EventTime, initiator = Initiator, - } + } world.onEvent( Event ) end - --- Creation of a `S_EVENT_PLAYER_ENTER_AIRCRAFT` event. - -- @param #BASE self - -- @param Wrapper.Unit#UNIT PlayerUnit The aircraft unit the player entered. - function BASE:CreateEventPlayerEnterAircraft( PlayerUnit ) - self:F( { PlayerUnit } ) - - local Event = { - id = EVENTS.PlayerEnterAircraft, - time = timer.getTime(), - initiator = PlayerUnit:GetDCSObject() - } - - world.onEvent(Event) - end +--- Creation of a `S_EVENT_PLAYER_ENTER_AIRCRAFT` event. +-- @param #BASE self +-- @param Wrapper.Unit#UNIT PlayerUnit The aircraft unit the player entered. +function BASE:CreateEventPlayerEnterAircraft( PlayerUnit ) + self:F( { PlayerUnit } ) --- TODO: Complete DCS#Event structure. + local Event = { + id = EVENTS.PlayerEnterAircraft, + time = timer.getTime(), + initiator = PlayerUnit:GetDCSObject(), + } + + world.onEvent( Event ) +end + +-- TODO: Complete DCS#Event structure. --- The main event handling function... This function captures all events generated for the class. -- @param #BASE self -- @param DCS#Event event -function BASE:onEvent(event) +function BASE:onEvent( event ) - if self then - - for EventID, EventObject in pairs(self.Events) do - if EventObject.EventEnabled then + if self then - if event.id == EventObject.Event then + for EventID, EventObject in pairs( self.Events ) do + if EventObject.EventEnabled 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 + 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 -- Scheduling @@ -861,28 +849,28 @@ do -- Scheduling function BASE:ScheduleOnce( Start, SchedulerFunction, ... ) self:F2( { Start } ) self:T3( { ... } ) - + local ObjectName = "-" ObjectName = self.ClassName .. self.ClassID - - self:F3( { "ScheduleOnce: ", ObjectName, Start } ) - + + 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 - + + local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule( + self, + SchedulerFunction, + { ... }, + Start, + nil, + nil, + nil + ) + + self._.Schedules[#self._.Schedules + 1] = ScheduleID + return self._.Schedules[#self._.Schedules] end @@ -898,29 +886,29 @@ do -- Scheduling 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 - + + local ScheduleID = self.Scheduler:Schedule( + self, + SchedulerFunction, + { ... }, + Start, + Repeat, + RandomizeFactor, + Stop, + 4 + ) + + self._.Schedules[#self._.Schedules + 1] = ScheduleID + return self._.Schedules[#self._.Schedules] end @@ -928,9 +916,9 @@ do -- Scheduling -- @param #BASE self -- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments. function BASE:ScheduleStop( SchedulerFunction ) - + self:F3( { "ScheduleStop:" } ) - + if self.Scheduler then _SCHEDULEDISPATCHER:Stop( self.Scheduler, self._.Schedules[SchedulerFunction] ) end @@ -938,9 +926,8 @@ do -- Scheduling end - --- Set a state or property of the Object given a Key and a Value. --- Note that if the Object is destroyed, nillified or garbage collected, then the Values and Keys will also be gone. +-- Note that if the Object is destroyed, set to nil, or garbage collected, then the Values and Keys will also be gone. -- @param #BASE self -- @param Object The object that will hold the Value set by the Key. -- @param Key The key that is used as a reference of the value. Note that the key can be a #string, but it can also be any other type! @@ -952,13 +939,12 @@ function BASE:SetState( Object, Key, Value ) self.States[ClassNameAndID] = self.States[ClassNameAndID] or {} self.States[ClassNameAndID][Key] = Value - + return self.States[ClassNameAndID][Key] end - --- Get a Value given a Key from the Object. --- Note that if the Object is destroyed, nillified or garbage collected, then the Values and Keys will also be gone. +-- Note that if the Object is destroyed, set to nil, or garbage collected, then the Values and Keys will also be gone. -- @param #BASE self -- @param Object The object that holds the Value set by the Key. -- @param Key The key that is used to retrieve the value. Note that the key can be a #string, but it can also be any other type! @@ -971,7 +957,7 @@ function BASE:GetState( Object, Key ) local Value = self.States[ClassNameAndID][Key] or false return Value end - + return nil end @@ -1010,8 +996,6 @@ function BASE:TraceOff() self:TraceOnOff( false ) end - - --- Set trace on or off -- Note that when trace is off, no BASE.Debug statement is performed, increasing performance! -- When Moose is loaded statically, (as one file), tracing is switched off by default. @@ -1020,28 +1004,29 @@ end -- @param #BASE self -- @param #boolean TraceOnOff Switch the tracing on or off. -- @usage --- -- Switch the tracing On --- BASE:TraceOnOff( true ) --- --- -- Switch the tracing Off --- BASE:TraceOnOff( false ) +-- +-- -- Switch the tracing On +-- BASE:TraceOnOff( true ) +-- +-- -- Switch the tracing Off +-- BASE:TraceOnOff( false ) +-- function BASE:TraceOnOff( TraceOnOff ) - if TraceOnOff==false then + if TraceOnOff == false then self:I( "Tracing in MOOSE is OFF" ) _TraceOnOff = false else - self:I( "Tracing in MOOSE is ON" ) + self:I( "Tracing in MOOSE is ON" ) _TraceOnOff = true end end - --- Enquires if tracing is on (for the class). -- @param #BASE self -- @return #boolean function BASE:IsTrace() - if BASE.Debug and ( _TraceAll == true ) or ( _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName] ) then + if BASE.Debug and (_TraceAll == true) or (_TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName]) then return true else return false @@ -1060,13 +1045,13 @@ end -- @param #BASE self -- @param #boolean TraceAll true = trace all methods in MOOSE. function BASE:TraceAll( TraceAll ) - - if TraceAll==false then - _TraceAll=false + + if TraceAll == false then + _TraceAll = false else _TraceAll = true end - + if _TraceAll then self:I( "Tracing all methods in MOOSE " ) else @@ -1101,16 +1086,16 @@ end -- @param Arguments A #table or any field. function BASE:_F( Arguments, DebugInfoCurrentParam, DebugInfoFromParam ) - if BASE.Debug and ( _TraceAll == true ) or ( _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName] ) then + 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 @@ -1120,7 +1105,7 @@ function BASE:_F( Arguments, DebugInfoCurrentParam, DebugInfoFromParam ) 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 ) ) ) + 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 @@ -1133,14 +1118,13 @@ 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 end - --- Trace a function call level 2. Must be at the beginning of the function logic. -- @param #BASE self -- @param Arguments A #table or any field. @@ -1149,11 +1133,11 @@ 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 end --- Trace a function call level 3. Must be at the beginning of the function logic. @@ -1164,11 +1148,11 @@ 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 end --- Trace a function logic. @@ -1176,28 +1160,28 @@ end -- @param Arguments A #table or any field. function BASE:_T( Arguments, DebugInfoCurrentParam, DebugInfoFromParam ) - if BASE.Debug and ( _TraceAll == true ) or ( _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName] ) then + 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 + + 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 ) ) ) + 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 end --- Trace a function logic level 1. Can be anywhere within the function logic. @@ -1208,14 +1192,13 @@ 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 end - --- Trace a function logic level 2. Can be anywhere within the function logic. -- @param #BASE self -- @param Arguments A #table or any field. @@ -1224,7 +1207,7 @@ 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 @@ -1239,7 +1222,7 @@ 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 @@ -1252,27 +1235,26 @@ 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 + 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 --- Log an information which will be traced always. Can be anywhere within the function logic. -- @param #BASE self @@ -1282,38 +1264,35 @@ 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 + 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 ) ) ) + + 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 ) ) ) + env.info( string.format( "%1s:%30s%05d(%s)", "I", self.ClassName, self.ClassID, routines.utils.oneLineSerialize( Arguments ) ) ) end - + end - - --- old stuff ---function BASE:_Destructor() +-- function BASE:_Destructor() -- --self:E("_Destructor") -- -- --self:EventRemoveAll() ---end - +-- end -- THIS IS WHY WE NEED LUA 5.2 ... ---function BASE:_SetDestructor() +-- function BASE:_SetDestructor() -- -- -- TODO: Okay, this is really technical... -- -- When you set a proxy to a table to catch __gc, weak tables don't behave like weak... @@ -1333,5 +1312,5 @@ end -- -- table is about to be garbage-collected - then the __gc hook -- -- will be invoked and the destructor called -- rawset( self, '__proxy', proxy ) --- ---end \ No newline at end of file +-- +-- end From 2694321256dbe14c0e0b3bf3211493a5641064eb Mon Sep 17 00:00:00 2001 From: TommyC81 Date: Fri, 17 Dec 2021 12:07:24 +0400 Subject: [PATCH 040/200] Update CTLD.lua (#1666) Code formatting. Minor typos and text fixes. --- Moose Development/Moose/Ops/CTLD.lua | 6499 +++++++++++++------------- 1 file changed, 3274 insertions(+), 3225 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 37e203ec7..6822d3f55 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -25,34 +25,34 @@ -- Date: Dec 2021 do ------------------------------------------------------- ---- **CTLD_ENGINEERING** class, extends Core.Base#BASE --- @type CTLD_ENGINEERING --- @field #string ClassName --- @field #string lid --- @field #string Name --- @field Wrapper.Group#GROUP Group --- @field Wrapper.Unit#UNIT Unit --- @field Wrapper.Group#GROUP HeliGroup --- @field Wrapper.Unit#UNIT HeliUnit --- @field #string State --- @extends Core.Base#BASE -CTLD_ENGINEERING = { - ClassName = "CTLD_ENGINEERING", - lid = "", - Name = "none", - Group = nil, - Unit = nil, - --C_Ops = nil, - HeliGroup = nil, - HeliUnit = nil, - State = "", + ------------------------------------------------------ + --- **CTLD_ENGINEERING** class, extends Core.Base#BASE + -- @type CTLD_ENGINEERING + -- @field #string ClassName + -- @field #string lid + -- @field #string Name + -- @field Wrapper.Group#GROUP Group + -- @field Wrapper.Unit#UNIT Unit + -- @field Wrapper.Group#GROUP HeliGroup + -- @field Wrapper.Unit#UNIT HeliUnit + -- @field #string State + -- @extends Core.Base#BASE + CTLD_ENGINEERING = { + ClassName = "CTLD_ENGINEERING", + lid = "", + Name = "none", + Group = nil, + Unit = nil, + -- C_Ops = nil, + HeliGroup = nil, + HeliUnit = nil, + State = "", } - + --- CTLD_ENGINEERING class version. -- @field #string version CTLD_ENGINEERING.Version = "0.0.3" - + --- Create a new instance. -- @param #CTLD_ENGINEERING self -- @param #string Name @@ -60,26 +60,26 @@ CTLD_ENGINEERING = { -- @param Wrapper.Group#GROUP HeliGroup HeliGroup -- @param Wrapper.Unit#UNIT HeliUnit HeliUnit -- @return #CTLD_ENGINEERING self - function CTLD_ENGINEERING:New(Name, GroupName, HeliGroup, HeliUnit) - - -- Inherit everything from BASE class. - local self=BASE:Inherit(self, BASE:New()) -- #CTLD_ENGINEERING - - --BASE:I({Name, GroupName}) - + function CTLD_ENGINEERING:New( Name, GroupName, HeliGroup, HeliUnit ) + + -- Inherit everything from BASE class. + local self = BASE:Inherit( self, BASE:New() ) -- #CTLD_ENGINEERING + + -- BASE:I({Name, GroupName}) + self.Name = Name or "Engineer Squad" -- #string - self.Group = GROUP:FindByName(GroupName) -- Wrapper.Group#GROUP - self.Unit = self.Group:GetUnit(1) -- Wrapper.Unit#UNIT - --self.C_Ops = C_Ops -- Ops.CTLD#CTLD + self.Group = GROUP:FindByName( GroupName ) -- Wrapper.Group#GROUP + self.Unit = self.Group:GetUnit( 1 ) -- Wrapper.Unit#UNIT + -- self.C_Ops = C_Ops -- Ops.CTLD#CTLD self.HeliGroup = HeliGroup -- Wrapper.Group#GROUP self.HeliUnit = HeliUnit -- Wrapper.Unit#UNIT - --self.distance = Distance or UTILS.NMToMeters(1) + -- self.distance = Distance or UTILS.NMToMeters(1) self.currwpt = nil -- Core.Point#COORDINATE - self.lid = string.format("%s (%s) | ",self.Name, self.Version) - -- Start State. + self.lid = string.format( "%s (%s) | ", self.Name, self.Version ) + -- Start State. self.State = "Stopped" self.marktimer = 300 -- wait this many secs before trying a crate again - + --[[ Add FSM transitions. -- From State --> Event --> To State self:AddTransition("Stopped", "Start", "Running") -- Start FSM. @@ -89,145 +89,145 @@ CTLD_ENGINEERING = { self:AddTransition("*", "Arrive", "Arrived") self:AddTransition("*", "Build", "Building") self:AddTransition("*", "Done", "Running") - self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. - + self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. + self:__Start(5) --]] self:Start() - local parent = self:GetParent(self) + local parent = self:GetParent( self ) return self end - + --- (Internal) Set the status -- @param #CTLD_ENGINEERING self -- @param #string State -- @return #CTLD_ENGINEERING self - function CTLD_ENGINEERING:SetStatus(State) + function CTLD_ENGINEERING:SetStatus( State ) self.State = State return self end - + --- (Internal) Get the status -- @param #CTLD_ENGINEERING self -- @return #string State function CTLD_ENGINEERING:GetStatus() return self.State end - + --- (Internal) Check the status -- @param #CTLD_ENGINEERING self -- @param #string State -- @return #boolean Outcome - function CTLD_ENGINEERING:IsStatus(State) + function CTLD_ENGINEERING:IsStatus( State ) return self.State == State end - + --- (Internal) Check the negative status -- @param #CTLD_ENGINEERING self -- @param #string State -- @return #boolean Outcome - function CTLD_ENGINEERING:IsNotStatus(State) + function CTLD_ENGINEERING:IsNotStatus( State ) return self.State ~= State end - + --- (Internal) Set start status. -- @param #CTLD_ENGINEERING self -- @return #CTLD_ENGINEERING self function CTLD_ENGINEERING:Start() - self:T(self.lid.."Start") - self:SetStatus("Running") + self:T( self.lid .. "Start" ) + self:SetStatus( "Running" ) return self end - + --- (Internal) Set stop status. -- @param #CTLD_ENGINEERING self -- @return #CTLD_ENGINEERING self function CTLD_ENGINEERING:Stop() - self:T(self.lid.."Stop") - self:SetStatus("Stopped") + self:T( self.lid .. "Stop" ) + self:SetStatus( "Stopped" ) return self end - + --- (Internal) Set build status. -- @param #CTLD_ENGINEERING self -- @return #CTLD_ENGINEERING self function CTLD_ENGINEERING:Build() - self:T(self.lid.."Build") - self:SetStatus("Building") + self:T( self.lid .. "Build" ) + self:SetStatus( "Building" ) return self end - + --- (Internal) Set done status. -- @param #CTLD_ENGINEERING self -- @return #CTLD_ENGINEERING self function CTLD_ENGINEERING:Done() - self:T(self.lid.."Done") + self:T( self.lid .. "Done" ) local grp = self.Group -- Wrapper.Group#GROUP - grp:RelocateGroundRandomInRadius(7,100,false,false,"Diamond") - self:SetStatus("Running") + grp:RelocateGroundRandomInRadius( 7, 100, false, false, "Diamond" ) + self:SetStatus( "Running" ) return self end - + --- (Internal) Search for crates in reach. -- @param #CTLD_ENGINEERING self -- @param #table crates Table of found crate Ops.CTLD#CTLD_CARGO objects. -- @param #number number Number of crates found. -- @return #CTLD_ENGINEERING self - function CTLD_ENGINEERING:Search(crates,number) - self:T(self.lid.."Search") - self:SetStatus("Searching") + function CTLD_ENGINEERING:Search( crates, number ) + self:T( self.lid .. "Search" ) + self:SetStatus( "Searching" ) -- find crates close by - --local COps = self.C_Ops -- Ops.CTLD#CTLD + -- local COps = self.C_Ops -- Ops.CTLD#CTLD local dist = self.distance -- #number local group = self.Group -- Wrapper.Group#GROUP - --local crates,number = COps:_FindCratesNearby(group,nil, dist) -- #table + -- local crates,number = COps:_FindCratesNearby(group,nil, dist) -- #table local ctable = {} local ind = 0 if number > 0 then -- get set of dropped only - for _,_cargo in pairs (crates) do - local cgotype = _cargo:GetType() - if _cargo:WasDropped() and cgotype ~= CTLD_CARGO.Enum.STATIC then - local ok = false - local chalk = _cargo:GetMark() - if chalk == nil then - ok = true - else - -- have we tried this cargo recently? - local tag = chalk.tag or "none" - local timestamp = chalk.timestamp or 0 - --self:I({chalk}) - -- enough time gone? - local gone = timer.getAbsTime() - timestamp - --self:I({time=gone}) - if gone >= self.marktimer then + for _, _cargo in pairs( crates ) do + local cgotype = _cargo:GetType() + if _cargo:WasDropped() and cgotype ~= CTLD_CARGO.Enum.STATIC then + local ok = false + local chalk = _cargo:GetMark() + if chalk == nil then ok = true - _cargo:WipeMark() - end -- end time check - end -- end chalk - if ok then - local chalk = {} - chalk.tag = "Engineers" - chalk.timestamp = timer.getAbsTime() - _cargo:AddMark(chalk) - ind = ind + 1 - table.insert(ctable,ind,_cargo) - end - end -- end dropped + else + -- have we tried this cargo recently? + local tag = chalk.tag or "none" + local timestamp = chalk.timestamp or 0 + -- self:I({chalk}) + -- enough time gone? + local gone = timer.getAbsTime() - timestamp + -- self:I({time=gone}) + if gone >= self.marktimer then + ok = true + _cargo:WipeMark() + end -- end time check + end -- end chalk + if ok then + local chalk = {} + chalk.tag = "Engineers" + chalk.timestamp = timer.getAbsTime() + _cargo:AddMark( chalk ) + ind = ind + 1 + table.insert( ctable, ind, _cargo ) + end + end -- end dropped end -- end for end -- end number - + if ind > 0 then local crate = ctable[1] -- Ops.CTLD#CTLD_CARGO local static = crate:GetPositionable() -- Wrapper.Static#STATIC local crate_pos = static:GetCoordinate() -- Core.Point#COORDINATE local gpos = group:GetCoordinate() -- Core.Point#COORDINATE -- see how far we are from the crate - local distance = self:_GetDistance(gpos,crate_pos) - self:T(string.format("%s Distance to crate: %d", self.lid, distance)) + local distance = self:_GetDistance( gpos, crate_pos ) + self:T( string.format( "%s Distance to crate: %d", self.lid, distance ) ) -- move there - if distance > 30 and distance ~= -1 and self:IsStatus("Searching") then - group:RouteGroundTo(crate_pos,15,"Line abreast",1) + if distance > 30 and distance ~= -1 and self:IsStatus( "Searching" ) then + group:RouteGroundTo( crate_pos, 15, "Line abreast", 1 ) self.currwpt = crate_pos -- Core.Point#COORDINATE self:Move() elseif distance <= 30 and distance ~= -1 then @@ -235,101 +235,102 @@ CTLD_ENGINEERING = { self:Arrive() end else - self:T(self.lid.."No crates in reach!") + self:T( self.lid .. "No crates in reach!" ) end return self end - + --- (Internal) Move towards crates in reach. -- @param #CTLD_ENGINEERING self -- @return #CTLD_ENGINEERING self function CTLD_ENGINEERING:Move() - self:T(self.lid.."Move") - self:SetStatus("Moving") + self:T( self.lid .. "Move" ) + self:SetStatus( "Moving" ) -- check if we arrived on target - --local COps = self.C_Ops -- Ops.CTLD#CTLD + -- local COps = self.C_Ops -- Ops.CTLD#CTLD local group = self.Group -- Wrapper.Group#GROUP local tgtpos = self.currwpt -- Core.Point#COORDINATE local gpos = group:GetCoordinate() -- Core.Point#COORDINATE -- see how far we are from the crate - local distance = self:_GetDistance(gpos,tgtpos) - self:T(string.format("%s Distance remaining: %d", self.lid, distance)) + local distance = self:_GetDistance( gpos, tgtpos ) + self:T( string.format( "%s Distance remaining: %d", self.lid, distance ) ) if distance <= 30 and distance ~= -1 then - -- arrived - self:Arrive() + -- arrived + self:Arrive() end return self end - + --- (Internal) Arrived at crates in reach. Stop group. -- @param #CTLD_ENGINEERING self -- @return #CTLD_ENGINEERING self function CTLD_ENGINEERING:Arrive() - self:T(self.lid.."Arrive") - self:SetStatus("Arrived") + self:T( self.lid .. "Arrive" ) + self:SetStatus( "Arrived" ) self.currwpt = nil local Grp = self.Group -- Wrapper.Group#GROUP Grp:RouteStop() return self end - + --- (Internal) Return distance in meters between two coordinates. -- @param #CTLD_ENGINEERING self -- @param Core.Point#COORDINATE _point1 Coordinate one -- @param Core.Point#COORDINATE _point2 Coordinate two -- @return #number Distance in meters or -1 - function CTLD_ENGINEERING:_GetDistance(_point1, _point2) - self:T(self.lid .. " _GetDistance") + function CTLD_ENGINEERING:_GetDistance( _point1, _point2 ) + self:T( self.lid .. " _GetDistance" ) if _point1 and _point2 then - local distance1 = _point1:Get2DDistance(_point2) - local distance2 = _point1:DistanceFromPointVec2(_point2) - --self:I({dist1=distance1, dist2=distance2}) - if distance1 and type(distance1) == "number" then + local distance1 = _point1:Get2DDistance( _point2 ) + local distance2 = _point1:DistanceFromPointVec2( _point2 ) + -- self:I({dist1=distance1, dist2=distance2}) + if distance1 and type( distance1 ) == "number" then return distance1 - elseif distance2 and type(distance2) == "number" then + elseif distance2 and type( distance2 ) == "number" then return distance2 else - self:E("*****Cannot calculate distance!") - self:E({_point1,_point2}) + self:E( "*****Cannot calculate distance!" ) + self:E( { _point1, _point2 } ) return -1 end else - self:E("******Cannot calculate distance!") - self:E({_point1,_point2}) + self:E( "******Cannot calculate distance!" ) + self:E( { _point1, _point2 } ) return -1 end end ------------------------------------------------------- ---- **CTLD_CARGO** class, extends Core.Base#BASE --- @type CTLD_CARGO --- @field #number ID ID of this cargo. --- @field #string Name Name for menu. --- @field #table Templates Table of #POSITIONABLE objects. --- @field #CTLD_CARGO.Enum CargoType Enumerator of Type. --- @field #boolean HasBeenMoved Flag for moving. --- @field #boolean LoadDirectly Flag for direct loading. --- @field #number CratesNeeded Crates needed to build. --- @field Wrapper.Positionable#POSITIONABLE Positionable Representation of cargo in the mission. --- @field #boolean HasBeenDropped True if dropped from heli. --- @field #number PerCrateMass Mass in kg --- @field #number Stock Number of builds available, -1 for unlimited --- @extends Core.Base#BASE -CTLD_CARGO = { - ClassName = "CTLD_CARGO", - ID = 0, - Name = "none", - Templates = {}, - CargoType = "none", - HasBeenMoved = false, - LoadDirectly = false, - CratesNeeded = 0, - Positionable = nil, - HasBeenDropped = false, - PerCrateMass = 0, - Stock = nil, - Mark = nil, + ------------------------------------------------------ + --- **CTLD_CARGO** class, extends Core.Base#BASE + -- @type CTLD_CARGO + -- @field #number ID ID of this cargo. + -- @field #string Name Name for menu. + -- @field #table Templates Table of #POSITIONABLE objects. + -- @field #CTLD_CARGO.Enum CargoType Enumerator of Type. + -- @field #boolean HasBeenMoved Flag for moving. + -- @field #boolean LoadDirectly Flag for direct loading. + -- @field #number CratesNeeded Crates needed to build. + -- @field Wrapper.Positionable#POSITIONABLE Positionable Representation of cargo in the mission. + -- @field #boolean HasBeenDropped True if dropped from heli. + -- @field #number PerCrateMass Mass in kg + -- @field #number Stock Number of builds available, -1 for unlimited + -- @extends Core.Base#BASE + CTLD_CARGO = { + ClassName = "CTLD_CARGO", + ID = 0, + Name = "none", + Templates = { + }, + CargoType = "none", + HasBeenMoved = false, + LoadDirectly = false, + CratesNeeded = 0, + Positionable = nil, + HasBeenDropped = false, + PerCrateMass = 0, + Stock = nil, + Mark = nil, } - + --- Define cargo types. -- @type CTLD_CARGO.Enum -- @field #string Type Type of Cargo. @@ -342,13 +343,13 @@ CTLD_CARGO = { ["ENGINEERS"] = "Engineers", -- #string engineers ["STATIC"] = "Static", -- #string engineers } - + --- Function to create new CTLD_CARGO object. -- @param #CTLD_CARGO self -- @param #number ID ID of this #CTLD_CARGO -- @param #string Name Name for menu. -- @param #table Templates Table of #POSITIONABLE objects. - -- @param #CTLD_CARGO.Enum Sorte Enumerator of Type. + -- @param #CTLD_CARGO.Enum SortEnum Enumerator of Type. -- @param #boolean HasBeenMoved Flag for moving. -- @param #boolean LoadDirectly Flag for direct loading. -- @param #number CratesNeeded Crates needed to build. @@ -357,119 +358,119 @@ CTLD_CARGO = { -- @param #number PerCrateMass Mass in kg -- @param #number Stock Number of builds available, nil for unlimited -- @return #CTLD_CARGO self - function CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass, Stock) + function CTLD_CARGO:New( ID, Name, Templates, SortEnum, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass, Stock ) -- Inherit everything from BASE class. - local self=BASE:Inherit(self, BASE:New()) -- #CTLD - self:T({ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped}) - self.ID = ID or math.random(100000,1000000) + local self = BASE:Inherit( self, BASE:New() ) -- #CTLD + self:T( { ID, Name, Templates, SortEnum, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped } ) + self.ID = ID or math.random( 100000, 1000000 ) self.Name = Name or "none" -- #string self.Templates = Templates or {} -- #table - self.CargoType = Sorte or "type" -- #CTLD_CARGO.Enum + self.CargoType = SortEnum or "type" -- #CTLD_CARGO.Enum self.HasBeenMoved = HasBeenMoved or false -- #boolean self.LoadDirectly = LoadDirectly or false -- #boolean self.CratesNeeded = CratesNeeded or 0 -- #number self.Positionable = Positionable or nil -- Wrapper.Positionable#POSITIONABLE - self.HasBeenDropped = Dropped or false --#boolean + self.HasBeenDropped = Dropped or false -- #boolean self.PerCrateMass = PerCrateMass or 0 -- #number - self.Stock = Stock or nil --#number + self.Stock = Stock or nil -- #number self.Mark = nil return self end - + --- Query ID. -- @param #CTLD_CARGO self -- @return #number ID function CTLD_CARGO:GetID() return self.ID end - + --- Query Mass. -- @param #CTLD_CARGO self -- @return #number Mass in kg function CTLD_CARGO:GetMass() return self.PerCrateMass - end + end --- Query Name. -- @param #CTLD_CARGO self -- @return #string Name function CTLD_CARGO:GetName() return self.Name end - + --- Query Templates. -- @param #CTLD_CARGO self -- @return #table Templates function CTLD_CARGO:GetTemplates() return self.Templates end - + --- Query has moved. -- @param #CTLD_CARGO self -- @return #boolean Has moved function CTLD_CARGO:HasMoved() return self.HasBeenMoved end - + --- Query was dropped. -- @param #CTLD_CARGO self -- @return #boolean Has been dropped. function CTLD_CARGO:WasDropped() return self.HasBeenDropped end - + --- Query directly loadable. -- @param #CTLD_CARGO self -- @return #boolean loadable function CTLD_CARGO:CanLoadDirectly() return self.LoadDirectly end - + --- Query number of crates or troopsize. -- @param #CTLD_CARGO self -- @return #number Crates or size of troops. function CTLD_CARGO:GetCratesNeeded() return self.CratesNeeded end - + --- Query type. -- @param #CTLD_CARGO self -- @return #CTLD_CARGO.Enum Type function CTLD_CARGO:GetType() return self.CargoType end - + --- Query type. -- @param #CTLD_CARGO self -- @return Wrapper.Positionable#POSITIONABLE Positionable function CTLD_CARGO:GetPositionable() return self.Positionable end - + --- Set HasMoved. -- @param #CTLD_CARGO self -- @param #boolean moved - function CTLD_CARGO:SetHasMoved(moved) + function CTLD_CARGO:SetHasMoved( moved ) self.HasBeenMoved = moved or false end - - --- Query if cargo has been loaded. + + --- Query if cargo has been loaded. -- @param #CTLD_CARGO self -- @param #boolean loaded function CTLD_CARGO:Isloaded() if self.HasBeenMoved and not self.WasDropped() then return true else - return false - end + return false + end end - + --- Set WasDropped. -- @param #CTLD_CARGO self -- @param #boolean dropped - function CTLD_CARGO:SetWasDropped(dropped) + function CTLD_CARGO:SetWasDropped( dropped ) self.HasBeenDropped = dropped or false end - + --- Get Stock. -- @param #CTLD_CARGO self -- @return #number Stock @@ -480,1043 +481,1048 @@ CTLD_CARGO = { return -1 end end - + --- Add Stock. -- @param #CTLD_CARGO self -- @param #number Number to add, one if nil. -- @return #CTLD_CARGO self - function CTLD_CARGO:AddStock(Number) + function CTLD_CARGO:AddStock( Number ) if self.Stock then -- Stock nil? local number = Number or 1 self.Stock = self.Stock + number end return self end - + --- Remove Stock. -- @param #CTLD_CARGO self -- @param #number Number to reduce, one if nil. -- @return #CTLD_CARGO self - function CTLD_CARGO:RemoveStock(Number) + function CTLD_CARGO:RemoveStock( Number ) if self.Stock then -- Stock nil? local number = Number or 1 self.Stock = self.Stock - number - if self.Stock < 0 then self.Stock = 0 end + if self.Stock < 0 then + self.Stock = 0 + end end return self end - + --- Query crate type for REPAIR -- @param #CTLD_CARGO self -- @param #boolean function CTLD_CARGO:IsRepair() - if self.CargoType == "Repair" then - return true - else - return false - end + if self.CargoType == "Repair" then + return true + else + return false + end end - + --- Query crate type for STATIC -- @param #CTLD_CARGO self -- @param #boolean function CTLD_CARGO:IsStatic() - if self.CargoType == "Static" then - return true - else - return false - end + if self.CargoType == "Static" then + return true + else + return false + end end - - function CTLD_CARGO:AddMark(Mark) + + function CTLD_CARGO:AddMark( Mark ) self.Mark = Mark return self end - - function CTLD_CARGO:GetMark(Mark) + + function CTLD_CARGO:GetMark( Mark ) return self.Mark end - + function CTLD_CARGO:WipeMark() self.Mark = nil return self end - + end do -------------------------------------------------------------------------- ---- **CTLD** class, extends Core.Base#BASE, Core.Fsm#FSM --- @type CTLD --- @field #string ClassName Name of the class. --- @field #number verbose Verbosity level. --- @field #string lid Class id string for output to DCS log file. --- @field #number coalition Coalition side number, e.g. `coalition.side.RED`. --- @extends Core.Fsm#FSM + ------------------------------------------------------------------------- + --- **CTLD** class, extends Core.Base#BASE, Core.Fsm#FSM + -- @type CTLD + -- @field #string ClassName Name of the class. + -- @field #number verbose Verbosity level. + -- @field #string lid Class id string for output to DCS log file. + -- @field #number coalition Coalition side number, e.g. `coalition.side.RED`. + -- @extends Core.Fsm#FSM ---- *Combat Troop & Logistics Deployment (CTLD): Everyone wants to be a POG, until there\'s POG stuff to be done.* (Mil Saying) --- --- === --- --- ![Banner Image](OPS_CTLD.jpg) --- --- # CTLD Concept --- --- * MOOSE-based CTLD for Players. --- * Object oriented refactoring of Ciribob\'s fantastic CTLD script. --- * No need for extra MIST loading. --- * Additional events to tailor your mission. --- * ANY late activated group can serve as cargo, either as troops, crates, which have to be build on-location, or static like ammo chests. --- * Option to persist (save&load) your dropped troops, crates and vehicles. --- --- ## 0. Prerequisites --- --- You need to load an .ogg soundfile for the pilot\'s beacons into the mission, e.g. "beacon.ogg", use a once trigger, "sound to country" for that. --- Create the late-activated troops, vehicles (no statics at this point!) that will make up your deployable forces. --- --- ## 1. Basic Setup --- --- ## 1.1 Create and start a CTLD instance --- --- A basic setup example is the following: --- --- -- Instantiate and start a CTLD for the blue side, using helicopter groups named "Helicargo" and alias "Lufttransportbrigade I" --- local my_ctld = CTLD:New(coalition.side.BLUE,{"Helicargo"},"Lufttransportbrigade I") --- my_ctld:__Start(5) --- --- ## 1.2 Add cargo types available --- --- Add *generic* cargo types that you need for your missions, here infantry units, vehicles and a FOB. These need to be late-activated Wrapper.Group#GROUP objects: --- --- -- add infantry unit called "Anti-Tank Small" using template "ATS", of type TROOP with size 3 --- -- infantry units will be loaded directly from LOAD zones into the heli (matching number of free seats needed) --- my_ctld:AddTroopsCargo("Anti-Tank Small",{"ATS"},CTLD_CARGO.Enum.TROOPS,3) --- -- if you want to add weight to your Heli, troops can have a weight in kg **per person**. Currently no max weight checked. Fly carefully. --- my_ctld:AddTroopsCargo("Anti-Tank Small",{"ATS"},CTLD_CARGO.Enum.TROOPS,3,80) --- --- -- add infantry unit called "Anti-Tank" using templates "AA" and "AA"", of type TROOP with size 4. No weight. We only have 2 in stock: --- my_ctld:AddTroopsCargo("Anti-Air",{"AA","AA2"},CTLD_CARGO.Enum.TROOPS,4,nil,2) --- --- -- add an engineers unit called "Wrenches" using template "Engineers", of type ENGINEERS with size 2. Engineers can be loaded, dropped, --- -- and extracted like troops. However, they will seek to build and/or repair crates found in a given radius. Handy if you can\'t stay --- -- to build or repair or under fire. --- my_ctld:AddTroopsCargo("Wrenches",{"Engineers"},CTLD_CARGO.Enum.ENGINEERS,4) --- myctld.EngineerSearch = 2000 -- teams will search for crates in this radius. --- --- -- add vehicle called "Humvee" using template "Humvee", of type VEHICLE, size 2, i.e. needs two crates to be build --- -- vehicles and FOB will be spawned as crates in a LOAD zone first. Once transported to DROP zones, they can be build into the objects --- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2) --- -- if you want to add weight to your Heli, crates can have a weight in kg **per crate**. Currently no max weight checked. Fly carefully. --- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2,2775) --- -- if you want to limit your stock, add a number (here: 10) as parameter after weight. No parameter / nil means unlimited stock. --- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2,2775,10) --- --- -- add infantry unit called "Forward Ops Base" using template "FOB", of type FOB, size 4, i.e. needs four crates to be build: --- my_ctld:AddCratesCargo("Forward Ops Base",{"FOB"},CTLD_CARGO.Enum.FOB,4) --- --- -- add crates to repair FOB or VEHICLE type units - the 2nd parameter needs to match the template you want to repair --- my_ctld:AddCratesRepair("Humvee Repair","Humvee",CTLD_CARGO.Enum.REPAIR,1) --- my_ctld.repairtime = 300 -- takes 300 seconds to repair something --- --- -- add static cargo objects, e.g ammo chests - the name needs to refer to a STATIC object in the mission editor, --- -- here: it\'s the UNIT name (not the GROUP name!), the second parameter is the weight in kg. --- my_ctld:AddStaticsCargo("Ammunition",500) --- --- ## 1.3 Add logistics zones --- --- Add zones for loading troops and crates and dropping, building crates --- --- -- Add a zone of type LOAD to our setup. Players can load troops and crates. --- -- "Loadzone" is the name of the zone from the ME. Players can load, if they are inside the zone. --- -- Smoke and Flare color for this zone is blue, it is active (can be used) and has a radio beacon. --- my_ctld:AddCTLDZone("Loadzone",CTLD.CargoZoneType.LOAD,SMOKECOLOR.Blue,true,true) --- --- -- Add a zone of type DROP. Players can drop crates here. --- -- Smoke and Flare color for this zone is blue, it is active (can be used) and has a radio beacon. --- -- NOTE: Troops can be unloaded anywhere, also when hovering in parameters. --- my_ctld:AddCTLDZone("Dropzone",CTLD.CargoZoneType.DROP,SMOKECOLOR.Red,true,true) --- --- -- Add two zones of type MOVE. Dropped troops and vehicles will move to the nearest one. See options. --- -- Smoke and Flare color for this zone is blue, it is active (can be used) and has a radio beacon. --- my_ctld:AddCTLDZone("Movezone",CTLD.CargoZoneType.MOVE,SMOKECOLOR.Orange,false,false) --- --- my_ctld:AddCTLDZone("Movezone2",CTLD.CargoZoneType.MOVE,SMOKECOLOR.White,true,true) --- --- -- Add a zone of type SHIP to our setup. Players can load troops and crates from this ship --- -- "Tarawa" is the unitname (callsign) of the ship from the ME. Players can load, if they are inside the zone. --- -- The ship is 240 meters long and 20 meters wide. --- -- Note that you need to adjust the max hover height to deck height plus 5 meters or so for loading to work. --- -- When the ship is moving, forcing hoverload might not be a good idea. --- my_ctld:AddCTLDZone("Tarawa",CTLD.CargoZoneType.SHIP,SMOKECOLOR.Blue,true,true,240,20) --- --- ## 2. Options --- --- The following options are available (with their defaults). Only set the ones you want changed: --- --- my_ctld.useprefix = true -- (DO NOT SWITCH THIS OFF UNLESS YOU KNOW WHAT YOU ARE DOING!) Adjust **before** starting CTLD. If set to false, *all* choppers of the coalition side will be enabled for CTLD. --- my_ctld.CrateDistance = 35 -- List and Load crates in this radius only. --- my_ctld.dropcratesanywhere = false -- Option to allow crates to be dropped anywhere. --- my_ctld.maximumHoverHeight = 15 -- Hover max this high to load. --- my_ctld.minimumHoverHeight = 4 -- Hover min this low to load. --- my_ctld.forcehoverload = true -- Crates (not: troops) can **only** be loaded while hovering. --- my_ctld.hoverautoloading = true -- Crates in CrateDistance in a LOAD zone will be loaded automatically if space allows. --- my_ctld.smokedistance = 2000 -- Smoke or flares can be request for zones this far away (in meters). --- my_ctld.movetroopstowpzone = true -- Troops and vehicles will move to the nearest MOVE zone... --- my_ctld.movetroopsdistance = 5000 -- .. but only if this far away (in meters) --- my_ctld.smokedistance = 2000 -- Only smoke or flare zones if requesting player unit is this far away (in meters) --- my_ctld.suppressmessages = false -- Set to true if you want to script your own messages. --- my_ctld.repairtime = 300 -- Number of seconds it takes to repair a unit. --- my_ctld.cratecountry = country.id.GERMANY -- ID of crates. Will default to country.id.RUSSIA for RED coalition setups. --- my_ctld.allowcratepickupagain = true -- allow re-pickup crates that were dropped. --- my_ctld.enableslingload = false -- allow cargos to be slingloaded - might not work for all cargo types --- my_ctld.pilotmustopendoors = false -- force opening of doors --- my_ctld.SmokeColor = SMOKECOLOR.Red -- color to use when dropping smoke from heli --- my_ctld.FlareColor = FLARECOLOR.Red -- color to use when flaring from heli --- --- ## 2.1 User functions --- --- ### 2.1.1 Adjust or add chopper unit-type capabilities --- --- Use this function to adjust what a heli type can or cannot do: --- --- -- E.g. update unit capabilities for testing. Please stay realistic in your mission design. --- -- Make a Gazelle into a heavy truck, this type can load both crates and troops and eight of each type: --- my_ctld:UnitCapabilities("SA342L", true, true, 8, 8, 12) --- --- -- Default unit type capabilities are: --- --- ["SA342Mistral"] = {type="SA342Mistral", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12}, --- ["SA342L"] = {type="SA342L", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12}, --- ["SA342M"] = {type="SA342M", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12}, --- ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12}, --- ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8, length = 15}, --- ["Mi-8MT"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15}, --- ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 15}, --- ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18}, --- ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18}, --- ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25}, --- --- --- ### 2.1.2 Activate and deactivate zones --- --- Activate a zone: --- --- -- Activate zone called Name of type #CTLD.CargoZoneType ZoneType: --- my_ctld:ActivateZone(Name,CTLD.CargoZoneType.MOVE) --- --- Deactivate a zone: --- --- -- Deactivate zone called Name of type #CTLD.CargoZoneType ZoneType: --- my_ctld:DeactivateZone(Name,CTLD.CargoZoneType.DROP) --- --- ## 2.1.3 Limit and manage available resources --- --- When adding generic cargo types, you can effectively limit how many units can be dropped/build by the players, e.g. --- --- -- if you want to limit your stock, add a number (here: 10) as parameter after weight. No parameter / nil means unlimited stock. --- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2,2775,10) --- --- You can manually add or remove the available stock like so: --- --- -- Crates --- my_ctld:AddStockCrates("Humvee", 2) --- my_ctld:RemoveStockCrates("Humvee", 2) --- --- -- Troops --- my_ctld:AddStockTroops("Anti-Air", 2) --- my_ctld:RemoveStockTroops("Anti-Air", 2) --- --- Notes: --- Troops dropped back into a LOAD zone will effectively be added to the stock. Crates lost in e.g. a heli crash are just that - lost. --- --- ## 3. Events --- --- The class comes with a number of FSM-based events that missions designers can use to shape their mission. --- These are: --- --- ## 3.1 OnAfterTroopsPickedUp --- --- This function is called when a player has loaded Troops: --- --- function my_ctld:OnAfterTroopsPickedUp(From, Event, To, Group, Unit, Cargo) --- ... your code here ... --- end --- --- ## 3.2 OnAfterCratesPickedUp --- --- This function is called when a player has picked up crates: --- --- function my_ctld:OnAfterCratesPickedUp(From, Event, To, Group, Unit, Cargo) --- ... your code here ... --- end --- --- ## 3.3 OnAfterTroopsDeployed --- --- This function is called when a player has deployed troops into the field: --- --- function my_ctld:OnAfterTroopsDeployed(From, Event, To, Group, Unit, Troops) --- ... your code here ... --- end --- --- ## 3.4 OnAfterTroopsExtracted --- --- This function is called when a player has re-boarded already deployed troops from the field: --- --- function my_ctld:OnAfterTroopsExtracted(From, Event, To, Group, Unit, Troops) --- ... your code here ... --- end --- --- ## 3.5 OnAfterCratesDropped --- --- This function is called when a player has deployed crates to a DROP zone: --- --- function my_ctld:OnAfterCratesDropped(From, Event, To, Group, Unit, Cargotable) --- ... your code here ... --- end --- --- ## 3.6 OnAfterCratesBuild, OnAfterCratesRepaired --- --- This function is called when a player has build a vehicle or FOB: --- --- function my_ctld:OnAfterCratesBuild(From, Event, To, Group, Unit, Vehicle) --- ... your code here ... --- end --- --- function my_ctld:OnAfterCratesRepaired(From, Event, To, Group, Unit, Vehicle) --- ... your code here ... --- end - -- --- ## 3.7 A simple SCORING example: --- --- To award player with points, using the SCORING Class (SCORING: my_Scoring, CTLD: CTLD_Cargotransport) --- --- function CTLD_Cargotransport:OnAfterCratesDropped(From, Event, To, Group, Unit, Cargotable) --- local points = 10 --- if Unit then --- local PlayerName = Unit:GetPlayerName() --- my_scoring:_AddPlayerFromUnit( Unit ) --- my_scoring:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for transporting cargo crates!", PlayerName, points), points) --- end --- end --- --- function CTLD_Cargotransport:OnAfterCratesBuild(From, Event, To, Group, Unit, Vehicle) --- local points = 5 --- if Unit then + --- *Combat Troop & Logistics Deployment (CTLD): Everyone wants to be a POG, until there's POG stuff to be done.* (Mil Saying) + -- + -- === + -- + -- ![Banner Image](OPS_CTLD.jpg) + -- + -- # CTLD Concept + -- + -- * MOOSE-based CTLD for Players. + -- * Object oriented refactoring of Ciribob's fantastic CTLD script. + -- * No need for extra MIST loading. + -- * Additional events to tailor your mission. + -- * ANY late activated group can serve as cargo, either as troops, crates, which have to be build on-location, or static like ammo chests. + -- * Option to persist (save&load) your dropped troops, crates and vehicles. + -- + -- ## 0. Prerequisites + -- + -- You need to load an .ogg sound file for the pilot's beacons into the mission, e.g. "beacon.ogg", use a once trigger, "sound to country" for that. + -- Create the late-activated troops, vehicles (no statics at this point!) that will make up your deployable forces. + -- + -- ## 1. Basic Setup + -- + -- ## 1.1 Create and start a CTLD instance + -- + -- A basic setup example is the following: + -- + -- -- Instantiate and start a CTLD for the blue side, using helicopter groups named "Helicargo" and alias "Lufttransportbrigade I" + -- local my_ctld = CTLD:New(coalition.side.BLUE,{"Helicargo"},"Lufttransportbrigade I") + -- my_ctld:__Start(5) + -- + -- ## 1.2 Add cargo types available + -- + -- Add *generic* cargo types that you need for your missions, here infantry units, vehicles and a FOB. These need to be late-activated Wrapper.Group#GROUP objects: + -- + -- -- add infantry unit called "Anti-Tank Small" using template "ATS", of type TROOP with size 3 + -- -- infantry units will be loaded directly from LOAD zones into the heli (matching number of free seats needed) + -- my_ctld:AddTroopsCargo("Anti-Tank Small",{"ATS"},CTLD_CARGO.Enum.TROOPS,3) + -- -- if you want to add weight to your Heli, troops can have a weight in kg **per person**. Currently no max weight checked. Fly carefully. + -- my_ctld:AddTroopsCargo("Anti-Tank Small",{"ATS"},CTLD_CARGO.Enum.TROOPS,3,80) + -- + -- -- add infantry unit called "Anti-Tank" using templates "AA" and "AA"", of type TROOP with size 4. No weight. We only have 2 in stock: + -- my_ctld:AddTroopsCargo("Anti-Air",{"AA","AA2"},CTLD_CARGO.Enum.TROOPS,4,nil,2) + -- + -- -- add an engineers unit called "Wrenches" using template "Engineers", of type ENGINEERS with size 2. Engineers can be loaded, dropped, + -- -- and extracted like troops. However, they will seek to build and/or repair crates found in a given radius. Handy if you can't stay + -- -- to build or repair or under fire. + -- my_ctld:AddTroopsCargo("Wrenches",{"Engineers"},CTLD_CARGO.Enum.ENGINEERS,4) + -- myctld.EngineerSearch = 2000 -- teams will search for crates in this radius. + -- + -- -- add vehicle called "Humvee" using template "Humvee", of type VEHICLE, size 2, i.e. needs two crates to be build + -- -- vehicles and FOB will be spawned as crates in a LOAD zone first. Once transported to DROP zones, they can be build into the objects + -- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2) + -- -- if you want to add weight to your Heli, crates can have a weight in kg **per crate**. Currently no max weight checked. Fly carefully. + -- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2,2775) + -- -- if you want to limit your stock, add a number (here: 10) as parameter after weight. No parameter / nil means unlimited stock. + -- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2,2775,10) + -- + -- -- add infantry unit called "Forward Ops Base" using template "FOB", of type FOB, size 4, i.e. needs four crates to be build: + -- my_ctld:AddCratesCargo("Forward Ops Base",{"FOB"},CTLD_CARGO.Enum.FOB,4) + -- + -- -- add crates to repair FOB or VEHICLE type units - the 2nd parameter needs to match the template you want to repair + -- my_ctld:AddCratesRepair("Humvee Repair","Humvee",CTLD_CARGO.Enum.REPAIR,1) + -- my_ctld.repairtime = 300 -- takes 300 seconds to repair something + -- + -- -- add static cargo objects, e.g ammo chests - the name needs to refer to a STATIC object in the mission editor, + -- -- here: it's the UNIT name (not the GROUP name!), the second parameter is the weight in kg. + -- my_ctld:AddStaticsCargo("Ammunition",500) + -- + -- ## 1.3 Add logistics zones + -- + -- Add zones for loading troops and crates and dropping, building crates + -- + -- -- Add a zone of type LOAD to our setup. Players can load troops and crates. + -- -- "Loadzone" is the name of the zone from the ME. Players can load, if they are inside the zone. + -- -- Smoke and Flare color for this zone is blue, it is active (can be used) and has a radio beacon. + -- my_ctld:AddCTLDZone("Loadzone",CTLD.CargoZoneType.LOAD,SMOKECOLOR.Blue,true,true) + -- + -- -- Add a zone of type DROP. Players can drop crates here. + -- -- Smoke and Flare color for this zone is blue, it is active (can be used) and has a radio beacon. + -- -- NOTE: Troops can be unloaded anywhere, also when hovering in parameters. + -- my_ctld:AddCTLDZone("Dropzone",CTLD.CargoZoneType.DROP,SMOKECOLOR.Red,true,true) + -- + -- -- Add two zones of type MOVE. Dropped troops and vehicles will move to the nearest one. See options. + -- -- Smoke and Flare color for this zone is blue, it is active (can be used) and has a radio beacon. + -- my_ctld:AddCTLDZone("Movezone",CTLD.CargoZoneType.MOVE,SMOKECOLOR.Orange,false,false) + -- + -- my_ctld:AddCTLDZone("Movezone2",CTLD.CargoZoneType.MOVE,SMOKECOLOR.White,true,true) + -- + -- -- Add a zone of type SHIP to our setup. Players can load troops and crates from this ship + -- -- "Tarawa" is the unitname (callsign) of the ship from the ME. Players can load, if they are inside the zone. + -- -- The ship is 240 meters long and 20 meters wide. + -- -- Note that you need to adjust the max hover height to deck height plus 5 meters or so for loading to work. + -- -- When the ship is moving, forcing hoverload might not be a good idea. + -- my_ctld:AddCTLDZone("Tarawa",CTLD.CargoZoneType.SHIP,SMOKECOLOR.Blue,true,true,240,20) + -- + -- ## 2. Options + -- + -- The following options are available (with their defaults). Only set the ones you want changed: + -- + -- my_ctld.useprefix = true -- (DO NOT SWITCH THIS OFF UNLESS YOU KNOW WHAT YOU ARE DOING!) Adjust **before** starting CTLD. If set to false, *all* choppers of the coalition side will be enabled for CTLD. + -- my_ctld.CrateDistance = 35 -- List and Load crates in this radius only. + -- my_ctld.dropcratesanywhere = false -- Option to allow crates to be dropped anywhere. + -- my_ctld.maximumHoverHeight = 15 -- Hover max this high to load. + -- my_ctld.minimumHoverHeight = 4 -- Hover min this low to load. + -- my_ctld.forcehoverload = true -- Crates (not: troops) can **only** be loaded while hovering. + -- my_ctld.hoverautoloading = true -- Crates in CrateDistance in a LOAD zone will be loaded automatically if space allows. + -- my_ctld.smokedistance = 2000 -- Smoke or flares can be request for zones this far away (in meters). + -- my_ctld.movetroopstowpzone = true -- Troops and vehicles will move to the nearest MOVE zone... + -- my_ctld.movetroopsdistance = 5000 -- .. but only if this far away (in meters) + -- my_ctld.smokedistance = 2000 -- Only smoke or flare zones if requesting player unit is this far away (in meters) + -- my_ctld.suppressmessages = false -- Set to true if you want to script your own messages. + -- my_ctld.repairtime = 300 -- Number of seconds it takes to repair a unit. + -- my_ctld.cratecountry = country.id.GERMANY -- ID of crates. Will default to country.id.RUSSIA for RED coalition setups. + -- my_ctld.allowcratepickupagain = true -- allow re-pickup crates that were dropped. + -- my_ctld.enableslingload = false -- allow cargos to be slingloaded - might not work for all cargo types + -- my_ctld.pilotmustopendoors = false -- force opening of doors + -- my_ctld.SmokeColor = SMOKECOLOR.Red -- color to use when dropping smoke from heli + -- my_ctld.FlareColor = FLARECOLOR.Red -- color to use when flaring from heli + -- + -- ## 2.1 User functions + -- + -- ### 2.1.1 Adjust or add chopper unit-type capabilities + -- + -- Use this function to adjust what a heli type can or cannot do: + -- + -- -- E.g. update unit capabilities for testing. Please stay realistic in your mission design. + -- -- Make a Gazelle into a heavy truck, this type can load both crates and troops and eight of each type: + -- my_ctld:UnitCapabilities("SA342L", true, true, 8, 8, 12) + -- + -- -- Default unit type capabilities are: + -- + -- ["SA342Mistral"] = {type="SA342Mistral", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12}, + -- ["SA342L"] = {type="SA342L", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12}, + -- ["SA342M"] = {type="SA342M", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12}, + -- ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12}, + -- ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8, length = 15}, + -- ["Mi-8MT"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15}, + -- ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 15}, + -- ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18}, + -- ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18}, + -- ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25}, + -- + -- ### 2.1.2 Activate and deactivate zones + -- + -- Activate a zone: + -- + -- -- Activate zone called Name of type #CTLD.CargoZoneType ZoneType: + -- my_ctld:ActivateZone(Name,CTLD.CargoZoneType.MOVE) + -- + -- Deactivate a zone: + -- + -- -- Deactivate zone called Name of type #CTLD.CargoZoneType ZoneType: + -- my_ctld:DeactivateZone(Name,CTLD.CargoZoneType.DROP) + -- + -- ## 2.1.3 Limit and manage available resources + -- + -- When adding generic cargo types, you can effectively limit how many units can be dropped/build by the players, e.g. + -- + -- -- if you want to limit your stock, add a number (here: 10) as parameter after weight. No parameter / nil means unlimited stock. + -- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2,2775,10) + -- + -- You can manually add or remove the available stock like so: + -- + -- -- Crates + -- my_ctld:AddStockCrates("Humvee", 2) + -- my_ctld:RemoveStockCrates("Humvee", 2) + -- + -- -- Troops + -- my_ctld:AddStockTroops("Anti-Air", 2) + -- my_ctld:RemoveStockTroops("Anti-Air", 2) + -- + -- Notes: + -- Troops dropped back into a LOAD zone will effectively be added to the stock. Crates lost in e.g. a heli crash are just that - lost. + -- + -- ## 3. Events + -- + -- The class comes with a number of FSM-based events that missions designers can use to shape their mission. + -- These are: + -- + -- ## 3.1 OnAfterTroopsPickedUp + -- + -- This function is called when a player has loaded Troops: + -- + -- function my_ctld:OnAfterTroopsPickedUp(From, Event, To, Group, Unit, Cargo) + -- ... your code here ... + -- end + -- + -- ## 3.2 OnAfterCratesPickedUp + -- + -- This function is called when a player has picked up crates: + -- + -- function my_ctld:OnAfterCratesPickedUp(From, Event, To, Group, Unit, Cargo) + -- ... your code here ... + -- end + -- + -- ## 3.3 OnAfterTroopsDeployed + -- + -- This function is called when a player has deployed troops into the field: + -- + -- function my_ctld:OnAfterTroopsDeployed(From, Event, To, Group, Unit, Troops) + -- ... your code here ... + -- end + -- + -- ## 3.4 OnAfterTroopsExtracted + -- + -- This function is called when a player has re-boarded already deployed troops from the field: + -- + -- function my_ctld:OnAfterTroopsExtracted(From, Event, To, Group, Unit, Troops) + -- ... your code here ... + -- end + -- + -- ## 3.5 OnAfterCratesDropped + -- + -- This function is called when a player has deployed crates to a DROP zone: + -- + -- function my_ctld:OnAfterCratesDropped(From, Event, To, Group, Unit, Cargotable) + -- ... your code here ... + -- end + -- + -- ## 3.6 OnAfterCratesBuild, OnAfterCratesRepaired + -- + -- This function is called when a player has build a vehicle or FOB: + -- + -- function my_ctld:OnAfterCratesBuild(From, Event, To, Group, Unit, Vehicle) + -- ... your code here ... + -- end + -- + -- function my_ctld:OnAfterCratesRepaired(From, Event, To, Group, Unit, Vehicle) + -- ... your code here ... + -- end + -- + -- ## 3.7 A simple SCORING example: + -- + -- To award player with points, using the SCORING Class (SCORING: my_Scoring, CTLD: CTLD_Cargotransport) + -- + -- function CTLD_Cargotransport:OnAfterCratesDropped(From, Event, To, Group, Unit, Cargotable) + -- local points = 10 + -- if Unit then + -- local PlayerName = Unit:GetPlayerName() + -- my_scoring:_AddPlayerFromUnit( Unit ) + -- my_scoring:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for transporting cargo crates!", PlayerName, points), points) + -- end + -- end + -- + -- function CTLD_Cargotransport:OnAfterCratesBuild(From, Event, To, Group, Unit, Vehicle) + -- local points = 5 + -- if Unit then -- local PlayerName = Unit:GetPlayerName() -- my_scoring:_AddPlayerFromUnit( Unit ) -- my_scoring:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for the construction of Units!", PlayerName, points), points) --- end --- end --- --- ## 4. F10 Menu structure --- --- CTLD management menu is under the F10 top menu and called "CTLD" --- --- ## 4.1 Manage Crates --- --- Use this entry to get, load, list nearby, drop, build and repair crates. Also see options. --- --- ## 4.2 Manage Troops --- --- Use this entry to load, drop and extract troops. NOTE - with extract you can only load troops from the field that were deployed prior. --- Currently limited CTLD_CARGO troops, which are build from **one** template. Also, this will heal/complete your units as they are respawned. --- --- ## 4.3 List boarded cargo --- --- Lists what you have loaded. Shows load capabilities for number of crates and number of seats for troops. --- --- ## 4.4 Smoke & Flare zones nearby or drop smoke or flare from Heli --- --- Does what it says. --- --- ## 4.5 List active zone beacons --- --- Lists active radio beacons for all zones, where zones are both active and have a beacon. @see `CTLD:AddCTLDZone()` --- --- ## 4.6 Show hover parameters --- --- Lists hover parameters and indicates if these are curently fulfilled. Also @see options on hover heights. --- --- ## 4.7 List Inventory --- --- Lists invetory of available units to drop or build. --- --- ## 5. Support for Hercules mod by Anubis --- --- Basic support for the Hercules mod By Anubis has been build into CTLD. Currently this does **not** cover objects and troops which can --- be loaded from the Rearm/Refuel menu, i.e. you can drop them into the field, but you cannot use them in functions scripted with this class. --- --- local my_ctld = CTLD:New(coalition.side.BLUE,{"Helicargo", "Hercules"},"Lufttransportbrigade I") --- --- Enable these options for Hercules support: --- --- my_ctld.enableHercules = true --- my_ctld.HercMinAngels = 155 -- for troop/cargo drop via chute in meters, ca 470 ft --- my_ctld.HercMaxAngels = 2000 -- for troop/cargo drop via chute in meters, ca 6000 ft --- my_ctld.HercMaxSpeed = 77 -- 77mps or 270kph or 150kn --- --- Also, the following options need to be set to `true`: --- --- my_ctld.useprefix = true -- this is true by default and MUST BE ON. --- --- Standard transport capabilities as per the real Hercules are: --- --- ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64}, -- 19t cargo, 64 paratroopers --- --- ## 6. Save and load back units - persistance --- --- You can save and later load back units dropped or build to make your mission persistent. --- For this to work, you need to de-sanitize **io** and **lfs** in your MissionScripting.lua, which is located in your DCS installtion folder under Scripts. --- There is a risk involved in doing that; if you do not know what that means, this is possibly not for you. --- --- Use the following options to manage your saves: --- --- my_ctld.enableLoadSave = true -- allow auto-saving and loading of files --- my_ctld.saveinterval = 600 -- save every 10 minutes --- my_ctld.filename = "missionsave.csv" -- example filename --- my_ctld.filepath = "C:\\Users\\myname\\Saved Games\\DCS\Missions\\MyMission" -- example path --- my_ctld.eventoninject = true -- fire OnAfterCratesBuild and OnAfterTroopsDeployed events when loading (uses Inject functions) --- --- Then use an initial load at the beginning of your mission: --- --- my_ctld:__Load(10) --- --- **Caveat:** --- If you use units build by multiple templates, they will effectively double on loading. Dropped crates are not saved. Current stock is not saved. --- --- @field #CTLD -CTLD = { - ClassName = "CTLD", - verbose = 0, - lid = "", - coalition = 1, - coalitiontxt = "blue", - PilotGroups = {}, -- #GROUP_SET of heli pilots - CtldUnits = {}, -- Table of helicopter #GROUPs - FreeVHFFrequencies = {}, -- Table of VHF - FreeUHFFrequencies = {}, -- Table of UHF - FreeFMFrequencies = {}, -- Table of FM - CargoCounter = 0, - wpZones = {}, - Cargo_Troops = {}, -- generic troops objects - Cargo_Crates = {}, -- generic crate objects - Loaded_Cargo = {}, -- cargo aboard units - Spawned_Crates = {}, -- Holds objects for crates spawned generally - Spawned_Cargo = {}, -- Binds together spawned_crates and their CTLD_CARGO objects - CrateDistance = 35, -- list crates in this radius - debug = false, - wpZones = {}, - dropOffZones = {}, - pickupZones = {}, -} + -- end + -- end + -- + -- ## 4. F10 Menu structure + -- + -- CTLD management menu is under the F10 top menu and called "CTLD" + -- + -- ## 4.1 Manage Crates + -- + -- Use this entry to get, load, list nearby, drop, build and repair crates. Also see options. + -- + -- ## 4.2 Manage Troops + -- + -- Use this entry to load, drop and extract troops. NOTE - with extract you can only load troops from the field that were deployed prior. + -- Currently limited CTLD_CARGO troops, which are build from **one** template. Also, this will heal/complete your units as they are respawned. + -- + -- ## 4.3 List boarded cargo + -- + -- Lists what you have loaded. Shows load capabilities for number of crates and number of seats for troops. + -- + -- ## 4.4 Smoke & Flare zones nearby or drop smoke or flare from Heli + -- + -- Does what it says. + -- + -- ## 4.5 List active zone beacons + -- + -- Lists active radio beacons for all zones, where zones are both active and have a beacon. @see `CTLD:AddCTLDZone()` + -- + -- ## 4.6 Show hover parameters + -- + -- Lists hover parameters and indicates if these are curently fulfilled. Also @see options on hover heights. + -- + -- ## 4.7 List Inventory + -- + -- Lists invetory of available units to drop or build. + -- + -- ## 5. Support for Hercules mod by Anubis + -- + -- Basic support for the Hercules mod By Anubis has been build into CTLD. Currently this does **not** cover objects and troops which can + -- be loaded from the Rearm/Refuel menu, i.e. you can drop them into the field, but you cannot use them in functions scripted with this class. + -- + -- local my_ctld = CTLD:New(coalition.side.BLUE,{"Helicargo", "Hercules"},"Lufttransportbrigade I") + -- + -- Enable these options for Hercules support: + -- + -- my_ctld.enableHercules = true + -- my_ctld.HercMinAngels = 155 -- for troop/cargo drop via chute in meters, ca 470 ft + -- my_ctld.HercMaxAngels = 2000 -- for troop/cargo drop via chute in meters, ca 6000 ft + -- my_ctld.HercMaxSpeed = 77 -- 77mps or 270kph or 150kn + -- + -- Also, the following options need to be set to `true`: + -- + -- my_ctld.useprefix = true -- this is true by default and MUST BE ON. + -- + -- Standard transport capabilities as per the real Hercules are: + -- + -- ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64}, -- 19t cargo, 64 paratroopers + -- + -- ## 6. Save and load back units - persistance + -- + -- You can save and later load back units dropped or build to make your mission persistent. + -- For this to work, you need to de-sanitize **io** and **lfs** in your MissionScripting.lua, which is located in your DCS installtion folder under Scripts. + -- There is a risk involved in doing that; if you do not know what that means, this is possibly not for you. + -- + -- Use the following options to manage your saves: + -- + -- my_ctld.enableLoadSave = true -- allow auto-saving and loading of files + -- my_ctld.saveinterval = 600 -- save every 10 minutes + -- my_ctld.filename = "missionsave.csv" -- example filename + -- my_ctld.filepath = "C:\\Users\\myname\\Saved Games\\DCS\Missions\\MyMission" -- example path + -- my_ctld.eventoninject = true -- fire OnAfterCratesBuild and OnAfterTroopsDeployed events when loading (uses Inject functions) + -- + -- Then use an initial load at the beginning of your mission: + -- + -- my_ctld:__Load(10) + -- + -- **Caveat:** + -- If you use units build by multiple templates, they will effectively double on loading. Dropped crates are not saved. Current stock is not saved. + -- + -- @field #CTLD + CTLD = { + ClassName = "CTLD", + verbose = 0, + lid = "", + coalition = 1, + coalitiontxt = "blue", + PilotGroups = {}, -- #GROUP_SET of heli pilots + CtldUnits = {}, -- Table of helicopter #GROUPs + FreeVHFFrequencies = {}, -- Table of VHF + FreeUHFFrequencies = {}, -- Table of UHF + FreeFMFrequencies = {}, -- Table of FM + CargoCounter = 0, + wpZones = {}, + Cargo_Troops = {}, -- generic troops objects + Cargo_Crates = {}, -- generic crate objects + Loaded_Cargo = {}, -- cargo aboard units + Spawned_Crates = {}, -- Holds objects for crates spawned generally + Spawned_Cargo = {}, -- Binds together spawned_crates and their CTLD_CARGO objects + CrateDistance = 35, -- list crates in this radius + debug = false, + wpZones = {}, + dropOffZones = {}, + pickupZones = {}, + } ------------------------------- --- DONE: Zone Checks --- DONE: TEST Hover load and unload --- DONE: Crate unload --- DONE: Hover (auto-)load --- DONE: (More) Housekeeping --- DONE: Troops running to WP Zone --- DONE: Zone Radio Beacons --- DONE: Stats Running --- DONE: Added support for Hercules --- TODO: Possibly - either/or loading crates and troops --- DONE: Make inject respect existing cargo types --- DONE: Drop beacons or flares/smoke --- DONE: Add statics as cargo --- DONE: List cargo in stock --- DONE: Limit of troops, crates buildable? --- DONE: Allow saving of Troops & Vehicles ------------------------------- + ------------------------------ + -- DONE: Zone Checks + -- DONE: TEST Hover load and unload + -- DONE: Crate unload + -- DONE: Hover (auto-)load + -- DONE: (More) Housekeeping + -- DONE: Troops running to WP Zone + -- DONE: Zone Radio Beacons + -- DONE: Stats Running + -- DONE: Added support for Hercules + -- TODO: Possibly - either/or loading crates and troops + -- DONE: Make inject respect existing cargo types + -- DONE: Drop beacons or flares/smoke + -- DONE: Add statics as cargo + -- DONE: List cargo in stock + -- DONE: Limit of troops, crates buildable? + -- DONE: Allow saving of Troops & Vehicles + ------------------------------ ---- Radio Beacons --- @type CTLD.ZoneBeacon --- @field #string name -- Name of zone for the coordinate --- @field #number frequency -- in mHz --- @field #number modulation -- i.e.radio.modulation.FM or radio.modulation.AM + --- Radio Beacons + -- @type CTLD.ZoneBeacon + -- @field #string name -- Name of zone for the coordinate + -- @field #number frequency -- in mHz + -- @field #number modulation -- i.e.radio.modulation.FM or radio.modulation.AM ---- Zone Info. --- @type CTLD.CargoZone --- @field #string name Name of Zone. --- @field #string color Smoke color for zone, e.g. SMOKECOLOR.Red. --- @field #boolean active Active or not. --- @field #string type Type of zone, i.e. load,drop,move,ship --- @field #boolean hasbeacon Create and run radio beacons if active. --- @field #table fmbeacon Beacon info as #CTLD.ZoneBeacon --- @field #table uhfbeacon Beacon info as #CTLD.ZoneBeacon --- @field #table vhfbeacon Beacon info as #CTLD.ZoneBeacon --- @field #number shiplength For ships - length of ship --- @field #number shipwidth For ships - width of ship + --- Zone Info. + -- @type CTLD.CargoZone + -- @field #string name Name of Zone. + -- @field #string color Smoke color for zone, e.g. SMOKECOLOR.Red. + -- @field #boolean active Active or not. + -- @field #string type Type of zone, i.e. load,drop,move,ship + -- @field #boolean hasbeacon Create and run radio beacons if active. + -- @field #table fmbeacon Beacon info as #CTLD.ZoneBeacon + -- @field #table uhfbeacon Beacon info as #CTLD.ZoneBeacon + -- @field #table vhfbeacon Beacon info as #CTLD.ZoneBeacon + -- @field #number shiplength For ships - length of ship + -- @field #number shipwidth For ships - width of ship ---- Zone Type Info. --- @type CTLD.CargoZoneType -CTLD.CargoZoneType = { - LOAD = "load", - DROP = "drop", - MOVE = "move", - SHIP = "ship", -} + --- Zone Type Info. + -- @type CTLD.CargoZoneType + CTLD.CargoZoneType = { + LOAD = "load", + DROP = "drop", + MOVE = "move", + SHIP = "ship", + } ---- Buildable table info. --- @type CTLD.Buildable --- @field #string Name Name of the object. --- @field #number Required Required crates. --- @field #number Found Found crates. --- @field #table Template Template names for this build. --- @field #boolean CanBuild Is buildable or not. --- @field #CTLD_CARGO.Enum Type Type enumerator (for moves). + --- Buildable table info. + -- @type CTLD.Buildable + -- @field #string Name Name of the object. + -- @field #number Required Required crates. + -- @field #number Found Found crates. + -- @field #table Template Template names for this build. + -- @field #boolean CanBuild Is buildable or not. + -- @field #CTLD_CARGO.Enum Type Type enumerator (for moves). ---- Unit capabilities. --- @type CTLD.UnitCapabilities --- @field #string type Unit type. --- @field #boolean crates Can transport crate. --- @field #boolean troops Can transport troops. --- @field #number cratelimit Number of crates transportable. --- @field #number trooplimit Number of troop units transportable. -CTLD.UnitTypes = { - ["SA342Mistral"] = {type="SA342Mistral", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12}, - ["SA342L"] = {type="SA342L", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12}, - ["SA342M"] = {type="SA342M", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12}, - ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12}, - ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8, length = 15}, - ["Mi-8MTV2"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15}, - ["Mi-8MT"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15}, - ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 15}, - ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18}, - ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18}, - ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25}, -- 19t cargo, 64 paratroopers. - --Actually it's longer, but the center coord is off-center of the model. -} + --- Unit capabilities. + -- @type CTLD.UnitCapabilities + -- @field #string type Unit type. + -- @field #boolean crates Can transport crate. + -- @field #boolean troops Can transport troops. + -- @field #number cratelimit Number of crates transportable. + -- @field #number trooplimit Number of troop units transportable. + CTLD.UnitTypes = { + ["SA342Mistral"] = { type = "SA342Mistral", crates = false, troops = true, cratelimit = 0, trooplimit = 4, length = 12 }, + ["SA342L"] = { type = "SA342L", crates = false, troops = true, cratelimit = 0, trooplimit = 2, length = 12 }, + ["SA342M"] = { type = "SA342M", crates = false, troops = true, cratelimit = 0, trooplimit = 4, length = 12 }, + ["SA342Minigun"] = { type = "SA342Minigun", crates = false, troops = true, cratelimit = 0, trooplimit = 2, length = 12 }, + ["UH-1H"] = { type = "UH-1H", crates = true, troops = true, cratelimit = 1, trooplimit = 8, length = 15 }, + ["Mi-8MTV2"] = { type = "Mi-8MTV2", crates = true, troops = true, cratelimit = 2, trooplimit = 12, length = 15 }, + ["Mi-8MT"] = { type = "Mi-8MTV2", crates = true, troops = true, cratelimit = 2, trooplimit = 12, length = 15 }, + ["Ka-50"] = { type = "Ka-50", crates = false, troops = false, cratelimit = 0, trooplimit = 0, length = 15 }, + ["Mi-24P"] = { type = "Mi-24P", crates = true, troops = true, cratelimit = 2, trooplimit = 8, length = 18 }, + ["Mi-24V"] = { type = "Mi-24V", crates = true, troops = true, cratelimit = 2, trooplimit = 8, length = 18 }, + ["Hercules"] = { type = "Hercules", crates = true, troops = true, cratelimit = 7, trooplimit = 64, length = 25 }, -- 19t cargo, 64 paratroopers. Actually it's longer, but the center coord is off-center of the model. + } ---- CTLD class version. --- @field #string version -CTLD.version="1.0.1" + --- CTLD class version. + -- @field #string version + CTLD.version = "1.0.1" ---- Instantiate a new CTLD. --- @param #CTLD self --- @param #string Coalition Coalition of this CTLD. I.e. coalition.side.BLUE or coalition.side.RED or coalition.side.NEUTRAL --- @param #table Prefixes Table of pilot prefixes. --- @param #string Alias Alias of this CTLD for logging. --- @return #CTLD self -function CTLD:New(Coalition, Prefixes, Alias) - -- Inherit everything from FSM class. - local self=BASE:Inherit(self, FSM:New()) -- #CTLD - - BASE:T({Coalition, Prefixes, Alias}) - - --set Coalition - if Coalition and type(Coalition)=="string" then - if Coalition=="blue" then - self.coalition=coalition.side.BLUE - self.coalitiontxt = Coalition - elseif Coalition=="red" then - self.coalition=coalition.side.RED - self.coalitiontxt = Coalition - elseif Coalition=="neutral" then - self.coalition=coalition.side.NEUTRAL - self.coalitiontxt = Coalition + --- Instantiate a new CTLD. + -- @param #CTLD self + -- @param #string Coalition Coalition of this CTLD. I.e. coalition.side.BLUE or coalition.side.RED or coalition.side.NEUTRAL + -- @param #table Prefixes Table of pilot prefixes. + -- @param #string Alias Alias of this CTLD for logging. + -- @return #CTLD self + function CTLD:New( Coalition, Prefixes, Alias ) + -- Inherit everything from FSM class. + local self = BASE:Inherit( self, FSM:New() ) -- #CTLD + + BASE:T( { Coalition, Prefixes, Alias } ) + + -- set Coalition + if Coalition and type( Coalition ) == "string" then + if Coalition == "blue" then + self.coalition = coalition.side.BLUE + self.coalitiontxt = Coalition + elseif Coalition == "red" then + self.coalition = coalition.side.RED + self.coalitiontxt = Coalition + elseif Coalition == "neutral" then + self.coalition = coalition.side.NEUTRAL + self.coalitiontxt = Coalition + else + self:E( "ERROR: Unknown coalition in CTLD!" ) + end else - self:E("ERROR: Unknown coalition in CTLD!") + self.coalition = Coalition + self.coalitiontxt = string.lower( UTILS.GetCoalitionName( self.coalition ) ) end - else - self.coalition = Coalition - self.coalitiontxt = string.lower(UTILS.GetCoalitionName(self.coalition)) - end - - -- Set alias. - if Alias then - self.alias=tostring(Alias) - else - self.alias="UNHCR" - if self.coalition then - if self.coalition==coalition.side.RED then - self.alias="Red CTLD" - elseif self.coalition==coalition.side.BLUE then - self.alias="Blue CTLD" + + -- Set alias. + if Alias then + self.alias = tostring( Alias ) + else + self.alias = "UNHCR" + if self.coalition then + if self.coalition == coalition.side.RED then + self.alias = "Red CTLD" + elseif self.coalition == coalition.side.BLUE then + self.alias = "Blue CTLD" + end end end - end - - -- Set some string id for output to DCS.log file. - self.lid=string.format("%s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown") - - -- Start State. - self:SetStartState("Stopped") - -- Add FSM transitions. - -- From State --> Event --> To State + -- Set some string id for output to DCS.log file. + self.lid = string.format( "%s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName( self.coalition ) or "unknown" ) + + -- Start State. + self:SetStartState( "Stopped" ) + + -- Add FSM transitions. + -- From State --> Event --> To State self:AddTransition("Stopped", "Start", "Running") -- Start FSM. self:AddTransition("*", "Status", "*") -- CTLD status update. - self:AddTransition("*", "TroopsPickedUp", "*") -- CTLD pickup event. - self:AddTransition("*", "TroopsExtracted", "*") -- CTLD extract event. - self:AddTransition("*", "CratesPickedUp", "*") -- CTLD pickup event. - self:AddTransition("*", "TroopsDeployed", "*") -- CTLD deploy event. - self:AddTransition("*", "TroopsRTB", "*") -- CTLD deploy event. - self:AddTransition("*", "CratesDropped", "*") -- CTLD deploy event. - self:AddTransition("*", "CratesBuild", "*") -- CTLD build event. - self:AddTransition("*", "CratesRepaired", "*") -- CTLD repair event. - self:AddTransition("*", "Load", "*") -- CTLD load event. - self:AddTransition("*", "Save", "*") -- CTLD save event. + self:AddTransition("*", "TroopsPickedUp", "*") -- CTLD pickup event. + self:AddTransition("*", "TroopsExtracted", "*") -- CTLD extract event. + self:AddTransition("*", "CratesPickedUp", "*") -- CTLD pickup event. + self:AddTransition("*", "TroopsDeployed", "*") -- CTLD deploy event. + self:AddTransition("*", "TroopsRTB", "*") -- CTLD deploy event. + self:AddTransition("*", "CratesDropped", "*") -- CTLD deploy event. + self:AddTransition("*", "CratesBuild", "*") -- CTLD build event. + self:AddTransition("*", "CratesRepaired", "*") -- CTLD repair event. + self:AddTransition("*", "Load", "*") -- CTLD load event. + self:AddTransition("*", "Save", "*") -- CTLD save event. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. - - -- tables - self.PilotGroups ={} - self.CtldUnits = {} - - -- Beacons - self.FreeVHFFrequencies = {} - self.FreeUHFFrequencies = {} - self.FreeFMFrequencies = {} - self.UsedVHFFrequencies = {} - self.UsedUHFFrequencies = {} - self.UsedFMFrequencies = {} - - -- radio beacons - self.RadioSound = "beacon.ogg" - - -- zones stuff - self.pickupZones = {} - self.dropOffZones = {} - self.wpZones = {} - self.shipZones = {} - - -- Cargo - self.Cargo_Crates = {} - self.Cargo_Troops = {} - self.Cargo_Statics = {} - self.Loaded_Cargo = {} - self.Spawned_Crates = {} - self.Spawned_Cargo = {} - self.MenusDone = {} - self.DroppedTroops = {} - self.DroppedCrates = {} - self.CargoCounter = 0 - self.CrateCounter = 0 - self.TroopCounter = 0 - - -- added engineering - self.Engineers = 0 -- #number use as counter - self.EngineersInField = {} -- #table holds #CTLD_ENGINEERING objects - self.EngineerSearch = 2000 -- #number search distance for crates to build or repair - - -- setup - self.CrateDistance = 35 -- list/load crates in this radius - self.ExtractFactor = 3.33 -- factor for troops extraction, i.e. CrateDistance * Extractfactor - self.prefixes = Prefixes or {"Cargoheli"} - --self.I({prefixes = self.prefixes}) - self.useprefix = true - - self.maximumHoverHeight = 15 - self.minimumHoverHeight = 4 - self.forcehoverload = true - self.hoverautoloading = true - self.dropcratesanywhere = false -- #1570 - - self.smokedistance = 2000 - self.movetroopstowpzone = true - self.movetroopsdistance = 5000 - - -- added support Hercules Mod - self.enableHercules = false - self.HercMinAngels = 165 -- for troop/cargo drop via chute - self.HercMaxAngels = 2000 -- for troop/cargo drop via chute - self.HercMaxSpeed = 77 -- 280 kph or 150kn eq 77 mps - - -- message suppression - self.suppressmessages = false - - -- time to repair a unit/group - self.repairtime = 300 - - -- place spawned crates in front of aircraft - self.placeCratesAhead = false - - -- country of crates spawned - self.cratecountry = country.id.GERMANY - - -- for opening doors - self.pilotmustopendoors = false - - if self.coalition == coalition.side.RED then - self.cratecountry = country.id.RUSSIA - end - - -- load and save dropped TROOPS - self.enableLoadSave = false - self.filepath = nil - self.saveinterval = 600 - self.eventoninject = true - - local AliaS = string.gsub(self.alias," ","_") - self.filename = string.format("CTLD_%s_Persist.csv",AliaS) - - -- allow re-pickup crates - self.allowcratepickupagain = true - - -- slingload - self.enableslingload = false - - -- Smokes and Flares - self.SmokeColor = SMOKECOLOR.Red - self.FlareColor = FLARECOLOR.Red - - for i=1,100 do - math.random() - end - - self:_GenerateVHFrequencies() - self:_GenerateUHFrequencies() - self:_GenerateFMFrequencies() - - ------------------------ - --- Pseudo Functions --- - ------------------------ - - --- Triggers the FSM event "Start". Starts the CTLD. Initializes parameters and starts event handlers. - -- @function [parent=#CTLD] Start - -- @param #CTLD self - --- Triggers the FSM event "Start" after a delay. Starts the CTLD. Initializes parameters and starts event handlers. - -- @function [parent=#CTLD] __Start - -- @param #CTLD self - -- @param #number delay Delay in seconds. + -- tables + self.PilotGroups = {} + self.CtldUnits = {} - --- Triggers the FSM event "Stop". Stops the CTLD and all its event handlers. - -- @param #CTLD self - - --- Triggers the FSM event "Stop" after a delay. Stops the CTLD and all its event handlers. - -- @function [parent=#CTLD] __Stop - -- @param #CTLD self - -- @param #number delay Delay in seconds. - - --- Triggers the FSM event "Status". - -- @function [parent=#CTLD] Status - -- @param #CTLD self - - --- Triggers the FSM event "Status" after a delay. - -- @function [parent=#CTLD] __Status - -- @param #CTLD self - -- @param #number delay Delay in seconds. - - --- Triggers the FSM event "Load". - -- @function [parent=#CTLD] Load - -- @param #CTLD self - - --- Triggers the FSM event "Load" after a delay. - -- @function [parent=#CTLD] __Load - -- @param #CTLD self - -- @param #number delay Delay in seconds. - - --- Triggers the FSM event "Save". - -- @function [parent=#CTLD] Load - -- @param #CTLD self - - --- Triggers the FSM event "Save" after a delay. - -- @function [parent=#CTLD] __Save - -- @param #CTLD self - -- @param #number delay Delay in seconds. - - --- FSM Function OnAfterTroopsPickedUp. - -- @function [parent=#CTLD] OnAfterTroopsPickedUp - -- @param #CTLD self - -- @param #string From State. - -- @param #string Event Trigger. - -- @param #string To State. - -- @param Wrapper.Group#GROUP Group Group Object. - -- @param Wrapper.Unit#UNIT Unit Unit Object. - -- @param #CTLD_CARGO Cargo Cargo troops. - -- @return #CTLD self - - --- FSM Function OnAfterTroopsExtracted. - -- @function [parent=#CTLD] OnAfterTroopsExtracted - -- @param #CTLD self - -- @param #string From State. - -- @param #string Event Trigger. - -- @param #string To State. - -- @param Wrapper.Group#GROUP Group Group Object. - -- @param Wrapper.Unit#UNIT Unit Unit Object. - -- @param #CTLD_CARGO Cargo Cargo troops. - -- @return #CTLD self - - --- FSM Function OnAfterCratesPickedUp. - -- @function [parent=#CTLD] OnAfterCratesPickedUp - -- @param #CTLD self - -- @param #string From State . - -- @param #string Event Trigger. - -- @param #string To State. - -- @param Wrapper.Group#GROUP Group Group Object. - -- @param Wrapper.Unit#UNIT Unit Unit Object. - -- @param #CTLD_CARGO Cargo Cargo crate. - -- @return #CTLD self - - --- FSM Function OnAfterTroopsDeployed. - -- @function [parent=#CTLD] OnAfterTroopsDeployed - -- @param #CTLD self - -- @param #string From State. - -- @param #string Event Trigger. - -- @param #string To State. - -- @param Wrapper.Group#GROUP Group Group Object. - -- @param Wrapper.Unit#UNIT Unit Unit Object. - -- @param Wrapper.Group#GROUP Troops Troops #GROUP Object. - -- @return #CTLD self - - --- FSM Function OnAfterCratesDropped. - -- @function [parent=#CTLD] OnAfterCratesDropped - -- @param #CTLD self - -- @param #string From State. - -- @param #string Event Trigger. - -- @param #string To State. - -- @param Wrapper.Group#GROUP Group Group Object. - -- @param Wrapper.Unit#UNIT Unit Unit Object. - -- @param #table Cargotable Table of #CTLD_CARGO objects dropped. - -- @return #CTLD self - - --- FSM Function OnAfterCratesBuild. - -- @function [parent=#CTLD] OnAfterCratesBuild - -- @param #CTLD self - -- @param #string From State. - -- @param #string Event Trigger. - -- @param #string To State. - -- @param Wrapper.Group#GROUP Group Group Object. - -- @param Wrapper.Unit#UNIT Unit Unit Object. - -- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB build. - -- @return #CTLD self - - --- FSM Function OnAfterCratesRepaired. - -- @function [parent=#CTLD] OnAfterCratesRepaired - -- @param #CTLD self - -- @param #string From State. - -- @param #string Event Trigger. - -- @param #string To State. - -- @param Wrapper.Group#GROUP Group Group Object. - -- @param Wrapper.Unit#UNIT Unit Unit Object. - -- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB repaired. - -- @return #CTLD self - - --- FSM Function OnAfterTroopsRTB. - -- @function [parent=#CTLD] OnAfterTroopsRTB - -- @param #CTLD self - -- @param #string From State. - -- @param #string Event Trigger. - -- @param #string To State. - -- @param Wrapper.Group#GROUP Group Group Object. - -- @param Wrapper.Unit#UNIT Unit Unit Object. - - --- FSM Function OnAfterLoad. - -- @function [parent=#CTLD] OnAfterLoad - -- @param #CTLD self - -- @param #string From From state. - -- @param #string Event Event. - -- @param #string To To state. - -- @param #string path (Optional) Path where the file is located. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized. - -- @param #string filename (Optional) File name for loading. Default is "CTLD__Persist.csv". - - --- FSM Function OnAfterSave. - -- @function [parent=#CTLD] OnAfterSave - -- @param #CTLD self - -- @param #string From From state. - -- @param #string Event Event. - -- @param #string To To state. - -- @param #string path (Optional) Path where the file is saved. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized. - -- @param #string filename (Optional) File name for saving. Default is "CTLD__Persist.csv". - - return self -end - -------------------------------------------------------------------- --- Helper and User Functions -------------------------------------------------------------------- - ---- (Internal) Function to get capabilities of a chopper --- @param #CTLD self --- @param Wrapper.Unit#UNIT Unit The unit --- @return #table Capabilities Table of caps -function CTLD:_GetUnitCapabilities(Unit) - self:T(self.lid .. " _GetUnitCapabilities") - local _unit = Unit -- Wrapper.Unit#UNIT - local unittype = _unit:GetTypeName() - local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities - if not capabilities or capabilities == {} then - -- e.g. ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0}, - capabilities = {} - capabilities.troops = false - capabilities.crates = false - capabilities.cratelimit = 0 - capabilities.trooplimit = 0 - capabilities.type = "generic" - capabilities.length = 20 - end - return capabilities -end - - ---- (Internal) Function to generate valid UHF Frequencies --- @param #CTLD self -function CTLD:_GenerateUHFrequencies() - self:T(self.lid .. " _GenerateUHFrequencies") + -- Beacons + self.FreeVHFFrequencies = {} self.FreeUHFFrequencies = {} - self.FreeUHFFrequencies = UTILS.GenerateUHFrequencies() - return self -end + self.FreeFMFrequencies = {} + self.UsedVHFFrequencies = {} + self.UsedUHFFrequencies = {} + self.UsedFMFrequencies = {} ---- (Internal) Function to generate valid FM Frequencies --- @param #CTLD self -function CTLD:_GenerateFMFrequencies() - self:T(self.lid .. " _GenerateFMrequencies") + -- radio beacons + self.RadioSound = "beacon.ogg" + + -- zones stuff + self.pickupZones = {} + self.dropOffZones = {} + self.wpZones = {} + self.shipZones = {} + + -- Cargo + self.Cargo_Crates = {} + self.Cargo_Troops = {} + self.Cargo_Statics = {} + self.Loaded_Cargo = {} + self.Spawned_Crates = {} + self.Spawned_Cargo = {} + self.MenusDone = {} + self.DroppedTroops = {} + self.DroppedCrates = {} + self.CargoCounter = 0 + self.CrateCounter = 0 + self.TroopCounter = 0 + + -- added engineering + self.Engineers = 0 -- #number use as counter + self.EngineersInField = {} -- #table holds #CTLD_ENGINEERING objects + self.EngineerSearch = 2000 -- #number search distance for crates to build or repair + + -- setup + self.CrateDistance = 35 -- list/load crates in this radius + self.ExtractFactor = 3.33 -- factor for troops extraction, i.e. CrateDistance * Extractfactor + self.prefixes = Prefixes or { "Cargoheli" } + -- self.I({prefixes = self.prefixes}) + self.useprefix = true + + self.maximumHoverHeight = 15 + self.minimumHoverHeight = 4 + self.forcehoverload = true + self.hoverautoloading = true + self.dropcratesanywhere = false -- #1570 + + self.smokedistance = 2000 + self.movetroopstowpzone = true + self.movetroopsdistance = 5000 + + -- added support Hercules Mod + self.enableHercules = false + self.HercMinAngels = 165 -- for troop/cargo drop via chute + self.HercMaxAngels = 2000 -- for troop/cargo drop via chute + self.HercMaxSpeed = 77 -- 280 kph or 150kn eq 77 mps + + -- message suppression + self.suppressmessages = false + + -- time to repair a unit/group + self.repairtime = 300 + + -- place spawned crates in front of aircraft + self.placeCratesAhead = false + + -- country of crates spawned + self.cratecountry = country.id.GERMANY + + -- for opening doors + self.pilotmustopendoors = false + + if self.coalition == coalition.side.RED then + self.cratecountry = country.id.RUSSIA + end + + -- load and save dropped TROOPS + self.enableLoadSave = false + self.filepath = nil + self.saveinterval = 600 + self.eventoninject = true + + local AliaS = string.gsub( self.alias, " ", "_" ) + self.filename = string.format( "CTLD_%s_Persist.csv", AliaS ) + + -- allow re-pickup crates + self.allowcratepickupagain = true + + -- slingload + self.enableslingload = false + + -- Smokes and Flares + self.SmokeColor = SMOKECOLOR.Red + self.FlareColor = FLARECOLOR.Red + + for i = 1, 100 do + math.random() + end + + self:_GenerateVHFrequencies() + self:_GenerateUHFrequencies() + self:_GenerateFMFrequencies() + + ------------------------ + --- Pseudo Functions --- + ------------------------ + + --- Triggers the FSM event "Start". Starts the CTLD. Initializes parameters and starts event handlers. + -- @function [parent=#CTLD] Start + -- @param #CTLD self + + --- Triggers the FSM event "Start" after a delay. Starts the CTLD. Initializes parameters and starts event handlers. + -- @function [parent=#CTLD] __Start + -- @param #CTLD self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Stop". Stops the CTLD and all its event handlers. + -- @param #CTLD self + + --- Triggers the FSM event "Stop" after a delay. Stops the CTLD and all its event handlers. + -- @function [parent=#CTLD] __Stop + -- @param #CTLD self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Status". + -- @function [parent=#CTLD] Status + -- @param #CTLD self + + --- Triggers the FSM event "Status" after a delay. + -- @function [parent=#CTLD] __Status + -- @param #CTLD self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Load". + -- @function [parent=#CTLD] Load + -- @param #CTLD self + + --- Triggers the FSM event "Load" after a delay. + -- @function [parent=#CTLD] __Load + -- @param #CTLD self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Save". + -- @function [parent=#CTLD] Load + -- @param #CTLD self + + --- Triggers the FSM event "Save" after a delay. + -- @function [parent=#CTLD] __Save + -- @param #CTLD self + -- @param #number delay Delay in seconds. + + --- FSM Function OnAfterTroopsPickedUp. + -- @function [parent=#CTLD] OnAfterTroopsPickedUp + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #CTLD_CARGO Cargo Cargo troops. + -- @return #CTLD self + + --- FSM Function OnAfterTroopsExtracted. + -- @function [parent=#CTLD] OnAfterTroopsExtracted + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #CTLD_CARGO Cargo Cargo troops. + -- @return #CTLD self + + --- FSM Function OnAfterCratesPickedUp. + -- @function [parent=#CTLD] OnAfterCratesPickedUp + -- @param #CTLD self + -- @param #string From State . + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #CTLD_CARGO Cargo Cargo crate. + -- @return #CTLD self + + --- FSM Function OnAfterTroopsDeployed. + -- @function [parent=#CTLD] OnAfterTroopsDeployed + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param Wrapper.Group#GROUP Troops Troops #GROUP Object. + -- @return #CTLD self + + --- FSM Function OnAfterCratesDropped. + -- @function [parent=#CTLD] OnAfterCratesDropped + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #table Cargotable Table of #CTLD_CARGO objects dropped. + -- @return #CTLD self + + --- FSM Function OnAfterCratesBuild. + -- @function [parent=#CTLD] OnAfterCratesBuild + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB build. + -- @return #CTLD self + + --- FSM Function OnAfterCratesRepaired. + -- @function [parent=#CTLD] OnAfterCratesRepaired + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB repaired. + -- @return #CTLD self + + --- FSM Function OnAfterTroopsRTB. + -- @function [parent=#CTLD] OnAfterTroopsRTB + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + + --- FSM Function OnAfterLoad. + -- @function [parent=#CTLD] OnAfterLoad + -- @param #CTLD self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string path (Optional) Path where the file is located. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized. + -- @param #string filename (Optional) File name for loading. Default is "CTLD__Persist.csv". + + --- FSM Function OnAfterSave. + -- @function [parent=#CTLD] OnAfterSave + -- @param #CTLD self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string path (Optional) Path where the file is saved. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized. + -- @param #string filename (Optional) File name for saving. Default is "CTLD__Persist.csv". + + return self + end + + ------------------------------------------------------------------- + -- Helper and User Functions + ------------------------------------------------------------------- + + --- (Internal) Function to get capabilities of a chopper + -- @param #CTLD self + -- @param Wrapper.Unit#UNIT Unit The unit + -- @return #table Capabilities Table of caps + function CTLD:_GetUnitCapabilities( Unit ) + self:T( self.lid .. " _GetUnitCapabilities" ) + local _unit = Unit -- Wrapper.Unit#UNIT + local unittype = _unit:GetTypeName() + local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + if not capabilities or capabilities == {} then + -- e.g. ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0}, + capabilities = {} + capabilities.troops = false + capabilities.crates = false + capabilities.cratelimit = 0 + capabilities.trooplimit = 0 + capabilities.type = "generic" + capabilities.length = 20 + end + return capabilities + end + + --- (Internal) Function to generate valid UHF Frequencies + -- @param #CTLD self + function CTLD:_GenerateUHFrequencies() + self:T( self.lid .. " _GenerateUHFrequencies" ) + self.FreeUHFFrequencies = {} + self.FreeUHFFrequencies = UTILS.GenerateUHFrequencies() + return self + end + + --- (Internal) Function to generate valid FM Frequencies + -- @param #CTLD self + function CTLD:_GenerateFMFrequencies() + self:T( self.lid .. " _GenerateFMrequencies" ) self.FreeFMFrequencies = {} self.FreeFMFrequencies = UTILS.GenerateFMFrequencies() return self -end - ---- (Internal) Populate table with available VHF beacon frequencies. --- @param #CTLD self -function CTLD:_GenerateVHFrequencies() - self:T(self.lid .. " _GenerateVHFrequencies") - self.FreeVHFFrequencies = {} - self.UsedVHFFrequencies = {} - self.FreeVHFFrequencies = UTILS.GenerateVHFrequencies() - return self -end - ---- (Internal) Event handler function --- @param #CTLD self --- @param Core.Event#EVENTDATA EventData -function CTLD:_EventHandler(EventData) - self:T(string.format("%s Event = %d",self.lid, EventData.id)) - local event = EventData -- Core.Event#EVENTDATA - if event.id == EVENTS.PlayerEnterAircraft or event.id == EVENTS.PlayerEnterUnit then - local _coalition = event.IniCoalition - if _coalition ~= self.coalition then - return --ignore! - end - -- check is Helicopter - local _unit = event.IniUnit - local _group = event.IniGroup - if _unit:IsHelicopter() or _group:IsHelicopter() then - local unitname = event.IniUnitName or "none" - self.Loaded_Cargo[unitname] = nil - self:_RefreshF10Menus() - end - -- Herc support - --self:T_unit:GetTypeName()) - if _unit:GetTypeName() == "Hercules" and self.enableHercules then - self.Loaded_Cargo[unitname] = nil - self:_RefreshF10Menus() - end - return - elseif event.id == EVENTS.PlayerLeaveUnit then - -- remove from pilot table - local unitname = event.IniUnitName or "none" - self.CtldUnits[unitname] = nil - self.Loaded_Cargo[unitname] = nil end - return self -end ---- (Internal) Function to message a group. --- @param #CTLD self --- @param #string Text The text to display. --- @param #number Time Number of seconds to display the message. --- @param #boolean Clearscreen Clear screen or not. --- @param Wrapper.Group#GROUP Group The group receiving the message. -function CTLD:_SendMessage(Text, Time, Clearscreen, Group) - self:T(self.lid .. " _SendMessage") - if not self.suppressmessages then - local m = MESSAGE:New(Text,Time,"CTLD",Clearscreen):ToGroup(Group) - end - return self -end - ---- (Internal) Function to load troops into a heli. --- @param #CTLD self --- @param Wrapper.Group#GROUP Group --- @param Wrapper.Unit#UNIT Unit --- @param #CTLD_CARGO Cargotype -function CTLD:_LoadTroops(Group, Unit, Cargotype) - self:T(self.lid .. " _LoadTroops") - -- check if we have stock - local instock = Cargotype:GetStock() - local cgoname = Cargotype:GetName() - local cgotype = Cargotype:GetType() - if type(instock) == "number" and tonumber(instock) <= 0 and tonumber(instock) ~= -1 then - -- nothing left over - self:_SendMessage(string.format("Sorry, all %s are gone!", cgoname), 10, false, Group) + --- (Internal) Populate table with available VHF beacon frequencies. + -- @param #CTLD self + function CTLD:_GenerateVHFrequencies() + self:T( self.lid .. " _GenerateVHFrequencies" ) + self.FreeVHFFrequencies = {} + self.UsedVHFFrequencies = {} + self.FreeVHFFrequencies = UTILS.GenerateVHFrequencies() return self end - -- landed or hovering over load zone? - local grounded = not self:IsUnitInAir(Unit) - local hoverload = self:CanHoverLoad(Unit) - --local dooropen = UTILS.IsLoadingDoorOpen(Unit:GetName()) and self.pilotmustopendoors - -- check if we are in LOAD zone - local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) - if not inzone then - inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) - end - if not inzone then - self:_SendMessage("You are not close enough to a logistics zone!", 10, false, Group) - if not self.debug then return self end - elseif not grounded and not hoverload then - self:_SendMessage("You need to land or hover in position to load!", 10, false, Group) - if not self.debug then return self end - elseif self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then - self:_SendMessage("You need to open the door(s) to load troops!", 10, false, Group) - if not self.debug then return self end - end - -- load troops into heli - local group = Group -- Wrapper.Group#GROUP - local unit = Unit -- Wrapper.Unit#UNIT - local unitname = unit:GetName() - local cargotype = Cargotype -- #CTLD_CARGO - local cratename = cargotype:GetName() -- #string - -- see if this heli can load troops - local unittype = unit:GetTypeName() - local capabilities = self:_GetUnitCapabilities(Unit) - local cantroops = capabilities.troops -- #boolean - local trooplimit = capabilities.trooplimit -- #number - local troopsize = cargotype:GetCratesNeeded() -- #number - -- have we loaded stuff already? - local numberonboard = 0 - local loaded = {} - if self.Loaded_Cargo[unitname] then - loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo - numberonboard = loaded.Troopsloaded or 0 - else - loaded = {} -- #CTLD.LoadedCargo - loaded.Troopsloaded = 0 - loaded.Cratesloaded = 0 - loaded.Cargo = {} - end - if troopsize + numberonboard > trooplimit then - self:_SendMessage("Sorry, we\'re crammed already!", 10, false, Group) - return - else - self.CargoCounter = self.CargoCounter + 1 - local loadcargotype = CTLD_CARGO:New(self.CargoCounter, Cargotype.Name, Cargotype.Templates, cgotype, true, true, Cargotype.CratesNeeded,nil,nil,Cargotype.PerCrateMass) - self:T({cargotype=loadcargotype}) - loaded.Troopsloaded = loaded.Troopsloaded + troopsize - table.insert(loaded.Cargo,loadcargotype) - self.Loaded_Cargo[unitname] = loaded - self:_SendMessage("Troops boarded!", 10, false, Group) - self:__TroopsPickedUp(1,Group, Unit, Cargotype) - self:_UpdateUnitCargoMass(Unit) - Cargotype:RemoveStock() - end - return self -end -function CTLD:_FindRepairNearby(Group, Unit, Repairtype) - self:T(self.lid .. " _FindRepairNearby") + --- (Internal) Event handler function + -- @param #CTLD self + -- @param Core.Event#EVENTDATA EventData + function CTLD:_EventHandler( EventData ) + self:T( string.format( "%s Event = %d", self.lid, EventData.id ) ) + local event = EventData -- Core.Event#EVENTDATA + if event.id == EVENTS.PlayerEnterAircraft or event.id == EVENTS.PlayerEnterUnit then + local _coalition = event.IniCoalition + if _coalition ~= self.coalition then + return -- ignore! + end + -- check is Helicopter + local _unit = event.IniUnit + local _group = event.IniGroup + if _unit:IsHelicopter() or _group:IsHelicopter() then + local unitname = event.IniUnitName or "none" + self.Loaded_Cargo[unitname] = nil + self:_RefreshF10Menus() + end + -- Herc support + -- self:T_unit:GetTypeName()) + if _unit:GetTypeName() == "Hercules" and self.enableHercules then + self.Loaded_Cargo[unitname] = nil + self:_RefreshF10Menus() + end + return + elseif event.id == EVENTS.PlayerLeaveUnit then + -- remove from pilot table + local unitname = event.IniUnitName or "none" + self.CtldUnits[unitname] = nil + self.Loaded_Cargo[unitname] = nil + end + return self + end + + --- (Internal) Function to message a group. + -- @param #CTLD self + -- @param #string Text The text to display. + -- @param #number Time Number of seconds to display the message. + -- @param #boolean Clearscreen Clear screen or not. + -- @param Wrapper.Group#GROUP Group The group receiving the message. + function CTLD:_SendMessage( Text, Time, Clearscreen, Group ) + self:T( self.lid .. " _SendMessage" ) + if not self.suppressmessages then + local m = MESSAGE:New( Text, Time, "CTLD", Clearscreen ):ToGroup( Group ) + end + return self + end + + --- (Internal) Function to load troops into a heli. + -- @param #CTLD self + -- @param Wrapper.Group#GROUP Group + -- @param Wrapper.Unit#UNIT Unit + -- @param #CTLD_CARGO Cargotype + function CTLD:_LoadTroops( Group, Unit, Cargotype ) + self:T( self.lid .. " _LoadTroops" ) + -- check if we have stock + local instock = Cargotype:GetStock() + local cgoname = Cargotype:GetName() + local cgotype = Cargotype:GetType() + if type( instock ) == "number" and tonumber( instock ) <= 0 and tonumber( instock ) ~= -1 then + -- nothing left over + self:_SendMessage( string.format( "Sorry, all %s are gone!", cgoname ), 10, false, Group ) + return self + end + -- landed or hovering over load zone? + local grounded = not self:IsUnitInAir( Unit ) + local hoverload = self:CanHoverLoad( Unit ) + -- local dooropen = UTILS.IsLoadingDoorOpen(Unit:GetName()) and self.pilotmustopendoors + -- check if we are in LOAD zone + local inzone, zonename, zone, distance = self:IsUnitInZone( Unit, CTLD.CargoZoneType.LOAD ) + if not inzone then + inzone, zonename, zone, distance = self:IsUnitInZone( Unit, CTLD.CargoZoneType.SHIP ) + end + if not inzone then + self:_SendMessage( "You are not close enough to a logistics zone!", 10, false, Group ) + if not self.debug then + return self + end + elseif not grounded and not hoverload then + self:_SendMessage( "You need to land or hover in position to load!", 10, false, Group ) + if not self.debug then + return self + end + elseif self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen( Unit:GetName() ) then + self:_SendMessage( "You need to open the door(s) to load troops!", 10, false, Group ) + if not self.debug then + return self + end + end + -- load troops into heli + local group = Group -- Wrapper.Group#GROUP + local unit = Unit -- Wrapper.Unit#UNIT + local unitname = unit:GetName() + local cargotype = Cargotype -- #CTLD_CARGO + local cratename = cargotype:GetName() -- #string + -- see if this heli can load troops + local unittype = unit:GetTypeName() + local capabilities = self:_GetUnitCapabilities( Unit ) + local cantroops = capabilities.troops -- #boolean + local trooplimit = capabilities.trooplimit -- #number + local troopsize = cargotype:GetCratesNeeded() -- #number + -- have we loaded stuff already? + local numberonboard = 0 + local loaded = {} + if self.Loaded_Cargo[unitname] then + loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo + numberonboard = loaded.Troopsloaded or 0 + else + loaded = {} -- #CTLD.LoadedCargo + loaded.Troopsloaded = 0 + loaded.Cratesloaded = 0 + loaded.Cargo = {} + end + if troopsize + numberonboard > trooplimit then + self:_SendMessage( "Sorry, we\'re crammed already!", 10, false, Group ) + return + else + self.CargoCounter = self.CargoCounter + 1 + local loadcargotype = CTLD_CARGO:New( self.CargoCounter, Cargotype.Name, Cargotype.Templates, cgotype, true, true, Cargotype.CratesNeeded, nil, nil, Cargotype.PerCrateMass ) + self:T( { cargotype = loadcargotype } ) + loaded.Troopsloaded = loaded.Troopsloaded + troopsize + table.insert( loaded.Cargo, loadcargotype ) + self.Loaded_Cargo[unitname] = loaded + self:_SendMessage( "Troops boarded!", 10, false, Group ) + self:__TroopsPickedUp( 1, Group, Unit, Cargotype ) + self:_UpdateUnitCargoMass( Unit ) + Cargotype:RemoveStock() + end + return self + end + + function CTLD:_FindRepairNearby( Group, Unit, Repairtype ) + self:T( self.lid .. " _FindRepairNearby" ) local unitcoord = Unit:GetCoordinate() - + -- find nearest group of deployed groups local nearestGroup = nil local nearestGroupIndex = -1 local nearestDistance = 10000 - for k,v in pairs(self.DroppedTroops) do - local distance = self:_GetDistance(v:GetCoordinate(),unitcoord) - local unit = v:GetUnit(1) -- Wrapper.Unit#UNIT + for k, v in pairs( self.DroppedTroops ) do + local distance = self:_GetDistance( v:GetCoordinate(), unitcoord ) + local unit = v:GetUnit( 1 ) -- Wrapper.Unit#UNIT local desc = unit:GetDesc() or nil - --self:I({desc = desc.attributes}) + -- self:I({desc = desc.attributes}) if distance < nearestDistance and distance ~= -1 and not desc.attributes.Infantry then nearestGroup = v nearestGroupIndex = k @@ -1526,126 +1532,136 @@ function CTLD:_FindRepairNearby(Group, Unit, Repairtype) -- found one and matching distance? if nearestGroup == nil or nearestDistance > self.EngineerSearch then - self:_SendMessage("No unit close enough to repair!", 10, false, Group) + self:_SendMessage( "No unit close enough to repair!", 10, false, Group ) return nil, nil end - + local groupname = nearestGroup:GetName() - + -- helper to find matching template - local function matchstring(String,Table) + local function matchstring( String, Table ) local match = false - if type(Table) == "table" then - for _,_name in pairs (Table) do - if string.find(String,_name) then + if type( Table ) == "table" then + for _, _name in pairs( Table ) do + if string.find( String, _name ) then match = true break end end else - if type(String) == "string" then - if string.find(String,Table) then match = true end + if type( String ) == "string" then + if string.find( String, Table ) then + match = true + end end - end + end return match end - + -- walk through generics and find matching type local Cargotype = nil - for k,v in pairs(self.Cargo_Crates) do - --self:I({groupname,v.Templates}) - if matchstring(groupname,v.Templates) and matchstring(groupname,Repairtype) then + for k, v in pairs( self.Cargo_Crates ) do + -- self:I({groupname,v.Templates}) + if matchstring( groupname, v.Templates ) and matchstring( groupname, Repairtype ) then Cargotype = v -- #CTLD_CARGO break end end if Cargotype == nil then - --self:_SendMessage("Can't find a matching group for " .. Repairtype, 10, false, Group) + -- self:_SendMessage("Can't find a matching group for " .. Repairtype, 10, false, Group) return nil, nil else return nearestGroup, Cargotype end - -end ---- (Internal) Function to repair an object. --- @param #CTLD self --- @param Wrapper.Group#GROUP Group --- @param Wrapper.Unit#UNIT Unit --- @param #table Crates Table of #CTLD_CARGO objects near the unit. --- @param #CTLD.Buildable Build Table build object. --- @param #number Number Number of objects in Crates (found) to limit search. --- @param #boolean Engineering If true it is an Engineering repair. -function CTLD:_RepairObjectFromCrates(Group,Unit,Crates,Build,Number,Engineering) - self:T(self.lid .. " _RepairObjectFromCrates") - local build = Build -- -- #CTLD.Buildable - --self:I({Build=Build}) - local Repairtype = build.Template -- #string - local NearestGroup, CargoType = self:_FindRepairNearby(Group,Unit,Repairtype) -- Wrapper.Group#GROUP, #CTLD_CARGO - --self:I({Repairtype=Repairtype, CargoType=CargoType, NearestGroup=NearestGroup}) - if NearestGroup ~= nil then - if self.repairtime < 2 then self.repairtime = 30 end -- noob catch - if not Engineering then - self:_SendMessage(string.format("Repair started using %s taking %d secs", build.Name, self.repairtime), 10, false, Group) - end - -- now we can build .... - --NearestGroup:Destroy(false) - local name = CargoType:GetName() - local required = CargoType:GetCratesNeeded() - local template = CargoType:GetTemplates() - local ctype = CargoType:GetType() - local object = {} -- #CTLD.Buildable - object.Name = CargoType:GetName() - object.Required = required - object.Found = required - object.Template = template - object.CanBuild = true - object.Type = ctype -- #CTLD_CARGO.Enum - self:_CleanUpCrates(Crates,Build,Number) - local desttimer = TIMER:New(function() NearestGroup:Destroy(false) end, self) - desttimer:Start(self.repairtime - 1) - local buildtimer = TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,object,true,NearestGroup:GetCoordinate()) - buildtimer:Start(self.repairtime) - --self:_BuildObjectFromCrates(Group,Unit,object) - else - if not Engineering then - self:_SendMessage("Can't repair this unit with " .. build.Name, 10, false, Group) - else - self:T("Can't repair this unit with " .. build.Name) - end end - return self -end + + --- (Internal) Function to repair an object. + -- @param #CTLD self + -- @param Wrapper.Group#GROUP Group + -- @param Wrapper.Unit#UNIT Unit + -- @param #table Crates Table of #CTLD_CARGO objects near the unit. + -- @param #CTLD.Buildable Build Table build object. + -- @param #number Number Number of objects in Crates (found) to limit search. + -- @param #boolean Engineering If true it is an Engineering repair. + function CTLD:_RepairObjectFromCrates( Group, Unit, Crates, Build, Number, Engineering ) + self:T( self.lid .. " _RepairObjectFromCrates" ) + local build = Build -- -- #CTLD.Buildable + -- self:I({Build=Build}) + local Repairtype = build.Template -- #string + local NearestGroup, CargoType = self:_FindRepairNearby( Group, Unit, Repairtype ) -- Wrapper.Group#GROUP, #CTLD_CARGO + -- self:I({Repairtype=Repairtype, CargoType=CargoType, NearestGroup=NearestGroup}) + if NearestGroup ~= nil then + if self.repairtime < 2 then + self.repairtime = 30 + end -- noob catch + if not Engineering then + self:_SendMessage( string.format( "Repair started using %s taking %d secs", build.Name, self.repairtime ), 10, false, Group ) + end + -- now we can build .... + -- NearestGroup:Destroy(false) + local name = CargoType:GetName() + local required = CargoType:GetCratesNeeded() + local template = CargoType:GetTemplates() + local ctype = CargoType:GetType() + local object = {} -- #CTLD.Buildable + object.Name = CargoType:GetName() + object.Required = required + object.Found = required + object.Template = template + object.CanBuild = true + object.Type = ctype -- #CTLD_CARGO.Enum + self:_CleanUpCrates( Crates, Build, Number ) + local desttimer = TIMER:New( function() + NearestGroup:Destroy( false ) + end, self ) + desttimer:Start( self.repairtime - 1 ) + local buildtimer = TIMER:New( self._BuildObjectFromCrates, self, Group, Unit, object, true, NearestGroup:GetCoordinate() ) + buildtimer:Start( self.repairtime ) + -- self:_BuildObjectFromCrates(Group,Unit,object) + else + if not Engineering then + self:_SendMessage( "Can't repair this unit with " .. build.Name, 10, false, Group ) + else + self:T( "Can't repair this unit with " .. build.Name ) + end + end + return self + end --- (Internal) Function to extract (load from the field) troops into a heli. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit - function CTLD:_ExtractTroops(Group, Unit) -- #1574 thanks to @bbirchnz! - self:T(self.lid .. " _ExtractTroops") + function CTLD:_ExtractTroops( Group, Unit ) -- #1574 thanks to @bbirchnz! + self:T( self.lid .. " _ExtractTroops" ) -- landed or hovering over load zone? - local grounded = not self:IsUnitInAir(Unit) - local hoverload = self:CanHoverLoad(Unit) - + local grounded = not self:IsUnitInAir( Unit ) + local hoverload = self:CanHoverLoad( Unit ) + if not grounded and not hoverload then - self:_SendMessage("You need to land or hover in position to load!", 10, false, Group) - if not self.debug then return self end + self:_SendMessage( "You need to land or hover in position to load!", 10, false, Group ) + if not self.debug then + return self + end end - if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then - self:_SendMessage("You need to open the door(s) to extract troops!", 10, false, Group) - if not self.debug then return self end + if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen( Unit:GetName() ) then + self:_SendMessage( "You need to open the door(s) to extract troops!", 10, false, Group ) + if not self.debug then + return self + end end -- load troops into heli local unit = Unit -- Wrapper.Unit#UNIT local unitname = unit:GetName() -- see if this heli can load troops local unittype = unit:GetTypeName() - local capabilities = self:_GetUnitCapabilities(Unit) + local capabilities = self:_GetUnitCapabilities( Unit ) local cantroops = capabilities.troops -- #boolean local trooplimit = capabilities.trooplimit -- #number local unitcoord = unit:GetCoordinate() - + -- find nearest group of deployed troops local nearestGroup = nil local nearestGroupIndex = -1 @@ -1653,44 +1669,48 @@ end local nearestList = {} local distancekeys = {} local extractdistance = self.CrateDistance * self.ExtractFactor - for k,v in pairs(self.DroppedTroops) do - local distance = self:_GetDistance(v:GetCoordinate(),unitcoord) + for k, v in pairs( self.DroppedTroops ) do + local distance = self:_GetDistance( v:GetCoordinate(), unitcoord ) if distance <= extractdistance and distance ~= -1 then nearestGroup = v nearestGroupIndex = k nearestDistance = distance - table.insert(nearestList, math.floor(distance), v) - distancekeys[#distancekeys+1] = math.floor(distance) + table.insert( nearestList, math.floor( distance ), v ) + distancekeys[#distancekeys + 1] = math.floor( distance ) end end - + if nearestGroup == nil or nearestDistance > extractdistance then - self:_SendMessage("No units close enough to extract!", 10, false, Group) + self:_SendMessage( "No units close enough to extract!", 10, false, Group ) return self end - + -- sort reference keys - table.sort(distancekeys) - + table.sort( distancekeys ) + local secondarygroups = {} - - for i=1,#distancekeys do + + for i = 1, #distancekeys do local nearestGroup = nearestList[distancekeys[i]] - -- find matching cargo type - local groupType = string.match(nearestGroup:GetName(), "(.+)-(.+)$") + -- find matching cargo type + local groupType = string.match( nearestGroup:GetName(), "(.+)-(.+)$" ) local Cargotype = nil - for k,v in pairs(self.Cargo_Troops) do + for k, v in pairs( self.Cargo_Troops ) do local comparison = "" - if type(v.Templates) == "string" then comparison = v.Templates else comparison = v.Templates[1] end + if type( v.Templates ) == "string" then + comparison = v.Templates + else + comparison = v.Templates[1] + end if comparison == groupType then Cargotype = v break end end if Cargotype == nil then - self:_SendMessage("Can't onboard " .. groupType, 10, false, Group) + self:_SendMessage( "Can't onboard " .. groupType, 10, false, Group ) else - + local troopsize = Cargotype:GetCratesNeeded() -- #number -- have we loaded stuff already? local numberonboard = 0 @@ -1705,38 +1725,38 @@ end loaded.Cargo = {} end if troopsize + numberonboard > trooplimit then - self:_SendMessage("Sorry, we\'re crammed already!", 10, false, Group) - --return self + self:_SendMessage( "Sorry, we\'re crammed already!", 10, false, Group ) + -- return self else self.CargoCounter = self.CargoCounter + 1 - local loadcargotype = CTLD_CARGO:New(self.CargoCounter, Cargotype.Name, Cargotype.Templates, Cargotype.CargoType, true, true, Cargotype.CratesNeeded,nil,nil,Cargotype.PerCrateMass) - self:T({cargotype=loadcargotype}) + local loadcargotype = CTLD_CARGO:New( self.CargoCounter, Cargotype.Name, Cargotype.Templates, Cargotype.CargoType, true, true, Cargotype.CratesNeeded, nil, nil, Cargotype.PerCrateMass ) + self:T( { cargotype = loadcargotype } ) loaded.Troopsloaded = loaded.Troopsloaded + troopsize - table.insert(loaded.Cargo,loadcargotype) + table.insert( loaded.Cargo, loadcargotype ) self.Loaded_Cargo[unitname] = loaded - self:_SendMessage("Troops boarded!", 10, false, Group) - self:_UpdateUnitCargoMass(Unit) - self:__TroopsExtracted(1,Group, Unit, nearestGroup) - + self:_SendMessage( "Troops boarded!", 10, false, Group ) + self:_UpdateUnitCargoMass( Unit ) + self:__TroopsExtracted( 1, Group, Unit, nearestGroup ) + -- clean up: - --table.remove(self.DroppedTroops, nearestGroupIndex) - if type(Cargotype.Templates) == "table" and Cargotype.Templates[2] then - --self:I("*****This CargoType has multiple templates: "..Cargotype.Name) - for _,_key in pairs (Cargotype.Templates) do - table.insert(secondarygroups,_key) + -- table.remove(self.DroppedTroops, nearestGroupIndex) + if type( Cargotype.Templates ) == "table" and Cargotype.Templates[2] then + -- self:I("*****This CargoType has multiple templates: "..Cargotype.Name) + for _, _key in pairs( Cargotype.Templates ) do + table.insert( secondarygroups, _key ) end end - nearestGroup:Destroy(false) + nearestGroup:Destroy( false ) end end end -- clean up secondary groups - for _,_name in pairs(secondarygroups) do - for _,_group in pairs(nearestList) do + for _, _name in pairs( secondarygroups ) do + for _, _group in pairs( nearestList ) do if _group and _group:IsAlive() then - local groupname = string.match(_group:GetName(), "(.+)-(.+)$") + local groupname = string.match( _group:GetName(), "(.+)-(.+)$" ) if _name == groupname then - _group:Destroy(false) + _group:Destroy( false ) end end end @@ -1744,1712 +1764,1734 @@ end self:CleanDroppedTroops() return self end - ---- (Internal) Function to spawn crates in front of the heli. --- @param #CTLD self --- @param Wrapper.Group#GROUP Group --- @param Wrapper.Unit#UNIT Unit --- @param #CTLD_CARGO Cargo --- @param #number number Number of crates to generate (for dropping) --- @param #boolean drop If true we\'re dropping from heli rather than loading. -function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) - self:T(self.lid .. " _GetCrates") - if not drop then - local cgoname = Cargo:GetName() - -- check if we have stock - local instock = Cargo:GetStock() - if type(instock) == "number" and tonumber(instock) <= 0 and tonumber(instock) ~= -1 then - -- nothing left over - self:_SendMessage(string.format("Sorry, we ran out of %s", cgoname), 10, false, Group) - return self - end - end - -- check if we are in LOAD zone - local inzone = false - local drop = drop or false - local ship = nil - local width = 20 - if not drop then - inzone = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) - if not inzone then - inzone, ship, zone, distance, width = self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) - end - else - if self.dropcratesanywhere then -- #1570 - inzone = true - else - inzone = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) - end - end - - if not inzone then - self:_SendMessage("You are not close enough to a logistics zone!", 10, false, Group) - if not self.debug then return self end - end - -- avoid crate spam - local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities - local canloadcratesno = capabilities.cratelimit - local loaddist = self.CrateDistance or 35 - local nearcrates, numbernearby = self:_FindCratesNearby(Group,Unit,loaddist) - if numbernearby >= canloadcratesno and not drop then - self:_SendMessage("There are enough crates nearby already! Take care of those first!", 10, false, Group) - return self - end - -- spawn crates in front of helicopter - local IsHerc = self:IsHercules(Unit) -- Herc - local cargotype = Cargo -- Ops.CTLD#CTLD_CARGO - local number = number or cargotype:GetCratesNeeded() --#number - local cratesneeded = cargotype:GetCratesNeeded() --#number - local cratename = cargotype:GetName() - local cratetemplate = "Container"-- #string - local cgotype = cargotype:GetType() - local cgomass = cargotype:GetMass() - local isstatic = false - if cgotype == CTLD_CARGO.Enum.STATIC then - cratetemplate = cargotype:GetTemplates() - isstatic = true - end - -- get position and heading of heli - local position = Unit:GetCoordinate() - local heading = Unit:GetHeading() + 1 - local height = Unit:GetHeight() - local droppedcargo = {} - local cratedistance = 0 - local rheading = 0 - local angleOffNose = 0 - local addon = 0 - if IsHerc then - -- spawn behind the Herc - addon = 180 - end - -- loop crates needed - for i=1,number do - local cratealias = string.format("%s-%d", cratetemplate, math.random(1,100000)) - if not self.placeCratesAhead then - cratedistance = (i-1)*2.5 + capabilities.length - if cratedistance > self.CrateDistance then cratedistance = self.CrateDistance end - -- altered heading logic - -- DONE: right standard deviation? - rheading = UTILS.RandomGaussian(0,30,-90,90,100) - rheading = math.fmod((heading + rheading + addon), 360) - else - local initialSpacing = IsHerc and 16 or 12 -- initial spacing of the first crates - local crateSpacing = 4 -- further spacing of remaining crates - local lateralSpacing = 4 -- lateral spacing of crates - local nrSideBySideCrates = 3 -- number of crates that are placed side-by-side - - if cratesneeded == 1 then - -- single crate needed spawns straight ahead - cratedistance = initialSpacing - rheading = heading - else - if (i - 1) % nrSideBySideCrates == 0 then - cratedistance = i == 1 and initialSpacing or cratedistance + crateSpacing - angleOffNose = math.ceil(math.deg(math.atan(lateralSpacing / cratedistance))) - rheading = heading - angleOffNose - else - rheading = rheading + angleOffNose - end + --- (Internal) Function to spawn crates in front of the heli. + -- @param #CTLD self + -- @param Wrapper.Group#GROUP Group + -- @param Wrapper.Unit#UNIT Unit + -- @param #CTLD_CARGO Cargo + -- @param #number number Number of crates to generate (for dropping) + -- @param #boolean drop If true we're dropping from heli rather than loading. + function CTLD:_GetCrates( Group, Unit, Cargo, number, drop ) + self:T( self.lid .. " _GetCrates" ) + if not drop then + local cgoname = Cargo:GetName() + -- check if we have stock + local instock = Cargo:GetStock() + if type( instock ) == "number" and tonumber( instock ) <= 0 and tonumber( instock ) ~= -1 then + -- nothing left over + self:_SendMessage( string.format( "Sorry, we ran out of %s", cgoname ), 10, false, Group ) + return self end end - local cratecoord = position:Translate(cratedistance,rheading) - local cratevec2 = cratecoord:GetVec2() - self.CrateCounter = self.CrateCounter + 1 + -- check if we are in LOAD zone + local inzone = false + local drop = drop or false + local ship = nil + local width = 20 + if not drop then + inzone = self:IsUnitInZone( Unit, CTLD.CargoZoneType.LOAD ) + if not inzone then + inzone, ship, zone, distance, width = self:IsUnitInZone( Unit, CTLD.CargoZoneType.SHIP ) + end + else + if self.dropcratesanywhere then -- #1570 + inzone = true + else + inzone = self:IsUnitInZone( Unit, CTLD.CargoZoneType.DROP ) + end + end + + if not inzone then + self:_SendMessage( "You are not close enough to a logistics zone!", 10, false, Group ) + if not self.debug then + return self + end + end + + -- avoid crate spam + local capabilities = self:_GetUnitCapabilities( Unit ) -- #CTLD.UnitCapabilities + local canloadcratesno = capabilities.cratelimit + local loaddist = self.CrateDistance or 35 + local nearcrates, numbernearby = self:_FindCratesNearby( Group, Unit, loaddist ) + if numbernearby >= canloadcratesno and not drop then + self:_SendMessage( "There are enough crates nearby already! Take care of those first!", 10, false, Group ) + return self + end + -- spawn crates in front of helicopter + local IsHerc = self:IsHercules( Unit ) -- Herc + local cargotype = Cargo -- Ops.CTLD#CTLD_CARGO + local number = number or cargotype:GetCratesNeeded() -- #number + local cratesneeded = cargotype:GetCratesNeeded() -- #number + local cratename = cargotype:GetName() + local cratetemplate = "Container" -- #string + local cgotype = cargotype:GetType() + local cgomass = cargotype:GetMass() + local isstatic = false + if cgotype == CTLD_CARGO.Enum.STATIC then + cratetemplate = cargotype:GetTemplates() + isstatic = true + end + -- get position and heading of heli + local position = Unit:GetCoordinate() + local heading = Unit:GetHeading() + 1 + local height = Unit:GetHeight() + local droppedcargo = {} + local cratedistance = 0 + local rheading = 0 + local angleOffNose = 0 + local addon = 0 + if IsHerc then + -- spawn behind the Herc + addon = 180 + end + -- loop crates needed + for i = 1, number do + local cratealias = string.format( "%s-%d", cratetemplate, math.random( 1, 100000 ) ) + if not self.placeCratesAhead then + cratedistance = (i - 1) * 2.5 + capabilities.length + if cratedistance > self.CrateDistance then + cratedistance = self.CrateDistance + end + -- altered heading logic + -- DONE: right standard deviation? + rheading = UTILS.RandomGaussian( 0, 30, -90, 90, 100 ) + rheading = math.fmod( (heading + rheading + addon), 360 ) + else + local initialSpacing = IsHerc and 16 or 12 -- initial spacing of the first crates + local crateSpacing = 4 -- further spacing of remaining crates + local lateralSpacing = 4 -- lateral spacing of crates + local nrSideBySideCrates = 3 -- number of crates that are placed side-by-side + + if cratesneeded == 1 then + -- single crate needed spawns straight ahead + cratedistance = initialSpacing + rheading = heading + else + if (i - 1) % nrSideBySideCrates == 0 then + cratedistance = i == 1 and initialSpacing or cratedistance + crateSpacing + angleOffNose = math.ceil( math.deg( math.atan( lateralSpacing / cratedistance ) ) ) + rheading = heading - angleOffNose + else + rheading = rheading + angleOffNose + end + end + end + local cratecoord = position:Translate( cratedistance, rheading ) + local cratevec2 = cratecoord:GetVec2() + self.CrateCounter = self.CrateCounter + 1 + local basetype = "container_cargo" + if isstatic then + basetype = cratetemplate + end + if type( ship ) == "string" then + self:T( "Spawning on ship " .. ship ) + local Ship = UNIT:FindByName( ship ) + local shipcoord = Ship:GetCoordinate() + local unitcoord = Unit:GetCoordinate() + local dist = shipcoord:Get2DDistance( unitcoord ) + dist = dist - (20 + math.random( 1, 10 )) + local width = width / 2 + local Offy = math.random( -width, width ) + self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType( basetype, "Cargos", self.cratecountry ) -- :InitCoordinate(cratecoord) + :InitCargoMass( cgomass ) + :InitCargo( self.enableslingload ) + :InitLinkToUnit( Ship, dist, Offy, 0 ) + :Spawn( 270, cratealias ) + else + self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType( basetype, "Cargos", self.cratecountry ) + :InitCoordinate( cratecoord ) + :InitCargoMass( cgomass ) + :InitCargo( self.enableslingload ) -- :InitLinkToUnit(Unit,OffsetX,OffsetY,OffsetAngle) + :Spawn( 270, cratealias ) + end + local templ = cargotype:GetTemplates() + local sorte = cargotype:GetType() + self.CargoCounter = self.CargoCounter + 1 + local realcargo = nil + if drop then + realcargo = CTLD_CARGO:New( self.CargoCounter, cratename, templ, sorte, true, false, cratesneeded, self.Spawned_Crates[self.CrateCounter], true, cargotype.PerCrateMass ) + table.insert( droppedcargo, realcargo ) + else + realcargo = CTLD_CARGO:New( self.CargoCounter, cratename, templ, sorte, false, false, cratesneeded, self.Spawned_Crates[self.CrateCounter], true, cargotype.PerCrateMass ) + Cargo:RemoveStock() + end + table.insert( self.Spawned_Cargo, realcargo ) + end + local text = string.format( "Crates for %s have been positioned near you!", cratename ) + if drop then + text = string.format( "Crates for %s have been dropped!", cratename ) + self:__CratesDropped( 1, Group, Unit, droppedcargo ) + end + self:_SendMessage( text, 10, false, Group ) + return self + end + + --- (Internal) Inject crates and static cargo objects. + -- @param #CTLD self + -- @param Core.Zone#ZONE Zone Zone to spawn in. + -- @param #CTLD_CARGO Cargo The cargo type to spawn. + -- @param #boolean RandomCoord Randomize coordinate. + -- @return #CTLD self + function CTLD:InjectStatics( Zone, Cargo, RandomCoord ) + self:T( self.lid .. " InjectStatics" ) + local cratecoord = Zone:GetCoordinate() + if RandomCoord then + cratecoord = Zone:GetRandomCoordinate( 5, 20 ) + end + local surface = cratecoord:GetSurfaceType() + if surface == land.SurfaceType.WATER then + return self + end + local cargotype = Cargo -- #CTLD_CARGO + -- local number = 1 + local cratesneeded = cargotype:GetCratesNeeded() -- #number + local cratetemplate = "Container" -- #string + local cratealias = string.format( "%s-%d", cratetemplate, math.random( 1, 100000 ) ) + local cratename = cargotype:GetName() + local cgotype = cargotype:GetType() + local cgomass = cargotype:GetMass() + local isstatic = false + if cgotype == CTLD_CARGO.Enum.STATIC then + cratetemplate = cargotype:GetTemplates() + isstatic = true + end local basetype = "container_cargo" if isstatic then basetype = cratetemplate end - if type(ship) == "string" then - self:T("Spawning on ship "..ship) - local Ship = UNIT:FindByName(ship) - local shipcoord = Ship:GetCoordinate() - local unitcoord = Unit:GetCoordinate() - local dist = shipcoord:Get2DDistance(unitcoord) - dist = dist - (20 + math.random(1,10)) - local width = width / 2 - local Offy = math.random(-width,width) - self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) - --:InitCoordinate(cratecoord) - :InitCargoMass(cgomass) - :InitCargo(self.enableslingload) - :InitLinkToUnit(Ship,dist,Offy,0) - :Spawn(270,cratealias) - else - self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) - :InitCoordinate(cratecoord) - :InitCargoMass(cgomass) - :InitCargo(self.enableslingload) - --:InitLinkToUnit(Unit,OffsetX,OffsetY,OffsetAngle) - :Spawn(270,cratealias) - end + self.CrateCounter = self.CrateCounter + 1 + self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType( basetype, "Cargos", self.cratecountry ) + :InitCargoMass( cgomass ) + :InitCargo( self.enableslingload ) + :InitCoordinate( cratecoord ) + :Spawn( 270, cratealias ) local templ = cargotype:GetTemplates() local sorte = cargotype:GetType() self.CargoCounter = self.CargoCounter + 1 - local realcargo = nil - if drop then - realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,true,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass) - table.insert(droppedcargo,realcargo) - else - realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,false,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass) - Cargo:RemoveStock() - end - table.insert(self.Spawned_Cargo, realcargo) - end - local text = string.format("Crates for %s have been positioned near you!",cratename) - if drop then - text = string.format("Crates for %s have been dropped!",cratename) - self:__CratesDropped(1, Group, Unit, droppedcargo) - end - self:_SendMessage(text, 10, false, Group) - return self -end - ---- (Internal) Inject crates and static cargo objects. --- @param #CTLD self --- @param Core.Zone#ZONE Zone Zone to spawn in. --- @param #CTLD_CARGO Cargo The cargo type to spawn. --- @param #boolean RandomCoord Randomize coordinate. --- @return #CTLD self -function CTLD:InjectStatics(Zone, Cargo, RandomCoord) - self:T(self.lid .. " InjectStatics") - local cratecoord = Zone:GetCoordinate() - if RandomCoord then - cratecoord = Zone:GetRandomCoordinate(5,20) - end - local surface = cratecoord:GetSurfaceType() - if surface == land.SurfaceType.WATER then + cargotype.Positionable = self.Spawned_Crates[self.CrateCounter] + table.insert( self.Spawned_Cargo, cargotype ) return self end - local cargotype = Cargo -- #CTLD_CARGO - --local number = 1 - local cratesneeded = cargotype:GetCratesNeeded() --#number - local cratetemplate = "Container"-- #string - local cratealias = string.format("%s-%d", cratetemplate, math.random(1,100000)) - local cratename = cargotype:GetName() - local cgotype = cargotype:GetType() - local cgomass = cargotype:GetMass() - local isstatic = false - if cgotype == CTLD_CARGO.Enum.STATIC then - cratetemplate = cargotype:GetTemplates() - isstatic = true - end - local basetype = "container_cargo" - if isstatic then - basetype = cratetemplate - end - self.CrateCounter = self.CrateCounter + 1 - self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) - :InitCargoMass(cgomass) - :InitCargo(self.enableslingload) - :InitCoordinate(cratecoord) - :Spawn(270,cratealias) - local templ = cargotype:GetTemplates() - local sorte = cargotype:GetType() - self.CargoCounter = self.CargoCounter + 1 - cargotype.Positionable = self.Spawned_Crates[self.CrateCounter] - table.insert(self.Spawned_Cargo, cargotype) - return self -end ---- (User) Inject static cargo objects. --- @param #CTLD self --- @param Core.Zone#ZONE Zone Zone to spawn in. Will be a somewhat random coordinate. --- @param #string Template Unit(!) name of the static cargo object to be used as template. --- @param #number Mass Mass of the static in kg. --- @return #CTLD self -function CTLD:InjectStaticFromTemplate(Zone, Template, Mass) - self:T(self.lid .. " InjectStaticFromTemplate") - local cargotype = self:GetStaticsCargoFromTemplate(Template,Mass) -- #CTLD_CARGO - self:InjectStatics(Zone,cargotype,true) - return self -end + --- (User) Inject static cargo objects. + -- @param #CTLD self + -- @param Core.Zone#ZONE Zone Zone to spawn in. Will be a somewhat random coordinate. + -- @param #string Template Unit(!) name of the static cargo object to be used as template. + -- @param #number Mass Mass of the static in kg. + -- @return #CTLD self + function CTLD:InjectStaticFromTemplate( Zone, Template, Mass ) + self:T( self.lid .. " InjectStaticFromTemplate" ) + local cargotype = self:GetStaticsCargoFromTemplate( Template, Mass ) -- #CTLD_CARGO + self:InjectStatics( Zone, cargotype, true ) + return self + end ---- (Internal) Function to find and list nearby crates. --- @param #CTLD self --- @param Wrapper.Group#GROUP Group --- @param Wrapper.Unit#UNIT Unit --- @return #CTLD self -function CTLD:_ListCratesNearby( _group, _unit) - self:T(self.lid .. " _ListCratesNearby") - local finddist = self.CrateDistance or 35 - local crates,number = self:_FindCratesNearby(_group,_unit, finddist) -- #table - if number > 0 then - local text = REPORT:New("Crates Found Nearby:") - text:Add("------------------------------------------------------------") - for _,_entry in pairs (crates) do - local entry = _entry -- #CTLD_CARGO - local name = entry:GetName() --#string - local dropped = entry:WasDropped() - if dropped then - text:Add(string.format("Dropped crate for %s, %dkg",name, entry.PerCrateMass)) - else - text:Add(string.format("Crate for %s, %dkg",name, entry.PerCrateMass)) + --- (Internal) Function to find and list nearby crates. + -- @param #CTLD self + -- @param Wrapper.Group#GROUP Group + -- @param Wrapper.Unit#UNIT Unit + -- @return #CTLD self + function CTLD:_ListCratesNearby( _group, _unit ) + self:T( self.lid .. " _ListCratesNearby" ) + local finddist = self.CrateDistance or 35 + local crates, number = self:_FindCratesNearby( _group, _unit, finddist ) -- #table + if number > 0 then + local text = REPORT:New( "Crates Found Nearby:" ) + text:Add( "------------------------------------------------------------" ) + for _, _entry in pairs( crates ) do + local entry = _entry -- #CTLD_CARGO + local name = entry:GetName() -- #string + local dropped = entry:WasDropped() + if dropped then + text:Add( string.format( "Dropped crate for %s, %dkg", name, entry.PerCrateMass ) ) + else + text:Add( string.format( "Crate for %s, %dkg", name, entry.PerCrateMass ) ) + end end - end - if text:GetCount() == 1 then - text:Add(" N O N E") - end - text:Add("------------------------------------------------------------") - self:_SendMessage(text:Text(), 30, true, _group) - else - self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist), 10, false, _group) - end - return self -end - ---- (Internal) Return distance in meters between two coordinates. --- @param #CTLD self --- @param Core.Point#COORDINATE _point1 Coordinate one --- @param Core.Point#COORDINATE _point2 Coordinate two --- @return #number Distance in meters -function CTLD:_GetDistance(_point1, _point2) - self:T(self.lid .. " _GetDistance") - if _point1 and _point2 then - local distance1 = _point1:Get2DDistance(_point2) - local distance2 = _point1:DistanceFromPointVec2(_point2) - --self:I({dist1=distance1, dist2=distance2}) - if distance1 and type(distance1) == "number" then - return distance1 - elseif distance2 and type(distance2) == "number" then - return distance2 + if text:GetCount() == 1 then + text:Add( " N O N E" ) + end + text:Add( "------------------------------------------------------------" ) + self:_SendMessage( text:Text(), 30, true, _group ) else - self:E("*****Cannot calculate distance!") - self:E({_point1,_point2}) + self:_SendMessage( string.format( "No (loadable) crates within %d meters!", finddist ), 10, false, _group ) + end + return self + end + + --- (Internal) Return distance in meters between two coordinates. + -- @param #CTLD self + -- @param Core.Point#COORDINATE _point1 Coordinate one + -- @param Core.Point#COORDINATE _point2 Coordinate two + -- @return #number Distance in meters + function CTLD:_GetDistance( _point1, _point2 ) + self:T( self.lid .. " _GetDistance" ) + if _point1 and _point2 then + local distance1 = _point1:Get2DDistance( _point2 ) + local distance2 = _point1:DistanceFromPointVec2( _point2 ) + -- self:I({dist1=distance1, dist2=distance2}) + if distance1 and type( distance1 ) == "number" then + return distance1 + elseif distance2 and type( distance2 ) == "number" then + return distance2 + else + self:E( "*****Cannot calculate distance!" ) + self:E( { _point1, _point2 } ) + return -1 + end + else + self:E( "******Cannot calculate distance!" ) + self:E( { _point1, _point2 } ) return -1 end - else - self:E("******Cannot calculate distance!") - self:E({_point1,_point2}) - return -1 end -end ---- (Internal) Function to find and return nearby crates. --- @param #CTLD self --- @param Wrapper.Group#GROUP _group Group --- @param Wrapper.Unit#UNIT _unit Unit --- @param #number _dist Distance --- @return #table Table of crates --- @return #number Number Number of crates found -function CTLD:_FindCratesNearby( _group, _unit, _dist) - self:T(self.lid .. " _FindCratesNearby") - local finddist = _dist - local location = _group:GetCoordinate() - local existingcrates = self.Spawned_Cargo -- #table - -- cycle - local index = 0 - local found = {} - for _,_cargoobject in pairs (existingcrates) do - local cargo = _cargoobject -- #CTLD_CARGO - local static = cargo:GetPositionable() -- Wrapper.Static#STATIC -- crates - local staticid = cargo:GetID() - if static and static:IsAlive() then - local staticpos = static:GetCoordinate() - local distance = self:_GetDistance(location,staticpos) - if distance <= finddist and static then - index = index + 1 - table.insert(found, staticid, cargo) + --- (Internal) Function to find and return nearby crates. + -- @param #CTLD self + -- @param Wrapper.Group#GROUP _group Group + -- @param Wrapper.Unit#UNIT _unit Unit + -- @param #number _dist Distance + -- @return #table Table of crates + -- @return #number Number Number of crates found + function CTLD:_FindCratesNearby( _group, _unit, _dist ) + self:T( self.lid .. " _FindCratesNearby" ) + local finddist = _dist + local location = _group:GetCoordinate() + local existingcrates = self.Spawned_Cargo -- #table + -- cycle + local index = 0 + local found = {} + for _, _cargoobject in pairs( existingcrates ) do + local cargo = _cargoobject -- #CTLD_CARGO + local static = cargo:GetPositionable() -- Wrapper.Static#STATIC -- crates + local staticid = cargo:GetID() + if static and static:IsAlive() then + local staticpos = static:GetCoordinate() + local distance = self:_GetDistance( location, staticpos ) + if distance <= finddist and static then + index = index + 1 + table.insert( found, staticid, cargo ) + end end end + return found, index end - return found, index -end ---- (Internal) Function to get and load nearby crates. --- @param #CTLD self --- @param Wrapper.Group#GROUP Group --- @param Wrapper.Unit#UNIT Unit --- @return #CTLD self -function CTLD:_LoadCratesNearby(Group, Unit) - self:T(self.lid .. " _LoadCratesNearby") + --- (Internal) Function to get and load nearby crates. + -- @param #CTLD self + -- @param Wrapper.Group#GROUP Group + -- @param Wrapper.Unit#UNIT Unit + -- @return #CTLD self + function CTLD:_LoadCratesNearby( Group, Unit ) + self:T( self.lid .. " _LoadCratesNearby" ) -- load crates into heli - local group = Group -- Wrapper.Group#GROUP - local unit = Unit -- Wrapper.Unit#UNIT - local unitname = unit:GetName() - -- see if this heli can load crates - local unittype = unit:GetTypeName() - local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities - --local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities - local cancrates = capabilities.crates -- #boolean - local cratelimit = capabilities.cratelimit -- #number - local grounded = not self:IsUnitInAir(Unit) - local canhoverload = self:CanHoverLoad(Unit) - --- cases ------------------------------- - -- Chopper can\'t do crates - bark & return - -- Chopper can do crates - - -- --> hover if forcedhover or bark and return - -- --> hover or land if not forcedhover - ----------------------------------------- - if not cancrates then - self:_SendMessage("Sorry this chopper cannot carry crates!", 10, false, Group) - elseif self.forcehoverload and not canhoverload then - self:_SendMessage("Hover over the crates to pick them up!", 10, false, Group) - elseif not grounded and not canhoverload then - self:_SendMessage("Land or hover over the crates to pick them up!", 10, false, Group) - else - -- have we loaded stuff already? - local numberonboard = 0 - local massonboard = 0 - local loaded = {} - if self.Loaded_Cargo[unitname] then - loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo - numberonboard = loaded.Cratesloaded or 0 + local group = Group -- Wrapper.Group#GROUP + local unit = Unit -- Wrapper.Unit#UNIT + local unitname = unit:GetName() + -- see if this heli can load crates + local unittype = unit:GetTypeName() + local capabilities = self:_GetUnitCapabilities( Unit ) -- #CTLD.UnitCapabilities + -- local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + local cancrates = capabilities.crates -- #boolean + local cratelimit = capabilities.cratelimit -- #number + local grounded = not self:IsUnitInAir( Unit ) + local canhoverload = self:CanHoverLoad( Unit ) + --- cases ------------------------------- + -- Chopper can't do crates - bark & return + -- Chopper can do crates - + -- --> hover if forcedhover or bark and return + -- --> hover or land if not forcedhover + ----------------------------------------- + if not cancrates then + self:_SendMessage( "Sorry this chopper cannot carry crates!", 10, false, Group ) + elseif self.forcehoverload and not canhoverload then + self:_SendMessage( "Hover over the crates to pick them up!", 10, false, Group ) + elseif not grounded and not canhoverload then + self:_SendMessage( "Land or hover over the crates to pick them up!", 10, false, Group ) else - loaded = {} -- #CTLD.LoadedCargo - loaded.Troopsloaded = 0 - loaded.Cratesloaded = 0 - loaded.Cargo = {} - end - -- get nearby crates - local finddist = self.CrateDistance or 35 - local nearcrates,number = self:_FindCratesNearby(Group,Unit,finddist) -- #table - if number == 0 and self.hoverautoloading then - return self -- exit - elseif number == 0 then - self:_SendMessage("Sorry no loadable crates nearby!", 10, false, Group) - return self -- exit - elseif numberonboard == cratelimit then - self:_SendMessage("Sorry no fully loaded!", 10, false, Group) - return self -- exit - else - -- go through crates and load - local capacity = cratelimit - numberonboard - local crateidsloaded = {} - local loops = 0 - while loaded.Cratesloaded < cratelimit and loops < number do - loops = loops + 1 - local crateind = 0 - -- get crate with largest index - for _ind,_crate in pairs (nearcrates) do - if self.allowcratepickupagain then - if _crate:GetID() > crateind and _crate.Positionable ~= nil then - crateind = _crate:GetID() - end - else - if not _crate:HasMoved() and _crate:WasDropped() and _crate:GetID() > crateind then - crateind = _crate:GetID() + -- have we loaded stuff already? + local numberonboard = 0 + local massonboard = 0 + local loaded = {} + if self.Loaded_Cargo[unitname] then + loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo + numberonboard = loaded.Cratesloaded or 0 + else + loaded = {} -- #CTLD.LoadedCargo + loaded.Troopsloaded = 0 + loaded.Cratesloaded = 0 + loaded.Cargo = {} + end + -- get nearby crates + local finddist = self.CrateDistance or 35 + local nearcrates, number = self:_FindCratesNearby( Group, Unit, finddist ) -- #table + if number == 0 and self.hoverautoloading then + return self -- exit + elseif number == 0 then + self:_SendMessage( "Sorry no loadable crates nearby!", 10, false, Group ) + return self -- exit + elseif numberonboard == cratelimit then + self:_SendMessage( "Sorry no fully loaded!", 10, false, Group ) + return self -- exit + else + -- go through crates and load + local capacity = cratelimit - numberonboard + local crateidsloaded = {} + local loops = 0 + while loaded.Cratesloaded < cratelimit and loops < number do + loops = loops + 1 + local crateind = 0 + -- get crate with largest index + for _ind, _crate in pairs( nearcrates ) do + if self.allowcratepickupagain then + if _crate:GetID() > crateind and _crate.Positionable ~= nil then + crateind = _crate:GetID() + end + else + if not _crate:HasMoved() and _crate:WasDropped() and _crate:GetID() > crateind then + crateind = _crate:GetID() + end end end + -- load one if we found one + if crateind > 0 then + local crate = nearcrates[crateind] -- #CTLD_CARGO + loaded.Cratesloaded = loaded.Cratesloaded + 1 + crate:SetHasMoved( true ) + crate:SetWasDropped( false ) + table.insert( loaded.Cargo, crate ) + table.insert( crateidsloaded, crate:GetID() ) + -- destroy crate + crate:GetPositionable():Destroy( false ) + crate.Positionable = nil + self:_SendMessage( string.format( "Crate ID %d for %s loaded!", crate:GetID(), crate:GetName() ), 10, false, Group ) + table.remove( nearcrates, crate:GetID() ) + self:__CratesPickedUp( 1, Group, Unit, crate ) + end end - -- load one if we found one - if crateind > 0 then - local crate = nearcrates[crateind] -- #CTLD_CARGO - loaded.Cratesloaded = loaded.Cratesloaded + 1 - crate:SetHasMoved(true) - crate:SetWasDropped(false) - table.insert(loaded.Cargo, crate) - table.insert(crateidsloaded,crate:GetID()) - -- destroy crate - crate:GetPositionable():Destroy(false) - crate.Positionable = nil - self:_SendMessage(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()), 10, false, Group) - table.remove(nearcrates,crate:GetID()) - self:__CratesPickedUp(1, Group, Unit, crate) + self.Loaded_Cargo[unitname] = loaded + self:_UpdateUnitCargoMass( Unit ) + -- clean up real world crates + self:_CleanupTrackedCrates( crateidsloaded ) + end + end + return self + end + + --- (Internal) Function to clean up tracked cargo crates + function CTLD:_CleanupTrackedCrates( crateIdsToRemove ) + local existingcrates = self.Spawned_Cargo -- #table + local newexcrates = {} + for _, _crate in pairs( existingcrates ) do + local excrate = _crate -- #CTLD_CARGO + local ID = excrate:GetID() + local keep = true + for _, _ID in pairs( crateIdsToRemove ) do + if ID == _ID then + keep = false end end - self.Loaded_Cargo[unitname] = loaded - self:_UpdateUnitCargoMass(Unit) - -- clean up real world crates - self:_CleanupTrackedCrates(crateidsloaded) - end - end - return self -end - ---- (Internal) Function to clean up tracked cargo crates -function CTLD:_CleanupTrackedCrates(crateIdsToRemove) - local existingcrates = self.Spawned_Cargo -- #table - local newexcrates = {} - for _,_crate in pairs(existingcrates) do - local excrate = _crate -- #CTLD_CARGO - local ID = excrate:GetID() - local keep = true - for _,_ID in pairs(crateIdsToRemove) do - if ID == _ID then + -- remove destroyed crates here too + local static = _crate:GetPositionable() -- Wrapper.Static#STATIC -- crates + if not static or not static:IsAlive() then keep = false end - end - -- remove destroyed crates here too - local static = _crate:GetPositionable() -- Wrapper.Static#STATIC -- crates - if not static or not static:IsAlive() then - keep = false - end - if keep then - table.insert(newexcrates,_crate) - end - end - self.Spawned_Cargo = nil - self.Spawned_Cargo = newexcrates - return self -end - ---- (Internal) Function to get current loaded mass --- @param #CTLD self --- @param Wrapper.Unit#UNIT Unit --- @return #number mass in kgs -function CTLD:_GetUnitCargoMass(Unit) - self:T(self.lid .. " _GetUnitCargoMass") - local unitname = Unit:GetName() - local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo - local loadedmass = 0 -- #number - if self.Loaded_Cargo[unitname] then - local cargotable = loadedcargo.Cargo or {} -- #table - for _,_cargo in pairs(cargotable) do - local cargo = _cargo -- #CTLD_CARGO - local type = cargo:GetType() -- #CTLD_CARGO.Enum - if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and not cargo:WasDropped() then - loadedmass = loadedmass + (cargo.PerCrateMass * cargo:GetCratesNeeded()) - end - if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and not cargo:WasDropped() then - loadedmass = loadedmass + cargo.PerCrateMass + if keep then + table.insert( newexcrates, _crate ) end end + self.Spawned_Cargo = nil + self.Spawned_Cargo = newexcrates + return self end - return loadedmass -end ---- (Internal) Function to calculate and set Unit internal cargo mass --- @param #CTLD self --- @param Wrapper.Unit#UNIT Unit -function CTLD:_UpdateUnitCargoMass(Unit) - self:T(self.lid .. " _UpdateUnitCargoMass") - local calculatedMass = self:_GetUnitCargoMass(Unit) - Unit:SetUnitInternalCargo(calculatedMass) - --local report = REPORT:New("Loadmaster report") - --report:Add("Carrying " .. calculatedMass .. "Kg") - --self:_SendMessage(report:Text(),10,false,Unit:GetGroup()) - return self -end - ---- (Internal) Function to list loaded cargo. --- @param #CTLD self --- @param Wrapper.Group#GROUP Group --- @param Wrapper.Unit#UNIT Unit --- @return #CTLD self -function CTLD:_ListCargo(Group, Unit) - self:T(self.lid .. " _ListCargo") - local unitname = Unit:GetName() - local unittype = Unit:GetTypeName() - local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities - local trooplimit = capabilities.trooplimit -- #boolean - local cratelimit = capabilities.cratelimit -- #number - local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo - local loadedmass = self:_GetUnitCargoMass(Unit) -- #number - if self.Loaded_Cargo[unitname] then - local no_troops = loadedcargo.Troopsloaded or 0 - local no_crates = loadedcargo.Cratesloaded or 0 - local cargotable = loadedcargo.Cargo or {} -- #table - local report = REPORT:New("Transport Checkout Sheet") - report:Add("------------------------------------------------------------") - report:Add(string.format("Troops: %d(%d), Crates: %d(%d)",no_troops,trooplimit,no_crates,cratelimit)) - report:Add("------------------------------------------------------------") - report:Add(" -- TROOPS --") - for _,_cargo in pairs(cargotable) do - local cargo = _cargo -- #CTLD_CARGO - local type = cargo:GetType() -- #CTLD_CARGO.Enum - if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and (not cargo:WasDropped() or self.allowcratepickupagain) then - report:Add(string.format("Troop: %s size %d",cargo:GetName(),cargo:GetCratesNeeded())) - end - end - if report:GetCount() == 4 then - report:Add(" N O N E") - end - report:Add("------------------------------------------------------------") - report:Add(" -- CRATES --") - local cratecount = 0 - for _,_cargo in pairs(cargotable) do - local cargo = _cargo -- #CTLD_CARGO - local type = cargo:GetType() -- #CTLD_CARGO.Enum - if (type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS) and (not cargo:WasDropped() or self.allowcratepickupagain) then - report:Add(string.format("Crate: %s size 1",cargo:GetName())) - cratecount = cratecount + 1 - end - end - if cratecount == 0 then - report:Add(" N O N E") - end - report:Add("------------------------------------------------------------") - report:Add("Total Mass: ".. loadedmass .. " kg") - local text = report:Text() - self:_SendMessage(text, 30, true, Group) - else - self:_SendMessage(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d",trooplimit,cratelimit), 10, false, Group) - end - return self -end - ---- (Internal) Function to list loaded cargo. --- @param #CTLD self --- @param Wrapper.Group#GROUP Group --- @param Wrapper.Unit#UNIT Unit --- @return #CTLD self -function CTLD:_ListInventory(Group, Unit) - self:T(self.lid .. " _ListInventory") - local unitname = Unit:GetName() - local unittype = Unit:GetTypeName() - local cgotypes = self.Cargo_Crates - local trptypes = self.Cargo_Troops - local stctypes = self.Cargo_Statics - - local function countcargo(cgotable) - local counter = 0 - for _,_cgo in pairs(cgotable) do - counter = counter + 1 - end - return counter - end - - local crateno = countcargo(cgotypes) - local troopno = countcargo(trptypes) - local staticno = countcargo(stctypes) - - if (crateno > 0 or troopno > 0 or staticno > 0) then - - local report = REPORT:New("Inventory Sheet") - report:Add("------------------------------------------------------------") - report:Add(string.format("Troops: %d, Cratetypes: %d",troopno,crateno+staticno)) - report:Add("------------------------------------------------------------") - report:Add(" -- TROOPS --") - for _,_cargo in pairs(trptypes) do - local cargo = _cargo -- #CTLD_CARGO - local type = cargo:GetType() -- #CTLD_CARGO.Enum - if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and not cargo:WasDropped() then - local stockn = cargo:GetStock() - local stock = "none" - if stockn == -1 then - stock = "unlimited" - elseif stockn > 0 then - stock = tostring(stockn) - end - report:Add(string.format("Unit: %s | Soldiers: %d | Stock: %s",cargo:GetName(),cargo:GetCratesNeeded(),stock)) - end - end - if report:GetCount() == 4 then - report:Add(" N O N E") - end - report:Add("------------------------------------------------------------") - report:Add(" -- CRATES --") - local cratecount = 0 - for _,_cargo in pairs(cgotypes) do - local cargo = _cargo -- #CTLD_CARGO - local type = cargo:GetType() -- #CTLD_CARGO.Enum - if (type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS) and not cargo:WasDropped() then - local stockn = cargo:GetStock() - local stock = "none" - if stockn == -1 then - stock = "unlimited" - elseif stockn > 0 then - stock = tostring(stockn) - end - report:Add(string.format("Type: %s | Crates per Set: %d | Stock: %s",cargo:GetName(),cargo:GetCratesNeeded(),stock)) - cratecount = cratecount + 1 - end - end - -- Statics - for _,_cargo in pairs(stctypes) do - local cargo = _cargo -- #CTLD_CARGO - local type = cargo:GetType() -- #CTLD_CARGO.Enum - if (type == CTLD_CARGO.Enum.STATIC) and not cargo:WasDropped() then - local stockn = cargo:GetStock() - local stock = "none" - if stockn == -1 then - stock = "unlimited" - elseif stockn > 0 then - stock = tostring(stockn) - end - report:Add(string.format("Type: %s | Stock: %s",cargo:GetName(),stock)) - cratecount = cratecount + 1 - end - end - if cratecount == 0 then - report:Add(" N O N E") - end - local text = report:Text() - self:_SendMessage(text, 30, true, Group) - else - self:_SendMessage(string.format("Nothing in stock!"), 10, false, Group) - end - return self -end - ---- (Internal) Function to check if a unit is a Hercules C-130. --- @param #CTLD self --- @param Wrapper.Unit#UNIT Unit --- @return #boolean Outcome -function CTLD:IsHercules(Unit) - if Unit:GetTypeName() == "Hercules" then - return true - else - return false - end -end - ---- (Internal) Function to unload troops from heli. --- @param #CTLD self --- @param Wrapper.Group#GROUP Group --- @param Wrapper.Unit#UNIT Unit -function CTLD:_UnloadTroops(Group, Unit) - self:T(self.lid .. " _UnloadTroops") - -- check if we are in LOAD zone - local droppingatbase = false - local canunload = true - if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then - self:_SendMessage("You need to open the door(s) to unload troops!", 10, false, Group) - if not self.debug then return self end - end - local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) - if not inzone then - inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) - end - if inzone then - droppingatbase = true - end - -- check for hover unload - local hoverunload = self:IsCorrectHover(Unit) --if true we\'re hovering in parameters - local IsHerc = self:IsHercules(Unit) - if IsHerc then - -- no hover but airdrop here - hoverunload = self:IsCorrectFlightParameters(Unit) - end - -- check if we\'re landed - local grounded = not self:IsUnitInAir(Unit) - -- Get what we have loaded - local unitname = Unit:GetName() - if self.Loaded_Cargo[unitname] and (grounded or hoverunload) then - if not droppingatbase or self.debug then - local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo - -- looking for troops - local cargotable = loadedcargo.Cargo - for _,_cargo in pairs (cargotable) do + --- (Internal) Function to get current loaded mass + -- @param #CTLD self + -- @param Wrapper.Unit#UNIT Unit + -- @return #number mass in kgs + function CTLD:_GetUnitCargoMass( Unit ) + self:T( self.lid .. " _GetUnitCargoMass" ) + local unitname = Unit:GetName() + local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo + local loadedmass = 0 -- #number + if self.Loaded_Cargo[unitname] then + local cargotable = loadedcargo.Cargo or {} -- #table + for _, _cargo in pairs( cargotable ) do local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and not cargo:WasDropped() then - -- unload troops - local name = cargo:GetName() or "none" - local temptable = cargo:GetTemplates() or {} - local position = Group:GetCoordinate() - local zoneradius = 100 -- drop zone radius - local factor = 1 - if IsHerc then - factor = cargo:GetCratesNeeded() or 1 -- spread a bit more if airdropping - zoneradius = Unit:GetVelocityMPS() or 100 - end - local zone = ZONE_GROUP:New(string.format("Unload zone-%s",unitname),Group,zoneradius*factor) - local randomcoord = zone:GetRandomCoordinate(10,30*factor):GetVec2() - for _,_template in pairs(temptable) do - self.TroopCounter = self.TroopCounter + 1 - local alias = string.format("%s-%d", _template, math.random(1,100000)) - self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) - :InitRandomizeUnits(true,20,2) - :InitDelayOff() - :SpawnFromVec2(randomcoord) - if self.movetroopstowpzone and type ~= CTLD_CARGO.Enum.ENGINEERS then - self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) - end - end -- template loop - cargo:SetWasDropped(true) - -- engineering group? - --self:I("Dropped Troop Type: "..type) - if type == CTLD_CARGO.Enum.ENGINEERS then - self.Engineers = self.Engineers + 1 - local grpname = self.DroppedTroops[self.TroopCounter]:GetName() - self.EngineersInField[self.Engineers] = CTLD_ENGINEERING:New(name, grpname) - self:_SendMessage(string.format("Dropped Engineers %s into action!",name), 10, false, Group) - else - self:_SendMessage(string.format("Dropped Troops %s into action!",name), 10, false, Group) - end - self:__TroopsDeployed(1, Group, Unit, self.DroppedTroops[self.TroopCounter]) - end -- if type end - end -- cargotable loop - else -- droppingatbase - self:_SendMessage("Troops have returned to base!", 10, false, Group) - self:__TroopsRTB(1, Group, Unit) - end - -- cleanup load list - local loaded = {} -- #CTLD.LoadedCargo - loaded.Troopsloaded = 0 - loaded.Cratesloaded = 0 - loaded.Cargo = {} - local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo - local cargotable = loadedcargo.Cargo or {} - for _,_cargo in pairs (cargotable) do - local cargo = _cargo -- #CTLD_CARGO - local type = cargo:GetType() -- #CTLD_CARGO.Enum - local dropped = cargo:WasDropped() - if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and not dropped then - table.insert(loaded.Cargo,_cargo) - loaded.Cratesloaded = loaded.Cratesloaded + 1 - else - -- add troops back to stock - if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and droppingatbase then - -- find right generic type - local name = cargo:GetName() - local gentroops = self.Cargo_Troops - for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO - if _troop.Name == name then - local stock = _troop:GetStock() - -- avoid making unlimited stock limited - if stock and tonumber(stock) >= 0 then _troop:AddStock() end - end - end + loadedmass = loadedmass + (cargo.PerCrateMass * cargo:GetCratesNeeded()) + end + if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and not cargo:WasDropped() then + loadedmass = loadedmass + cargo.PerCrateMass end end end - self.Loaded_Cargo[unitname] = nil - self.Loaded_Cargo[unitname] = loaded - self:_UpdateUnitCargoMass(Unit) - else - if IsHerc then - self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) - else - self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) - end + return loadedmass end - return self -end ---- (Internal) Function to unload crates from heli. --- @param #CTLD self --- @param Wrapper.Group#GROUP Group --- @param Wrapper.Unit#UNIT Unit -function CTLD:_UnloadCrates(Group, Unit) - self:T(self.lid .. " _UnloadCrates") - - if not self.dropcratesanywhere then -- #1570 - -- check if we are in DROP zone - local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) + --- (Internal) Function to calculate and set Unit internal cargo mass + -- @param #CTLD self + -- @param Wrapper.Unit#UNIT Unit + function CTLD:_UpdateUnitCargoMass( Unit ) + self:T( self.lid .. " _UpdateUnitCargoMass" ) + local calculatedMass = self:_GetUnitCargoMass( Unit ) + Unit:SetUnitInternalCargo( calculatedMass ) + -- local report = REPORT:New("Loadmaster report") + -- report:Add("Carrying " .. calculatedMass .. "Kg") + -- self:_SendMessage(report:Text(),10,false,Unit:GetGroup()) + return self + end + + --- (Internal) Function to list loaded cargo. + -- @param #CTLD self + -- @param Wrapper.Group#GROUP Group + -- @param Wrapper.Unit#UNIT Unit + -- @return #CTLD self + function CTLD:_ListCargo( Group, Unit ) + self:T( self.lid .. " _ListCargo" ) + local unitname = Unit:GetName() + local unittype = Unit:GetTypeName() + local capabilities = self:_GetUnitCapabilities( Unit ) -- #CTLD.UnitCapabilities + local trooplimit = capabilities.trooplimit -- #boolean + local cratelimit = capabilities.cratelimit -- #number + local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo + local loadedmass = self:_GetUnitCargoMass( Unit ) -- #number + if self.Loaded_Cargo[unitname] then + local no_troops = loadedcargo.Troopsloaded or 0 + local no_crates = loadedcargo.Cratesloaded or 0 + local cargotable = loadedcargo.Cargo or {} -- #table + local report = REPORT:New( "Transport Checkout Sheet" ) + report:Add( "------------------------------------------------------------" ) + report:Add( string.format( "Troops: %d(%d), Crates: %d(%d)", no_troops, trooplimit, no_crates, cratelimit ) ) + report:Add( "------------------------------------------------------------" ) + report:Add( " -- TROOPS --" ) + for _, _cargo in pairs( cargotable ) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and (not cargo:WasDropped() or self.allowcratepickupagain) then + report:Add( string.format( "Troop: %s size %d", cargo:GetName(), cargo:GetCratesNeeded() ) ) + end + end + if report:GetCount() == 4 then + report:Add( " N O N E" ) + end + report:Add( "------------------------------------------------------------" ) + report:Add( " -- CRATES --" ) + local cratecount = 0 + for _, _cargo in pairs( cargotable ) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if (type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS) and (not cargo:WasDropped() or self.allowcratepickupagain) then + report:Add( string.format( "Crate: %s size 1", cargo:GetName() ) ) + cratecount = cratecount + 1 + end + end + if cratecount == 0 then + report:Add( " N O N E" ) + end + report:Add( "------------------------------------------------------------" ) + report:Add( "Total Mass: " .. loadedmass .. " kg" ) + local text = report:Text() + self:_SendMessage( text, 30, true, Group ) + else + self:_SendMessage( string.format( "Nothing loaded!\nTroop limit: %d | Crate limit %d", trooplimit, cratelimit ), 10, false, Group ) + end + return self + end + + --- (Internal) Function to list loaded cargo. + -- @param #CTLD self + -- @param Wrapper.Group#GROUP Group + -- @param Wrapper.Unit#UNIT Unit + -- @return #CTLD self + function CTLD:_ListInventory( Group, Unit ) + self:T( self.lid .. " _ListInventory" ) + local unitname = Unit:GetName() + local unittype = Unit:GetTypeName() + local cgotypes = self.Cargo_Crates + local trptypes = self.Cargo_Troops + local stctypes = self.Cargo_Statics + + local function countcargo( cgotable ) + local counter = 0 + for _, _cgo in pairs( cgotable ) do + counter = counter + 1 + end + return counter + end + + local crateno = countcargo( cgotypes ) + local troopno = countcargo( trptypes ) + local staticno = countcargo( stctypes ) + + if (crateno > 0 or troopno > 0 or staticno > 0) then + + local report = REPORT:New( "Inventory Sheet" ) + report:Add( "------------------------------------------------------------" ) + report:Add( string.format( "Troops: %d, Cratetypes: %d", troopno, crateno + staticno ) ) + report:Add( "------------------------------------------------------------" ) + report:Add( " -- TROOPS --" ) + for _, _cargo in pairs( trptypes ) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and not cargo:WasDropped() then + local stockn = cargo:GetStock() + local stock = "none" + if stockn == -1 then + stock = "unlimited" + elseif stockn > 0 then + stock = tostring( stockn ) + end + report:Add( string.format( "Unit: %s | Soldiers: %d | Stock: %s", cargo:GetName(), cargo:GetCratesNeeded(), stock ) ) + end + end + if report:GetCount() == 4 then + report:Add( " N O N E" ) + end + report:Add( "------------------------------------------------------------" ) + report:Add( " -- CRATES --" ) + local cratecount = 0 + for _, _cargo in pairs( cgotypes ) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if (type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS) and not cargo:WasDropped() then + local stockn = cargo:GetStock() + local stock = "none" + if stockn == -1 then + stock = "unlimited" + elseif stockn > 0 then + stock = tostring( stockn ) + end + report:Add( string.format( "Type: %s | Crates per Set: %d | Stock: %s", cargo:GetName(), cargo:GetCratesNeeded(), stock ) ) + cratecount = cratecount + 1 + end + end + -- Statics + for _, _cargo in pairs( stctypes ) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if (type == CTLD_CARGO.Enum.STATIC) and not cargo:WasDropped() then + local stockn = cargo:GetStock() + local stock = "none" + if stockn == -1 then + stock = "unlimited" + elseif stockn > 0 then + stock = tostring( stockn ) + end + report:Add( string.format( "Type: %s | Stock: %s", cargo:GetName(), stock ) ) + cratecount = cratecount + 1 + end + end + if cratecount == 0 then + report:Add( " N O N E" ) + end + local text = report:Text() + self:_SendMessage( text, 30, true, Group ) + else + self:_SendMessage( string.format( "Nothing in stock!" ), 10, false, Group ) + end + return self + end + + --- (Internal) Function to check if a unit is a Hercules C-130. + -- @param #CTLD self + -- @param Wrapper.Unit#UNIT Unit + -- @return #boolean Outcome + function CTLD:IsHercules( Unit ) + if Unit:GetTypeName() == "Hercules" then + return true + else + return false + end + end + + --- (Internal) Function to unload troops from heli. + -- @param #CTLD self + -- @param Wrapper.Group#GROUP Group + -- @param Wrapper.Unit#UNIT Unit + function CTLD:_UnloadTroops( Group, Unit ) + self:T( self.lid .. " _UnloadTroops" ) + -- check if we are in LOAD zone + local droppingatbase = false + local canunload = true + if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen( Unit:GetName() ) then + self:_SendMessage( "You need to open the door(s) to unload troops!", 10, false, Group ) + if not self.debug then + return self + end + end + local inzone, zonename, zone, distance = self:IsUnitInZone( Unit, CTLD.CargoZoneType.LOAD ) if not inzone then - self:_SendMessage("You are not close enough to a drop zone!", 10, false, Group) - if not self.debug then - return self - end + inzone, zonename, zone, distance = self:IsUnitInZone( Unit, CTLD.CargoZoneType.SHIP ) end - end - -- check for hover unload - local hoverunload = self:IsCorrectHover(Unit) --if true we\'re hovering in parameters - local IsHerc = self:IsHercules(Unit) - if IsHerc then - -- no hover but airdrop here - hoverunload = self:IsCorrectFlightParameters(Unit) - end - -- check if we\'re landed - local grounded = not self:IsUnitInAir(Unit) - -- Get what we have loaded - local unitname = Unit:GetName() - if self.Loaded_Cargo[unitname] and (grounded or hoverunload) then - local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo - -- looking for crate - local cargotable = loadedcargo.Cargo - for _,_cargo in pairs (cargotable) do - local cargo = _cargo -- #CTLD_CARGO - local type = cargo:GetType() -- #CTLD_CARGO.Enum - if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and (not cargo:WasDropped() or self.allowcratepickupagain) then - -- unload crates - self:_GetCrates(Group, Unit, cargo, 1, true) - cargo:SetWasDropped(true) - cargo:SetHasMoved(true) - end + if inzone then + droppingatbase = true end - -- cleanup load list - local loaded = {} -- #CTLD.LoadedCargo - loaded.Troopsloaded = 0 - loaded.Cratesloaded = 0 - loaded.Cargo = {} - - for _,_cargo in pairs (cargotable) do - local cargo = _cargo -- #CTLD_CARGO - local type = cargo:GetType() -- #CTLD_CARGO.Enum - local size = cargo:GetCratesNeeded() - if type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS then - table.insert(loaded.Cargo,_cargo) - loaded.Troopsloaded = loaded.Troopsloaded + size - end - end - self.Loaded_Cargo[unitname] = nil - self.Loaded_Cargo[unitname] = loaded - - self:_UpdateUnitCargoMass(Unit) - else + -- check for hover unload + local hoverunload = self:IsCorrectHover( Unit ) -- if true we're hovering in parameters + local IsHerc = self:IsHercules( Unit ) if IsHerc then - self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) - else - self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) - end - end - return self -end - ---- (Internal) Function to build nearby crates. --- @param #CTLD self --- @param Wrapper.Group#GROUP Group --- @param Wrapper.Unit#UNIT Unit --- @param #boolean Engineering If true build is by an engineering team. -function CTLD:_BuildCrates(Group, Unit,Engineering) - self:T(self.lid .. " _BuildCrates") - -- avoid users trying to build from flying Hercs - local type = Unit:GetTypeName() - if type == "Hercules" and self.enableHercules and not Engineering then - local speed = Unit:GetVelocityKMH() - if speed > 1 then - self:_SendMessage("You need to land / stop to build something, Pilot!", 10, false, Group) - return self + -- no hover but airdrop here + hoverunload = self:IsCorrectFlightParameters( Unit ) end - end - -- get nearby crates - local finddist = self.CrateDistance or 35 - local crates,number = self:_FindCratesNearby(Group,Unit, finddist) -- #table - local buildables = {} - local foundbuilds = false - local canbuild = false - if number > 0 then - -- get dropped crates - for _,_crate in pairs(crates) do - local Crate = _crate -- #CTLD_CARGO - if Crate:WasDropped() and not Crate:IsRepair() and not Crate:IsStatic() then - -- we can build these - maybe - local name = Crate:GetName() - local required = Crate:GetCratesNeeded() - local template = Crate:GetTemplates() - local ctype = Crate:GetType() - if not buildables[name] then - local object = {} -- #CTLD.Buildable - object.Name = name - object.Required = required - object.Found = 1 - object.Template = template - object.CanBuild = false - object.Type = ctype -- #CTLD_CARGO.Enum - buildables[name] = object - foundbuilds = true + -- check if we're landed + local grounded = not self:IsUnitInAir( Unit ) + -- Get what we have loaded + local unitname = Unit:GetName() + if self.Loaded_Cargo[unitname] and (grounded or hoverunload) then + if not droppingatbase or self.debug then + local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo + -- looking for troops + local cargotable = loadedcargo.Cargo + for _, _cargo in pairs( cargotable ) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and not cargo:WasDropped() then + -- unload troops + local name = cargo:GetName() or "none" + local temptable = cargo:GetTemplates() or {} + local position = Group:GetCoordinate() + local zoneradius = 100 -- drop zone radius + local factor = 1 + if IsHerc then + factor = cargo:GetCratesNeeded() or 1 -- spread a bit more if airdropping + zoneradius = Unit:GetVelocityMPS() or 100 + end + local zone = ZONE_GROUP:New( string.format( "Unload zone-%s", unitname ), Group, zoneradius * factor ) + local randomcoord = zone:GetRandomCoordinate( 10, 30 * factor ):GetVec2() + for _, _template in pairs( temptable ) do + self.TroopCounter = self.TroopCounter + 1 + local alias = string.format( "%s-%d", _template, math.random( 1, 100000 ) ) + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias( _template, alias ) + :InitRandomizeUnits( true, 20, 2 ) + :InitDelayOff() + :SpawnFromVec2( randomcoord ) + if self.movetroopstowpzone and type ~= CTLD_CARGO.Enum.ENGINEERS then + self:_MoveGroupToZone( self.DroppedTroops[self.TroopCounter] ) + end + end -- template loop + cargo:SetWasDropped( true ) + -- engineering group? + -- self:I("Dropped Troop Type: "..type) + if type == CTLD_CARGO.Enum.ENGINEERS then + self.Engineers = self.Engineers + 1 + local grpname = self.DroppedTroops[self.TroopCounter]:GetName() + self.EngineersInField[self.Engineers] = CTLD_ENGINEERING:New( name, grpname ) + self:_SendMessage( string.format( "Dropped Engineers %s into action!", name ), 10, false, Group ) + else + self:_SendMessage( string.format( "Dropped Troops %s into action!", name ), 10, false, Group ) + end + self:__TroopsDeployed( 1, Group, Unit, self.DroppedTroops[self.TroopCounter] ) + end -- if type end + end -- cargotable loop + else -- droppingatbase + self:_SendMessage( "Troops have returned to base!", 10, false, Group ) + self:__TroopsRTB( 1, Group, Unit ) + end + -- cleanup load list + local loaded = {} -- #CTLD.LoadedCargo + loaded.Troopsloaded = 0 + loaded.Cratesloaded = 0 + loaded.Cargo = {} + local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo + local cargotable = loadedcargo.Cargo or {} + for _, _cargo in pairs( cargotable ) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + local dropped = cargo:WasDropped() + if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and not dropped then + table.insert( loaded.Cargo, _cargo ) + loaded.Cratesloaded = loaded.Cratesloaded + 1 else - buildables[name].Found = buildables[name].Found + 1 - foundbuilds = true + -- add troops back to stock + if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and droppingatbase then + -- find right generic type + local name = cargo:GetName() + local gentroops = self.Cargo_Troops + for _id, _troop in pairs( gentroops ) do -- #number, #CTLD_CARGO + if _troop.Name == name then + local stock = _troop:GetStock() + -- avoid making unlimited stock limited + if stock and tonumber( stock ) >= 0 then + _troop:AddStock() + end + end + end + end end - if buildables[name].Found >= buildables[name].Required then - buildables[name].CanBuild = true - canbuild = true - end - self:T({buildables = buildables}) - end -- end dropped - end -- end crate loop - -- ok let\'s list what we have - local report = REPORT:New("Checklist Buildable Crates") - report:Add("------------------------------------------------------------") - for _,_build in pairs(buildables) do - local build = _build -- Object table from above - local name = build.Name - local needed = build.Required - local found = build.Found - local txtok = "NO" - if build.CanBuild then - txtok = "YES" end - local text = string.format("Type: %s | Required %d | Found %d | Can Build %s", name, needed, found, txtok) - report:Add(text) - end -- end list buildables - if not foundbuilds then report:Add(" --- None Found ---") end - report:Add("------------------------------------------------------------") - local text = report:Text() - if not Engineering then - self:_SendMessage(text, 30, true, Group) + self.Loaded_Cargo[unitname] = nil + self.Loaded_Cargo[unitname] = loaded + self:_UpdateUnitCargoMass( Unit ) else - self:T(text) - end - -- let\'s get going - if canbuild then - -- loop again - for _,_build in pairs(buildables) do - local build = _build -- #CTLD.Buildable - if build.CanBuild then - self:_CleanUpCrates(crates,build,number) - self:_BuildObjectFromCrates(Group,Unit,build) - end - end - end - else - if not Engineering then self:_SendMessage(string.format("No crates within %d meters!",finddist), 10, false, Group) end - end -- number > 0 - return self -end - ---- (Internal) Function to repair nearby vehicles / FOBs --- @param #CTLD self --- @param Wrapper.Group#GROUP Group --- @param Wrapper.Unit#UNIT Unit --- @param #boolean Engineering If true, this is an engineering role -function CTLD:_RepairCrates(Group, Unit, Engineering) - self:T(self.lid .. " _RepairCrates") - -- get nearby crates - local finddist = self.CrateDistance or 35 - local crates,number = self:_FindCratesNearby(Group,Unit,finddist) -- #table - local buildables = {} - local foundbuilds = false - local canbuild = false - if number > 0 then - -- get dropped crates - for _,_crate in pairs(crates) do - local Crate = _crate -- #CTLD_CARGO - if Crate:WasDropped() and Crate:IsRepair() and not Crate:IsStatic() then - -- we can build these - maybe - local name = Crate:GetName() - local required = Crate:GetCratesNeeded() - local template = Crate:GetTemplates() - local ctype = Crate:GetType() - if not buildables[name] then - local object = {} -- #CTLD.Buildable - object.Name = name - object.Required = required - object.Found = 1 - object.Template = template - object.CanBuild = false - object.Type = ctype -- #CTLD_CARGO.Enum - buildables[name] = object - foundbuilds = true - else - buildables[name].Found = buildables[name].Found + 1 - foundbuilds = true - end - if buildables[name].Found >= buildables[name].Required then - buildables[name].CanBuild = true - canbuild = true - end - self:T({repair = buildables}) - end -- end dropped - end -- end crate loop - -- ok let\'s list what we have - local report = REPORT:New("Checklist Repairs") - report:Add("------------------------------------------------------------") - for _,_build in pairs(buildables) do - local build = _build -- Object table from above - local name = build.Name - local needed = build.Required - local found = build.Found - local txtok = "NO" - if build.CanBuild then - txtok = "YES" - end - local text = string.format("Type: %s | Required %d | Found %d | Can Repair %s", name, needed, found, txtok) - report:Add(text) - end -- end list buildables - if not foundbuilds then report:Add(" --- None Found ---") end - report:Add("------------------------------------------------------------") - local text = report:Text() - if not Engineering then - self:_SendMessage(text, 30, true, Group) - else - self:T(text) - end - -- let\'s get going - if canbuild then - -- loop again - for _,_build in pairs(buildables) do - local build = _build -- #CTLD.Buildable - if build.CanBuild then - self:_RepairObjectFromCrates(Group,Unit,crates,build,number,Engineering) - end - end - end - else - if not Engineering then self:_SendMessage(string.format("No crates within %d meters!",finddist), 10, false, Group) end - end -- number > 0 - return self -end - ---- (Internal) Function to actually SPAWN buildables in the mission. --- @param #CTLD self --- @param Wrapper.Group#GROUP Group --- @param Wrapper.Group#UNIT Unit --- @param #CTLD.Buildable Build --- @param #boolean Repair If true this is a repair and not a new build --- @param Core.Point#COORDINATE Coordinate Location for repair (e.g. where the destroyed unit was) -function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation) - self:T(self.lid .. " _BuildObjectFromCrates") - -- Spawn-a-crate-content - if Group and Group:IsAlive() then - local position = Unit:GetCoordinate() or Group:GetCoordinate() - local unitname = Unit:GetName() or Group:GetName() - local name = Build.Name - local ctype = Build.Type -- #CTLD_CARGO.Enum - local canmove = false - if ctype == CTLD_CARGO.Enum.VEHICLE then canmove = true end - if ctype == CTLD_CARGO.Enum.STATIC then - return self - end - local temptable = Build.Template or {} - if type(temptable) == "string" then - temptable = {temptable} - end - local zone = ZONE_GROUP:New(string.format("Unload zone-%s",unitname),Group,100) - local randomcoord = zone:GetRandomCoordinate(35):GetVec2() - if Repair then - randomcoord = RepairLocation:GetVec2() - end - for _,_template in pairs(temptable) do - self.TroopCounter = self.TroopCounter + 1 - local alias = string.format("%s-%d", _template, math.random(1,100000)) - if canmove then - self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) - :InitRandomizeUnits(true,20,2) - :InitDelayOff() - :SpawnFromVec2(randomcoord) - else -- don't random position of e.g. SAM units build as FOB - self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) - :InitDelayOff() - :SpawnFromVec2(randomcoord) - end - if self.movetroopstowpzone and canmove then - self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) - end - if Repair then - self:__CratesRepaired(1,Group,Unit,self.DroppedTroops[self.TroopCounter]) + if IsHerc then + self:_SendMessage( "Nothing loaded or not within airdrop parameters!", 10, false, Group ) else - self:__CratesBuild(1,Group,Unit,self.DroppedTroops[self.TroopCounter]) + self:_SendMessage( "Nothing loaded or not hovering within parameters!", 10, false, Group ) end - end -- template loop - else - self:T(self.lid.."Group KIA while building!") - end - return self -end - ---- (Internal) Function to move group to WP zone. --- @param #CTLD self --- @param Wrapper.Group#GROUP Group The Group to move. -function CTLD:_MoveGroupToZone(Group) - self:T(self.lid .. " _MoveGroupToZone") - local groupname = Group:GetName() or "none" - local groupcoord = Group:GetCoordinate() - -- Get closest zone of type - local outcome, name, zone, distance = self:IsUnitInZone(Group,CTLD.CargoZoneType.MOVE) - --self:Tstring.format("Closest WP zone %s is %d meters",name,distance)) - if (distance <= self.movetroopsdistance) and zone then - -- yes, we can ;) - local groupname = Group:GetName() - local zonecoord = zone:GetRandomCoordinate(20,125) -- Core.Point#COORDINATE - local coordinate = zonecoord:GetVec2() - Group:SetAIOn() - Group:OptionAlarmStateAuto() - Group:OptionDisperseOnAttack(30) - Group:OptionROEOpenFirePossible() - Group:RouteToVec2(coordinate,5) end - return self -end - ---- (Internal) Housekeeping - Cleanup crates when build --- @param #CTLD self --- @param #table Crates Table of #CTLD_CARGO objects near the unit. --- @param #CTLD.Buildable Build Table build object. --- @param #number Number Number of objects in Crates (found) to limit search. -function CTLD:_CleanUpCrates(Crates,Build,Number) - self:T(self.lid .. " _CleanUpCrates") - -- clean up real world crates - local build = Build -- #CTLD.Buildable - local existingcrates = self.Spawned_Cargo -- #table of exising crates - local newexcrates = {} - -- get right number of crates to destroy - local numberdest = Build.Required - local nametype = Build.Name - local found = 0 - local rounds = Number - local destIDs = {} - - -- loop and find matching IDs in the set - for _,_crate in pairs(Crates) do - local nowcrate = _crate -- #CTLD_CARGO - local name = nowcrate:GetName() - local thisID = nowcrate:GetID() - if name == nametype then -- matching crate type - table.insert(destIDs,thisID) - found = found + 1 - nowcrate:GetPositionable():Destroy(false) - nowcrate.Positionable = nil - nowcrate.HasBeenDropped = false - end - if found == numberdest then break end -- got enough + return self end - -- loop and remove from real world representation - self:_CleanupTrackedCrates(destIDs) - return self -end ---- (Internal) Housekeeping - Function to refresh F10 menus. --- @param #CTLD self --- @return #CTLD self -function CTLD:_RefreshF10Menus() - self:T(self.lid .. " _RefreshF10Menus") - local PlayerSet = self.PilotGroups -- Core.Set#SET_GROUP - local PlayerTable = PlayerSet:GetSetObjects() -- #table of #GROUP objects - -- rebuild units table - local _UnitList = {} - for _key, _group in pairs (PlayerTable) do - local _unit = _group:GetUnit(1) -- Wrapper.Unit#UNIT Asume that there is only one unit in the flight for players - if _unit then - if _unit:IsAlive() and _unit:IsPlayer() then - if _unit:IsHelicopter() or (_unit:GetTypeName() == "Hercules" and self.enableHercules) then --ensure no stupid unit entries here - local unitName = _unit:GetName() - _UnitList[unitName] = unitName - end - end -- end isAlive - end -- end if _unit - end -- end for - self.CtldUnits = _UnitList - - -- build unit menus - local menucount = 0 - local menus = {} - for _, _unitName in pairs(self.CtldUnits) do - if not self.MenusDone[_unitName] then - local _unit = UNIT:FindByName(_unitName) -- Wrapper.Unit#UNIT - if _unit then - local _group = _unit:GetGroup() -- Wrapper.Group#GROUP - if _group then - -- get chopper capabilities - local unittype = _unit:GetTypeName() - local capabilities = self:_GetUnitCapabilities(_unit) -- #CTLD.UnitCapabilities - local cantroops = capabilities.troops - local cancrates = capabilities.crates - -- top menu - local topmenu = MENU_GROUP:New(_group,"CTLD",nil) - local toptroops = MENU_GROUP:New(_group,"Manage Troops",topmenu) - local topcrates = MENU_GROUP:New(_group,"Manage Crates",topmenu) - local listmenu = MENU_GROUP_COMMAND:New(_group,"List boarded cargo",topmenu, self._ListCargo, self, _group, _unit) - local invtry = MENU_GROUP_COMMAND:New(_group,"Inventory",topmenu, self._ListInventory, self, _group, _unit) - local rbcns = MENU_GROUP_COMMAND:New(_group,"List active zone beacons",topmenu, self._ListRadioBeacons, self, _group, _unit) - local smoketopmenu = MENU_GROUP:New(_group,"Smokes & Flares",topmenu) - local smokemenu = MENU_GROUP_COMMAND:New(_group,"Smoke zones nearby",smoketopmenu, self.SmokeZoneNearBy, self, _unit, false) - local smokeself = MENU_GROUP_COMMAND:New(_group,"Drop smoke now",smoketopmenu, self.SmokePositionNow, self, _unit, false) - local flaremenu = MENU_GROUP_COMMAND:New(_group,"Flare zones nearby",smoketopmenu, self.SmokeZoneNearBy, self, _unit, true) - local flareself = MENU_GROUP_COMMAND:New(_group,"Fire flare now",smoketopmenu, self.SmokePositionNow, self, _unit, true):Refresh() - -- sub menus - -- sub menu troops management - if cantroops then - local troopsmenu = MENU_GROUP:New(_group,"Load troops",toptroops) - for _,_entry in pairs(self.Cargo_Troops) do - local entry = _entry -- #CTLD_CARGO - menucount = menucount + 1 - menus[menucount] = MENU_GROUP_COMMAND:New(_group,entry.Name,troopsmenu,self._LoadTroops, self, _group, _unit, entry) - end - local unloadmenu1 = MENU_GROUP_COMMAND:New(_group,"Drop troops",toptroops, self._UnloadTroops, self, _group, _unit):Refresh() - local extractMenu1 = MENU_GROUP_COMMAND:New(_group, "Extract troops", toptroops, self._ExtractTroops, self, _group, _unit):Refresh() - end - -- sub menu crates management - if cancrates then - local loadmenu = MENU_GROUP_COMMAND:New(_group,"Load crates",topcrates, self._LoadCratesNearby, self, _group, _unit) - local cratesmenu = MENU_GROUP:New(_group,"Get Crates",topcrates) - for _,_entry in pairs(self.Cargo_Crates) do - local entry = _entry -- #CTLD_CARGO - menucount = menucount + 1 - local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) - menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) - end - for _,_entry in pairs(self.Cargo_Statics) do - local entry = _entry -- #CTLD_CARGO - menucount = menucount + 1 - local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) - menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) - end - listmenu = MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates, self._ListCratesNearby, self, _group, _unit) - local unloadmenu = MENU_GROUP_COMMAND:New(_group,"Drop crates",topcrates, self._UnloadCrates, self, _group, _unit) - local buildmenu = MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates, self._BuildCrates, self, _group, _unit) - local repairmenu = MENU_GROUP_COMMAND:New(_group,"Repair",topcrates, self._RepairCrates, self, _group, _unit):Refresh() - end - if unittype == "Hercules" then - local hoverpars = MENU_GROUP_COMMAND:New(_group,"Show flight parameters",topmenu, self._ShowFlightParams, self, _group, _unit):Refresh() + --- (Internal) Function to unload crates from heli. + -- @param #CTLD self + -- @param Wrapper.Group#GROUP Group + -- @param Wrapper.Unit#UNIT Unit + function CTLD:_UnloadCrates( Group, Unit ) + self:T( self.lid .. " _UnloadCrates" ) + + if not self.dropcratesanywhere then -- #1570 + -- check if we are in DROP zone + local inzone, zonename, zone, distance = self:IsUnitInZone( Unit, CTLD.CargoZoneType.DROP ) + if not inzone then + self:_SendMessage( "You are not close enough to a drop zone!", 10, false, Group ) + if not self.debug then + return self + end + end + end + -- check for hover unload + local hoverunload = self:IsCorrectHover( Unit ) -- if true we're hovering in parameters + local IsHerc = self:IsHercules( Unit ) + if IsHerc then + -- no hover but airdrop here + hoverunload = self:IsCorrectFlightParameters( Unit ) + end + -- check if we're landed + local grounded = not self:IsUnitInAir( Unit ) + -- Get what we have loaded + local unitname = Unit:GetName() + if self.Loaded_Cargo[unitname] and (grounded or hoverunload) then + local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo + -- looking for crate + local cargotable = loadedcargo.Cargo + for _, _cargo in pairs( cargotable ) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and (not cargo:WasDropped() or self.allowcratepickupagain) then + -- unload crates + self:_GetCrates( Group, Unit, cargo, 1, true ) + cargo:SetWasDropped( true ) + cargo:SetHasMoved( true ) + end + end + -- cleanup load list + local loaded = {} -- #CTLD.LoadedCargo + loaded.Troopsloaded = 0 + loaded.Cratesloaded = 0 + loaded.Cargo = {} + + for _, _cargo in pairs( cargotable ) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + local size = cargo:GetCratesNeeded() + if type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS then + table.insert( loaded.Cargo, _cargo ) + loaded.Troopsloaded = loaded.Troopsloaded + size + end + end + self.Loaded_Cargo[unitname] = nil + self.Loaded_Cargo[unitname] = loaded + + self:_UpdateUnitCargoMass( Unit ) + else + if IsHerc then + self:_SendMessage( "Nothing loaded or not within airdrop parameters!", 10, false, Group ) + else + self:_SendMessage( "Nothing loaded or not hovering within parameters!", 10, false, Group ) + end + end + return self + end + + --- (Internal) Function to build nearby crates. + -- @param #CTLD self + -- @param Wrapper.Group#GROUP Group + -- @param Wrapper.Unit#UNIT Unit + -- @param #boolean Engineering If true build is by an engineering team. + function CTLD:_BuildCrates( Group, Unit, Engineering ) + self:T( self.lid .. " _BuildCrates" ) + -- avoid users trying to build from flying Hercs + local type = Unit:GetTypeName() + if type == "Hercules" and self.enableHercules and not Engineering then + local speed = Unit:GetVelocityKMH() + if speed > 1 then + self:_SendMessage( "You need to land / stop to build something, Pilot!", 10, false, Group ) + return self + end + end + -- get nearby crates + local finddist = self.CrateDistance or 35 + local crates, number = self:_FindCratesNearby( Group, Unit, finddist ) -- #table + local buildables = {} + local foundbuilds = false + local canbuild = false + if number > 0 then + -- get dropped crates + for _, _crate in pairs( crates ) do + local Crate = _crate -- #CTLD_CARGO + if Crate:WasDropped() and not Crate:IsRepair() and not Crate:IsStatic() then + -- we can build these - maybe + local name = Crate:GetName() + local required = Crate:GetCratesNeeded() + local template = Crate:GetTemplates() + local ctype = Crate:GetType() + if not buildables[name] then + local object = {} -- #CTLD.Buildable + object.Name = name + object.Required = required + object.Found = 1 + object.Template = template + object.CanBuild = false + object.Type = ctype -- #CTLD_CARGO.Enum + buildables[name] = object + foundbuilds = true else - local hoverpars = MENU_GROUP_COMMAND:New(_group,"Show hover parameters",topmenu, self._ShowHoverParams, self, _group, _unit):Refresh() + buildables[name].Found = buildables[name].Found + 1 + foundbuilds = true end - self.MenusDone[_unitName] = true - end -- end group - end -- end unit - else -- menu build check - self:T(self.lid .. " Menus already done for this group!") - end -- end menu build check - end -- end for - return self - end - ---- User function - Add *generic* troop type loadable as cargo. This type will load directly into the heli without crates. --- @param #CTLD self --- @param #string Name Unique name of this type of troop. E.g. "Anti-Air Small". --- @param #table Templates Table of #string names of late activated Wrapper.Group#GROUP making up this troop. --- @param #CTLD_CARGO.Enum Type Type of cargo, here TROOPS - these will move to a nearby destination zone when dropped/build. --- @param #number NoTroops Size of the group in number of Units across combined templates (for loading). --- @param #number PerTroopMass Mass in kg of each soldier --- @param #number Stock Number of groups in stock. Nil for unlimited. -function CTLD:AddTroopsCargo(Name,Templates,Type,NoTroops,PerTroopMass,Stock) - self:T(self.lid .. " AddTroopsCargo") - self:T({Name,Templates,Type,NoTroops,PerTroopMass,Stock}) - self.CargoCounter = self.CargoCounter + 1 - -- Troops are directly loadable - local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,true,NoTroops,nil,nil,PerTroopMass,Stock) - table.insert(self.Cargo_Troops,cargo) - return self -end - ---- User function - Add *generic* crate-type loadable as cargo. This type will create crates that need to be loaded, moved, dropped and built. --- @param #CTLD self --- @param #string Name Unique name of this type of cargo. E.g. "Humvee". --- @param #table Templates Table of #string names of late activated Wrapper.Group#GROUP building this cargo. --- @param #CTLD_CARGO.Enum Type Type of cargo. I.e. VEHICLE or FOB. VEHICLE will move to destination zones when dropped/build, FOB stays put. --- @param #number NoCrates Number of crates needed to build this cargo. --- @param #number PerCrateMass Mass in kg of each crate --- @param #number Stock Number of groups in stock. Nil for unlimited. -function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock) - self:T(self.lid .. " AddCratesCargo") - self.CargoCounter = self.CargoCounter + 1 - -- Crates are not directly loadable - local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,false,NoCrates,nil,nil,PerCrateMass,Stock) - table.insert(self.Cargo_Crates,cargo) - return self -end - ---- User function - Add *generic* static-type loadable as cargo. This type will create cargo that needs to be loaded, moved and dropped. --- @param #CTLD self --- @param #string Name Unique name of this type of cargo as set in the mission editor (note: UNIT name!), e.g. "Ammunition-1". --- @param #number Mass Mass in kg of each static in kg, e.g. 100. --- @param #number Stock Number of groups in stock. Nil for unlimited. -function CTLD:AddStaticsCargo(Name,Mass,Stock) - self:T(self.lid .. " AddStaticsCargo") - self.CargoCounter = self.CargoCounter + 1 - local type = CTLD_CARGO.Enum.STATIC - local template = STATIC:FindByName(Name,true):GetTypeName() - -- Crates are not directly loadable - local cargo = CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,Stock) - table.insert(self.Cargo_Statics,cargo) - return self -end - ---- User function - Get a *generic* static-type loadable as #CTLD_CARGO object. --- @param #CTLD self --- @param #string Name Unique Unit(!) name of this type of cargo as set in the mission editor (not: GROUP name!), e.g. "Ammunition-1". --- @param #number Mass Mass in kg of each static in kg, e.g. 100. --- @return #CTLD_CARGO Cargo object -function CTLD:GetStaticsCargoFromTemplate(Name,Mass) - self:T(self.lid .. " GetStaticsCargoFromTemplate") - self.CargoCounter = self.CargoCounter + 1 - local type = CTLD_CARGO.Enum.STATIC - local template = STATIC:FindByName(Name,true):GetTypeName() - -- Crates are not directly loadable - local cargo = CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,1) - --table.insert(self.Cargo_Statics,cargo) - return cargo -end - ---- User function - Add *generic* repair crates loadable as cargo. This type will create crates that need to be loaded, moved, dropped and built. --- @param #CTLD self --- @param #string Name Unique name of this type of cargo. E.g. "Humvee". --- @param #string Template Template of VEHICLE or FOB cargo that this can repair. --- @param #CTLD_CARGO.Enum Type Type of cargo, here REPAIR. --- @param #number NoCrates Number of crates needed to build this cargo. --- @param #number PerCrateMass Mass in kg of each crate --- @param #number Stock Number of groups in stock. Nil for unlimited. -function CTLD:AddCratesRepair(Name,Template,Type,NoCrates, PerCrateMass,Stock) - self:T(self.lid .. " AddCratesRepair") - self.CargoCounter = self.CargoCounter + 1 - -- Crates are not directly loadable - local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Template,Type,false,false,NoCrates,nil,nil,PerCrateMass,Stock) - table.insert(self.Cargo_Crates,cargo) - return self -end - ---- User function - Add a #CTLD.CargoZoneType zone for this CTLD instance. --- @param #CTLD self --- @param #CTLD.CargoZone Zone Zone #CTLD.CargoZone describing the zone. -function CTLD:AddZone(Zone) - self:T(self.lid .. " AddZone") - local zone = Zone -- #CTLD.CargoZone - if zone.type == CTLD.CargoZoneType.LOAD then - table.insert(self.pickupZones,zone) - elseif zone.type == CTLD.CargoZoneType.DROP then - table.insert(self.dropOffZones,zone) - elseif zone.type == CTLD.CargoZoneType.SHIP then - table.insert(self.shipZones,zone) - else - table.insert(self.wpZones,zone) + if buildables[name].Found >= buildables[name].Required then + buildables[name].CanBuild = true + canbuild = true + end + self:T( { buildables = buildables } ) + end -- end dropped + end -- end crate loop + -- ok let's list what we have + local report = REPORT:New( "Checklist Buildable Crates" ) + report:Add( "------------------------------------------------------------" ) + for _, _build in pairs( buildables ) do + local build = _build -- Object table from above + local name = build.Name + local needed = build.Required + local found = build.Found + local txtok = "NO" + if build.CanBuild then + txtok = "YES" + end + local text = string.format( "Type: %s | Required %d | Found %d | Can Build %s", name, needed, found, txtok ) + report:Add( text ) + end -- end list buildables + if not foundbuilds then + report:Add( " --- None Found ---" ) + end + report:Add( "------------------------------------------------------------" ) + local text = report:Text() + if not Engineering then + self:_SendMessage( text, 30, true, Group ) + else + self:T( text ) + end + -- let's get going + if canbuild then + -- loop again + for _, _build in pairs( buildables ) do + local build = _build -- #CTLD.Buildable + if build.CanBuild then + self:_CleanUpCrates( crates, build, number ) + self:_BuildObjectFromCrates( Group, Unit, build ) + end + end + end + else + if not Engineering then + self:_SendMessage( string.format( "No crates within %d meters!", finddist ), 10, false, Group ) + end + end -- number > 0 + return self end - return self -end ---- User function - Activate Name #CTLD.CargoZone.Type ZoneType for this CTLD instance. --- @param #CTLD self --- @param #string Name Name of the zone to change in the ME. --- @param #CTLD.CargoZoneType ZoneType Type of zone this belongs to. --- @param #boolean NewState (Optional) Set to true to activate, false to switch off. -function CTLD:ActivateZone(Name,ZoneType,NewState) - self:T(self.lid .. " AddZone") - local newstate = true - -- set optional in case we\'re deactivating - if NewState ~= nil then - newstate = NewState - end - - -- get correct table - local table = {} - if ZoneType == CTLD.CargoZoneType.LOAD then - table = self.pickupZones - elseif ZoneType == CTLD.CargoZoneType.DROP then - table = self.dropOffZones - elseif ZoneType == CTLD.CargoZoneType.SHIP then - table = self.shipZones - else - table = self.wpZones + --- (Internal) Function to repair nearby vehicles / FOBs + -- @param #CTLD self + -- @param Wrapper.Group#GROUP Group + -- @param Wrapper.Unit#UNIT Unit + -- @param #boolean Engineering If true, this is an engineering role + function CTLD:_RepairCrates( Group, Unit, Engineering ) + self:T( self.lid .. " _RepairCrates" ) + -- get nearby crates + local finddist = self.CrateDistance or 35 + local crates, number = self:_FindCratesNearby( Group, Unit, finddist ) -- #table + local buildables = {} + local foundbuilds = false + local canbuild = false + if number > 0 then + -- get dropped crates + for _, _crate in pairs( crates ) do + local Crate = _crate -- #CTLD_CARGO + if Crate:WasDropped() and Crate:IsRepair() and not Crate:IsStatic() then + -- we can build these - maybe + local name = Crate:GetName() + local required = Crate:GetCratesNeeded() + local template = Crate:GetTemplates() + local ctype = Crate:GetType() + if not buildables[name] then + local object = {} -- #CTLD.Buildable + object.Name = name + object.Required = required + object.Found = 1 + object.Template = template + object.CanBuild = false + object.Type = ctype -- #CTLD_CARGO.Enum + buildables[name] = object + foundbuilds = true + else + buildables[name].Found = buildables[name].Found + 1 + foundbuilds = true + end + if buildables[name].Found >= buildables[name].Required then + buildables[name].CanBuild = true + canbuild = true + end + self:T( { repair = buildables } ) + end -- end dropped + end -- end crate loop + -- ok let's list what we have + local report = REPORT:New( "Checklist Repairs" ) + report:Add( "------------------------------------------------------------" ) + for _, _build in pairs( buildables ) do + local build = _build -- Object table from above + local name = build.Name + local needed = build.Required + local found = build.Found + local txtok = "NO" + if build.CanBuild then + txtok = "YES" + end + local text = string.format( "Type: %s | Required %d | Found %d | Can Repair %s", name, needed, found, txtok ) + report:Add( text ) + end -- end list buildables + if not foundbuilds then + report:Add( " --- None Found ---" ) + end + report:Add( "------------------------------------------------------------" ) + local text = report:Text() + if not Engineering then + self:_SendMessage( text, 30, true, Group ) + else + self:T( text ) + end + -- let's get going + if canbuild then + -- loop again + for _, _build in pairs( buildables ) do + local build = _build -- #CTLD.Buildable + if build.CanBuild then + self:_RepairObjectFromCrates( Group, Unit, crates, build, number, Engineering ) + end + end + end + else + if not Engineering then + self:_SendMessage( string.format( "No crates within %d meters!", finddist ), 10, false, Group ) + end + end -- number > 0 + return self end - -- loop table - for _,_zone in pairs(table) do - local thiszone = _zone --#CTLD.CargoZone - if thiszone.name == Name then - thiszone.active = newstate - break + + --- (Internal) Function to actually SPAWN buildables in the mission. + -- @param #CTLD self + -- @param Wrapper.Group#GROUP Group + -- @param Wrapper.Group#UNIT Unit + -- @param #CTLD.Buildable Build + -- @param #boolean Repair If true this is a repair and not a new build + -- @param Core.Point#COORDINATE Coordinate Location for repair (e.g. where the destroyed unit was) + function CTLD:_BuildObjectFromCrates( Group, Unit, Build, Repair, RepairLocation ) + self:T( self.lid .. " _BuildObjectFromCrates" ) + -- Spawn-a-crate-content + if Group and Group:IsAlive() then + local position = Unit:GetCoordinate() or Group:GetCoordinate() + local unitname = Unit:GetName() or Group:GetName() + local name = Build.Name + local ctype = Build.Type -- #CTLD_CARGO.Enum + local canmove = false + if ctype == CTLD_CARGO.Enum.VEHICLE then + canmove = true + end + if ctype == CTLD_CARGO.Enum.STATIC then + return self + end + local temptable = Build.Template or {} + if type( temptable ) == "string" then + temptable = { temptable } + end + local zone = ZONE_GROUP:New( string.format( "Unload zone-%s", unitname ), Group, 100 ) + local randomcoord = zone:GetRandomCoordinate( 35 ):GetVec2() + if Repair then + randomcoord = RepairLocation:GetVec2() + end + for _, _template in pairs( temptable ) do + self.TroopCounter = self.TroopCounter + 1 + local alias = string.format( "%s-%d", _template, math.random( 1, 100000 ) ) + if canmove then + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias( _template, alias ) + :InitRandomizeUnits( true, 20, 2 ) + :InitDelayOff() + :SpawnFromVec2( randomcoord ) + else -- don't random position of e.g. SAM units build as FOB + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias( _template, alias ) + :InitDelayOff() + :SpawnFromVec2( randomcoord ) + end + if self.movetroopstowpzone and canmove then + self:_MoveGroupToZone( self.DroppedTroops[self.TroopCounter] ) + end + if Repair then + self:__CratesRepaired( 1, Group, Unit, self.DroppedTroops[self.TroopCounter] ) + else + self:__CratesBuild( 1, Group, Unit, self.DroppedTroops[self.TroopCounter] ) + end + end -- template loop + else + self:T( self.lid .. "Group KIA while building!" ) end + return self end - return self -end + --- (Internal) Function to move group to WP zone. + -- @param #CTLD self + -- @param Wrapper.Group#GROUP Group The Group to move. + function CTLD:_MoveGroupToZone( Group ) + self:T( self.lid .. " _MoveGroupToZone" ) + local groupname = Group:GetName() or "none" + local groupcoord = Group:GetCoordinate() + -- Get closest zone of type + local outcome, name, zone, distance = self:IsUnitInZone( Group, CTLD.CargoZoneType.MOVE ) + -- self:Tstring.format("Closest WP zone %s is %d meters",name,distance)) + if (distance <= self.movetroopsdistance) and zone then + -- yes, we can ;) + local groupname = Group:GetName() + local zonecoord = zone:GetRandomCoordinate( 20, 125 ) -- Core.Point#COORDINATE + local coordinate = zonecoord:GetVec2() + Group:SetAIOn() + Group:OptionAlarmStateAuto() + Group:OptionDisperseOnAttack( 30 ) + Group:OptionROEOpenFirePossible() + Group:RouteToVec2( coordinate, 5 ) + end + return self + end ---- User function - Deactivate Name #CTLD.CargoZoneType ZoneType for this CTLD instance. --- @param #CTLD self --- @param #string Name Name of the zone to change in the ME. --- @param #CTLD.CargoZoneType ZoneType Type of zone this belongs to. -function CTLD:DeactivateZone(Name,ZoneType) - self:T(self.lid .. " AddZone") - self:ActivateZone(Name,ZoneType,false) - return self -end + --- (Internal) Housekeeping - Cleanup crates when build + -- @param #CTLD self + -- @param #table Crates Table of #CTLD_CARGO objects near the unit. + -- @param #CTLD.Buildable Build Table build object. + -- @param #number Number Number of objects in Crates (found) to limit search. + function CTLD:_CleanUpCrates( Crates, Build, Number ) + self:T( self.lid .. " _CleanUpCrates" ) + -- clean up real world crates + local build = Build -- #CTLD.Buildable + local existingcrates = self.Spawned_Cargo -- #table of exising crates + local newexcrates = {} + -- get right number of crates to destroy + local numberdest = Build.Required + local nametype = Build.Name + local found = 0 + local rounds = Number + local destIDs = {} ---- (Internal) Function to obtain a valid FM frequency. --- @param #CTLD self --- @param #string Name Name of zone. --- @return #CTLD.ZoneBeacon Beacon Beacon table. -function CTLD:_GetFMBeacon(Name) - self:T(self.lid .. " _GetFMBeacon") - local beacon = {} -- #CTLD.ZoneBeacon - if #self.FreeFMFrequencies <= 1 then + -- loop and find matching IDs in the set + for _, _crate in pairs( Crates ) do + local nowcrate = _crate -- #CTLD_CARGO + local name = nowcrate:GetName() + local thisID = nowcrate:GetID() + if name == nametype then -- matching crate type + table.insert( destIDs, thisID ) + found = found + 1 + nowcrate:GetPositionable():Destroy( false ) + nowcrate.Positionable = nil + nowcrate.HasBeenDropped = false + end + if found == numberdest then + break + end -- got enough + end + -- loop and remove from real world representation + self:_CleanupTrackedCrates( destIDs ) + return self + end + + --- (Internal) Housekeeping - Function to refresh F10 menus. + -- @param #CTLD self + -- @return #CTLD self + function CTLD:_RefreshF10Menus() + self:T( self.lid .. " _RefreshF10Menus" ) + local PlayerSet = self.PilotGroups -- Core.Set#SET_GROUP + local PlayerTable = PlayerSet:GetSetObjects() -- #table of #GROUP objects + -- rebuild units table + local _UnitList = {} + for _key, _group in pairs( PlayerTable ) do + local _unit = _group:GetUnit( 1 ) -- Wrapper.Unit#UNIT Asume that there is only one unit in the flight for players + if _unit then + if _unit:IsAlive() and _unit:IsPlayer() then + if _unit:IsHelicopter() or (_unit:GetTypeName() == "Hercules" and self.enableHercules) then -- ensure no stupid unit entries here + local unitName = _unit:GetName() + _UnitList[unitName] = unitName + end + end -- end isAlive + end -- end if _unit + end -- end for + self.CtldUnits = _UnitList + + -- build unit menus + local menucount = 0 + local menus = {} + for _, _unitName in pairs( self.CtldUnits ) do + if not self.MenusDone[_unitName] then + local _unit = UNIT:FindByName( _unitName ) -- Wrapper.Unit#UNIT + if _unit then + local _group = _unit:GetGroup() -- Wrapper.Group#GROUP + if _group then + -- get chopper capabilities + local unittype = _unit:GetTypeName() + local capabilities = self:_GetUnitCapabilities( _unit ) -- #CTLD.UnitCapabilities + local cantroops = capabilities.troops + local cancrates = capabilities.crates + -- top menu + local topmenu = MENU_GROUP:New( _group, "CTLD", nil ) + local toptroops = MENU_GROUP:New( _group, "Manage Troops", topmenu ) + local topcrates = MENU_GROUP:New( _group, "Manage Crates", topmenu ) + local listmenu = MENU_GROUP_COMMAND:New( _group, "List boarded cargo", topmenu, self._ListCargo, self, _group, _unit ) + local invtry = MENU_GROUP_COMMAND:New( _group, "Inventory", topmenu, self._ListInventory, self, _group, _unit ) + local rbcns = MENU_GROUP_COMMAND:New( _group, "List active zone beacons", topmenu, self._ListRadioBeacons, self, _group, _unit ) + local smoketopmenu = MENU_GROUP:New( _group, "Smokes & Flares", topmenu ) + local smokemenu = MENU_GROUP_COMMAND:New( _group, "Smoke zones nearby", smoketopmenu, self.SmokeZoneNearBy, self, _unit, false ) + local smokeself = MENU_GROUP_COMMAND:New( _group, "Drop smoke now", smoketopmenu, self.SmokePositionNow, self, _unit, false ) + local flaremenu = MENU_GROUP_COMMAND:New( _group, "Flare zones nearby", smoketopmenu, self.SmokeZoneNearBy, self, _unit, true ) + local flareself = MENU_GROUP_COMMAND:New( _group, "Fire flare now", smoketopmenu, self.SmokePositionNow, self, _unit, true ):Refresh() + -- sub menus + -- sub menu troops management + if cantroops then + local troopsmenu = MENU_GROUP:New( _group, "Load troops", toptroops ) + for _, _entry in pairs( self.Cargo_Troops ) do + local entry = _entry -- #CTLD_CARGO + menucount = menucount + 1 + menus[menucount] = MENU_GROUP_COMMAND:New( _group, entry.Name, troopsmenu, self._LoadTroops, self, _group, _unit, entry ) + end + local unloadmenu1 = MENU_GROUP_COMMAND:New( _group, "Drop troops", toptroops, self._UnloadTroops, self, _group, _unit ):Refresh() + local extractMenu1 = MENU_GROUP_COMMAND:New( _group, "Extract troops", toptroops, self._ExtractTroops, self, _group, _unit ):Refresh() + end + -- sub menu crates management + if cancrates then + local loadmenu = MENU_GROUP_COMMAND:New( _group, "Load crates", topcrates, self._LoadCratesNearby, self, _group, _unit ) + local cratesmenu = MENU_GROUP:New( _group, "Get Crates", topcrates ) + for _, _entry in pairs( self.Cargo_Crates ) do + local entry = _entry -- #CTLD_CARGO + menucount = menucount + 1 + local menutext = string.format( "Crate %s (%dkg)", entry.Name, entry.PerCrateMass or 0 ) + menus[menucount] = MENU_GROUP_COMMAND:New( _group, menutext, cratesmenu, self._GetCrates, self, _group, _unit, entry ) + end + for _, _entry in pairs( self.Cargo_Statics ) do + local entry = _entry -- #CTLD_CARGO + menucount = menucount + 1 + local menutext = string.format( "Crate %s (%dkg)", entry.Name, entry.PerCrateMass or 0 ) + menus[menucount] = MENU_GROUP_COMMAND:New( _group, menutext, cratesmenu, self._GetCrates, self, _group, _unit, entry ) + end + listmenu = MENU_GROUP_COMMAND:New( _group, "List crates nearby", topcrates, self._ListCratesNearby, self, _group, _unit ) + local unloadmenu = MENU_GROUP_COMMAND:New( _group, "Drop crates", topcrates, self._UnloadCrates, self, _group, _unit ) + local buildmenu = MENU_GROUP_COMMAND:New( _group, "Build crates", topcrates, self._BuildCrates, self, _group, _unit ) + local repairmenu = MENU_GROUP_COMMAND:New( _group, "Repair", topcrates, self._RepairCrates, self, _group, _unit ):Refresh() + end + if unittype == "Hercules" then + local hoverpars = MENU_GROUP_COMMAND:New( _group, "Show flight parameters", topmenu, self._ShowFlightParams, self, _group, _unit ):Refresh() + else + local hoverpars = MENU_GROUP_COMMAND:New( _group, "Show hover parameters", topmenu, self._ShowHoverParams, self, _group, _unit ):Refresh() + end + self.MenusDone[_unitName] = true + end -- end group + end -- end unit + else -- menu build check + self:T( self.lid .. " Menus already done for this group!" ) + end -- end menu build check + end -- end for + return self + end + + --- User function - Add *generic* troop type loadable as cargo. This type will load directly into the heli without crates. + -- @param #CTLD self + -- @param #string Name Unique name of this type of troop. E.g. "Anti-Air Small". + -- @param #table Templates Table of #string names of late activated Wrapper.Group#GROUP making up this troop. + -- @param #CTLD_CARGO.Enum Type Type of cargo, here TROOPS - these will move to a nearby destination zone when dropped/build. + -- @param #number NoTroops Size of the group in number of Units across combined templates (for loading). + -- @param #number PerTroopMass Mass in kg of each soldier + -- @param #number Stock Number of groups in stock. Nil for unlimited. + function CTLD:AddTroopsCargo( Name, Templates, Type, NoTroops, PerTroopMass, Stock ) + self:T( self.lid .. " AddTroopsCargo" ) + self:T( { Name, Templates, Type, NoTroops, PerTroopMass, Stock } ) + self.CargoCounter = self.CargoCounter + 1 + -- Troops are directly loadable + local cargo = CTLD_CARGO:New( self.CargoCounter, Name, Templates, Type, false, true, NoTroops, nil, nil, PerTroopMass, Stock ) + table.insert( self.Cargo_Troops, cargo ) + return self + end + + --- User function - Add *generic* crate-type loadable as cargo. This type will create crates that need to be loaded, moved, dropped and built. + -- @param #CTLD self + -- @param #string Name Unique name of this type of cargo. E.g. "Humvee". + -- @param #table Templates Table of #string names of late activated Wrapper.Group#GROUP building this cargo. + -- @param #CTLD_CARGO.Enum Type Type of cargo. I.e. VEHICLE or FOB. VEHICLE will move to destination zones when dropped/build, FOB stays put. + -- @param #number NoCrates Number of crates needed to build this cargo. + -- @param #number PerCrateMass Mass in kg of each crate + -- @param #number Stock Number of groups in stock. Nil for unlimited. + function CTLD:AddCratesCargo( Name, Templates, Type, NoCrates, PerCrateMass, Stock ) + self:T( self.lid .. " AddCratesCargo" ) + self.CargoCounter = self.CargoCounter + 1 + -- Crates are not directly loadable + local cargo = CTLD_CARGO:New( self.CargoCounter, Name, Templates, Type, false, false, NoCrates, nil, nil, PerCrateMass, Stock ) + table.insert( self.Cargo_Crates, cargo ) + return self + end + + --- User function - Add *generic* static-type loadable as cargo. This type will create cargo that needs to be loaded, moved and dropped. + -- @param #CTLD self + -- @param #string Name Unique name of this type of cargo as set in the mission editor (note: UNIT name!), e.g. "Ammunition-1". + -- @param #number Mass Mass in kg of each static in kg, e.g. 100. + -- @param #number Stock Number of groups in stock. Nil for unlimited. + function CTLD:AddStaticsCargo( Name, Mass, Stock ) + self:T( self.lid .. " AddStaticsCargo" ) + self.CargoCounter = self.CargoCounter + 1 + local type = CTLD_CARGO.Enum.STATIC + local template = STATIC:FindByName( Name, true ):GetTypeName() + -- Crates are not directly loadable + local cargo = CTLD_CARGO:New( self.CargoCounter, Name, template, type, false, false, 1, nil, nil, Mass, Stock ) + table.insert( self.Cargo_Statics, cargo ) + return self + end + + --- User function - Get a *generic* static-type loadable as #CTLD_CARGO object. + -- @param #CTLD self + -- @param #string Name Unique Unit(!) name of this type of cargo as set in the mission editor (not: GROUP name!), e.g. "Ammunition-1". + -- @param #number Mass Mass in kg of each static in kg, e.g. 100. + -- @return #CTLD_CARGO Cargo object + function CTLD:GetStaticsCargoFromTemplate( Name, Mass ) + self:T( self.lid .. " GetStaticsCargoFromTemplate" ) + self.CargoCounter = self.CargoCounter + 1 + local type = CTLD_CARGO.Enum.STATIC + local template = STATIC:FindByName( Name, true ):GetTypeName() + -- Crates are not directly loadable + local cargo = CTLD_CARGO:New( self.CargoCounter, Name, template, type, false, false, 1, nil, nil, Mass, 1 ) + -- table.insert(self.Cargo_Statics,cargo) + return cargo + end + + --- User function - Add *generic* repair crates loadable as cargo. This type will create crates that need to be loaded, moved, dropped and built. + -- @param #CTLD self + -- @param #string Name Unique name of this type of cargo. E.g. "Humvee". + -- @param #string Template Template of VEHICLE or FOB cargo that this can repair. + -- @param #CTLD_CARGO.Enum Type Type of cargo, here REPAIR. + -- @param #number NoCrates Number of crates needed to build this cargo. + -- @param #number PerCrateMass Mass in kg of each crate + -- @param #number Stock Number of groups in stock. Nil for unlimited. + function CTLD:AddCratesRepair( Name, Template, Type, NoCrates, PerCrateMass, Stock ) + self:T( self.lid .. " AddCratesRepair" ) + self.CargoCounter = self.CargoCounter + 1 + -- Crates are not directly loadable + local cargo = CTLD_CARGO:New( self.CargoCounter, Name, Template, Type, false, false, NoCrates, nil, nil, PerCrateMass, Stock ) + table.insert( self.Cargo_Crates, cargo ) + return self + end + + --- User function - Add a #CTLD.CargoZoneType zone for this CTLD instance. + -- @param #CTLD self + -- @param #CTLD.CargoZone Zone Zone #CTLD.CargoZone describing the zone. + function CTLD:AddZone( Zone ) + self:T( self.lid .. " AddZone" ) + local zone = Zone -- #CTLD.CargoZone + if zone.type == CTLD.CargoZoneType.LOAD then + table.insert( self.pickupZones, zone ) + elseif zone.type == CTLD.CargoZoneType.DROP then + table.insert( self.dropOffZones, zone ) + elseif zone.type == CTLD.CargoZoneType.SHIP then + table.insert( self.shipZones, zone ) + else + table.insert( self.wpZones, zone ) + end + return self + end + + --- User function - Activate Name #CTLD.CargoZone.Type ZoneType for this CTLD instance. + -- @param #CTLD self + -- @param #string Name Name of the zone to change in the ME. + -- @param #CTLD.CargoZoneType ZoneType Type of zone this belongs to. + -- @param #boolean NewState (Optional) Set to true to activate, false to switch off. + function CTLD:ActivateZone( Name, ZoneType, NewState ) + self:T( self.lid .. " AddZone" ) + local newstate = true + -- set optional in case we're deactivating + if NewState ~= nil then + newstate = NewState + end + + -- get correct table + local table = {} + if ZoneType == CTLD.CargoZoneType.LOAD then + table = self.pickupZones + elseif ZoneType == CTLD.CargoZoneType.DROP then + table = self.dropOffZones + elseif ZoneType == CTLD.CargoZoneType.SHIP then + table = self.shipZones + else + table = self.wpZones + end + -- loop table + for _, _zone in pairs( table ) do + local thiszone = _zone -- #CTLD.CargoZone + if thiszone.name == Name then + thiszone.active = newstate + break + end + end + return self + end + + --- User function - Deactivate Name #CTLD.CargoZoneType ZoneType for this CTLD instance. + -- @param #CTLD self + -- @param #string Name Name of the zone to change in the ME. + -- @param #CTLD.CargoZoneType ZoneType Type of zone this belongs to. + function CTLD:DeactivateZone( Name, ZoneType ) + self:T( self.lid .. " AddZone" ) + self:ActivateZone( Name, ZoneType, false ) + return self + end + + --- (Internal) Function to obtain a valid FM frequency. + -- @param #CTLD self + -- @param #string Name Name of zone. + -- @return #CTLD.ZoneBeacon Beacon Beacon table. + function CTLD:_GetFMBeacon( Name ) + self:T( self.lid .. " _GetFMBeacon" ) + local beacon = {} -- #CTLD.ZoneBeacon + if #self.FreeFMFrequencies <= 1 then self.FreeFMFrequencies = self.UsedFMFrequencies self.UsedFMFrequencies = {} - end - --random - local FM = table.remove(self.FreeFMFrequencies, math.random(#self.FreeFMFrequencies)) - table.insert(self.UsedFMFrequencies, FM) - beacon.name = Name - beacon.frequency = FM / 1000000 - beacon.modulation = radio.modulation.FM - return beacon -end + end + -- random + local FM = table.remove( self.FreeFMFrequencies, math.random( #self.FreeFMFrequencies ) ) + table.insert( self.UsedFMFrequencies, FM ) + beacon.name = Name + beacon.frequency = FM / 1000000 + beacon.modulation = radio.modulation.FM + return beacon + end ---- (Internal) Function to obtain a valid UHF frequency. --- @param #CTLD self --- @param #string Name Name of zone. --- @return #CTLD.ZoneBeacon Beacon Beacon table. -function CTLD:_GetUHFBeacon(Name) - self:T(self.lid .. " _GetUHFBeacon") - local beacon = {} -- #CTLD.ZoneBeacon - if #self.FreeUHFFrequencies <= 1 then + --- (Internal) Function to obtain a valid UHF frequency. + -- @param #CTLD self + -- @param #string Name Name of zone. + -- @return #CTLD.ZoneBeacon Beacon Beacon table. + function CTLD:_GetUHFBeacon( Name ) + self:T( self.lid .. " _GetUHFBeacon" ) + local beacon = {} -- #CTLD.ZoneBeacon + if #self.FreeUHFFrequencies <= 1 then self.FreeUHFFrequencies = self.UsedUHFFrequencies self.UsedUHFFrequencies = {} - end - --random - local UHF = table.remove(self.FreeUHFFrequencies, math.random(#self.FreeUHFFrequencies)) - table.insert(self.UsedUHFFrequencies, UHF) - beacon.name = Name - beacon.frequency = UHF / 1000000 - beacon.modulation = radio.modulation.AM + end + -- random + local UHF = table.remove( self.FreeUHFFrequencies, math.random( #self.FreeUHFFrequencies ) ) + table.insert( self.UsedUHFFrequencies, UHF ) + beacon.name = Name + beacon.frequency = UHF / 1000000 + beacon.modulation = radio.modulation.AM - return beacon -end + return beacon + end ---- (Internal) Function to obtain a valid VHF frequency. --- @param #CTLD self --- @param #string Name Name of zone. --- @return #CTLD.ZoneBeacon Beacon Beacon table. -function CTLD:_GetVHFBeacon(Name) - self:T(self.lid .. " _GetVHFBeacon") - local beacon = {} -- #CTLD.ZoneBeacon - if #self.FreeVHFFrequencies <= 3 then + --- (Internal) Function to obtain a valid VHF frequency. + -- @param #CTLD self + -- @param #string Name Name of zone. + -- @return #CTLD.ZoneBeacon Beacon Beacon table. + function CTLD:_GetVHFBeacon( Name ) + self:T( self.lid .. " _GetVHFBeacon" ) + local beacon = {} -- #CTLD.ZoneBeacon + if #self.FreeVHFFrequencies <= 3 then self.FreeVHFFrequencies = self.UsedVHFFrequencies self.UsedVHFFrequencies = {} - end - --get random - local VHF = table.remove(self.FreeVHFFrequencies, math.random(#self.FreeVHFFrequencies)) - table.insert(self.UsedVHFFrequencies, VHF) - beacon.name = Name - beacon.frequency = VHF / 1000000 - beacon.modulation = radio.modulation.FM - return beacon -end - - ---- User function - Crates and adds a #CTLD.CargoZone zone for this CTLD instance. --- Zones of type LOAD: Players load crates and troops here. --- Zones of type DROP: Players can drop crates here. Note that troops can be unloaded anywhere. --- Zone of type MOVE: Dropped troops and vehicles will start moving to the nearest zone of this type (also see options). --- @param #CTLD self --- @param #string Name Name of this zone, as in Mission Editor. --- @param #string Type Type of this zone, #CTLD.CargoZoneType --- @param #number Color Smoke/Flare color e.g. #SMOKECOLOR.Red --- @param #string Active Is this zone currently active? --- @param #string HasBeacon Does this zone have a beacon if it is active? --- @param #number Shiplength Length of Ship for shipzones --- @param #number Shipwidth Width of Ship for shipzones --- @return #CTLD self -function CTLD:AddCTLDZone(Name, Type, Color, Active, HasBeacon, Shiplength, Shipwidth) - self:T(self.lid .. " AddCTLDZone") - - local ctldzone = {} -- #CTLD.CargoZone - ctldzone.active = Active or false - ctldzone.color = Color or SMOKECOLOR.Red - ctldzone.name = Name or "NONE" - ctldzone.type = Type or CTLD.CargoZoneType.MOVE -- #CTLD.CargoZoneType - ctldzone.hasbeacon = HasBeacon or false - - if HasBeacon then - ctldzone.fmbeacon = self:_GetFMBeacon(Name) - ctldzone.uhfbeacon = self:_GetUHFBeacon(Name) - ctldzone.vhfbeacon = self:_GetVHFBeacon(Name) - else - ctldzone.fmbeacon = nil - ctldzone.uhfbeacon = nil - ctldzone.vhfbeacon = nil - end - - if Type == CTLD.CargoZoneType.SHIP then - ctldzone.shiplength = Shiplength or 100 - ctldzone.shipwidth = Shipwidth or 10 - end - - self:AddZone(ctldzone) - return self -end - ---- (Internal) Function to show list of radio beacons --- @param #CTLD self --- @param Wrapper.Group#GROUP Group --- @param Wrapper.Unit#UNIT Unit -function CTLD:_ListRadioBeacons(Group, Unit) - self:T(self.lid .. " _ListRadioBeacons") - local report = REPORT:New("Active Zone Beacons") - report:Add("------------------------------------------------------------") - local zones = {[1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones, [4] = self.shipZones} - for i=1,4 do - for index,cargozone in pairs(zones[i]) do - -- Get Beacon object from zone - local czone = cargozone -- #CTLD.CargoZone - if czone.active and czone.hasbeacon then - local FMbeacon = czone.fmbeacon -- #CTLD.ZoneBeacon - local VHFbeacon = czone.vhfbeacon -- #CTLD.ZoneBeacon - local UHFbeacon = czone.uhfbeacon -- #CTLD.ZoneBeacon - local Name = czone.name - local FM = FMbeacon.frequency -- MHz - local VHF = VHFbeacon.frequency * 1000 -- KHz - local UHF = UHFbeacon.frequency -- MHz - report:AddIndent(string.format(" %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ", Name, FM, VHF, UHF),"|") - end end + -- get random + local VHF = table.remove( self.FreeVHFFrequencies, math.random( #self.FreeVHFFrequencies ) ) + table.insert( self.UsedVHFFrequencies, VHF ) + beacon.name = Name + beacon.frequency = VHF / 1000000 + beacon.modulation = radio.modulation.FM + return beacon end - if report:GetCount() == 1 then - report:Add(" N O N E") - end - report:Add("------------------------------------------------------------") - self:_SendMessage(report:Text(), 30, true, Group) - return self -end ---- (Internal) Add radio beacon to zone. Runs 30 secs. --- @param #CTLD self --- @param #string Name Name of zone. --- @param #string Sound Name of soundfile. --- @param #number Mhz Frequency in Mhz. --- @param #number Modulation Modulation AM or FM. --- @param #boolean IsShip If true zone is a ship. -function CTLD:_AddRadioBeacon(Name, Sound, Mhz, Modulation, IsShip) - self:T(self.lid .. " _AddRadioBeacon") - local Zone = nil - if IsShip then - Zone = UNIT:FindByName(Name) - else - Zone = ZONE:FindByName(Name) - end - local Sound = Sound or "beacon.ogg" - if Zone then - local ZoneCoord = Zone:GetCoordinate() - local ZoneVec3 = ZoneCoord:GetVec3() - local Frequency = Mhz * 1000000 -- Freq in Hertz - local Sound = "l10n/DEFAULT/"..Sound - trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, Frequency, 1000) -- Beacon in MP only runs for 30secs straight - end - return self -end + --- User function - Crates and adds a #CTLD.CargoZone zone for this CTLD instance. + -- Zones of type LOAD: Players load crates and troops here. + -- Zones of type DROP: Players can drop crates here. Note that troops can be unloaded anywhere. + -- Zone of type MOVE: Dropped troops and vehicles will start moving to the nearest zone of this type (also see options). + -- @param #CTLD self + -- @param #string Name Name of this zone, as in Mission Editor. + -- @param #string Type Type of this zone, #CTLD.CargoZoneType + -- @param #number Color Smoke/Flare color e.g. #SMOKECOLOR.Red + -- @param #string Active Is this zone currently active? + -- @param #string HasBeacon Does this zone have a beacon if it is active? + -- @param #number Shiplength Length of Ship for shipzones + -- @param #number Shipwidth Width of Ship for shipzones + -- @return #CTLD self + function CTLD:AddCTLDZone( Name, Type, Color, Active, HasBeacon, Shiplength, Shipwidth ) + self:T( self.lid .. " AddCTLDZone" ) ---- (Internal) Function to refresh radio beacons --- @param #CTLD self -function CTLD:_RefreshRadioBeacons() - self:T(self.lid .. " _RefreshRadioBeacons") + local ctldzone = {} -- #CTLD.CargoZone + ctldzone.active = Active or false + ctldzone.color = Color or SMOKECOLOR.Red + ctldzone.name = Name or "NONE" + ctldzone.type = Type or CTLD.CargoZoneType.MOVE -- #CTLD.CargoZoneType + ctldzone.hasbeacon = HasBeacon or false - local zones = {[1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones, [4] = self.shipZones} - for i=1,4 do - local IsShip = false - if i == 4 then IsShip = true end - for index,cargozone in pairs(zones[i]) do - -- Get Beacon object from zone - local czone = cargozone -- #CTLD.CargoZone - local Sound = self.RadioSound - if czone.active and czone.hasbeacon then - local FMbeacon = czone.fmbeacon -- #CTLD.ZoneBeacon - local VHFbeacon = czone.vhfbeacon -- #CTLD.ZoneBeacon - local UHFbeacon = czone.uhfbeacon -- #CTLD.ZoneBeacon - local Name = czone.name - local FM = FMbeacon.frequency -- MHz - local VHF = VHFbeacon.frequency -- KHz - local UHF = UHFbeacon.frequency -- MHz - self:_AddRadioBeacon(Name,Sound,FM,radio.modulation.FM, IsShip) - self:_AddRadioBeacon(Name,Sound,VHF,radio.modulation.FM, IsShip) - self:_AddRadioBeacon(Name,Sound,UHF,radio.modulation.AM, IsShip) - end - end - end - return self -end - ---- (Internal) Function to see if a unit is in a specific zone type. --- @param #CTLD self --- @param Wrapper.Unit#UNIT Unit Unit --- @param #CTLD.CargoZoneType Zonetype Zonetype --- @return #boolean Outcome Is in zone or not --- @return #string name Closest zone name --- @return Core.Zone#ZONE zone Closest Core.Zone#ZONE object --- @return #number distance Distance to closest zone --- @return #number width Radius of zone or width of ship -function CTLD:IsUnitInZone(Unit,Zonetype) - self:T(self.lid .. " IsUnitInZone") - self:T(Zonetype) - local unitname = Unit:GetName() - local zonetable = {} - local outcome = false - if Zonetype == CTLD.CargoZoneType.LOAD then - zonetable = self.pickupZones -- #table - elseif Zonetype == CTLD.CargoZoneType.DROP then - zonetable = self.dropOffZones -- #table - elseif Zonetype == CTLD.CargoZoneType.SHIP then - zonetable = self.shipZones -- #table - else - zonetable = self.wpZones -- #table - end - --- now see if we\'re in - local zonecoord = nil - local colorret = nil - local maxdist = 1000000 -- 100km - local zoneret = nil - local zonewret = nil - local zonenameret = nil - for _,_cargozone in pairs(zonetable) do - local czone = _cargozone -- #CTLD.CargoZone - local unitcoord = Unit:GetCoordinate() - local zonename = czone.name - local active = czone.active - local color = czone.color - local zone = nil - local zoneradius = 100 - local zonewidth = 20 - if Zonetype == CTLD.CargoZoneType.SHIP then - self:T("Checking Type Ship: "..zonename) - zone = UNIT:FindByName(zonename) - zonecoord = zone:GetCoordinate() - zoneradius = czone.shiplength - zonewidth = czone.shipwidth + if HasBeacon then + ctldzone.fmbeacon = self:_GetFMBeacon( Name ) + ctldzone.uhfbeacon = self:_GetUHFBeacon( Name ) + ctldzone.vhfbeacon = self:_GetVHFBeacon( Name ) else - zone = ZONE:FindByName(zonename) - zonecoord = zone:GetCoordinate() - zoneradius = zone:GetRadius() - zonewidth = zoneradius + ctldzone.fmbeacon = nil + ctldzone.uhfbeacon = nil + ctldzone.vhfbeacon = nil end - local distance = self:_GetDistance(zonecoord,unitcoord) - if distance <= zoneradius and active then - outcome = true - end - if maxdist > distance then - maxdist = distance - zoneret = zone - zonenameret = zonename - zonewret = zonewidth - colorret = color - end - end - if Zonetype == CTLD.CargoZoneType.SHIP then - return outcome, zonenameret, zoneret, maxdist, zonewret - else - return outcome, zonenameret, zoneret, maxdist - end -end ---- User function - Drop a smoke or flare at current location. --- @param #CTLD self --- @param Wrapper.Unit#UNIT Unit The Unit. --- @param #boolean Flare If true, flare instead. -function CTLD:SmokePositionNow(Unit, Flare) - self:T(self.lid .. " SmokePositionNow") - local SmokeColor = self.SmokeColor or SMOKECOLOR.Red - local FlareColor = self.FlareColor or FLARECOLOR.Red - -- table of #CTLD.CargoZone table - local unitcoord = Unit:GetCoordinate() -- Core.Point#COORDINATE - local Group = Unit:GetGroup() - if Flare then - unitcoord:Flare(FlareColor, 90) - else - local height = unitcoord:GetLandHeight() + 2 - unitcoord.y = height - unitcoord:Smoke(SmokeColor) - end - return self -end + if Type == CTLD.CargoZoneType.SHIP then + ctldzone.shiplength = Shiplength or 100 + ctldzone.shipwidth = Shipwidth or 10 + end ---- User function - Start smoke/flare in a zone close to the Unit. --- @param #CTLD self --- @param Wrapper.Unit#UNIT Unit The Unit. --- @param #boolean Flare If true, flare instead. -function CTLD:SmokeZoneNearBy(Unit, Flare) - self:T(self.lid .. " SmokeZoneNearBy") - -- table of #CTLD.CargoZone table - local unitcoord = Unit:GetCoordinate() - local Group = Unit:GetGroup() - local smokedistance = self.smokedistance - local smoked = false - local zones = {[1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones, [4] = self.shipZones} - for i=1,4 do - for index,cargozone in pairs(zones[i]) do - local CZone = cargozone --#CTLD.CargoZone - local zonename = CZone.name - local zone = nil - if i == 4 then - zone = UNIT:FindByName(zonename) - else - zone = ZONE:FindByName(zonename) - end - local zonecoord = zone:GetCoordinate() - local active = CZone.active - local color = CZone.color - local distance = self:_GetDistance(zonecoord,unitcoord) - if distance < smokedistance and active then - -- smoke zone since we\'re nearby - if not Flare then - zonecoord:Smoke(color or SMOKECOLOR.White) - else - if color == SMOKECOLOR.Blue then color = FLARECOLOR.White end - zonecoord:Flare(color or FLARECOLOR.White) + self:AddZone( ctldzone ) + return self + end + + --- (Internal) Function to show list of radio beacons + -- @param #CTLD self + -- @param Wrapper.Group#GROUP Group + -- @param Wrapper.Unit#UNIT Unit + function CTLD:_ListRadioBeacons( Group, Unit ) + self:T( self.lid .. " _ListRadioBeacons" ) + local report = REPORT:New( "Active Zone Beacons" ) + report:Add( "------------------------------------------------------------" ) + local zones = { [1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones, [4] = self.shipZones } + for i = 1, 4 do + for index, cargozone in pairs( zones[i] ) do + -- Get Beacon object from zone + local czone = cargozone -- #CTLD.CargoZone + if czone.active and czone.hasbeacon then + local FMbeacon = czone.fmbeacon -- #CTLD.ZoneBeacon + local VHFbeacon = czone.vhfbeacon -- #CTLD.ZoneBeacon + local UHFbeacon = czone.uhfbeacon -- #CTLD.ZoneBeacon + local Name = czone.name + local FM = FMbeacon.frequency -- MHz + local VHF = VHFbeacon.frequency * 1000 -- KHz + local UHF = UHFbeacon.frequency -- MHz + report:AddIndent( string.format( " %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ", Name, FM, VHF, UHF ), "|" ) end - local txt = "smoking" - if Flare then txt = "flaring" end - self:_SendMessage(string.format("Roger, %s zone %s!",txt, zonename), 10, false, Group) - smoked = true end end + if report:GetCount() == 1 then + report:Add( " N O N E" ) + end + report:Add( "------------------------------------------------------------" ) + self:_SendMessage( report:Text(), 30, true, Group ) + return self end - if not smoked then - local distance = UTILS.MetersToNM(self.smokedistance) - self:_SendMessage(string.format("Negative, need to be closer than %dnm to a zone!",distance), 10, false, Group) + + --- (Internal) Add radio beacon to zone. Runs 30 secs. + -- @param #CTLD self + -- @param #string Name Name of zone. + -- @param #string Sound Name of soundfile. + -- @param #number Mhz Frequency in Mhz. + -- @param #number Modulation Modulation AM or FM. + -- @param #boolean IsShip If true zone is a ship. + function CTLD:_AddRadioBeacon( Name, Sound, Mhz, Modulation, IsShip ) + self:T( self.lid .. " _AddRadioBeacon" ) + local Zone = nil + if IsShip then + Zone = UNIT:FindByName( Name ) + else + Zone = ZONE:FindByName( Name ) + end + local Sound = Sound or "beacon.ogg" + if Zone then + local ZoneCoord = Zone:GetCoordinate() + local ZoneVec3 = ZoneCoord:GetVec3() + local Frequency = Mhz * 1000000 -- Freq in Hertz + local Sound = "l10n/DEFAULT/" .. Sound + trigger.action.radioTransmission( Sound, ZoneVec3, Modulation, false, Frequency, 1000 ) -- Beacon in MP only runs for 30secs straight + end + return self + end + + --- (Internal) Function to refresh radio beacons + -- @param #CTLD self + function CTLD:_RefreshRadioBeacons() + self:T( self.lid .. " _RefreshRadioBeacons" ) + + local zones = { [1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones, [4] = self.shipZones } + for i = 1, 4 do + local IsShip = false + if i == 4 then + IsShip = true + end + for index, cargozone in pairs( zones[i] ) do + -- Get Beacon object from zone + local czone = cargozone -- #CTLD.CargoZone + local Sound = self.RadioSound + if czone.active and czone.hasbeacon then + local FMbeacon = czone.fmbeacon -- #CTLD.ZoneBeacon + local VHFbeacon = czone.vhfbeacon -- #CTLD.ZoneBeacon + local UHFbeacon = czone.uhfbeacon -- #CTLD.ZoneBeacon + local Name = czone.name + local FM = FMbeacon.frequency -- MHz + local VHF = VHFbeacon.frequency -- KHz + local UHF = UHFbeacon.frequency -- MHz + self:_AddRadioBeacon( Name, Sound, FM, radio.modulation.FM, IsShip ) + self:_AddRadioBeacon( Name, Sound, VHF, radio.modulation.FM, IsShip ) + self:_AddRadioBeacon( Name, Sound, UHF, radio.modulation.AM, IsShip ) + end + end + end + return self + end + + --- (Internal) Function to see if a unit is in a specific zone type. + -- @param #CTLD self + -- @param Wrapper.Unit#UNIT Unit Unit + -- @param #CTLD.CargoZoneType Zonetype Zonetype + -- @return #boolean Outcome Is in zone or not + -- @return #string name Closest zone name + -- @return Core.Zone#ZONE zone Closest Core.Zone#ZONE object + -- @return #number distance Distance to closest zone + -- @return #number width Radius of zone or width of ship + function CTLD:IsUnitInZone( Unit, Zonetype ) + self:T( self.lid .. " IsUnitInZone" ) + self:T( Zonetype ) + local unitname = Unit:GetName() + local zonetable = {} + local outcome = false + if Zonetype == CTLD.CargoZoneType.LOAD then + zonetable = self.pickupZones -- #table + elseif Zonetype == CTLD.CargoZoneType.DROP then + zonetable = self.dropOffZones -- #table + elseif Zonetype == CTLD.CargoZoneType.SHIP then + zonetable = self.shipZones -- #table + else + zonetable = self.wpZones -- #table + end + --- now see if we're in + local zonecoord = nil + local colorret = nil + local maxdist = 1000000 -- 100km + local zoneret = nil + local zonewret = nil + local zonenameret = nil + for _, _cargozone in pairs( zonetable ) do + local czone = _cargozone -- #CTLD.CargoZone + local unitcoord = Unit:GetCoordinate() + local zonename = czone.name + local active = czone.active + local color = czone.color + local zone = nil + local zoneradius = 100 + local zonewidth = 20 + if Zonetype == CTLD.CargoZoneType.SHIP then + self:T( "Checking Type Ship: " .. zonename ) + zone = UNIT:FindByName( zonename ) + zonecoord = zone:GetCoordinate() + zoneradius = czone.shiplength + zonewidth = czone.shipwidth + else + zone = ZONE:FindByName( zonename ) + zonecoord = zone:GetCoordinate() + zoneradius = zone:GetRadius() + zonewidth = zoneradius + end + local distance = self:_GetDistance( zonecoord, unitcoord ) + if distance <= zoneradius and active then + outcome = true + end + if maxdist > distance then + maxdist = distance + zoneret = zone + zonenameret = zonename + zonewret = zonewidth + colorret = color + end + end + if Zonetype == CTLD.CargoZoneType.SHIP then + return outcome, zonenameret, zoneret, maxdist, zonewret + else + return outcome, zonenameret, zoneret, maxdist + end + end + + --- User function - Drop a smoke or flare at current location. + -- @param #CTLD self + -- @param Wrapper.Unit#UNIT Unit The Unit. + -- @param #boolean Flare If true, flare instead. + function CTLD:SmokePositionNow( Unit, Flare ) + self:T( self.lid .. " SmokePositionNow" ) + local SmokeColor = self.SmokeColor or SMOKECOLOR.Red + local FlareColor = self.FlareColor or FLARECOLOR.Red + -- table of #CTLD.CargoZone table + local unitcoord = Unit:GetCoordinate() -- Core.Point#COORDINATE + local Group = Unit:GetGroup() + if Flare then + unitcoord:Flare( FlareColor, 90 ) + else + local height = unitcoord:GetLandHeight() + 2 + unitcoord.y = height + unitcoord:Smoke( SmokeColor ) + end + return self + end + + --- User function - Start smoke/flare in a zone close to the Unit. + -- @param #CTLD self + -- @param Wrapper.Unit#UNIT Unit The Unit. + -- @param #boolean Flare If true, flare instead. + function CTLD:SmokeZoneNearBy( Unit, Flare ) + self:T( self.lid .. " SmokeZoneNearBy" ) + -- table of #CTLD.CargoZone table + local unitcoord = Unit:GetCoordinate() + local Group = Unit:GetGroup() + local smokedistance = self.smokedistance + local smoked = false + local zones = { [1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones, [4] = self.shipZones } + for i = 1, 4 do + for index, cargozone in pairs( zones[i] ) do + local CZone = cargozone -- #CTLD.CargoZone + local zonename = CZone.name + local zone = nil + if i == 4 then + zone = UNIT:FindByName( zonename ) + else + zone = ZONE:FindByName( zonename ) + end + local zonecoord = zone:GetCoordinate() + local active = CZone.active + local color = CZone.color + local distance = self:_GetDistance( zonecoord, unitcoord ) + if distance < smokedistance and active then + -- smoke zone since we're nearby + if not Flare then + zonecoord:Smoke( color or SMOKECOLOR.White ) + else + if color == SMOKECOLOR.Blue then + color = FLARECOLOR.White + end + zonecoord:Flare( color or FLARECOLOR.White ) + end + local txt = "smoking" + if Flare then + txt = "flaring" + end + self:_SendMessage( string.format( "Roger, %s zone %s!", txt, zonename ), 10, false, Group ) + smoked = true + end + end + end + if not smoked then + local distance = UTILS.MetersToNM( self.smokedistance ) + self:_SendMessage( string.format( "Negative, need to be closer than %dnm to a zone!", distance ), 10, false, Group ) + end + return self end - return self -end --- User - Function to add/adjust unittype capabilities. -- @param #CTLD self @@ -3459,14 +3501,14 @@ end -- @param #number Cratelimit Unit can carry number of crates. Default 0. -- @param #number Trooplimit Unit can carry number of troops. Default 0. -- @param #number Length Unit lenght (in mteres) for the load radius. Default 20. - function CTLD:UnitCapabilities(Unittype, Cancrates, Cantroops, Cratelimit, Trooplimit, Length) - self:T(self.lid .. " UnitCapabilities") - local unittype = nil + function CTLD:UnitCapabilities( Unittype, Cancrates, Cantroops, Cratelimit, Trooplimit, Length ) + self:T( self.lid .. " UnitCapabilities" ) + local unittype = nil local unit = nil - if type(Unittype) == "string" then + if type( Unittype ) == "string" then unittype = Unittype - elseif type(Unittype) == "table" then - unit = UNIT:FindByName(Unittype) -- Wrapper.Unit#UNIT + elseif type( Unittype ) == "table" then + unit = UNIT:FindByName( Unittype ) -- Wrapper.Unit#UNIT unittype = unit:GetTypeName() else return self @@ -3476,22 +3518,22 @@ end capabilities.type = unittype capabilities.crates = Cancrates or false capabilities.troops = Cantroops or false - capabilities.cratelimit = Cratelimit or 0 + capabilities.cratelimit = Cratelimit or 0 capabilities.trooplimit = Trooplimit or 0 capabilities.length = Length or 20 self.UnitTypes[unittype] = capabilities return self end - + --- (Internal) Check if a unit is hovering *in parameters*. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit -- @return #boolean Outcome - function CTLD:IsCorrectHover(Unit) - self:T(self.lid .. " IsCorrectHover") + function CTLD:IsCorrectHover( Unit ) + self:T( self.lid .. " IsCorrectHover" ) local outcome = false -- see if we are in air and within parameters. - if self:IsUnitInAir(Unit) then + if self:IsUnitInAir( Unit ) then -- get speed and height local uspeed = Unit:GetVelocityMPS() local uheight = Unit:GetHeight() @@ -3499,108 +3541,113 @@ end local gheight = ucoord:GetLandHeight() local aheight = uheight - gheight -- height above ground local maxh = self.maximumHoverHeight -- 15 - local minh = self.minimumHoverHeight -- 5 + local minh = self.minimumHoverHeight -- 5 local mspeed = 2 -- 2 m/s - if (uspeed <= mspeed) and (aheight <= maxh) and (aheight >= minh) then + if (uspeed <= mspeed) and (aheight <= maxh) and (aheight >= minh) then -- yep within parameters outcome = true end end return outcome end - - --- (Internal) Check if a Hercules is flying *in parameters* for air drops. + + --- (Internal) Check if a Hercules is flying *in parameters* for air drops. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit -- @return #boolean Outcome - function CTLD:IsCorrectFlightParameters(Unit) - self:T(self.lid .. " IsCorrectFlightParameters") + function CTLD:IsCorrectFlightParameters( Unit ) + self:T( self.lid .. " IsCorrectFlightParameters" ) local outcome = false -- see if we are in air and within parameters. - if self:IsUnitInAir(Unit) then + if self:IsUnitInAir( Unit ) then -- get speed and height local uspeed = Unit:GetVelocityMPS() local uheight = Unit:GetHeight() local ucoord = Unit:GetCoordinate() local gheight = ucoord:GetLandHeight() local aheight = uheight - gheight -- height above ground - local maxh = self.HercMinAngels-- 1500m - local minh = self.HercMaxAngels -- 5000m - local maxspeed = self.HercMaxSpeed -- 77 mps + local maxh = self.HercMinAngels -- 1500m + local minh = self.HercMaxAngels -- 5000m + local maxspeed = self.HercMaxSpeed -- 77 mps -- DONE: TEST - Speed test for Herc, should not be above 280kph/150kn local kmspeed = uspeed * 3.6 local knspeed = kmspeed / 1.86 - self:T(string.format("%s Unit parameters: at %dm AGL with %dmps | %dkph | %dkn",self.lid,aheight,uspeed,kmspeed,knspeed)) - if (aheight <= maxh) and (aheight >= minh) and (uspeed <= maxspeed) then + self:T( string.format( "%s Unit parameters: at %dm AGL with %dmps | %dkph | %dkn", self.lid, aheight, uspeed, kmspeed, knspeed ) ) + if (aheight <= maxh) and (aheight >= minh) and (uspeed <= maxspeed) then -- yep within parameters outcome = true end end return outcome end - + --- (Internal) List if a unit is hovering *in parameters*. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit - function CTLD:_ShowHoverParams(Group,Unit) - local inhover = self:IsCorrectHover(Unit) + function CTLD:_ShowHoverParams( Group, Unit ) + local inhover = self:IsCorrectHover( Unit ) local htxt = "true" - if not inhover then htxt = "false" end + if not inhover then + htxt = "false" + end local text = "" if _SETTINGS:IsMetric() then - text = string.format("Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 2mps \n - In parameter: %s", self.minimumHoverHeight, self.maximumHoverHeight, htxt) + text = string.format( "Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 2mps \n - In parameter: %s", self.minimumHoverHeight, self.maximumHoverHeight, htxt ) else - local minheight = UTILS.MetersToFeet(self.minimumHoverHeight) - local maxheight = UTILS.MetersToFeet(self.maximumHoverHeight) - text = string.format("Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 6fts \n - In parameter: %s", minheight, maxheight, htxt) + local minheight = UTILS.MetersToFeet( self.minimumHoverHeight ) + local maxheight = UTILS.MetersToFeet( self.maximumHoverHeight ) + text = string.format( "Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 6fts \n - In parameter: %s", minheight, maxheight, htxt ) end - self:_SendMessage(text, 10, false, Group) + self:_SendMessage( text, 10, false, Group ) return self end - - --- (Internal) List if a Herc unit is flying *in parameters*. + + --- (Internal) List if a Herc unit is flying *in parameters*. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit - function CTLD:_ShowFlightParams(Group,Unit) - local inhover = self:IsCorrectFlightParameters(Unit) + function CTLD:_ShowFlightParams( Group, Unit ) + local inhover = self:IsCorrectFlightParameters( Unit ) local htxt = "true" - if not inhover then htxt = "false" end + if not inhover then + htxt = "false" + end local text = "" if _SETTINGS:IsImperial() then - local minheight = UTILS.MetersToFeet(self.HercMinAngels) - local maxheight = UTILS.MetersToFeet(self.HercMaxAngels) - text = string.format("Flight parameters (airdrop):\n - Min height %dft \n - Max height %dft \n - In parameter: %s", minheight, maxheight, htxt) + local minheight = UTILS.MetersToFeet( self.HercMinAngels ) + local maxheight = UTILS.MetersToFeet( self.HercMaxAngels ) + text = string.format( "Flight parameters (airdrop):\n - Min height %dft \n - Max height %dft \n - In parameter: %s", minheight, maxheight, htxt ) else local minheight = self.HercMinAngels local maxheight = self.HercMaxAngels - text = string.format("Flight parameters (airdrop):\n - Min height %dm \n - Max height %dm \n - In parameter: %s", minheight, maxheight, htxt) + text = string.format( "Flight parameters (airdrop):\n - Min height %dm \n - Max height %dm \n - In parameter: %s", minheight, maxheight, htxt ) end - self:_SendMessage(text, 10, false, Group) + self:_SendMessage( text, 10, false, Group ) return self end - - + --- (Internal) Check if a unit is in a load zone and is hovering in parameters. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit -- @return #boolean Outcome - function CTLD:CanHoverLoad(Unit) - self:T(self.lid .. " CanHoverLoad") - if self:IsHercules(Unit) then return false end - local outcome = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) and self:IsCorrectHover(Unit) + function CTLD:CanHoverLoad( Unit ) + self:T( self.lid .. " CanHoverLoad" ) + if self:IsHercules( Unit ) then + return false + end + local outcome = self:IsUnitInZone( Unit, CTLD.CargoZoneType.LOAD ) and self:IsCorrectHover( Unit ) if not outcome then - outcome = self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) --and self:IsCorrectHover(Unit) + outcome = self:IsUnitInZone( Unit, CTLD.CargoZoneType.SHIP ) -- and self:IsCorrectHover(Unit) end return outcome end - + --- (Internal) Check if a unit is above ground. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit -- @return #boolean Outcome - function CTLD:IsUnitInAir(Unit) + function CTLD:IsUnitInAir( Unit ) -- get speed and height local minheight = self.minimumHoverHeight if self.enableHercules and Unit:GetTypeName() == "Hercules" then @@ -3616,18 +3663,18 @@ end return false end end - + --- (Internal) Autoload if we can do crates, have capacity free and are in a load zone. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit -- @return #CTLD self - function CTLD:AutoHoverLoad(Unit) - self:T(self.lid .. " AutoHoverLoad") + function CTLD:AutoHoverLoad( Unit ) + self:T( self.lid .. " AutoHoverLoad" ) -- get capabilities and current load local unittype = Unit:GetTypeName() local unitname = Unit:GetName() local Group = Unit:GetGroup() - local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities + local capabilities = self:_GetUnitCapabilities( Unit ) -- #CTLD.UnitCapabilities local cancrates = capabilities.crates -- #boolean local cratelimit = capabilities.cratelimit -- #number if cancrates then @@ -3639,27 +3686,29 @@ end numberonboard = loaded.Cratesloaded or 0 end local load = cratelimit - numberonboard - local canload = self:CanHoverLoad(Unit) + local canload = self:CanHoverLoad( Unit ) if canload and load > 0 then - self:_LoadCratesNearby(Group,Unit) + self:_LoadCratesNearby( Group, Unit ) end end return self end - + --- (Internal) Run through all pilots and see if we autoload. -- @param #CTLD self -- @return #CTLD self function CTLD:CheckAutoHoverload() if self.hoverautoloading then - for _,_pilot in pairs (self.CtldUnits) do - local Unit = UNIT:FindByName(_pilot) - if self:CanHoverLoad(Unit) then self:AutoHoverLoad(Unit) end + for _, _pilot in pairs( self.CtldUnits ) do + local Unit = UNIT:FindByName( _pilot ) + if self:CanHoverLoad( Unit ) then + self:AutoHoverLoad( Unit ) + end end end return self end - + --- (Internal) Run through DroppedTroops and capture alive units -- @param #CTLD self -- @return #CTLD self @@ -3667,8 +3716,8 @@ end -- Troops local troops = self.DroppedTroops local newtable = {} - for _index, _group in pairs (troops) do - self:T({_group.ClassName}) + for _index, _group in pairs( troops ) do + self:T( { _group.ClassName } ) if _group and _group.ClassName == "GROUP" then if _group:IsAlive() then newtable[_index] = _group @@ -3679,9 +3728,9 @@ end -- Engineers local engineers = self.EngineersInField local engtable = {} - for _index, _group in pairs (engineers) do - self:T({_group.ClassName}) - if _group and _group:IsNotStatus("Stopped") then + for _index, _group in pairs( engineers ) do + self:T( { _group.ClassName } ) + if _group and _group:IsNotStatus( "Stopped" ) then engtable[_index] = _group end end @@ -3694,91 +3743,91 @@ end -- @param #string Name Name as defined in the generic cargo. -- @param #number Number Number of units/groups to add. -- @return #CTLD self - function CTLD:AddStockTroops(Name, Number) + function CTLD:AddStockTroops( Name, Number ) local name = Name or "none" local number = Number or 1 -- find right generic type local gentroops = self.Cargo_Troops - for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO + for _id, _troop in pairs( gentroops ) do -- #number, #CTLD_CARGO if _troop.Name == name then - _troop:AddStock(number) + _troop:AddStock( number ) end end end - + --- User - function to add stock of a certain crates type -- @param #CTLD self -- @param #string Name Name as defined in the generic cargo. -- @param #number Number Number of units/groups to add. -- @return #CTLD self - function CTLD:AddStockCrates(Name, Number) + function CTLD:AddStockCrates( Name, Number ) local name = Name or "none" local number = Number or 1 -- find right generic type local gentroops = self.Cargo_Crates - for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO + for _id, _troop in pairs( gentroops ) do -- #number, #CTLD_CARGO if _troop.Name == name then - _troop:AddStock(number) + _troop:AddStock( number ) end end end - + --- User - function to remove stock of a certain troops type -- @param #CTLD self -- @param #string Name Name as defined in the generic cargo. -- @param #number Number Number of units/groups to add. -- @return #CTLD self - function CTLD:RemoveStockTroops(Name, Number) + function CTLD:RemoveStockTroops( Name, Number ) local name = Name or "none" local number = Number or 1 -- find right generic type local gentroops = self.Cargo_Troops - for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO + for _id, _troop in pairs( gentroops ) do -- #number, #CTLD_CARGO if _troop.Name == name then - _troop:RemoveStock(number) + _troop:RemoveStock( number ) end end end - + --- User - function to remove stock of a certain crates type -- @param #CTLD self -- @param #string Name Name as defined in the generic cargo. -- @param #number Number Number of units/groups to add. -- @return #CTLD self - function CTLD:RemoveStockCrates(Name, Number) + function CTLD:RemoveStockCrates( Name, Number ) local name = Name or "none" local number = Number or 1 -- find right generic type local gentroops = self.Cargo_Crates - for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO + for _id, _troop in pairs( gentroops ) do -- #number, #CTLD_CARGO if _troop.Name == name then - _troop:RemoveStock(number) + _troop:RemoveStock( number ) end end return self end - + --- (Internal) Check on engineering teams -- @param #CTLD self -- @return #CTLD self function CTLD:_CheckEngineers() - self:T(self.lid.." CheckEngineers") + self:T( self.lid .. " CheckEngineers" ) local engtable = self.EngineersInField - for _ind,_engineers in pairs (engtable) do + for _ind, _engineers in pairs( engtable ) do local engineers = _engineers -- #CTLD_ENGINEERING local wrenches = engineers.Group -- Wrapper.Group#GROUP - self:T(_engineers.lid .. _engineers:GetStatus()) + self:T( _engineers.lid .. _engineers:GetStatus() ) if wrenches and wrenches:IsAlive() then - if engineers:IsStatus("Running") or engineers:IsStatus("Searching") then - local crates,number = self:_FindCratesNearby(wrenches,nil, self.EngineerSearch) -- #table - engineers:Search(crates,number) - elseif engineers:IsStatus("Moving") then + if engineers:IsStatus( "Running" ) or engineers:IsStatus( "Searching" ) then + local crates, number = self:_FindCratesNearby( wrenches, nil, self.EngineerSearch ) -- #table + engineers:Search( crates, number ) + elseif engineers:IsStatus( "Moving" ) then engineers:Move() - elseif engineers:IsStatus("Arrived") then + elseif engineers:IsStatus( "Arrived" ) then engineers:Build() - local unit = wrenches:GetUnit(1) - self:_BuildCrates(wrenches,unit,true) - self:_RepairCrates(wrenches,unit,true) + local unit = wrenches:GetUnit( 1 ) + self:_BuildCrates( wrenches, unit, true ) + self:_RepairCrates( wrenches, unit, true ) engineers:Done() end else @@ -3787,7 +3836,7 @@ end end return self end - + --- (User) Pre-populate troops in the field. -- @param #CTLD self -- @param Core.Zone#ZONE Zone The zone where to drop the troops. @@ -3800,15 +3849,15 @@ end -- local dropzone = ZONE:New("InjectZone") -- Core.Zone#ZONE -- -- and go: -- my_ctld:InjectTroops(dropzone,InjectTroopsType) - function CTLD:InjectTroops(Zone,Cargo) - self:T(self.lid.." InjectTroops") + function CTLD:InjectTroops( Zone, Cargo ) + self:T( self.lid .. " InjectTroops" ) local cargo = Cargo -- #CTLD_CARGO - - local function IsTroopsMatch(cargo) + + local function IsTroopsMatch( cargo ) local match = false local cgotbl = self.Cargo_Troops local name = cargo:GetName() - for _,_cgo in pairs (cgotbl) do + for _, _cgo in pairs( cgotbl ) do local cname = _cgo:GetName() if name == cname then match = true @@ -3817,14 +3866,14 @@ end end return match end - - if not IsTroopsMatch(cargo) then + + if not IsTroopsMatch( cargo ) then self.CargoCounter = self.CargoCounter + 1 cargo.ID = self.CargoCounter cargo.Stock = 1 - table.insert(self.Cargo_Troops,cargo) + table.insert( self.Cargo_Troops, cargo ) end - + local type = cargo:GetType() -- #CTLD_CARGO.Enum if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) then -- unload @@ -3832,37 +3881,37 @@ end local temptable = cargo:GetTemplates() or {} local factor = 1.5 local zone = Zone - - local randomcoord = zone:GetRandomCoordinate(10,30*factor):GetVec2() - for _,_template in pairs(temptable) do + + local randomcoord = zone:GetRandomCoordinate( 10, 30 * factor ):GetVec2() + for _, _template in pairs( temptable ) do self.TroopCounter = self.TroopCounter + 1 - local alias = string.format("%s-%d", _template, math.random(1,100000)) - self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) - :InitRandomizeUnits(true,20,2) + local alias = string.format( "%s-%d", _template, math.random( 1, 100000 ) ) + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias( _template, alias ) + :InitRandomizeUnits( true, 20, 2 ) :InitDelayOff() - :SpawnFromVec2(randomcoord) + :SpawnFromVec2( randomcoord ) if self.movetroopstowpzone and type ~= CTLD_CARGO.Enum.ENGINEERS then - self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) + self:_MoveGroupToZone( self.DroppedTroops[self.TroopCounter] ) end end -- template loop - cargo:SetWasDropped(true) + cargo:SetWasDropped( true ) -- engineering group? if type == CTLD_CARGO.Enum.ENGINEERS then self.Engineers = self.Engineers + 1 local grpname = self.DroppedTroops[self.TroopCounter]:GetName() - self.EngineersInField[self.Engineers] = CTLD_ENGINEERING:New(name, grpname) - --self:I(string.format("%s Injected Engineers %s into action!",self.lid, name)) + self.EngineersInField[self.Engineers] = CTLD_ENGINEERING:New( name, grpname ) + -- self:I(string.format("%s Injected Engineers %s into action!",self.lid, name)) else - --self:I(string.format("%s Injected Troops %s into action!",self.lid, name)) + -- self:I(string.format("%s Injected Troops %s into action!",self.lid, name)) end if self.eventoninject then - self:__TroopsDeployed(1,nil,nil,self.DroppedTroops[self.TroopCounter]) + self:__TroopsDeployed( 1, nil, nil, self.DroppedTroops[self.TroopCounter] ) end end -- if type end return self end - - --- (User) Pre-populate vehicles in the field. + + --- (User) Pre-populate vehicles in the field. -- @param #CTLD self -- @param Core.Zone#ZONE Zone The zone where to drop the troops. -- @param Ops.CTLD#CTLD_CARGO Cargo The #CTLD_CARGO object to spawn. @@ -3874,15 +3923,15 @@ end -- local dropzone = ZONE:New("InjectZone") -- Core.Zone#ZONE -- -- and go: -- my_ctld:InjectVehicles(dropzone,InjectVehicleType) - function CTLD:InjectVehicles(Zone,Cargo) - self:T(self.lid.." InjectVehicles") + function CTLD:InjectVehicles( Zone, Cargo ) + self:T( self.lid .. " InjectVehicles" ) local cargo = Cargo -- #CTLD_CARGO - - local function IsVehicMatch(cargo) + + local function IsVehicMatch( cargo ) local match = false local cgotbl = self.Cargo_Crates local name = cargo:GetName() - for _,_cgo in pairs (cgotbl) do + for _, _cgo in pairs( cgotbl ) do local cname = _cgo:GetName() if name == cname then match = true @@ -3891,14 +3940,14 @@ end end return match end - - if not IsVehicMatch(cargo) then + + if not IsVehicMatch( cargo ) then self.CargoCounter = self.CargoCounter + 1 cargo.ID = self.CargoCounter cargo.Stock = 1 - table.insert(self.Cargo_Crates,cargo) + table.insert( self.Cargo_Crates, cargo ) end - + local type = cargo:GetType() -- #CTLD_CARGO.Enum if (type == CTLD_CARGO.Enum.VEHICLE or type == CTLD_CARGO.Enum.FOB) then -- unload @@ -3906,37 +3955,39 @@ end local temptable = cargo:GetTemplates() or {} local factor = 1.5 local zone = Zone - local randomcoord = zone:GetRandomCoordinate(10,30*factor):GetVec2() - cargo:SetWasDropped(true) + local randomcoord = zone:GetRandomCoordinate( 10, 30 * factor ):GetVec2() + cargo:SetWasDropped( true ) local canmove = false - if type == CTLD_CARGO.Enum.VEHICLE then canmove = true end - for _,_template in pairs(temptable) do + if type == CTLD_CARGO.Enum.VEHICLE then + canmove = true + end + for _, _template in pairs( temptable ) do self.TroopCounter = self.TroopCounter + 1 - local alias = string.format("%s-%d", _template, math.random(1,100000)) + local alias = string.format( "%s-%d", _template, math.random( 1, 100000 ) ) if canmove then - self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) - :InitRandomizeUnits(true,20,2) + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias( _template, alias ) + :InitRandomizeUnits( true, 20, 2 ) :InitDelayOff() - :SpawnFromVec2(randomcoord) + :SpawnFromVec2( randomcoord ) else -- don't random position of e.g. SAM units build as FOB - self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias( _template, alias ) :InitDelayOff() - :SpawnFromVec2(randomcoord) + :SpawnFromVec2( randomcoord ) end if self.movetroopstowpzone and canmove then - self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) + self:_MoveGroupToZone( self.DroppedTroops[self.TroopCounter] ) end if self.eventoninject then - self:__CratesBuild(1,nil,nil,self.DroppedTroops[self.TroopCounter]) + self:__CratesBuild( 1, nil, nil, self.DroppedTroops[self.TroopCounter] ) end end -- end loop end -- if type end return self end - -------------------------------------------------------------------- --- FSM functions -------------------------------------------------------------------- + + ------------------------------------------------------------------- + -- FSM functions + ------------------------------------------------------------------- --- (Internal) FSM Function onafterStart. -- @param #CTLD self @@ -3944,31 +3995,31 @@ end -- @param #string Event Trigger. -- @param #string To State. -- @return #CTLD self - function CTLD:onafterStart(From, Event, To) - self:T({From, Event, To}) - self:I(self.lid .. "Started ("..self.version..")") + function CTLD:onafterStart( From, Event, To ) + self:T( { From, Event, To } ) + self:I( self.lid .. "Started (" .. self.version .. ")" ) if self.useprefix or self.enableHercules then local prefix = self.prefixes if self.enableHercules then - self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterStart() + self.PilotGroups = SET_GROUP:New():FilterCoalitions( self.coalitiontxt ):FilterPrefixes( prefix ):FilterStart() else - self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterCategories("helicopter"):FilterStart() + self.PilotGroups = SET_GROUP:New():FilterCoalitions( self.coalitiontxt ):FilterPrefixes( prefix ):FilterCategories( "helicopter" ):FilterStart() end else - self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategories("helicopter"):FilterStart() + self.PilotGroups = SET_GROUP:New():FilterCoalitions( self.coalitiontxt ):FilterCategories( "helicopter" ):FilterStart() end -- Events - self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) - self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) - self:HandleEvent(EVENTS.PlayerLeaveUnit, self._EventHandler) - self:__Status(-5) - + self:HandleEvent( EVENTS.PlayerEnterAircraft, self._EventHandler ) + self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventHandler ) + self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventHandler ) + self:__Status( -5 ) + -- AutoSave if self.enableLoadSave then local interval = self.saveinterval local filename = self.filename local filepath = self.filepath - self:__Save(interval,filepath,filename) + self:__Save( interval, filepath, filename ) end return self end @@ -3979,8 +4030,8 @@ end -- @param #string Event Trigger. -- @param #string To State. -- @return #CTLD self - function CTLD:onbeforeStatus(From, Event, To) - self:T({From, Event, To}) + function CTLD:onbeforeStatus( From, Event, To ) + self:T( { From, Event, To } ) self:CleanDroppedTroops() self:_RefreshF10Menus() self:_RefreshRadioBeacons() @@ -3988,71 +4039,71 @@ end self:_CheckEngineers() return self end - + --- (Internal) FSM Function onafterStatus. -- @param #CTLD self -- @param #string From State. -- @param #string Event Trigger. -- @param #string To State. -- @return #CTLD self - function CTLD:onafterStatus(From, Event, To) - self:T({From, Event, To}) - -- gather some stats + function CTLD:onafterStatus( From, Event, To ) + self:T( { From, Event, To } ) + -- gather some stats -- pilots local pilots = 0 - for _,_pilot in pairs (self.CtldUnits) do - pilots = pilots + 1 + for _, _pilot in pairs( self.CtldUnits ) do + pilots = pilots + 1 end - + -- spawned cargo boxes curr in field local boxes = 0 - for _,_pilot in pairs (self.Spawned_Cargo) do - boxes = boxes + 1 + for _, _pilot in pairs( self.Spawned_Cargo ) do + boxes = boxes + 1 end - - local cc = self.CargoCounter + + local cc = self.CargoCounter local tc = self.TroopCounter - - if self.debug or self.verbose > 0 then - local text = string.format("%s Pilots %d | Live Crates %d |\nCargo Counter %d | Troop Counter %d", self.lid, pilots, boxes, cc, tc) - local m = MESSAGE:New(text,10,"CTLD"):ToAll() + + if self.debug or self.verbose > 0 then + local text = string.format( "%s Pilots %d | Live Crates %d |\nCargo Counter %d | Troop Counter %d", self.lid, pilots, boxes, cc, tc ) + local m = MESSAGE:New( text, 10, "CTLD" ):ToAll() if self.verbose > 0 then - self:I(self.lid.."Cargo and Troops in Stock:") - for _,_troop in pairs (self.Cargo_Crates) do + self:I( self.lid .. "Cargo and Troops in Stock:" ) + for _, _troop in pairs( self.Cargo_Crates ) do local name = _troop:GetName() local stock = _troop:GetStock() - self:I(string.format("-- %s \t\t\t %d", name, stock)) + self:I( string.format( "-- %s \t\t\t %d", name, stock ) ) end - for _,_troop in pairs (self.Cargo_Statics) do + for _, _troop in pairs( self.Cargo_Statics ) do local name = _troop:GetName() local stock = _troop:GetStock() - self:I(string.format("-- %s \t\t\t %d", name, stock)) + self:I( string.format( "-- %s \t\t\t %d", name, stock ) ) end - for _,_troop in pairs (self.Cargo_Troops) do + for _, _troop in pairs( self.Cargo_Troops ) do local name = _troop:GetName() local stock = _troop:GetStock() - self:I(string.format("-- %s \t\t %d", name, stock)) + self:I( string.format( "-- %s \t\t %d", name, stock ) ) end end end - self:__Status(-30) + self:__Status( -30 ) return self end - + --- (Internal) FSM Function onafterStop. -- @param #CTLD self -- @param #string From State. -- @param #string Event Trigger. -- @param #string To State. -- @return #CTLD self - function CTLD:onafterStop(From, Event, To) - self:T({From, Event, To}) - self:UnhandleEvent(EVENTS.PlayerEnterAircraft) - self:UnhandleEvent(EVENTS.PlayerEnterUnit) - self:UnhandleEvent(EVENTS.PlayerLeaveUnit) + function CTLD:onafterStop( From, Event, To ) + self:T( { From, Event, To } ) + self:UnhandleEvent( EVENTS.PlayerEnterAircraft ) + self:UnhandleEvent( EVENTS.PlayerEnterUnit ) + self:UnhandleEvent( EVENTS.PlayerLeaveUnit ) return self end - + --- (Internal) FSM Function onbeforeTroopsPickedUp. -- @param #CTLD self -- @param #string From State. @@ -4062,12 +4113,12 @@ end -- @param Wrapper.Unit#UNIT Unit Unit Object. -- @param #CTLD_CARGO Cargo Cargo crate. -- @return #CTLD self - function CTLD:onbeforeTroopsPickedUp(From, Event, To, Group, Unit, Cargo) - self:T({From, Event, To}) + function CTLD:onbeforeTroopsPickedUp( From, Event, To, Group, Unit, Cargo ) + self:T( { From, Event, To } ) return self end - - --- (Internal) FSM Function onbeforeCratesPickedUp. + + --- (Internal) FSM Function onbeforeCratesPickedUp. -- @param #CTLD self -- @param #string From State . -- @param #string Event Trigger. @@ -4076,26 +4127,25 @@ end -- @param Wrapper.Unit#UNIT Unit Unit Object. -- @param #CTLD_CARGO Cargo Cargo crate. -- @return #CTLD self - function CTLD:onbeforeCratesPickedUp(From, Event, To, Group, Unit, Cargo) - self:T({From, Event, To}) + function CTLD:onbeforeCratesPickedUp( From, Event, To, Group, Unit, Cargo ) + self:T( { From, Event, To } ) return self end - - --- (Internal) FSM Function onbeforeTroopsExtracted. - -- @param #CTLD self - -- @param #string From State. - -- @param #string Event Trigger. - -- @param #string To State. - -- @param Wrapper.Group#GROUP Group Group Object. - -- @param Wrapper.Unit#UNIT Unit Unit Object. - -- @param Wrapper.Group#GROUP Troops Troops #GROUP Object. - -- @return #CTLD self - function CTLD:onbeforeTroopsExtracted(From, Event, To, Group, Unit, Troops) - self:T({From, Event, To}) - return self - end - - + + --- (Internal) FSM Function onbeforeTroopsExtracted. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param Wrapper.Group#GROUP Troops Troops #GROUP Object. + -- @return #CTLD self + function CTLD:onbeforeTroopsExtracted( From, Event, To, Group, Unit, Troops ) + self:T( { From, Event, To } ) + return self + end + --- (Internal) FSM Function onbeforeTroopsDeployed. -- @param #CTLD self -- @param #string From State. @@ -4105,11 +4155,11 @@ end -- @param Wrapper.Unit#UNIT Unit Unit Object. -- @param Wrapper.Group#GROUP Troops Troops #GROUP Object. -- @return #CTLD self - function CTLD:onbeforeTroopsDeployed(From, Event, To, Group, Unit, Troops) - self:T({From, Event, To}) + function CTLD:onbeforeTroopsDeployed( From, Event, To, Group, Unit, Troops ) + self:T( { From, Event, To } ) return self end - + --- (Internal) FSM Function onbeforeCratesDropped. -- @param #CTLD self -- @param #string From State. @@ -4119,11 +4169,11 @@ end -- @param Wrapper.Unit#UNIT Unit Unit Object. -- @param #table Cargotable Table of #CTLD_CARGO objects dropped. -- @return #CTLD self - function CTLD:onbeforeCratesDropped(From, Event, To, Group, Unit, Cargotable) - self:T({From, Event, To}) + function CTLD:onbeforeCratesDropped( From, Event, To, Group, Unit, Cargotable ) + self:T( { From, Event, To } ) return self end - + --- (Internal) FSM Function onbeforeCratesBuild. -- @param #CTLD self -- @param #string From State. @@ -4133,11 +4183,11 @@ end -- @param Wrapper.Unit#UNIT Unit Unit Object. -- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB build. -- @return #CTLD self - function CTLD:onbeforeCratesBuild(From, Event, To, Group, Unit, Vehicle) - self:T({From, Event, To}) + function CTLD:onbeforeCratesBuild( From, Event, To, Group, Unit, Vehicle ) + self:T( { From, Event, To } ) return self end - + --- (Internal) FSM Function onbeforeTroopsRTB. -- @param #CTLD self -- @param #string From State. @@ -4146,11 +4196,11 @@ end -- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Unit#UNIT Unit Unit Object. -- @return #CTLD self - function CTLD:onbeforeTroopsRTB(From, Event, To, Group, Unit) - self:T({From, Event, To}) + function CTLD:onbeforeTroopsRTB( From, Event, To, Group, Unit ) + self:T( { From, Event, To } ) return self end - + --- On before "Save" event. Checks if io and lfs are available. -- @param #CTLD self -- @param #string From From state. @@ -4158,26 +4208,26 @@ end -- @param #string To To state. -- @param #string path (Optional) Path where the file is saved. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized. -- @param #string filename (Optional) File name for saving. Default is "CTLD__Persist.csv". - function CTLD:onbeforeSave(From, Event, To, path, filename) - self:T({From, Event, To, path, filename}) + function CTLD:onbeforeSave( From, Event, To, path, filename ) + self:T( { From, Event, To, path, filename } ) if not self.enableLoadSave then return self end -- Thanks to @FunkyFranky -- Check io module is available. if not io then - self:E(self.lid.."ERROR: io not desanitized. Can't save current state.") + self:E( self.lid .. "ERROR: io not desanitized. Can't save current state." ) return false end - + -- Check default path. - if path==nil and not lfs then - self:E(self.lid.."WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") + if path == nil and not lfs then + self:E( self.lid .. "WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder." ) end - + return true end - + --- On after "Save" event. Player data is saved to file. -- @param #CTLD self -- @param #string From From state. @@ -4185,89 +4235,90 @@ end -- @param #string To To state. -- @param #string path Path where the file is saved. If nil, file is saved in the DCS root installtion directory or your "Saved Games" folder if lfs was desanitized. -- @param #string filename (Optional) File name for saving. Default is Default is "CTLD__Persist.csv". - function CTLD:onafterSave(From, Event, To, path, filename) - self:T({From, Event, To, path, filename}) + function CTLD:onafterSave( From, Event, To, path, filename ) + self:T( { From, Event, To, path, filename } ) -- Thanks to @FunkyFranky if not self.enableLoadSave then return self end --- Function that saves data to file - local function _savefile(filename, data) - local f = assert(io.open(filename, "wb")) - f:write(data) + local function _savefile( filename, data ) + local f = assert( io.open( filename, "wb" ) ) + f:write( data ) f:close() end - + -- Set path or default. if lfs then - path=self.filepath or lfs.writedir() + path = self.filepath or lfs.writedir() end - + -- Set file name. - filename=filename or self.filename - + filename = filename or self.filename + -- Set path. - if path~=nil then - filename=path.."\\"..filename + if path ~= nil then + filename = path .. "\\" .. filename end - + local grouptable = self.DroppedTroops -- #table local cgovehic = self.Cargo_Crates local cgotable = self.Cargo_Troops local stcstable = self.Spawned_Cargo - + local statics = nil local statics = {} - self:I(self.lid.."Bulding Statics Table for Saving") - for _,_cargo in pairs (stcstable) do + self:I( self.lid .. "Bulding Statics Table for Saving" ) + for _, _cargo in pairs( stcstable ) do local cargo = _cargo -- #CTLD_CARGO local object = cargo:GetPositionable() -- Wrapper.Static#STATIC if object and object:IsAlive() and cargo:WasDropped() then - self:I({_cargo}) - statics[#statics+1] = cargo + self:I( { _cargo } ) + statics[#statics + 1] = cargo end end - + -- find matching cargo - local function FindCargoType(name,table) + local function FindCargoType( name, table ) -- name matching a template in the table local match = false local cargo = nil - for _ind,_cargo in pairs (table) do + for _ind, _cargo in pairs( table ) do local thiscargo = _cargo -- #CTLD_CARGO local template = thiscargo:GetTemplates() - if type(template) == "string" then + if type( template ) == "string" then template = { template } end - for _,_name in pairs (template) do - --self:I(string.format("*** Saving CTLD: Matching %s with %s",name,_name)) - if string.find(name,_name) and _cargo:GetType() ~= CTLD_CARGO.Enum.REPAIR then + for _, _name in pairs( template ) do + -- self:I(string.format("*** Saving CTLD: Matching %s with %s",name,_name)) + if string.find( name, _name ) and _cargo:GetType() ~= CTLD_CARGO.Enum.REPAIR then match = true cargo = thiscargo end end - if match then break end + if match then + break + end end return match, cargo end - - - --local data = "LoadedData = {\n" + + -- local data = "LoadedData = {\n" local data = "Group,x,y,z,CargoName,CargoTemplates,CargoType,CratesNeeded,CrateMass\n" local n = 0 - for _,_grp in pairs(grouptable) do + for _, _grp in pairs( grouptable ) do local group = _grp -- Wrapper.Group#GROUP if group and group:IsAlive() then -- get template name local name = group:GetName() - local template = string.gsub(name,"-(.+)$","") - if string.find(template,"#") then - template = string.gsub(name,"#(%d+)$","") + local template = string.gsub( name, "-(.+)$", "" ) + if string.find( template, "#" ) then + template = string.gsub( name, "#(%d+)$", "" ) end - - local match, cargo = FindCargoType(template,cgotable) + + local match, cargo = FindCargoType( template, cgotable ) if not match then - match, cargo = FindCargoType(template,cgovehic) + match, cargo = FindCargoType( template, cgovehic ) end if match then n = n + 1 @@ -4277,56 +4328,54 @@ end local cgotype = cargo.CargoType local cgoneed = cargo.CratesNeeded local cgomass = cargo.PerCrateMass - - if type(cgotemp) == "table" then + + if type( cgotemp ) == "table" then local templates = "{" - for _,_tmpl in pairs(cgotemp) do + for _, _tmpl in pairs( cgotemp ) do templates = templates .. _tmpl .. ";" end templates = templates .. "}" cgotemp = templates end - + local location = group:GetVec3() - local txt = string.format("%s,%d,%d,%d,%s,%s,%s,%d,%d\n" - ,template,location.x,location.y,location.z,cgoname,cgotemp,cgotype,cgoneed,cgomass) + local txt = string.format( "%s,%d,%d,%d,%s,%s,%s,%d,%d\n", template, location.x, location.y, location.z, cgoname, cgotemp, cgotype, cgoneed, cgomass ) data = data .. txt end end end - - for _,_cgo in pairs(statics) do + + for _, _cgo in pairs( statics ) do local object = _cgo -- #CTLD_CARGO local cgoname = object.Name local cgotemp = object.Templates - - if type(cgotemp) == "table" then + + if type( cgotemp ) == "table" then local templates = "{" - for _,_tmpl in pairs(cgotemp) do + for _, _tmpl in pairs( cgotemp ) do templates = templates .. _tmpl .. ";" end templates = templates .. "}" cgotemp = templates end - + local cgotype = object.CargoType local cgoneed = object.CratesNeeded local cgomass = object.PerCrateMass local crateobj = object.Positionable local location = crateobj:GetVec3() - local txt = string.format("%s,%d,%d,%d,%s,%s,%s,%d,%d\n" - ,"STATIC",location.x,location.y,location.z,cgoname,cgotemp,cgotype,cgoneed,cgomass) + local txt = string.format( "%s,%d,%d,%d,%s,%s,%s,%d,%d\n", "STATIC", location.x, location.y, location.z, cgoname, cgotemp, cgotype, cgoneed, cgomass ) data = data .. txt end - - _savefile(filename, data) - + + _savefile( filename, data ) + -- AutoSave if self.enableLoadSave then local interval = self.saveinterval local filename = self.filename local filepath = self.filepath - self:__Save(interval,filepath,filename) + self:__Save( interval, filepath, filename ) end return self end @@ -4338,58 +4387,58 @@ end -- @param #string To To state. -- @param #string path (Optional) Path where the file is located. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized. -- @param #string filename (Optional) File name for loading. Default is "CTLD__Persist.csv". - function CTLD:onbeforeLoad(From, Event, To, path, filename) - self:T({From, Event, To, path, filename}) + function CTLD:onbeforeLoad( From, Event, To, path, filename ) + self:T( { From, Event, To, path, filename } ) if not self.enableLoadSave then return self end --- Function that check if a file exists. - local function _fileexists(name) - local f=io.open(name,"r") - if f~=nil then - io.close(f) + local function _fileexists( name ) + local f = io.open( name, "r" ) + if f ~= nil then + io.close( f ) return true else return false end end - + -- Set file name and path - filename=filename or self.filename + filename = filename or self.filename path = path or self.filepath - + -- Check io module is available. if not io then - self:E(self.lid.."WARNING: io not desanitized. Cannot load file.") + self:E( self.lid .. "WARNING: io not desanitized. Cannot load file." ) return false end - + -- Check default path. - if path==nil and not lfs then - self:E(self.lid.."WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") + if path == nil and not lfs then + self:E( self.lid .. "WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder." ) end - + -- Set path or default. if lfs then - path=path or lfs.writedir() + path = path or lfs.writedir() end - + -- Set path. - if path~=nil then - filename=path.."\\"..filename + if path ~= nil then + filename = path .. "\\" .. filename end - + -- Check if file exists. - local exists=_fileexists(filename) - + local exists = _fileexists( filename ) + if exists then return true else - self:E(self.lid..string.format("WARNING: State file %s might not exist.", filename)) + self:E( self.lid .. string.format( "WARNING: State file %s might not exist.", filename ) ) return false - --return self + -- return self end - + end --- On after "Load" event. Loads dropped units from file. @@ -4399,98 +4448,98 @@ end -- @param #string To To state. -- @param #string path (Optional) Path where the file is located. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized. -- @param #string filename (Optional) File name for loading. Default is "CTLD__Persist.csv". - function CTLD:onafterLoad(From, Event, To, path, filename) - self:T({From, Event, To, path, filename}) + function CTLD:onafterLoad( From, Event, To, path, filename ) + self:T( { From, Event, To, path, filename } ) if not self.enableLoadSave then return self end --- Function that loads data from a file. - local function _loadfile(filename) - local f=assert(io.open(filename, "rb")) - local data=f:read("*all") + local function _loadfile( filename ) + local f = assert( io.open( filename, "rb" ) ) + local data = f:read( "*all" ) f:close() return data end - + -- Set file name and path - filename=filename or self.filename + filename = filename or self.filename path = path or self.filepath - + -- Set path or default. if lfs then - path=path or lfs.writedir() + path = path or lfs.writedir() end - + -- Set path. - if path~=nil then - filename=path.."\\"..filename + if path ~= nil then + filename = path .. "\\" .. filename end - + -- Info message. - local text=string.format("Loading CTLD state from file %s", filename) - MESSAGE:New(text,10):ToAllIf(self.Debug) - self:I(self.lid..text) - - local file=assert(io.open(filename, "rb")) - + local text = string.format( "Loading CTLD state from file %s", filename ) + MESSAGE:New( text, 10 ):ToAllIf( self.Debug ) + self:I( self.lid .. text ) + + local file = assert( io.open( filename, "rb" ) ) + local loadeddata = {} for line in file:lines() do - --self:I({line=type(line)}) - loadeddata[#loadeddata+1] = line + -- self:I({line=type(line)}) + loadeddata[#loadeddata + 1] = line end file:close() - + -- remove header - table.remove(loadeddata, 1) - - for _id,_entry in pairs (loadeddata) do - local dataset = UTILS.Split(_entry,",") + table.remove( loadeddata, 1 ) + + for _id, _entry in pairs( loadeddata ) do + local dataset = UTILS.Split( _entry, "," ) -- 1=Group,2=x,3=y,4=z,5=CargoName,6=CargoTemplates,7=CargoType,8=CratesNeeded,9=CrateMass local groupname = dataset[1] local vec2 = {} - vec2.x = tonumber(dataset[2]) - vec2.y = tonumber(dataset[4]) + vec2.x = tonumber( dataset[2] ) + vec2.y = tonumber( dataset[4] ) local cargoname = dataset[5] local cargotype = dataset[7] - if type(groupname) == "string" and groupname ~= "STATIC" then + if type( groupname ) == "string" and groupname ~= "STATIC" then local cargotemplates = dataset[6] - cargotemplates = string.gsub(cargotemplates,"{","") - cargotemplates = string.gsub(cargotemplates,"}","") - cargotemplates = UTILS.Split(cargotemplates,";") - local size = tonumber(dataset[8]) - local mass = tonumber(dataset[9]) - --self:I({groupname,vec3,cargoname,cargotemplates,cargotype,size,mass}) + cargotemplates = string.gsub( cargotemplates, "{", "" ) + cargotemplates = string.gsub( cargotemplates, "}", "" ) + cargotemplates = UTILS.Split( cargotemplates, ";" ) + local size = tonumber( dataset[8] ) + local mass = tonumber( dataset[9] ) + -- self:I({groupname,vec3,cargoname,cargotemplates,cargotype,size,mass}) -- inject at Vec2 - local dropzone = ZONE_RADIUS:New("DropZone",vec2,20) + local dropzone = ZONE_RADIUS:New( "DropZone", vec2, 20 ) if cargotype == CTLD_CARGO.Enum.VEHICLE or cargotype == CTLD_CARGO.Enum.FOB then - local injectvehicle = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) - self:InjectVehicles(dropzone,injectvehicle) + local injectvehicle = CTLD_CARGO:New( nil, cargoname, cargotemplates, cargotype, true, true, size, nil, true, mass ) + self:InjectVehicles( dropzone, injectvehicle ) elseif cargotype == CTLD_CARGO.Enum.TROOPS or cargotype == CTLD_CARGO.Enum.ENGINEERS then - local injecttroops = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) - self:InjectTroops(dropzone,injecttroops) + local injecttroops = CTLD_CARGO:New( nil, cargoname, cargotemplates, cargotype, true, true, size, nil, true, mass ) + self:InjectTroops( dropzone, injecttroops ) end - elseif (type(groupname) == "string" and groupname == "STATIC") or cargotype == CTLD_CARGO.Enum.REPAIR then + elseif (type( groupname ) == "string" and groupname == "STATIC") or cargotype == CTLD_CARGO.Enum.REPAIR then local cargotemplates = dataset[6] - local size = tonumber(dataset[8]) - local mass = tonumber(dataset[9]) - local dropzone = ZONE_RADIUS:New("DropZone",vec2,20) + local size = tonumber( dataset[8] ) + local mass = tonumber( dataset[9] ) + local dropzone = ZONE_RADIUS:New( "DropZone", vec2, 20 ) -- STATIC,-84037,154,834021,Humvee,{Humvee;},Vehicle,1,100 -- STATIC,-84036,154,834018,Ammunition-1,ammo_cargo,Static,1,500 local injectstatic = nil if cargotype == CTLD_CARGO.Enum.VEHICLE or cargotype == CTLD_CARGO.Enum.FOB then - cargotemplates = string.gsub(cargotemplates,"{","") - cargotemplates = string.gsub(cargotemplates,"}","") - cargotemplates = UTILS.Split(cargotemplates,";") - injectstatic = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) + cargotemplates = string.gsub( cargotemplates, "{", "" ) + cargotemplates = string.gsub( cargotemplates, "}", "" ) + cargotemplates = UTILS.Split( cargotemplates, ";" ) + injectstatic = CTLD_CARGO:New( nil, cargoname, cargotemplates, cargotype, true, true, size, nil, true, mass ) elseif cargotype == CTLD_CARGO.Enum.STATIC or cargotype == CTLD_CARGO.Enum.REPAIR then - injectstatic = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) + injectstatic = CTLD_CARGO:New( nil, cargoname, cargotemplates, cargotype, true, true, size, nil, true, mass ) end if injectstatic then - self:InjectStatics(dropzone,injectstatic) + self:InjectStatics( dropzone, injectstatic ) end - end + end end - + return self end end -- end do From 607c52c0b773a1e8b8adcededacd4230501dabc1 Mon Sep 17 00:00:00 2001 From: TommyC81 Date: Fri, 17 Dec 2021 12:07:32 +0400 Subject: [PATCH 041/200] Update CSAR.lua (#1665) Code formatting and general typo/documentation fixes. --- Moose Development/Moose/Ops/CSAR.lua | 1948 +++++++++++++------------- 1 file changed, 976 insertions(+), 972 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 0ca2f1058..df96af4c4 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -1,17 +1,17 @@ --- **Ops** -- Combat Search and Rescue. -- -- === --- +-- -- **CSAR** - MOOSE based Helicopter CSAR Operations. --- +-- -- === --- +-- -- ## Missions: -- -- ### [CSAR - Combat Search & Rescue](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20CSAR) --- +-- -- === --- +-- -- **Main Features:** -- -- * MOOSE-based Helicopter CSAR Operations for Players. @@ -40,22 +40,22 @@ -- ![Banner Image](OPS_CSAR.jpg) -- -- # CSAR Concept --- +-- -- * MOOSE-based Helicopter CSAR Operations for Players. --- * Object oriented refactoring of Ciribob\'s fantastic CSAR script. --- * No need for extra MIST loading. +-- * Object oriented refactoring of Ciribob's fantastic CSAR script. +-- * No need for extra MIST loading. -- * Additional events to tailor your mission. --- * Optional SpawnCASEVAC to create casualties without beacon (e.g. handling dead ground vehicles and create CASVAC requests). --- +-- * Optional SpawnCASEVAC to create casualties without beacon (e.g. handling dead ground vehicles and create CASEVAC requests). +-- -- ## 0. Prerequisites --- --- You need to load an .ogg soundfile for the pilot\'s beacons into the mission, e.g. "beacon.ogg", use a once trigger, "sound to country" for that. +-- +-- You need to load an .ogg sound file for the pilot's beacons into the mission, e.g. "beacon.ogg", use a once trigger, "sound to country" for that. -- Create a late-activated single infantry unit as template in the mission editor and name it e.g. "Downed Pilot". --- +-- -- ## 1. Basic Setup --- +-- -- A basic setup example is the following: --- +-- -- -- Instantiate and start a CSAR for the blue side, with template "Downed Pilot" and alias "Luftrettung" -- local my_csar = CSAR:New(coalition.side.BLUE,"Downed Pilot","Luftrettung") -- -- options @@ -63,35 +63,35 @@ -- my_csar.invisiblecrew = false -- downed pilot spawn is visible -- -- start the FSM -- my_csar:__Start(5) --- +-- -- ## 2. Options --- +-- -- The following options are available (with their defaults). Only set the ones you want changed: -- --- self.allowDownedPilotCAcontrol = false -- Set to false if you don\'t want to allow control by Combined Arms. +-- self.allowDownedPilotCAcontrol = false -- Set to false if you don't want to allow control by Combined Arms. -- self.allowFARPRescue = true -- allows pilots to be rescued by landing at a FARP or Airbase. Else MASH only! -- self.FARPRescueDistance = 1000 -- you need to be this close to a FARP or Airport for the pilot to be rescued. --- self.autosmoke = false -- automatically smoke a downed pilot\'s location when a heli is near. --- self.autosmokedistance = 1000 -- distance for autosmoke +-- self.autosmoke = false -- automatically smoke a downed pilot's location when a helicopter is near. +-- self.autosmokedistance = 1000 -- distance in meters for automatic smoke deployment -- self.coordtype = 1 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. -- self.csarOncrash = false -- (WIP) If set to true, will generate a downed pilot when a plane crashes as well. -- self.enableForAI = false -- set to false to disable AI pilots from being rescued. --- self.pilotRuntoExtractPoint = true -- Downed pilot will run to the rescue helicopter up to self.extractDistance in meters. +-- self.pilotRuntoExtractPoint = true -- Downed pilot will run to the rescue helicopter up to self.extractDistance in meters. -- self.extractDistance = 500 -- Distance the downed pilot will start to run to the rescue helicopter. -- self.immortalcrew = true -- Set to true to make wounded crew immortal. --- self.invisiblecrew = false -- Set to true to make wounded crew insvisible. +-- self.invisiblecrew = false -- Set to true to make wounded crew invisible. -- self.loadDistance = 75 -- configure distance for pilots to get into helicopter in meters. -- self.mashprefix = {"MASH"} -- prefixes of #GROUP objects used as MASHes. -- self.max_units = 6 -- max number of pilots that can be carried if #CSAR.AircraftType is undefined. -- self.messageTime = 15 -- Time to show messages for in seconds. Doubled for long messages. --- self.radioSound = "beacon.ogg" -- the name of the sound file to use for the pilots\' radio beacons. --- self.smokecolor = 4 -- Color of smokemarker, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue. +-- self.radioSound = "beacon.ogg" -- the name of the sound file to use for the pilots' radio beacons. +-- self.smokecolor = 4 -- Color of smoke marker, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue. -- self.useprefix = true -- Requires CSAR helicopter #GROUP names to have the prefix(es) defined below. --- self.csarPrefix = { "helicargo", "MEDEVAC"} -- #GROUP name prefixes used for useprefix=true - DO NOT use # in helicopter names in the Mission Editor! +-- self.csarPrefix = { "helicargo", "MEDEVAC"} -- #GROUP name prefixes used for useprefix=true - DO NOT use # in helicopter names in the Mission Editor! -- self.verbose = 0 -- set to > 1 for stats output for debugging. -- -- (added 0.1.4) limit amount of downed pilots spawned by **ejection** events -- self.limitmaxdownedpilots = true --- self.maxdownedpilots = 10 +-- self.maxdownedpilots = 10 -- -- (added 0.1.8) - allow to set far/near distance for approach and optionally pilot must open doors -- self.approachdist_far = 5000 -- switch do 10 sec interval approach mode, meters -- self.approachdist_near = 3000 -- switch to 5 sec interval approach mode, meters @@ -106,13 +106,13 @@ -- self.countryblue= country.id.USA -- self.countryred = country.id.RUSSIA -- self.countryneutral = country.id.UN_PEACEKEEPERS --- +-- -- ## 2.1 Experimental Features --- --- WARNING - Here\'ll be dragons! +-- +-- WARNING - Here'll be dragons! -- DANGER - For this to work you need to de-sanitize your mission environment (all three entries) in \Scripts\MissionScripting.lua -- Needs SRS => 1.9.6 to work (works on the **server** side of SRS) --- self.useSRS = false -- Set true to use FF\'s SRS integration +-- self.useSRS = false -- Set true to use FF's SRS integration -- self.SRSPath = "E:\\Progra~1\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your SRS installation -- server(!) -- self.SRSchannel = 300 -- radio channel -- self.SRSModulation = radio.modulation.AM -- modulation @@ -120,78 +120,78 @@ -- self.csarUsePara = false -- If set to true, will use the LandingAfterEjection Event instead of Ejection --shagrat -- -- ## 3. Results --- +-- -- Number of successful landings with save pilots and aggregated number of saved pilots is stored in these variables in the object: --- +-- -- self.rescues -- number of successful landings *with* saved pilots -- self.rescuedpilots -- aggregated number of pilots rescued from the field (of *all* players) --- +-- -- ## 4. Events -- -- The class comes with a number of FSM-based events that missions designers can use to shape their mission. -- These are: --- --- ### 4.1. PilotDown. --- +-- +-- ### 4.1. PilotDown. +-- -- The event is triggered when a new downed pilot is detected. Use e.g. `function my_csar:OnAfterPilotDown(...)` to link into this event: --- +-- -- function my_csar:OnAfterPilotDown(from, event, to, spawnedgroup, frequency, groupname, coordinates_text) -- ... your code here ... -- end --- --- ### 4.2. Approach. --- --- A CSAR helicpoter is closing in on a downed pilot. Use e.g. `function my_csar:OnAfterApproach(...)` to link into this event: --- +-- +-- ### 4.2. Approach. +-- +-- A CSAR helicopter is closing in on a downed pilot. Use e.g. `function my_csar:OnAfterApproach(...)` to link into this event: +-- -- function my_csar:OnAfterApproach(from, event, to, heliname, groupname) -- ... your code here ... -- end --- --- ### 4.3. Boarded. --- +-- +-- ### 4.3. Boarded. +-- -- The pilot has been boarded to the helicopter. Use e.g. `function my_csar:OnAfterBoarded(...)` to link into this event: --- +-- -- function my_csar:OnAfterBoarded(from, event, to, heliname, groupname) -- ... your code here ... -- end --- --- ### 4.4. Returning. --- +-- +-- ### 4.4. Returning. +-- -- The CSAR helicopter is ready to return to an Airbase, FARP or MASH. Use e.g. `function my_csar:OnAfterReturning(...)` to link into this event: --- +-- -- function my_csar:OnAfterReturning(from, event, to, heliname, groupname) -- ... your code here ... -- end --- --- ### 4.5. Rescued. --- +-- +-- ### 4.5. Rescued. +-- -- The CSAR helicopter has landed close to an Airbase/MASH/FARP and the pilots are safe. Use e.g. `function my_csar:OnAfterRescued(...)` to link into this event: --- +-- -- function my_csar:OnAfterRescued(from, event, to, heliunit, heliname, pilotssaved) -- ... your code here ... --- end +-- end -- -- ## 5. Spawn downed pilots at location to be picked up. --- +-- -- If missions designers want to spawn downed pilots into the field, e.g. at mission begin to give the helicopter guys works, they can do this like so: --- +-- -- -- Create downed "Pilot Wagner" in #ZONE "CSAR_Start_1" at a random point for the blue coalition -- my_csar:SpawnCSARAtZone( "CSAR_Start_1", coalition.side.BLUE, "Pilot Wagner", true ) -- -- --Create a casualty and CASEVAC request from a "Point" (VEC2) for the blue coalition --shagrat --- my_csar:SpawnCASEVAC(Point, coalition.side.BLUE) +-- my_csar:SpawnCASEVAC(Point, coalition.side.BLUE) -- -- @field #CSAR CSAR = { - ClassName = "CSAR", - verbose = 0, - lid = "", - coalition = 1, - coalitiontxt = "blue", + ClassName = "CSAR", + verbose = 0, + lid = "", + coalition = 1, + coalitiontxt = "blue", FreeVHFFrequencies = {}, UsedVHFFrequencies = {}, takenOff = {}, - csarUnits = {}, -- table of unit names + csarUnits = {}, -- table of unit names downedPilots = {}, woundedGroups = {}, landedStatus = {}, @@ -201,11 +201,11 @@ CSAR = { smokeMarkers = {}, -- tracks smoke markers for groups heliVisibleMessage = {}, -- tracks if the first message has been sent of the heli being visible heliCloseMessage = {}, -- tracks heli close message ie heli < 500m distance - max_units = 6, --number of pilots that can be carried + max_units = 6, -- number of pilots that can be carried hoverStatus = {}, -- tracks status of a helis hover above a downed pilot pilotDisabled = {}, -- tracks what aircraft a pilot is disabled for pilotLives = {}, -- tracks how many lives a pilot has - useprefix = true, -- Use the Prefixed defined below, Requires Unit have the Prefix defined below + useprefix = true, -- Use the Prefix defined below, requires Unit to have the Prefix defined csarPrefix = {}, template = nil, mash = {}, @@ -240,14 +240,14 @@ CSAR.AircraftType["SA342L"] = 4 CSAR.AircraftType["SA342M"] = 4 CSAR.AircraftType["UH-1H"] = 8 CSAR.AircraftType["Mi-8MTV2"] = 12 -CSAR.AircraftType["Mi-8MT"] = 12 -CSAR.AircraftType["Mi-24P"] = 8 +CSAR.AircraftType["Mi-8MT"] = 12 +CSAR.AircraftType["Mi-24P"] = 8 CSAR.AircraftType["Mi-24V"] = 8 -CSAR.AircraftType["Bell-47"] = 2 +CSAR.AircraftType["Bell-47"] = 2 --- CSAR class version. -- @field #string version -CSAR.version="1.0.1r1" +CSAR.version = "1.0.1r1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -266,49 +266,49 @@ CSAR.version="1.0.1r1" -- @param #string Template Name of the late activated infantry unit standing in for the downed pilot. -- @param #string Alias An *optional* alias how this object is called in the logs etc. -- @return #CSAR self -function CSAR:New(Coalition, Template, Alias) - +function CSAR:New( Coalition, Template, Alias ) + -- Inherit everything from FSM class. - local self=BASE:Inherit(self, FSM:New()) -- #CSAR - - --set Coalition - if Coalition and type(Coalition)=="string" then - if Coalition=="blue" then - self.coalition=coalition.side.BLUE + local self = BASE:Inherit( self, FSM:New() ) -- #CSAR + + -- set Coalition + if Coalition and type( Coalition ) == "string" then + if Coalition == "blue" then + self.coalition = coalition.side.BLUE self.coalitiontxt = Coalition - elseif Coalition=="red" then - self.coalition=coalition.side.RED + elseif Coalition == "red" then + self.coalition = coalition.side.RED self.coalitiontxt = Coalition - elseif Coalition=="neutral" then - self.coalition=coalition.side.NEUTRAL + elseif Coalition == "neutral" then + self.coalition = coalition.side.NEUTRAL self.coalitiontxt = Coalition else - self:E("ERROR: Unknown coalition in CSAR!") + self:E( "ERROR: Unknown coalition in CSAR!" ) end else self.coalition = Coalition - self.coalitiontxt = string.lower(UTILS.GetCoalitionName(self.coalition)) + self.coalitiontxt = string.lower( UTILS.GetCoalitionName( self.coalition ) ) end - + -- Set alias. if Alias then - self.alias=tostring(Alias) + self.alias = tostring( Alias ) else - self.alias="Red Cross" + self.alias = "Red Cross" if self.coalition then - if self.coalition==coalition.side.RED then - self.alias="IFRC" - elseif self.coalition==coalition.side.BLUE then - self.alias="CSAR" + if self.coalition == coalition.side.RED then + self.alias = "IFRC" + elseif self.coalition == coalition.side.BLUE then + self.alias = "CSAR" end end end - + -- Set some string id for output to DCS.log file. - self.lid=string.format("%s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown") - + self.lid = string.format( "%s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName( self.coalition ) or "unknown" ) + -- Start State. - self:SetStartState("Stopped") + self:SetStartState( "Stopped" ) -- Add FSM transitions. -- From State --> Event --> To State @@ -339,70 +339,70 @@ function CSAR:New(Coalition, Template, Alias) self.woundedGroups = {} -- contains the new group of units self.downedPilots = {} -- Replacement woundedGroups self.downedpilotcounter = 1 - + -- settings, counters etc self.rescues = 0 -- counter for successful rescue landings at FARP/AFB/MASH self.rescuedpilots = 0 -- counter for saved pilots self.csarOncrash = false -- If set to true, will generate a csar when a plane crashes as well. - self.allowDownedPilotCAcontrol = false -- Set to false if you don\'t want to allow control by Combined arms. + self.allowDownedPilotCAcontrol = false -- Set to false if you don't want to allow control by Combined arms. self.enableForAI = false -- set to false to disable AI units from being rescued. - self.smokecolor = 4 -- Color of smokemarker for blue side, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue + self.smokecolor = 4 -- Color of smoke marker for blue side, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue self.coordtype = 2 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. self.immortalcrew = true -- Set to true to make wounded crew immortal - self.invisiblecrew = false -- Set to true to make wounded crew insvisible - self.messageTime = 15 -- Time to show longer messages for in seconds - self.pilotRuntoExtractPoint = true -- Downed Pilot will run to the rescue helicopter up to self.extractDistance METERS + self.invisiblecrew = false -- Set to true to make wounded crew invisible + self.messageTime = 15 -- Time to show longer messages for in seconds + self.pilotRuntoExtractPoint = true -- Downed Pilot will run to the rescue helicopter up to self.extractDistance METERS. self.loadDistance = 75 -- configure distance for pilot to get in helicopter in meters. - self.extractDistance = 500 -- Distance the Downed pilot will run to the rescue helicopter + self.extractDistance = 500 -- Distance the Downed pilot will run to the rescue helicopter. self.loadtimemax = 135 -- seconds - self.radioSound = "beacon.ogg" -- the name of the sound file to use for the Pilot radio beacons. If this isnt added to the mission BEACONS WONT WORK! + self.radioSound = "beacon.ogg" -- the name of the sound file to use for the Pilot radio beacons. If this isn't added to the mission BEACONS WONT WORK! self.beaconRefresher = 29 -- seconds - self.allowFARPRescue = true --allows pilot to be rescued by landing at a FARP or Airbase + self.allowFARPRescue = true -- allows pilot to be rescued by landing at a FARP or Airbase self.FARPRescueDistance = 1000 -- you need to be this close to a FARP or Airport for the pilot to be rescued. - self.max_units = 6 --max number of pilots that can be carried - self.useprefix = true -- Use the Prefixed defined below, Requires Unit have the Prefix defined below - self.csarPrefix = { "helicargo", "MEDEVAC"} -- prefixes used for useprefix=true - DON\'T use # in names! + self.max_units = 6 -- max number of pilots that can be carried + self.useprefix = true -- Use the Prefixed defined below, Requires Unit have the Prefix defined below. + self.csarPrefix = { "helicargo", "MEDEVAC" } -- prefixes used for useprefix=true - DON'T use # in names! self.template = Template or "generic" -- template for downed pilot - self.mashprefix = {"MASH"} -- prefixes used to find MASHes - + self.mashprefix = { "MASH" } -- prefixes used to find MASHes + self.autosmoke = false -- automatically smoke location when heli is near - self.autosmokedistance = 2000 -- distance for autosmoke + self.autosmokedistance = 2000 -- distance in meters for automatic smoke deployment -- added 0.1.4 self.limitmaxdownedpilots = true self.maxdownedpilots = 25 -- generate Frequencies self:_GenerateVHFrequencies() -- added 0.1.8 - self.approachdist_far = 5000 -- switch do 10 sec interval approach mode, meters + self.approachdist_far = 5000 -- switch to 10 sec interval approach mode, meters self.approachdist_near = 3000 -- switch to 5 sec interval approach mode, meters self.pilotmustopendoors = false -- switch to true to enable check on open doors self.suppressmessages = false - + -- added 0.1.11r1 self.rescuehoverheight = 20 self.rescuehoverdistance = 10 - + -- added 0.1.12 - self.countryblue= country.id.USA + self.countryblue = country.id.USA self.countryred = country.id.RUSSIA self.countryneutral = country.id.UN_PEACEKEEPERS - + -- added 0.1.3 self.csarUsePara = false -- shagrat set to true, will use the LandingAfterEjection Event instead of Ejection - - -- WARNING - here\'ll be dragons + + -- WARNING - here'll be dragons -- for this to work you need to de-sanitize your mission environment in \Scripts\MissionScripting.lua -- needs SRS => 1.9.6 to work (works on the *server* side) - self.useSRS = false -- Use FF\'s SRS integration + self.useSRS = false -- Use FF's SRS integration self.SRSPath = "E:\\Progra~1\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your server(!) self.SRSchannel = 300 -- radio channel self.SRSModulation = radio.modulation.AM -- modulation - + ------------------------ --- Pseudo Functions --- ------------------------ - - --- Triggers the FSM event "Start". Starts the CSAR. Initializes parameters and starts event handlers. + + --- Triggers the FSM event "Start". Starts the CSAR. Initializes parameters and starts event handlers. -- @function [parent=#CSAR] Start -- @param #CSAR self @@ -427,7 +427,7 @@ function CSAR:New(Coalition, Template, Alias) -- @function [parent=#CSAR] __Status -- @param #CSAR self -- @param #number delay Delay in seconds. - + --- On After "PilotDown" event. Downed Pilot detected. -- @function [parent=#CSAR] OnAfterPilotDown -- @param #CSAR self @@ -438,7 +438,7 @@ function CSAR:New(Coalition, Template, Alias) -- @param #number Frequency Beacon frequency in kHz. -- @param #string Leadername Name of the #UNIT of the downed pilot. -- @param #string CoordinatesText String of the position of the pilot. Format determined by self.coordtype. - + --- On After "Aproach" event. Heli close to downed Pilot. -- @function [parent=#CSAR] OnAfterApproach -- @param #CSAR self @@ -446,27 +446,27 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string Event Event. -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. - -- @param #string Woundedgroupname Name of the downed pilot\'s group. - - --- On After "Boarded" event. Downed pilot boarded heli. + -- @param #string Woundedgroupname Name of the downed pilot's group. + + --- On After "Boarded" event. Downed pilot boarded heli. -- @function [parent=#CSAR] OnAfterBoarded -- @param #CSAR self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. - -- @param #string Woundedgroupname Name of the downed pilot\'s group. + -- @param #string Woundedgroupname Name of the downed pilot's group. - --- On After "Returning" event. Heli can return home with downed pilot(s). + --- On After "Returning" event. Heli can return home with downed pilot(s). -- @function [parent=#CSAR] OnAfterReturning -- @param #CSAR self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. - -- @param #string Woundedgroupname Name of the downed pilot\'s group. - - --- On After "Rescued" event. Pilot(s) have been brought to the MASH/FARP/AFB. + -- @param #string Woundedgroupname Name of the downed pilot's group. + + --- On After "Rescued" event. Pilot(s) have been brought to the MASH/FARP/AFB. -- @function [parent=#CSAR] OnAfterRescued -- @param #CSAR self -- @param #string From From state. @@ -475,7 +475,7 @@ function CSAR:New(Coalition, Template, Alias) -- @param Wrapper.Unit#UNIT HeliUnit Unit of the helicopter. -- @param #string HeliName Name of the helicopter group. -- @param #number PilotsSaved Number of the saved pilots on board when landing. - + --- On After "KIA" event. Pilot is dead. -- @function [parent=#CSAR] OnAfterKIA -- @param #CSAR self @@ -483,7 +483,7 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string Event Event. -- @param #string To To state. -- @param #string Pilotname Name of the pilot KIA. - + return self end @@ -502,9 +502,9 @@ end -- @param #number Frequency Frequency of the NDB in Hz -- @param #string Playername Name of Player (if applicable) -- @return #CSAR self. -function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername) - self:T({"_CreateDownedPilotTrack",Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername}) - +function CSAR:_CreateDownedPilotTrack( Group, Groupname, Side, OriginalUnit, Description, Typename, Frequency, Playername ) + self:T( { "_CreateDownedPilotTrack", Groupname, Side, OriginalUnit, Description, Typename, Frequency, Playername } ) + -- create new entry local DownedPilot = {} -- #CSAR.DownedPilot DownedPilot.desc = Description or "" @@ -518,16 +518,16 @@ function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Descript DownedPilot.group = Group DownedPilot.timestamp = 0 DownedPilot.alive = true - + -- Add Pilot local PilotTable = self.downedPilots local counter = self.downedpilotcounter PilotTable[counter] = {} PilotTable[counter] = DownedPilot - self:T({Table=PilotTable}) + self:T( { Table = PilotTable } ) self.downedPilots = PilotTable -- Increase counter - self.downedpilotcounter = self.downedpilotcounter+1 + self.downedpilotcounter = self.downedpilotcounter + 1 return self end @@ -535,13 +535,13 @@ end -- @param #CSAR self -- @param #string _heliName -- @return #number count -function CSAR:_PilotsOnboard(_heliName) - self:T(self.lid .. " _PilotsOnboard") - local count = 0 +function CSAR:_PilotsOnboard( _heliName ) + self:T( self.lid .. " _PilotsOnboard" ) + local count = 0 if self.inTransitGroups[_heliName] then - for _, _group in pairs(self.inTransitGroups[_heliName]) do - count = count + 1 - end + for _, _group in pairs( self.inTransitGroups[_heliName] ) do + count = count + 1 + end end return count end @@ -550,16 +550,16 @@ end -- @param #CSAR self -- @param #string _unitname Name of unit. -- @return #boolean Outcome -function CSAR:_DoubleEjection(_unitname) - if self.lastCrash[_unitname] then - local _time = self.lastCrash[_unitname] - if timer.getTime() - _time < 10 then - self:E(self.lid.."Caught double ejection!") - return true - end +function CSAR:_DoubleEjection( _unitname ) + if self.lastCrash[_unitname] then + local _time = self.lastCrash[_unitname] + if timer.getTime() - _time < 10 then + self:E( self.lid .. "Caught double ejection!" ) + return true end - self.lastCrash[_unitname] = timer.getTime() - return false + end + self.lastCrash[_unitname] = timer.getTime() + return false end --- (Internal) Spawn a downed pilot @@ -569,16 +569,18 @@ end -- @param #number frequency Frequency of the pilot's beacon -- @return Wrapper.Group#GROUP group The #GROUP object. -- @return #string alias The alias name. -function CSAR:_SpawnPilotInField(country,point,frequency) - self:T({country,point,frequency}) +function CSAR:_SpawnPilotInField( country, point, frequency ) + self:T( { country, point, frequency } ) local freq = frequency or 1000 local freq = freq / 1000 -- kHz - for i=1,10 do - math.random(i,10000) + for i = 1, 10 do + math.random( i, 10000 ) + end + if point:IsSurfaceTypeWater() then + point.y = 0 end - if point:IsSurfaceTypeWater() then point.y = 0 end local template = self.template - local alias = string.format("Pilot %.2fkHz-%d", freq, math.random(1,99)) + local alias = string.format( "Pilot %.2fkHz-%d", freq, math.random( 1, 99 ) ) local coalition = self.coalition local pilotcacontrol = self.allowDownedPilotCAcontrol -- Switch AI on/oof - is this really correct for CA? local _spawnedGroup = SPAWN @@ -595,32 +597,32 @@ end --- (Internal) Add options to a downed pilot -- @param #CSAR self -- @param Wrapper.Group#GROUP group Group to use. -function CSAR:_AddSpecialOptions(group) - self:T(self.lid.." _AddSpecialOptions") - self:T({group}) - +function CSAR:_AddSpecialOptions( group ) + self:T( self.lid .. " _AddSpecialOptions" ) + self:T( { group } ) + local immortalcrew = self.immortalcrew local invisiblecrew = self.invisiblecrew if immortalcrew then local _setImmortal = { - id = 'SetImmortal', - params = { - value = true - } + id = 'SetImmortal', + params = { + value = true, + }, } - group:SetCommand(_setImmortal) + group:SetCommand( _setImmortal ) end if invisiblecrew then local _setInvisible = { - id = 'SetInvisible', - params = { - value = true - } + id = 'SetInvisible', + params = { + value = true, + }, } - group:SetCommand(_setInvisible) + group:SetCommand( _setInvisible ) end - + group:OptionAlarmStateGreen() group:OptionROEHoldFire() return self @@ -638,59 +640,61 @@ end -- @param #boolean noMessage -- @param #string _description Description -- @param #boolean forcedesc Use the description only for the pilot track entry -function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description, forcedesc ) - self:T(self.lid .. " _AddCsar") - self:T({_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description}) +function CSAR:_AddCsar( _coalition, _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description, forcedesc ) + self:T( self.lid .. " _AddCsar" ) + self:T( { _coalition, _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description } ) local template = self.template - + if not _freq then _freq = self:_GenerateADFFrequency() - if not _freq then _freq = 333000 end --noob catch - end - - local _spawnedGroup, _alias = self:_SpawnPilotInField(_country,_point,_freq) - + if not _freq then + _freq = 333000 + end -- noob catch + end + + local _spawnedGroup, _alias = self:_SpawnPilotInField( _country, _point, _freq ) + local _typeName = _typeName or "Pilot" - + if not noMessage then - if _freq ~= 0 then --shagrat different CASEVAC msg - self:_DisplayToAllSAR("MAYDAY MAYDAY! " .. _typeName .. " is down. ", self.coalition, self.messageTime) - else - self:_DisplayToAllSAR("Troops In Contact. " .. _typeName .. " requests CASEVAC. ", self.coalition, self.messageTime) + if _freq ~= 0 then -- shagrat different CASEVAC msg + self:_DisplayToAllSAR( "MAYDAY MAYDAY! " .. _typeName .. " is down. ", self.coalition, self.messageTime ) + else + self:_DisplayToAllSAR( "Troops In Contact. " .. _typeName .. " requests CASEVAC. ", self.coalition, self.messageTime ) + end end + + if (_freq and _freq ~= 0) then -- shagrat only add beacon if _freq is NOT 0 + self:_AddBeaconToGroup( _spawnedGroup, _freq ) end - - if (_freq and _freq ~= 0) then --shagrat only add beacon if _freq is NOT 0 - self:_AddBeaconToGroup(_spawnedGroup, _freq) - end - - self:_AddSpecialOptions(_spawnedGroup) + + self:_AddSpecialOptions( _spawnedGroup ) local _text = _description if not forcedesc then if _playerName ~= nil then - if _freq ~= 0 then --shagrat - _text = "Pilot " .. _playerName - else - _text = "TIC - " .. _playerName - end + if _freq ~= 0 then -- shagrat + _text = "Pilot " .. _playerName + else + _text = "TIC - " .. _playerName + end elseif _unitName ~= nil then - if _freq ~= 0 then --shagrat - _text = "AI Pilot of " .. _unitName - else - _text = "TIC - " .. _unitName + if _freq ~= 0 then -- shagrat + _text = "AI Pilot of " .. _unitName + else + _text = "TIC - " .. _unitName + end end end - end - self:T({_spawnedGroup, _alias}) - + self:T( { _spawnedGroup, _alias } ) + local _GroupName = _spawnedGroup:GetName() or _alias - self:_CreateDownedPilotTrack(_spawnedGroup,_GroupName,_coalition,_unitName,_text,_typeName,_freq,_playerName) + self:_CreateDownedPilotTrack( _spawnedGroup, _GroupName, _coalition, _unitName, _text, _typeName, _freq, _playerName ) + + self:_InitSARForPilot( _spawnedGroup, _unitName, _freq, noMessage ) -- shagrat use unitName to have the aircraft callsign / descriptive "name" etc. - self:_InitSARForPilot(_spawnedGroup, _unitName, _freq, noMessage) --shagrat use unitName to have the aircraft callsign / descriptive "name" etc. - return self end @@ -700,31 +704,31 @@ end -- @param #number _coalition Coalition. -- @param #string _description (optional) Description. -- @param #boolean _randomPoint (optional) Random yes or no. --- @param #boolean _nomessage (optional) If true, don\'t send a message to SAR. +-- @param #boolean _nomessage (optional) If true, don't send a message to SAR. -- @param #string unitname (optional) Name of the lost unit. -- @param #string typename (optional) Type of plane. -- @param #boolean forcedesc (optional) Force to use the description passed only for the pilot track entry. Use to have fully custom names. -function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _nomessage, unitname, typename, forcedesc) - self:T(self.lid .. " _SpawnCsarAtZone") +function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _nomessage, unitname, typename, forcedesc ) + self:T( self.lid .. " _SpawnCsarAtZone" ) local freq = self:_GenerateADFFrequency() - local _triggerZone = ZONE:New(_zone) -- trigger to use as reference position + local _triggerZone = ZONE:New( _zone ) -- trigger to use as reference position if _triggerZone == nil then - self:E(self.lid.."ERROR: Can\'t find zone called " .. _zone, 10) + self:E( self.lid .. "ERROR: Can\'t find zone called " .. _zone, 10 ) return end - + local _description = _description or "PoW" local unitname = unitname or "Old Rusty" local typename = typename or "Phantom II" - + local pos = {} if _randomPoint then - local _pos = _triggerZone:GetRandomPointVec3() - pos = COORDINATE:NewFromVec3(_pos) + local _pos = _triggerZone:GetRandomPointVec3() + pos = COORDINATE:NewFromVec3( _pos ) else - pos = _triggerZone:GetCoordinate() + pos = _triggerZone:GetCoordinate() end - + local _country = 0 if _coalition == coalition.side.BLUE then _country = self.countryblue @@ -733,9 +737,9 @@ function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _ else _country = self.countryneutral end - - self:_AddCsar(_coalition, _country, pos, typename, unitname, _description, freq, _nomessage, _description, forcedesc) - + + self:_AddCsar( _coalition, _country, pos, typename, unitname, _description, freq, _nomessage, _description, forcedesc ) + return self end @@ -745,7 +749,7 @@ end -- @param #number Coalition Coalition. -- @param #string Description (optional) Description. -- @param #boolean RandomPoint (optional) Random yes or no. --- @param #boolean Nomessage (optional) If true, don\'t send a message to SAR. +-- @param #boolean Nomessage (optional) If true, don't send a message to SAR. -- @param #string Unitname (optional) Name of the lost unit. -- @param #string Typename (optional) Type of plane. -- @param #boolean Forcedesc (optional) Force to use the **description passed only** for the pilot track entry. Use to have fully custom names. @@ -753,8 +757,8 @@ end -- -- -- Create downed "Pilot Wagner" in #ZONE "CSAR_Start_1" at a random point for the blue coalition -- my_csar:SpawnCSARAtZone( "CSAR_Start_1", coalition.side.BLUE, "Wagner", true, false, "Charly-1-1", "F5E" ) -function CSAR:SpawnCSARAtZone(Zone, Coalition, Description, RandomPoint, Nomessage, Unitname, Typename, Forcedesc) - self:_SpawnCsarAtZone(Zone, Coalition, Description, RandomPoint, Nomessage, Unitname, Typename, Forcedesc) +function CSAR:SpawnCSARAtZone( Zone, Coalition, Description, RandomPoint, Nomessage, Unitname, Typename, Forcedesc ) + self:_SpawnCsarAtZone( Zone, Coalition, Description, RandomPoint, Nomessage, Unitname, Typename, Forcedesc ) return self end @@ -763,20 +767,20 @@ end -- @param #string _Point a POINT_VEC2. -- @param #number _coalition Coalition. -- @param #string _description (optional) Description. --- @param #boolean _nomessage (optional) If true, don\'t send a message to SAR. +-- @param #boolean _nomessage (optional) If true, don't send a message to SAR. -- @param #string unitname (optional) Name of the lost unit. -- @param #string typename (optional) Type of plane. -- @param #boolean forcedesc (optional) Force to use the description passed only for the pilot track entry. Use to have fully custom names. -function CSAR:_SpawnCASEVAC( _Point, _coalition, _description, _nomessage, unitname, typename, forcedesc) --shagrat added internal Function _SpawnCASEVAC - self:T(self.lid .. " _SpawnCASEVAC") - +function CSAR:_SpawnCASEVAC( _Point, _coalition, _description, _nomessage, unitname, typename, forcedesc ) -- shagrat added internal Function _SpawnCASEVAC + self:T( self.lid .. " _SpawnCASEVAC" ) + local _description = _description or "CASEVAC" local unitname = unitname or "CASEVAC" local typename = typename or "Ground Commander" - + local pos = {} - pos = _Point - + pos = _Point + local _country = 0 if _coalition == coalition.side.BLUE then _country = self.countryblue @@ -785,9 +789,9 @@ function CSAR:_SpawnCASEVAC( _Point, _coalition, _description, _nomessage, unitn else _country = self.countryneutral end - --shagrat set frequency to 0 as "flag" for no beacon - self:_AddCsar(_coalition, _country, pos, typename, unitname, _description, 0, _nomessage, _description, forcedesc) - + -- shagrat set frequency to 0 as "flag" for no beacon + self:_AddCsar( _coalition, _country, pos, typename, unitname, _description, 0, _nomessage, _description, forcedesc ) + return self end @@ -797,204 +801,204 @@ end -- @param #number Coalition Coalition. -- @param #string Description (optional) Description. -- @param #boolean addBeacon (optional) yes or no. --- @param #boolean Nomessage (optional) If true, don\'t send a message to SAR. +-- @param #boolean Nomessage (optional) If true, don't send a message to SAR. -- @param #string Unitname (optional) Name of the lost unit. -- @param #string Typename (optional) Type of plane. -- @param #boolean Forcedesc (optional) Force to use the **description passed only** for the pilot track entry. Use to have fully custom names. -- @usage If missions designers want to spawn downed pilots into the field, e.g. at mission begin, to give the helicopter guys work, they can do this like so: --- +-- -- -- Create casualty "CASEVAC" at Point #POINT_VEC2 for the blue coalition. -- my_csar:SpawnCASEVAC( POINT_VEC2, coalition.side.BLUE ) -function CSAR:SpawnCASEVAC(Point, Coalition, Description, Nomessage, Unitname, Typename, Forcedesc) - self:_SpawnCASEVAC(Point, Coalition, Description, Nomessage, Unitname, Typename, Forcedesc) +function CSAR:SpawnCASEVAC( Point, Coalition, Description, Nomessage, Unitname, Typename, Forcedesc ) + self:_SpawnCASEVAC( Point, Coalition, Description, Nomessage, Unitname, Typename, Forcedesc ) return self -end --shagrat end added CASEVAC +end -- shagrat end added CASEVAC --- (Internal) Event handler. -- @param #CSAR self -function CSAR:_EventHandler(EventData) - self:T(self.lid .. " _EventHandler") - self:T({Event = EventData.id}) - +function CSAR:_EventHandler( EventData ) + self:T( self.lid .. " _EventHandler" ) + self:T( { Event = EventData.id } ) + local _event = EventData -- Core.Event#EVENTDATA - - -- no Player + + -- no Player if self.enableForAI == false and _event.IniPlayerName == nil then - return - end - - -- no event + return + end + + -- no event if _event == nil or _event.initiator == nil then return false - - -- take off + + -- take off elseif _event.id == EVENTS.Takeoff then -- taken off - self:T(self.lid .. " Event unit - Takeoff") - + self:T( self.lid .. " Event unit - Takeoff" ) + local _coalition = _event.IniCoalition if _coalition ~= self.coalition then - return --ignore! + return -- ignore! end - + if _event.IniGroupName then - self.takenOff[_event.IniUnitName] = true + self.takenOff[_event.IniUnitName] = true end - + return true - - -- player enter unit - elseif _event.id == EVENTS.PlayerEnterAircraft or _event.id == EVENTS.PlayerEnterUnit then --player entered unit - self:T(self.lid .. " Event unit - Player Enter") - + + -- player enter unit + elseif _event.id == EVENTS.PlayerEnterAircraft or _event.id == EVENTS.PlayerEnterUnit then -- player entered unit + self:T( self.lid .. " Event unit - Player Enter" ) + local _coalition = _event.IniCoalition if _coalition ~= self.coalition then - return --ignore! + return -- ignore! end - + if _event.IniPlayerName then - self.takenOff[_event.IniPlayerName] = nil + self.takenOff[_event.IniPlayerName] = nil end - + local _unit = _event.IniUnit local _group = _event.IniGroup if _unit:IsHelicopter() or _group:IsHelicopter() then self:_AddMedevacMenuItem() - end - - return true - - elseif (_event.id == EVENTS.PilotDead and self.csarOncrash == false) then - -- Pilot dead - - self:T(self.lid .. " Event unit - Pilot Dead") - - local _unit = _event.IniUnit - local _unitname = _event.IniUnitName - local _group = _event.IniGroup - - if _unit == nil then - return -- error! - end - - local _coalition = _event.IniCoalition - if _coalition ~= self.coalition then - return --ignore! - end - - -- Catch multiple events here? - if self.takenOff[_event.IniUnitName] == true or _group:IsAirborne() then - if self:_DoubleEjection(_unitname) then - return - end - - else - self:T(self.lid .. " Pilot has not taken off, ignore") - end - - return - - elseif _event.id == EVENTS.PilotDead or _event.id == EVENTS.Ejection then - if _event.id == EVENTS.PilotDead and self.csarOncrash == false then - return - end - self:T(self.lid .. " Event unit - Pilot Ejected") - - local _unit = _event.IniUnit - local _unitname = _event.IniUnitName - local _group = _event.IniGroup - - if _unit == nil then - return -- error! - end - - local _coalition = _unit:GetCoalition() - if _coalition ~= self.coalition then - return --ignore! - end - - if not self.takenOff[_event.IniUnitName] and not _group:IsAirborne() then - self:T(self.lid .. " Pilot has not taken off, ignore") - return -- give up, pilot hasnt taken off - end - - if self:_DoubleEjection(_unitname) then - return - end - - -- limit no of pilots in the field. - if self.limitmaxdownedpilots and self:_ReachedPilotLimit() then - return - end - - -- all checks passed, get going. - if self.csarUsePara == false then --shagrat check parameter LandingAfterEjection, if true don't spawn a Pilot from EJECTION event, wait for the Chute to land - local _freq = self:_GenerateADFFrequency() - self:_AddCsar(_coalition, _unit:GetCountry(), _unit:GetCoordinate() , _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, false, "none") - return true end - + + return true + + elseif (_event.id == EVENTS.PilotDead and self.csarOncrash == false) then + -- Pilot dead + + self:T( self.lid .. " Event unit - Pilot Dead" ) + + local _unit = _event.IniUnit + local _unitname = _event.IniUnitName + local _group = _event.IniGroup + + if _unit == nil then + return -- error! + end + + local _coalition = _event.IniCoalition + if _coalition ~= self.coalition then + return -- ignore! + end + + -- Catch multiple events here? + if self.takenOff[_event.IniUnitName] == true or _group:IsAirborne() then + if self:_DoubleEjection( _unitname ) then + return + end + + else + self:T( self.lid .. " Pilot has not taken off, ignore" ) + end + + return + + elseif _event.id == EVENTS.PilotDead or _event.id == EVENTS.Ejection then + if _event.id == EVENTS.PilotDead and self.csarOncrash == false then + return + end + self:T( self.lid .. " Event unit - Pilot Ejected" ) + + local _unit = _event.IniUnit + local _unitname = _event.IniUnitName + local _group = _event.IniGroup + + if _unit == nil then + return -- error! + end + + local _coalition = _unit:GetCoalition() + if _coalition ~= self.coalition then + return -- ignore! + end + + if not self.takenOff[_event.IniUnitName] and not _group:IsAirborne() then + self:T( self.lid .. " Pilot has not taken off, ignore" ) + return -- give up, pilot hasnt taken off + end + + if self:_DoubleEjection( _unitname ) then + return + end + + -- limit no of pilots in the field. + if self.limitmaxdownedpilots and self:_ReachedPilotLimit() then + return + end + + -- all checks passed, get going. + if self.csarUsePara == false then -- shagrat check parameter LandingAfterEjection, if true don't spawn a Pilot from EJECTION event, wait for the Chute to land + local _freq = self:_GenerateADFFrequency() + self:_AddCsar( _coalition, _unit:GetCountry(), _unit:GetCoordinate(), _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, false, "none" ) + return true + end + ---- shagrat on event LANDING_AFTER_EJECTION spawn pilot at parachute location elseif (_event.id == EVENTS.LandingAfterEjection and self.csarUsePara == true) then - self:I({EVENT=_event}) - local _LandingPos = COORDINATE:NewFromVec3(_event.initiator:getPosition().p) - local _unitname = "Aircraft" --_event.initiator:getName() or "Aircraft" --shagrat Optional use of Object name which is unfortunately 'f15_Pilot_Parachute' - local _typename = "Ejected Pilot" --_event.Initiator.getTypeName() or "Ejected Pilot" + self:I( { EVENT = _event } ) + local _LandingPos = COORDINATE:NewFromVec3( _event.initiator:getPosition().p ) + local _unitname = "Aircraft" -- _event.initiator:getName() or "Aircraft" --shagrat Optional use of Object name which is unfortunately 'f15_Pilot_Parachute' + local _typename = "Ejected Pilot" -- _event.Initiator.getTypeName() or "Ejected Pilot" local _country = _event.initiator:getCountry() local _coalition = coalition.getCountryCoalition( _country ) if _coalition == self.coalition then local _freq = self:_GenerateADFFrequency() - self:I({coalition=_coalition,country= _country, coord=_LandingPos, name=_unitname, player=_event.IniPlayerName, freq=_freq}) - self:_AddCsar(_coalition, _country, _LandingPos, nil, _unitname, _event.IniPlayerName, _freq, false, "none")--shagrat add CSAR at Parachute location. - - Unit.destroy(_event.initiator) -- shagrat remove static Pilot model - end + self:I( { coalition = _coalition, country = _country, coord = _LandingPos, name = _unitname, player = _event.IniPlayerName, freq = _freq } ) + self:_AddCsar( _coalition, _country, _LandingPos, nil, _unitname, _event.IniPlayerName, _freq, false, "none" ) -- shagrat add CSAR at Parachute location. + + Unit.destroy( _event.initiator ) -- shagrat remove static Pilot model + end return true - + elseif _event.id == EVENTS.Land then - self:T(self.lid .. " Landing") - - if _event.IniUnitName then - self.takenOff[_event.IniUnitName] = nil + self:T( self.lid .. " Landing" ) + + if _event.IniUnitName then + self.takenOff[_event.IniUnitName] = nil + end + + if self.allowFARPRescue then + + local _unit = _event.IniUnit -- Wrapper.Unit#UNIT + + if _unit == nil then + self:T( self.lid .. " Unit nil on landing" ) + return -- error! end - - if self.allowFARPRescue then - - local _unit = _event.IniUnit -- Wrapper.Unit#UNIT - - if _unit == nil then - self:T(self.lid .. " Unit nil on landing") - return -- error! - end - - local _coalition = _event.IniCoalition - if _coalition ~= self.coalition then - return --ignore! - end - - self.takenOff[_event.IniUnitName] = nil - - local _place = _event.Place -- Wrapper.Airbase#AIRBASE - - if _place == nil then - self:T(self.lid .. " Landing Place Nil") - return -- error! - end - - -- anyone on board? - if self.inTransitGroups[_event.IniUnitName] == nil then - -- ignore - return - end - - if _place:GetCoalition() == self.coalition or _place:GetCoalition() == coalition.side.NEUTRAL then - self:_ScheduledSARFlight(_event.IniUnitName,_event.IniGroupName,true) - else - self:T(string.format("Airfield %d, Unit %d", _place:GetCoalition(), _unit:GetCoalition())) - end - end - - return true + + local _coalition = _event.IniCoalition + if _coalition ~= self.coalition then + return -- ignore! end + + self.takenOff[_event.IniUnitName] = nil + + local _place = _event.Place -- Wrapper.Airbase#AIRBASE + + if _place == nil then + self:T( self.lid .. " Landing Place Nil" ) + return -- error! + end + + -- anyone on board? + if self.inTransitGroups[_event.IniUnitName] == nil then + -- ignore + return + end + + if _place:GetCoalition() == self.coalition or _place:GetCoalition() == coalition.side.NEUTRAL then + self:_ScheduledSARFlight( _event.IniUnitName, _event.IniGroupName, true ) + else + self:T( string.format( "Airfield %d, Unit %d", _place:GetCoalition(), _unit:GetCoalition() ) ) + end + end + + return true + end return self end @@ -1004,31 +1008,31 @@ end -- @param #string _GroupName Name of the Group -- @param #number _freq Beacon frequency. -- @param #boolean _nomessage Send message true or false. -function CSAR:_InitSARForPilot(_downedGroup, _GroupName, _freq, _nomessage) - self:T(self.lid .. " _InitSARForPilot") - local _leader = _downedGroup:GetUnit(1) +function CSAR:_InitSARForPilot( _downedGroup, _GroupName, _freq, _nomessage ) + self:T( self.lid .. " _InitSARForPilot" ) + local _leader = _downedGroup:GetUnit( 1 ) local _groupName = _GroupName local _freqk = _freq / 1000 - local _coordinatesText = self:_GetPositionOfWounded(_downedGroup) + local _coordinatesText = self:_GetPositionOfWounded( _downedGroup ) local _leadername = _leader:GetName() - + if not _nomessage then - if _freq ~= 0 then --shagrat - local _text = string.format("%s requests SAR at %s, beacon at %.2f KHz", _groupName, _coordinatesText, _freqk)--shagrat _groupName to prevent 'f15_Pilot_Parachute' - self:_DisplayToAllSAR(_text,self.coalition,self.messageTime) - else --shagrat CASEVAC msg - local _text = string.format("Pickup Zone at %s.", _coordinatesText ) - self:_DisplayToAllSAR(_text,self.coalition,self.messageTime) - end - end - - for _,_heliName in pairs(self.csarUnits) do - self:_CheckWoundedGroupStatus(_heliName, _groupName) + if _freq ~= 0 then -- shagrat + local _text = string.format( "%s requests SAR at %s, beacon at %.2f KHz", _groupName, _coordinatesText, _freqk ) -- shagrat _groupName to prevent 'f15_Pilot_Parachute' + self:_DisplayToAllSAR( _text, self.coalition, self.messageTime ) + else -- shagrat CASEVAC msg + local _text = string.format( "Pickup Zone at %s.", _coordinatesText ) + self:_DisplayToAllSAR( _text, self.coalition, self.messageTime ) + end end - -- trigger FSM event - self:__PilotDown(2,_downedGroup, _freqk, _groupName, _coordinatesText) - + for _, _heliName in pairs( self.csarUnits ) do + self:_CheckWoundedGroupStatus( _heliName, _groupName ) + end + + -- trigger FSM event + self:__PilotDown( 2, _downedGroup, _freqk, _groupName, _coordinatesText ) + return self end @@ -1037,16 +1041,16 @@ end -- @param #string name Name to search for. -- @return #boolean Outcome. -- @return #CSAR.DownedPilot Table if found else nil. -function CSAR:_CheckNameInDownedPilots(name) - local PilotTable = self.downedPilots --#CSAR.DownedPilot +function CSAR:_CheckNameInDownedPilots( name ) + local PilotTable = self.downedPilots -- #CSAR.DownedPilot local found = false local table = nil - for _,_pilot in pairs(PilotTable) do + for _, _pilot in pairs( PilotTable ) do if _pilot.name == name and _pilot.alive == true then found = true table = _pilot break - end + end end return found, table end @@ -1056,10 +1060,10 @@ end -- @param #string name Name to search for. -- @param #boolean force Force removal. -- @return #boolean Outcome. -function CSAR:_RemoveNameFromDownedPilots(name,force) - local PilotTable = self.downedPilots --#CSAR.DownedPilot +function CSAR:_RemoveNameFromDownedPilots( name, force ) + local PilotTable = self.downedPilots -- #CSAR.DownedPilot local found = false - for _index,_pilot in pairs(PilotTable) do + for _index, _pilot in pairs( PilotTable ) do if _pilot.name == name then self.downedPilots[_index].alive = false end @@ -1071,95 +1075,95 @@ end -- @param #CSAR self -- @param #string heliname heliname -- @param #string woundedgroupname woundedgroupname -function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) - self:T(self.lid .. " _CheckWoundedGroupStatus") +function CSAR:_CheckWoundedGroupStatus( heliname, woundedgroupname ) + self:T( self.lid .. " _CheckWoundedGroupStatus" ) local _heliName = heliname local _woundedGroupName = woundedgroupname - self:T({Heli = _heliName, Downed = _woundedGroupName}) + self:T( { Heli = _heliName, Downed = _woundedGroupName } ) -- if wounded group is not here then message already been sent to SARs -- stop processing any further - local _found, _downedpilot = self:_CheckNameInDownedPilots(_woundedGroupName) + local _found, _downedpilot = self:_CheckNameInDownedPilots( _woundedGroupName ) if not _found then - self:T("...not found in list!") + self:T( "...not found in list!" ) return end - + local _woundedGroup = _downedpilot.group - if _woundedGroup ~= nil and _woundedGroup:IsAlive() then - local _heliUnit = self:_GetSARHeli(_heliName) -- Wrapper.Unit#UNIT - - local _lookupKeyHeli = _heliName .. "_" .. _woundedGroupName --lookup key for message state tracking - + if _woundedGroup ~= nil and _woundedGroup:IsAlive() then + local _heliUnit = self:_GetSARHeli( _heliName ) -- Wrapper.Unit#UNIT + + local _lookupKeyHeli = _heliName .. "_" .. _woundedGroupName -- lookup key for message state tracking + if _heliUnit == nil then self.heliVisibleMessage[_lookupKeyHeli] = nil self.heliCloseMessage[_lookupKeyHeli] = nil self.landedStatus[_lookupKeyHeli] = nil - self:T("...helinunit nil!") + self:T( "...helinunit nil!" ) return end - + local _heliCoord = _heliUnit:GetCoordinate() local _leaderCoord = _woundedGroup:GetCoordinate() - local _distance = self:_GetDistance(_heliCoord,_leaderCoord) + local _distance = self:_GetDistance( _heliCoord, _leaderCoord ) -- autosmoke if (self.autosmoke == true) and (_distance < self.autosmokedistance) and (_distance ~= -1) then - self:_PopSmokeForGroup(_woundedGroupName, _woundedGroup) + self:_PopSmokeForGroup( _woundedGroupName, _woundedGroup ) end - + if _distance < self.approachdist_near and _distance > 0 then - if self:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName) == true then - -- we\'re close, reschedule + if self:_CheckCloseWoundedGroup( _distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName ) == true then + -- we're close, reschedule _downedpilot.timestamp = timer.getAbsTime() - self:__Approach(-5,heliname,woundedgroupname) + self:__Approach( -5, heliname, woundedgroupname ) end elseif _distance >= self.approachdist_near and _distance < self.approachdist_far then -- message once if self.heliVisibleMessage[_lookupKeyHeli] == nil then - local _pilotName = _downedpilot.desc - if self.autosmoke == true then - local dist = self.autosmokedistance / 1000 - local disttext = string.format("%.0fkm",dist) - if _SETTINGS:IsImperial() then - local dist = UTILS.MetersToNM(self.autosmokedistance) - disttext = string.format("%.0fnm",dist) - end - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Finally, that is music in my ears!\nI'll pop a smoke when you are %s away.\nLand or hover by the smoke.", _heliName, _pilotName, disttext), self.messageTime,false,true) - else - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Finally, that is music in my ears!\nRequest a flare or smoke if you need.", _heliName, _pilotName), self.messageTime,false,true) + local _pilotName = _downedpilot.desc + if self.autosmoke == true then + local dist = self.autosmokedistance / 1000 + local disttext = string.format( "%.0fkm", dist ) + if _SETTINGS:IsImperial() then + local dist = UTILS.MetersToNM( self.autosmokedistance ) + disttext = string.format( "%.0fnm", dist ) end - --mark as shown for THIS heli and THIS group - self.heliVisibleMessage[_lookupKeyHeli] = true - end + self:_DisplayMessageToSAR( _heliUnit, string.format( "%s: %s. I hear you! Finally, that is music in my ears!\nI'll pop a smoke when you are %s away.\nLand or hover by the smoke.", _heliName, _pilotName, disttext ), self.messageTime, false, true ) + else + self:_DisplayMessageToSAR( _heliUnit, string.format( "%s: %s. I hear you! Finally, that is music in my ears!\nRequest a flare or smoke if you need.", _heliName, _pilotName ), self.messageTime, false, true ) + end + -- mark as shown for THIS heli and THIS group + self.heliVisibleMessage[_lookupKeyHeli] = true + end self.heliCloseMessage[_lookupKeyHeli] = nil self.landedStatus[_lookupKeyHeli] = nil - --reschedule as units aren\'t dead yet , schedule for a bit slower though as we\'re far away + -- reschedule as units aren't dead yet , schedule for a bit slower though as we're far away _downedpilot.timestamp = timer.getAbsTime() - self:__Approach(-10,heliname,woundedgroupname) + self:__Approach( -10, heliname, woundedgroupname ) end else - self:T("...Downed Pilot KIA?!") - if not _downedpilot.alive then - --self:__KIA(1,_downedpilot.name) - self:_RemoveNameFromDownedPilots(_downedpilot.name, true) - end + self:T( "...Downed Pilot KIA?!" ) + if not _downedpilot.alive then + -- self:__KIA(1,_downedpilot.name) + self:_RemoveNameFromDownedPilots( _downedpilot.name, true ) + end end return self end ---- (Internal) Function to pop a smoke at a wounded pilot\'s positions. +--- (Internal) Function to pop a smoke at a wounded pilot's positions. -- @param #CSAR self -- @param #string _woundedGroupName Name of the group. -- @param Wrapper.Group#GROUP _woundedLeader Object of the group. -function CSAR:_PopSmokeForGroup(_woundedGroupName, _woundedLeader) - self:T(self.lid .. " _PopSmokeForGroup") +function CSAR:_PopSmokeForGroup( _woundedGroupName, _woundedLeader ) + self:T( self.lid .. " _PopSmokeForGroup" ) -- have we popped smoke already in the last 5 mins local _lastSmoke = self.smokeMarkers[_woundedGroupName] if _lastSmoke == nil or timer.getTime() > _lastSmoke then - - local _smokecolor = self.smokecolor - local _smokecoord = _woundedLeader:GetCoordinate():Translate( 6, math.random( 1, 360) ) --shagrat place smoke at a random 6 m distance, so smoke does not obscure the pilot - _smokecoord:Smoke(_smokecolor) - self.smokeMarkers[_woundedGroupName] = timer.getTime() + 300 -- next smoke time + + local _smokecolor = self.smokecolor + local _smokecoord = _woundedLeader:GetCoordinate():Translate( 6, math.random( 1, 360 ) ) -- shagrat place smoke at a random 6 m distance, so smoke does not obscure the pilot + _smokecoord:Smoke( _smokecolor ) + self.smokeMarkers[_woundedGroupName] = timer.getTime() + 300 -- next smoke time end return self end @@ -1170,47 +1174,40 @@ end -- @param #string _pilotName Name of the pilot. -- @param Wrapper.Group#GROUP _woundedGroup Object of the group. -- @param #string _woundedGroupName Name of the group. -function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) - self:T(self.lid .. " _PickupUnit") +function CSAR:_PickupUnit( _heliUnit, _pilotName, _woundedGroup, _woundedGroupName ) + self:T( self.lid .. " _PickupUnit" ) -- board local _heliName = _heliUnit:GetName() local _groups = self.inTransitGroups[_heliName] - local _unitsInHelicopter = self:_PilotsOnboard(_heliName) - + local _unitsInHelicopter = self:_PilotsOnboard( _heliName ) + -- init table if there is none for this helicopter if not _groups then - self.inTransitGroups[_heliName] = {} - _groups = self.inTransitGroups[_heliName] + self.inTransitGroups[_heliName] = {} + _groups = self.inTransitGroups[_heliName] end - + -- if the heli can\'t pick them up, show a message and return local _maxUnits = self.AircraftType[_heliUnit:GetTypeName()] if _maxUnits == nil then _maxUnits = self.max_units end if _unitsInHelicopter + 1 > _maxUnits then - self:_DisplayMessageToSAR(_heliUnit, string.format("%s, %s. We\'re already crammed with %d guys! Sorry!", _pilotName, _heliName, _unitsInHelicopter, _unitsInHelicopter), self.messageTime) - return true + self:_DisplayMessageToSAR( _heliUnit, string.format( "%s, %s. We\'re already crammed with %d guys! Sorry!", _pilotName, _heliName, _unitsInHelicopter, _unitsInHelicopter ), self.messageTime ) + return true end - - local found,downedgrouptable = self:_CheckNameInDownedPilots(_woundedGroupName) - local grouptable = downedgrouptable --#CSAR.DownedPilot - self.inTransitGroups[_heliName][_woundedGroupName] = - { - originalUnit = grouptable.originalUnit, - woundedGroup = _woundedGroupName, - side = self.coalition, - desc = grouptable.desc, - player = grouptable.player, - } - _woundedGroup:Destroy(false) - self:_RemoveNameFromDownedPilots(_woundedGroupName,true) - - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s I\'m in! Get to the MASH ASAP! ", _heliName, _pilotName), self.messageTime,true,true) - - self:__Boarded(5,_heliName,_woundedGroupName) - + local found, downedgrouptable = self:_CheckNameInDownedPilots( _woundedGroupName ) + local grouptable = downedgrouptable -- #CSAR.DownedPilot + self.inTransitGroups[_heliName][_woundedGroupName] = { originalUnit = grouptable.originalUnit, woundedGroup = _woundedGroupName, side = self.coalition, desc = grouptable.desc, player = grouptable.player } + + _woundedGroup:Destroy( false ) + self:_RemoveNameFromDownedPilots( _woundedGroupName, true ) + + self:_DisplayMessageToSAR( _heliUnit, string.format( "%s: %s I\'m in! Get to the MASH ASAP! ", _heliName, _pilotName ), self.messageTime, true, true ) + + self:__Boarded( 5, _heliName, _woundedGroupName ) + return true end @@ -1218,24 +1215,23 @@ end -- @param #CSAR self -- @param Wrapper.Group#GROUP _leader -- @param Core.Point#COORDINATE _destination -function CSAR:_OrderGroupToMoveToPoint(_leader, _destination) - self:T(self.lid .. " _OrderGroupToMoveToPoint") +function CSAR:_OrderGroupToMoveToPoint( _leader, _destination ) + self:T( self.lid .. " _OrderGroupToMoveToPoint" ) local group = _leader local coordinate = _destination:GetVec2() group:SetAIOn() - group:RouteToVec2(coordinate,5) + group:RouteToVec2( coordinate, 5 ) return self end - --- (internal) Function to check if the heli door(s) are open. Thanks to Shadowze. -- @param #CSAR self -- @param #string unit_name Name of unit. -- @return #boolean outcome The outcome. function CSAR:_IsLoadingDoorOpen( unit_name ) - self:T(self.lid .. " _IsLoadingDoorOpen") - return UTILS.IsLoadingDoorOpen(unit_name) + self:T( self.lid .. " _IsLoadingDoorOpen" ) + return UTILS.IsLoadingDoorOpen( unit_name ) end --- (Internal) Function to check if heli is close to group. @@ -1246,122 +1242,123 @@ end -- @param Wrapper.Group#GROUP _woundedGroup -- @param #string _woundedGroupName -- @return #boolean Outcome -function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName) - self:T(self.lid .. " _CheckCloseWoundedGroup") +function CSAR:_CheckCloseWoundedGroup( _distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName ) + self:T( self.lid .. " _CheckCloseWoundedGroup" ) local _woundedLeader = _woundedGroup - local _lookupKeyHeli = _heliUnit:GetName() .. "_" .. _woundedGroupName --lookup key for message state tracking - - local _found, _pilotable = self:_CheckNameInDownedPilots(_woundedGroupName) -- #boolean, #CSAR.DownedPilot + local _lookupKeyHeli = _heliUnit:GetName() .. "_" .. _woundedGroupName -- lookup key for message state tracking + + local _found, _pilotable = self:_CheckNameInDownedPilots( _woundedGroupName ) -- #boolean, #CSAR.DownedPilot local _pilotName = _pilotable.desc - local _reset = true - + if (_distance < 500) then - - if self.heliCloseMessage[_lookupKeyHeli] == nil then - if self.autosmoke == true then - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land or hover at the smoke.", _heliName, _pilotName), self.messageTime,false,true) - else - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land in a safe place, I will go there ", _heliName, _pilotName), self.messageTime,false,true) - end - self.heliCloseMessage[_lookupKeyHeli] = true + + if self.heliCloseMessage[_lookupKeyHeli] == nil then + if self.autosmoke == true then + self:_DisplayMessageToSAR( _heliUnit, string.format( "%s: %s. You\'re close now! Land or hover at the smoke.", _heliName, _pilotName ), self.messageTime, false, true ) + else + self:_DisplayMessageToSAR( _heliUnit, string.format( "%s: %s. You\'re close now! Land in a safe place, I will go there ", _heliName, _pilotName ), self.messageTime, false, true ) end - - -- have we landed close enough? - if not _heliUnit:InAir() then - - if self.pilotRuntoExtractPoint == true then - if (_distance < self.extractDistance) then - local _time = self.landedStatus[_lookupKeyHeli] - if _time == nil then - self.landedStatus[_lookupKeyHeli] = math.floor( (_distance - self.loadDistance) / 3.6 ) - _time = self.landedStatus[_lookupKeyHeli] - self:_OrderGroupToMoveToPoint(_woundedGroup, _heliUnit:GetCoordinate()) - self:_DisplayMessageToSAR(_heliUnit, "Wait till " .. _pilotName .. " gets in. \nETA " .. _time .. " more seconds.", self.messageTime, false) - else - _time = self.landedStatus[_lookupKeyHeli] - 10 - self.landedStatus[_lookupKeyHeli] = _time - end - if _time <= 0 or _distance < self.loadDistance then - if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then - self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true) - return true - else - self.landedStatus[_lookupKeyHeli] = nil - self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) - return false - end - end + self.heliCloseMessage[_lookupKeyHeli] = true + end + + -- have we landed close enough? + if not _heliUnit:InAir() then + + if self.pilotRuntoExtractPoint == true then + if (_distance < self.extractDistance) then + local _time = self.landedStatus[_lookupKeyHeli] + if _time == nil then + self.landedStatus[_lookupKeyHeli] = math.floor( (_distance - self.loadDistance) / 3.6 ) + _time = self.landedStatus[_lookupKeyHeli] + self:_OrderGroupToMoveToPoint( _woundedGroup, _heliUnit:GetCoordinate() ) + self:_DisplayMessageToSAR( _heliUnit, "Wait till " .. _pilotName .. " gets in. \nETA " .. _time .. " more seconds.", self.messageTime, false ) + else + _time = self.landedStatus[_lookupKeyHeli] - 10 + self.landedStatus[_lookupKeyHeli] = _time + end + if _time <= 0 or _distance < self.loadDistance then + if self.pilotmustopendoors and not self:_IsLoadingDoorOpen( _heliName ) then + self:_DisplayMessageToSAR( _heliUnit, "Open the door to let me in!", self.messageTime, true ) + return true + else + self.landedStatus[_lookupKeyHeli] = nil + self:_PickupUnit( _heliUnit, _pilotName, _woundedGroup, _woundedGroupName ) + return false end - else - if (_distance < self.loadDistance) then - if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then - self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true) - return true - else - self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) - return false - end end end else - - local _unitsInHelicopter = self:_PilotsOnboard(_heliName) - local _maxUnits = self.AircraftType[_heliUnit:GetTypeName()] - if _maxUnits == nil then - _maxUnits = self.max_units - end - - if _heliUnit:InAir() and _unitsInHelicopter + 1 <= _maxUnits then - -- TODO - make variable - if _distance < self.rescuehoverdistance then - - --check height! - local leaderheight = _woundedLeader:GetHeight() - if leaderheight < 0 then leaderheight = 0 end - local _height = _heliUnit:GetHeight() - leaderheight - - -- TODO - make variable - if _height <= self.rescuehoverheight then - - local _time = self.hoverStatus[_lookupKeyHeli] - - if _time == nil then - self.hoverStatus[_lookupKeyHeli] = 10 - _time = 10 - else - _time = self.hoverStatus[_lookupKeyHeli] - 10 - self.hoverStatus[_lookupKeyHeli] = _time - end - - if _time > 0 then - self:_DisplayMessageToSAR(_heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you\'re too far away!", self.messageTime, true) - else - if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then - self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true) - return true - else - self.hoverStatus[_lookupKeyHeli] = nil - self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) - return false - end - end - _reset = false - else - self:_DisplayMessageToSAR(_heliUnit, "Too high to winch " .. _pilotName .. " \nReduce height and hover for 10 seconds!", self.messageTime, true,true) - end - end - + if (_distance < self.loadDistance) then + if self.pilotmustopendoors and not self:_IsLoadingDoorOpen( _heliName ) then + self:_DisplayMessageToSAR( _heliUnit, "Open the door to let me in!", self.messageTime, true ) + return true + else + self:_PickupUnit( _heliUnit, _pilotName, _woundedGroup, _woundedGroupName ) + return false end + end end + else + + local _unitsInHelicopter = self:_PilotsOnboard( _heliName ) + local _maxUnits = self.AircraftType[_heliUnit:GetTypeName()] + if _maxUnits == nil then + _maxUnits = self.max_units + end + + if _heliUnit:InAir() and _unitsInHelicopter + 1 <= _maxUnits then + -- TODO - make variable + if _distance < self.rescuehoverdistance then + + -- check height! + local leaderheight = _woundedLeader:GetHeight() + if leaderheight < 0 then + leaderheight = 0 + end + local _height = _heliUnit:GetHeight() - leaderheight + + -- TODO - make variable + if _height <= self.rescuehoverheight then + + local _time = self.hoverStatus[_lookupKeyHeli] + + if _time == nil then + self.hoverStatus[_lookupKeyHeli] = 10 + _time = 10 + else + _time = self.hoverStatus[_lookupKeyHeli] - 10 + self.hoverStatus[_lookupKeyHeli] = _time + end + + if _time > 0 then + self:_DisplayMessageToSAR( _heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you\'re too far away!", self.messageTime, true ) + else + if self.pilotmustopendoors and not self:_IsLoadingDoorOpen( _heliName ) then + self:_DisplayMessageToSAR( _heliUnit, "Open the door to let me in!", self.messageTime, true ) + return true + else + self.hoverStatus[_lookupKeyHeli] = nil + self:_PickupUnit( _heliUnit, _pilotName, _woundedGroup, _woundedGroupName ) + return false + end + end + _reset = false + else + self:_DisplayMessageToSAR( _heliUnit, "Too high to winch " .. _pilotName .. " \nReduce height and hover for 10 seconds!", self.messageTime, true, true ) + end + end + + end + end end - + if _reset then - self.hoverStatus[_lookupKeyHeli] = nil + self.hoverStatus[_lookupKeyHeli] = nil end - + if _distance < 500 then return true else @@ -1374,65 +1371,65 @@ end -- @param #string heliname Heli name -- @param #string groupname Group name -- @param #boolean isairport If true, EVENT.Landing took place at an airport or FARP -function CSAR:_ScheduledSARFlight(heliname,groupname, isairport) - self:T(self.lid .. " _ScheduledSARFlight") - self:T({heliname,groupname}) - local _heliUnit = self:_GetSARHeli(heliname) +function CSAR:_ScheduledSARFlight( heliname, groupname, isairport ) + self:T( self.lid .. " _ScheduledSARFlight" ) + self:T( { heliname, groupname } ) + local _heliUnit = self:_GetSARHeli( heliname ) local _woundedGroupName = groupname if (_heliUnit == nil) then - --helicopter crashed? - self.inTransitGroups[heliname] = nil - return + -- helicopter crashed? + self.inTransitGroups[heliname] = nil + return end if self.inTransitGroups[heliname] == nil or self.inTransitGroups[heliname][_woundedGroupName] == nil then - -- Groups already rescued - return + -- Groups already rescued + return end - local _dist = self:_GetClosestMASH(_heliUnit) + local _dist = self:_GetClosestMASH( _heliUnit ) if _dist == -1 then - return + return end - if ( _dist < self.FARPRescueDistance or isairport ) and _heliUnit:InAir() == false then - if self.pilotmustopendoors and self:_IsLoadingDoorOpen(heliname) == false then - self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me out!", self.messageTime, true) + if (_dist < self.FARPRescueDistance or isairport) and _heliUnit:InAir() == false then + if self.pilotmustopendoors and self:_IsLoadingDoorOpen( heliname ) == false then + self:_DisplayMessageToSAR( _heliUnit, "Open the door to let me out!", self.messageTime, true ) else - self:_RescuePilots(_heliUnit) + self:_RescuePilots( _heliUnit ) return end end - --queue up - self:__Returning(-5,heliname,_woundedGroupName, isairport) + -- queue up + self:__Returning( -5, heliname, _woundedGroupName, isairport ) return self end --- (Internal) Mark pilot as rescued and remove from tables. -- @param #CSAR self -- @param Wrapper.Unit#UNIT _heliUnit -function CSAR:_RescuePilots(_heliUnit) - self:T(self.lid .. " _RescuePilots") +function CSAR:_RescuePilots( _heliUnit ) + self:T( self.lid .. " _RescuePilots" ) local _heliName = _heliUnit:GetName() local _rescuedGroups = self.inTransitGroups[_heliName] - + if _rescuedGroups == nil then - -- Groups already rescued - return + -- Groups already rescued + return end - local PilotsSaved = self:_PilotsOnboard(_heliName) - + local PilotsSaved = self:_PilotsOnboard( _heliName ) + self.inTransitGroups[_heliName] = nil - - local _txt = string.format("%s: The %d pilot(s) have been taken to the\nmedical clinic. Good job!", _heliName, PilotsSaved) - - self:_DisplayMessageToSAR(_heliUnit, _txt, self.messageTime) + + local _txt = string.format( "%s: The %d pilot(s) have been taken to the\nmedical clinic. Good job!", _heliName, PilotsSaved ) + + self:_DisplayMessageToSAR( _heliUnit, _txt, self.messageTime ) -- trigger event - self:__Rescued(-1,_heliUnit,_heliName, PilotsSaved) + self:__Rescued( -1, _heliUnit, _heliName, PilotsSaved ) return self end @@ -1440,9 +1437,9 @@ end -- @param #CSAR self -- @param #string _unitname Name of Unit -- @return Wrapper.Unit#UNIT The unit or nil -function CSAR:_GetSARHeli(_unitName) - self:T(self.lid .. " _GetSARHeli") - local unit = UNIT:FindByName(_unitName) +function CSAR:_GetSARHeli( _unitName ) + self:T( self.lid .. " _GetSARHeli" ) + local unit = UNIT:FindByName( _unitName ) if unit and unit:IsAlive() then return unit else @@ -1458,43 +1455,43 @@ end -- @param #boolean _clear (optional) Clear screen. -- @param #boolean _speak (optional) Speak message via SRS. -- @param #boolean _override (optional) Override message suppression -function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear, _speak, _override) - self:T(self.lid .. " _DisplayMessageToSAR") +function CSAR:_DisplayMessageToSAR( _unit, _text, _time, _clear, _speak, _override ) + self:T( self.lid .. " _DisplayMessageToSAR" ) local group = _unit:GetGroup() local _clear = _clear or nil local _time = _time or self.messageTime if _override or not self.suppressmessages then - local m = MESSAGE:New(_text,_time,"Info",_clear):ToGroup(group) + local m = MESSAGE:New( _text, _time, "Info", _clear ):ToGroup( group ) end -- integrate SRS if _speak and self.useSRS then - local srstext = SOUNDTEXT:New(_text) + local srstext = SOUNDTEXT:New( _text ) local path = self.SRSPath local modulation = self.SRSModulation local channel = self.SRSchannel - local msrs = MSRS:New(path,channel,modulation) - msrs:PlaySoundText(srstext, 2) + local msrs = MSRS:New( path, channel, modulation ) + msrs:PlaySoundText( srstext, 2 ) end return self end ---- (Internal) Function to get string of a group\'s position. +--- (Internal) Function to get string of a group's position. -- @param #CSAR self -- @param Wrapper.Controllable#CONTROLLABLE _woundedGroup Group or Unit object. -- @return #string Coordinates as Text -function CSAR:_GetPositionOfWounded(_woundedGroup) - self:T(self.lid .. " _GetPositionOfWounded") +function CSAR:_GetPositionOfWounded( _woundedGroup ) + self:T( self.lid .. " _GetPositionOfWounded" ) local _coordinate = _woundedGroup:GetCoordinate() local _coordinatesText = "None" if _coordinate then if self.coordtype == 0 then -- Lat/Long DMTM _coordinatesText = _coordinate:ToStringLLDDM() elseif self.coordtype == 1 then -- Lat/Long DMS - _coordinatesText = _coordinate:ToStringLLDMS() + _coordinatesText = _coordinate:ToStringLLDMS() elseif self.coordtype == 2 then -- MGRS - _coordinatesText = _coordinate:ToStringMGRS() + _coordinatesText = _coordinate:ToStringMGRS() else -- Bullseye Metric --(medevac.coordtype == 4 or 3) - _coordinatesText = _coordinate:ToStringBULLS(self.coalition) + _coordinatesText = _coordinate:ToStringBULLS( self.coalition ) end end return _coordinatesText @@ -1503,55 +1500,55 @@ end --- (Internal) Display active SAR tasks to player. -- @param #CSAR self -- @param #string _unitName Unit to display to -function CSAR:_DisplayActiveSAR(_unitName) - self:T(self.lid .. " _DisplayActiveSAR") - local _msg = "Active MEDEVAC/SAR:" - local _heli = self:_GetSARHeli(_unitName) -- Wrapper.Unit#UNIT +function CSAR:_DisplayActiveSAR( _unitName ) + self:T( self.lid .. " _DisplayActiveSAR" ) + local _msg = "Active MEDEVAC/SAR:" + local _heli = self:_GetSARHeli( _unitName ) -- Wrapper.Unit#UNIT if _heli == nil then - return + return end - + local _heliSide = self.coalition local _csarList = {} - + local _DownedPilotTable = self.downedPilots - self:T({Table=_DownedPilotTable}) - for _, _value in pairs(_DownedPilotTable) do + self:T( { Table = _DownedPilotTable } ) + for _, _value in pairs( _DownedPilotTable ) do local _groupName = _value.name - self:T(string.format("Display Active Pilot: %s", tostring(_groupName))) - self:T({Table=_value}) + self:T( string.format( "Display Active Pilot: %s", tostring( _groupName ) ) ) + self:T( { Table = _value } ) local _woundedGroup = _value.group - if _woundedGroup and _value.alive then - local _coordinatesText = self:_GetPositionOfWounded(_woundedGroup) - local _helicoord = _heli:GetCoordinate() - local _woundcoord = _woundedGroup:GetCoordinate() - local _distance = self:_GetDistance(_helicoord, _woundcoord) - self:T({_distance = _distance}) - local distancetext = "" - if _SETTINGS:IsImperial() then - distancetext = string.format("%.1fnm",UTILS.MetersToNM(_distance)) - else - distancetext = string.format("%.1fkm", _distance/1000.0) - end - if _value.frequency == 0 then--shagrat insert CASEVAC without Frequency - table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %s ", _value.desc, _coordinatesText, distancetext) }) - else - table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %s ", _value.desc, _coordinatesText, _value.frequency / 1000, distancetext) }) - end + if _woundedGroup and _value.alive then + local _coordinatesText = self:_GetPositionOfWounded( _woundedGroup ) + local _helicoord = _heli:GetCoordinate() + local _woundcoord = _woundedGroup:GetCoordinate() + local _distance = self:_GetDistance( _helicoord, _woundcoord ) + self:T( { _distance = _distance } ) + local distancetext = "" + if _SETTINGS:IsImperial() then + distancetext = string.format( "%.1fnm", UTILS.MetersToNM( _distance ) ) + else + distancetext = string.format( "%.1fkm", _distance / 1000.0 ) + end + if _value.frequency == 0 then -- shagrat insert CASEVAC without Frequency + table.insert( _csarList, { dist = _distance, msg = string.format( "%s at %s - %s ", _value.desc, _coordinatesText, distancetext ) } ) + else + table.insert( _csarList, { dist = _distance, msg = string.format( "%s at %s - %.2f KHz ADF - %s ", _value.desc, _coordinatesText, _value.frequency / 1000, distancetext ) } ) + end end end - - local function sortDistance(a, b) - return a.dist < b.dist + + local function sortDistance( a, b ) + return a.dist < b.dist end - - table.sort(_csarList, sortDistance) - - for _, _line in pairs(_csarList) do - _msg = _msg .. "\n" .. _line.msg + + table.sort( _csarList, sortDistance ) + + for _, _line in pairs( _csarList ) do + _msg = _msg .. "\n" .. _line.msg end - - self:_DisplayMessageToSAR(_heli, _msg, self.messageTime*2, false, false, true) + + self:_DisplayMessageToSAR( _heli, _msg, self.messageTime * 2, false, false, true ) return self end @@ -1559,38 +1556,38 @@ end -- @param #CSAR self -- @param Wrapper.Unit#UNIT _heli Helicopter #UNIT -- @return #table Table of results -function CSAR:_GetClosestDownedPilot(_heli) - self:T(self.lid .. " _GetClosestDownedPilot") +function CSAR:_GetClosestDownedPilot( _heli ) + self:T( self.lid .. " _GetClosestDownedPilot" ) local _side = self.coalition local _closestGroup = nil local _shortestDistance = -1 local _distance = 0 local _closestGroupInfo = nil local _heliCoord = _heli:GetCoordinate() or _heli:GetCoordinate() - - if _heliCoord == nil then - self:E("****Error obtaining coordinate!") - return nil - end - - local DownedPilotsTable = self.downedPilots - - for _, _groupInfo in UTILS.spairs(DownedPilotsTable) do - --for _, _groupInfo in pairs(DownedPilotsTable) do - local _woundedName = _groupInfo.name - local _tempWounded = _groupInfo.group - - -- check group exists and not moving to someone else - if _tempWounded then - local _tempCoord = _tempWounded:GetCoordinate() - _distance = self:_GetDistance(_heliCoord, _tempCoord) - if _distance ~= nil and (_shortestDistance == -1 or _distance < _shortestDistance) then - _shortestDistance = _distance - _closestGroup = _tempWounded - _closestGroupInfo = _groupInfo - end + if _heliCoord == nil then + self:E( "****Error obtaining coordinate!" ) + return nil + end + + local DownedPilotsTable = self.downedPilots + + for _, _groupInfo in UTILS.spairs( DownedPilotsTable ) do + -- for _, _groupInfo in pairs(DownedPilotsTable) do + local _woundedName = _groupInfo.name + local _tempWounded = _groupInfo.group + + -- check group exists and not moving to someone else + if _tempWounded then + local _tempCoord = _tempWounded:GetCoordinate() + _distance = self:_GetDistance( _heliCoord, _tempCoord ) + + if _distance ~= nil and (_shortestDistance == -1 or _distance < _shortestDistance) then + _shortestDistance = _distance + _closestGroup = _tempWounded + _closestGroupInfo = _groupInfo end + end end return { pilot = _closestGroup, distance = _shortestDistance, groupInfo = _closestGroupInfo } @@ -1599,38 +1596,40 @@ end --- (Internal) Fire a flare at the point of a downed pilot. -- @param #CSAR self -- @param #string _unitName Name of the unit. -function CSAR:_SignalFlare(_unitName) - self:T(self.lid .. " _SignalFlare") - local _heli = self:_GetSARHeli(_unitName) +function CSAR:_SignalFlare( _unitName ) + self:T( self.lid .. " _SignalFlare" ) + local _heli = self:_GetSARHeli( _unitName ) if _heli == nil then - return + return end - - local _closest = self:_GetClosestDownedPilot(_heli) + + local _closest = self:_GetClosestDownedPilot( _heli ) local smokedist = 8000 - if self.approachdist_far > smokedist then smokedist = self.approachdist_far end + if self.approachdist_far > smokedist then + smokedist = self.approachdist_far + end if _closest ~= nil and _closest.pilot ~= nil and _closest.distance > 0 and _closest.distance < smokedist then - - local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) - local _distance = 0 - if _SETTINGS:IsImperial() then - _distance = string.format("%.1fnm",UTILS.MetersToNM(_closest.distance)) - else - _distance = string.format("%.1fkm",_closest.distance) - end - local _msg = string.format("%s - Popping signal flare at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance) - self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true) - - local _coord = _closest.pilot:GetCoordinate() - _coord:FlareRed(_clockDir) + + local _clockDir = self:_GetClockDirection( _heli, _closest.pilot ) + local _distance = 0 + if _SETTINGS:IsImperial() then + _distance = string.format( "%.1fnm", UTILS.MetersToNM( _closest.distance ) ) + else + _distance = string.format( "%.1fkm", _closest.distance ) + end + local _msg = string.format( "%s - Popping signal flare at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance ) + self:_DisplayMessageToSAR( _heli, _msg, self.messageTime, false, true, true ) + + local _coord = _closest.pilot:GetCoordinate() + _coord:FlareRed( _clockDir ) else - local _distance = smokedist - if _SETTINGS:IsImperial() then - _distance = string.format("%.1fnm",UTILS.MetersToNM(smokedist)) - else - _distance = string.format("%.1fkm",smokedist/1000) - end - self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",_distance), self.messageTime, false, false, true) + local _distance = smokedist + if _SETTINGS:IsImperial() then + _distance = string.format( "%.1fnm", UTILS.MetersToNM( smokedist ) ) + else + _distance = string.format( "%.1fkm", smokedist / 1000 ) + end + self:_DisplayMessageToSAR( _heli, string.format( "No Pilots within %s", _distance ), self.messageTime, false, false, true ) end return self end @@ -1640,51 +1639,53 @@ end -- @param #string _message Message to display. -- @param #number _side Coalition of message. -- @param #number _messagetime How long to show. -function CSAR:_DisplayToAllSAR(_message, _side, _messagetime) - self:T(self.lid .. " _DisplayToAllSAR") +function CSAR:_DisplayToAllSAR( _message, _side, _messagetime ) + self:T( self.lid .. " _DisplayToAllSAR" ) local messagetime = _messagetime or self.messageTime - for _, _unitName in pairs(self.csarUnits) do - local _unit = self:_GetSARHeli(_unitName) + for _, _unitName in pairs( self.csarUnits ) do + local _unit = self:_GetSARHeli( _unitName ) if _unit and not self.suppressmessages then - self:_DisplayMessageToSAR(_unit, _message, _messagetime) + self:_DisplayMessageToSAR( _unit, _message, _messagetime ) end end return self end ---(Internal) Request smoke at closest downed pilot. ---@param #CSAR self ---@param #string _unitName Name of the helicopter +-- @param #CSAR self +-- @param #string _unitName Name of the helicopter function CSAR:_Reqsmoke( _unitName ) - self:T(self.lid .. " _Reqsmoke") - local _heli = self:_GetSARHeli(_unitName) + self:T( self.lid .. " _Reqsmoke" ) + local _heli = self:_GetSARHeli( _unitName ) if _heli == nil then - return + return end local smokedist = 8000 - if smokedist < self.approachdist_far then smokedist = self.approachdist_far end - local _closest = self:_GetClosestDownedPilot(_heli) + if smokedist < self.approachdist_far then + smokedist = self.approachdist_far + end + local _closest = self:_GetClosestDownedPilot( _heli ) if _closest ~= nil and _closest.pilot ~= nil and _closest.distance > 0 and _closest.distance < smokedist then - local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) - local _distance = 0 - if _SETTINGS:IsImperial() then - _distance = string.format("%.1fnm",UTILS.MetersToNM(_closest.distance)) - else - _distance = string.format("%.1fkm",_closest.distance/1000) - end - local _msg = string.format("%s - Popping smoke at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance) - self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true) - local _coord = _closest.pilot:GetCoordinate() - local color = self.smokecolor - _coord:Smoke(color) + local _clockDir = self:_GetClockDirection( _heli, _closest.pilot ) + local _distance = 0 + if _SETTINGS:IsImperial() then + _distance = string.format( "%.1fnm", UTILS.MetersToNM( _closest.distance ) ) + else + _distance = string.format( "%.1fkm", _closest.distance / 1000 ) + end + local _msg = string.format( "%s - Popping smoke at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance ) + self:_DisplayMessageToSAR( _heli, _msg, self.messageTime, false, true, true ) + local _coord = _closest.pilot:GetCoordinate() + local color = self.smokecolor + _coord:Smoke( color ) else - local _distance = 0 - if _SETTINGS:IsImperial() then - _distance = string.format("%.1fnm",UTILS.MetersToNM(smokedist)) - else - _distance = string.format("%.1fkm",smokedist/1000) - end - self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",_distance), self.messageTime, false, false, true) + local _distance = 0 + if _SETTINGS:IsImperial() then + _distance = string.format( "%.1fnm", UTILS.MetersToNM( smokedist ) ) + else + _distance = string.format( "%.1fkm", smokedist / 1000 ) + end + self:_DisplayMessageToSAR( _heli, string.format( "No Pilots within %s", _distance ), self.messageTime, false, false, true ) end return self end @@ -1693,120 +1694,120 @@ end -- @param #CSAR self -- @param Wrapper.Unit#UNIT _heli Helicopter #UNIT -- @retunr -function CSAR:_GetClosestMASH(_heli) - self:T(self.lid .. " _GetClosestMASH") +function CSAR:_GetClosestMASH( _heli ) + self:T( self.lid .. " _GetClosestMASH" ) local _mashset = self.mash -- Core.Set#SET_GROUP local _mashes = _mashset:GetSetObjects() -- #table local _shortestDistance = -1 local _distance = 0 local _helicoord = _heli:GetCoordinate() - - local function GetCloseAirbase(coordinate,Coalition,Category) - - local a=coordinate:GetVec3() - local distmin=math.huge - local airbase=nil - for DCSairbaseID, DCSairbase in pairs(world.getAirbases(Coalition)) do - local b=DCSairbase:getPoint() - - local c=UTILS.VecSubstract(a,b) - local dist=UTILS.VecNorm(c) - - if dist 0 then - local PilotTable = self.downedPilots - for _,_pilot in pairs (PilotTable) do - self:T({_pilot}) - local pilot = _pilot -- #CSAR.DownedPilot - local group = pilot.group - local frequency = pilot.frequency or 0 -- thanks to @Thrud - if group and group:IsAlive() and frequency > 0 then - self:_AddBeaconToGroup(group,frequency) - end + self:T( self.lid .. " _RefreshRadioBeacons" ) + if self:_CountActiveDownedPilots() > 0 then + local PilotTable = self.downedPilots + for _, _pilot in pairs( PilotTable ) do + self:T( { _pilot } ) + local pilot = _pilot -- #CSAR.DownedPilot + local group = pilot.group + local frequency = pilot.frequency or 0 -- thanks to @Thrud + if group and group:IsAlive() and frequency > 0 then + self:_AddBeaconToGroup( group, frequency ) end end - return self + end + return self end --- (Internal) Helper function to count active downed pilots. -- @param #CSAR self -- @return #number Number of pilots in the field. function CSAR:_CountActiveDownedPilots() - self:T(self.lid .. " _CountActiveDownedPilots") + self:T( self.lid .. " _CountActiveDownedPilots" ) local PilotsInFieldN = 0 - for _, _unitName in pairs(self.downedPilots) do - self:T({_unitName.desc}) + for _, _unitName in pairs( self.downedPilots ) do + self:T( { _unitName.desc } ) if _unitName.alive == true then PilotsInFieldN = PilotsInFieldN + 1 end @@ -1951,45 +1956,45 @@ end -- @param #CSAR self -- @return #boolean True or false. function CSAR:_ReachedPilotLimit() - self:T(self.lid .. " _ReachedPilotLimit") - local limit = self.maxdownedpilots - local islimited = self.limitmaxdownedpilots - local count = self:_CountActiveDownedPilots() - if islimited and (count >= limit) then - return true - else - return false - end + self:T( self.lid .. " _ReachedPilotLimit" ) + local limit = self.maxdownedpilots + local islimited = self.limitmaxdownedpilots + local count = self:_CountActiveDownedPilots() + if islimited and (count >= limit) then + return true + else + return false + end end - ------------------------------ - --- FSM internal Functions --- - ------------------------------ +------------------------------ +--- FSM internal Functions --- +------------------------------ --- (Internal) Function called after Start() event. -- @param #CSAR self. -- @param #string From From state. -- @param #string Event Event triggered. -- @param #string To To state. -function CSAR:onafterStart(From, Event, To) - self:T({From, Event, To}) - self:I(self.lid .. "Started.") +function CSAR:onafterStart( From, Event, To ) + self:T( { From, Event, To } ) + self:I( self.lid .. "Started." ) -- event handler - self:HandleEvent(EVENTS.Takeoff, self._EventHandler) - self:HandleEvent(EVENTS.Land, self._EventHandler) - self:HandleEvent(EVENTS.Ejection, self._EventHandler) - self:HandleEvent(EVENTS.LandingAfterEjection, self._EventHandler) --shagrat - self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) - self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) - self:HandleEvent(EVENTS.PilotDead, self._EventHandler) + self:HandleEvent( EVENTS.Takeoff, self._EventHandler ) + self:HandleEvent( EVENTS.Land, self._EventHandler ) + self:HandleEvent( EVENTS.Ejection, self._EventHandler ) + self:HandleEvent( EVENTS.LandingAfterEjection, self._EventHandler ) -- shagrat + self:HandleEvent( EVENTS.PlayerEnterAircraft, self._EventHandler ) + self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventHandler ) + self:HandleEvent( EVENTS.PilotDead, self._EventHandler ) if self.useprefix then local prefixes = self.csarPrefix or {} - self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefixes):FilterCategoryHelicopter():FilterStart() + self.allheligroupset = SET_GROUP:New():FilterCoalitions( self.coalitiontxt ):FilterPrefixes( prefixes ):FilterCategoryHelicopter():FilterStart() else - self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart() + self.allheligroupset = SET_GROUP:New():FilterCoalitions( self.coalitiontxt ):FilterCategoryHelicopter():FilterStart() end - self.mash = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart() -- currently only GROUP objects, maybe support STATICs also? - self:__Status(-10) + self.mash = SET_GROUP:New():FilterCoalitions( self.coalitiontxt ):FilterPrefixes( self.mashprefix ):FilterStart() -- currently only GROUP objects, maybe support STATICs also? + self:__Status( -10 ) return self end @@ -1998,14 +2003,14 @@ end function CSAR:_CheckDownedPilotTable() local pilots = self.downedPilots local npilots = {} - - for _ind,_entry in pairs(pilots) do + + for _ind, _entry in pairs( pilots ) do local _group = _entry.group if _group:IsAlive() then - npilots[_ind] = _entry + npilots[_ind] = _entry else if _entry.alive then - self:__KIA(1,_entry.desc) + self:__KIA( 1, _entry.desc ) end end end @@ -2018,27 +2023,27 @@ end -- @param #string From From state. -- @param #string Event Event triggered. -- @param #string To To state. -function CSAR:onbeforeStatus(From, Event, To) - self:T({From, Event, To}) +function CSAR:onbeforeStatus( From, Event, To ) + self:T( { From, Event, To } ) -- housekeeping self:_AddMedevacMenuItem() - + if not self.BeaconTimer or (self.BeaconTimer and not self.BeaconTimer:IsRunning()) then - self.BeaconTimer = TIMER:New(self._RefreshRadioBeacons,self) - self.BeaconTimer:Start(2,self.beaconRefresher) + self.BeaconTimer = TIMER:New( self._RefreshRadioBeacons, self ) + self.BeaconTimer:Start( 2, self.beaconRefresher ) end - + self:_CheckDownedPilotTable() - for _,_sar in pairs (self.csarUnits) do + for _, _sar in pairs( self.csarUnits ) do local PilotTable = self.downedPilots - for _,_entry in pairs (PilotTable) do + for _, _entry in pairs( PilotTable ) do if _entry.alive then local entry = _entry -- #CSAR.DownedPilot local name = entry.name local timestamp = entry.timestamp or 0 local now = timer.getAbsTime() - if now - timestamp > 17 then -- only check if we\'re not in approach mode, which is iterations of 5 and 10. - self:_CheckWoundedGroupStatus(_sar,name) + if now - timestamp > 17 then -- only check if we're not in approach mode, which is iterations of 5 and 10. + self:_CheckWoundedGroupStatus( _sar, name ) end end end @@ -2051,35 +2056,34 @@ end -- @param #string From From state. -- @param #string Event Event triggered. -- @param #string To To state. -function CSAR:onafterStatus(From, Event, To) - self:T({From, Event, To}) +function CSAR:onafterStatus( From, Event, To ) + self:T( { From, Event, To } ) -- collect some stats local NumberOfSARPilots = 0 - for _, _unitName in pairs(self.csarUnits) do + for _, _unitName in pairs( self.csarUnits ) do NumberOfSARPilots = NumberOfSARPilots + 1 end local PilotsInFieldN = self:_CountActiveDownedPilots() - + local PilotsBoarded = 0 - for _, _unitName in pairs(self.inTransitGroups) do - for _,_units in pairs(_unitName) do + for _, _unitName in pairs( self.inTransitGroups ) do + for _, _units in pairs( _unitName ) do PilotsBoarded = PilotsBoarded + 1 end end - + if self.verbose > 0 then - local text = string.format("%s Active SAR: %d | Downed Pilots in field: %d (max %d) | Pilots boarded: %d | Landings: %d | Pilots rescued: %d", - self.lid,NumberOfSARPilots,PilotsInFieldN,self.maxdownedpilots,PilotsBoarded,self.rescues,self.rescuedpilots) - self:T(text) + local text = string.format( "%s Active SAR: %d | Downed Pilots in field: %d (max %d) | Pilots boarded: %d | Landings: %d | Pilots rescued: %d", self.lid, NumberOfSARPilots, PilotsInFieldN, self.maxdownedpilots, PilotsBoarded, self.rescues, self.rescuedpilots ) + self:T( text ) if self.verbose < 2 then - self:I(text) + self:I( text ) elseif self.verbose > 1 then - self:I(text) - local m = MESSAGE:New(text,"10","Status",true):ToCoalition(self.coalition) + self:I( text ) + local m = MESSAGE:New( text, "10", "Status", true ):ToCoalition( self.coalition ) end end - self:__Status(-20) + self:__Status( -20 ) return self end @@ -2088,17 +2092,17 @@ end -- @param #string From From state. -- @param #string Event Event triggered. -- @param #string To To state. -function CSAR:onafterStop(From, Event, To) - self:T({From, Event, To}) +function CSAR:onafterStop( From, Event, To ) + self:T( { From, Event, To } ) -- event handler - self:UnHandleEvent(EVENTS.Takeoff) - self:UnHandleEvent(EVENTS.Land) - self:UnHandleEvent(EVENTS.Ejection) - self:UnHandleEvent(EVENTS.LandingAfterEjection) -- shagrat - self:UnHandleEvent(EVENTS.PlayerEnterUnit) - self:UnHandleEvent(EVENTS.PlayerEnterAircraft) - self:UnHandleEvent(EVENTS.PilotDead) - self:T(self.lid .. "Stopped.") + self:UnHandleEvent( EVENTS.Takeoff ) + self:UnHandleEvent( EVENTS.Land ) + self:UnHandleEvent( EVENTS.Ejection ) + self:UnHandleEvent( EVENTS.LandingAfterEjection ) -- shagrat + self:UnHandleEvent( EVENTS.PlayerEnterUnit ) + self:UnHandleEvent( EVENTS.PlayerEnterAircraft ) + self:UnHandleEvent( EVENTS.PilotDead ) + self:T( self.lid .. "Stopped." ) return self end @@ -2108,10 +2112,10 @@ end -- @param #string Event Event triggered. -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. --- @param #string Woundedgroupname Name of the downed pilot\'s group. -function CSAR:onbeforeApproach(From, Event, To, Heliname, Woundedgroupname) - self:T({From, Event, To, Heliname, Woundedgroupname}) - self:_CheckWoundedGroupStatus(Heliname,Woundedgroupname) +-- @param #string Woundedgroupname Name of the downed pilot's group. +function CSAR:onbeforeApproach( From, Event, To, Heliname, Woundedgroupname ) + self:T( { From, Event, To, Heliname, Woundedgroupname } ) + self:_CheckWoundedGroupStatus( Heliname, Woundedgroupname ) return self end @@ -2121,10 +2125,10 @@ end -- @param #string Event Event triggered. -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. --- @param #string Woundedgroupname Name of the downed pilot\'s group. -function CSAR:onbeforeBoarded(From, Event, To, Heliname, Woundedgroupname) - self:T({From, Event, To, Heliname, Woundedgroupname}) - self:_ScheduledSARFlight(Heliname,Woundedgroupname) +-- @param #string Woundedgroupname Name of the downed pilot's group. +function CSAR:onbeforeBoarded( From, Event, To, Heliname, Woundedgroupname ) + self:T( { From, Event, To, Heliname, Woundedgroupname } ) + self:_ScheduledSARFlight( Heliname, Woundedgroupname ) return self end @@ -2134,11 +2138,11 @@ end -- @param #string Event Event triggered. -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. --- @param #string Woundedgroupname Name of the downed pilot\'s group. +-- @param #string Woundedgroupname Name of the downed pilot's group. -- @param #boolean IsAirport True if heli has landed on an AFB (from event land). -function CSAR:onbeforeReturning(From, Event, To, Heliname, Woundedgroupname, IsAirPort) - self:T({From, Event, To, Heliname, Woundedgroupname}) - self:_ScheduledSARFlight(Heliname,Woundedgroupname, IsAirPort) +function CSAR:onbeforeReturning( From, Event, To, Heliname, Woundedgroupname, IsAirPort ) + self:T( { From, Event, To, Heliname, Woundedgroupname } ) + self:_ScheduledSARFlight( Heliname, Woundedgroupname, IsAirPort ) return self end @@ -2150,8 +2154,8 @@ end -- @param Wrapper.Unit#UNIT HeliUnit Unit of the helicopter. -- @param #string HeliName Name of the helicopter group. -- @param #number PilotsSaved Number of the saved pilots on board when landing. -function CSAR:onbeforeRescued(From, Event, To, HeliUnit, HeliName, PilotsSaved) - self:T({From, Event, To, HeliName, HeliUnit}) +function CSAR:onbeforeRescued( From, Event, To, HeliUnit, HeliName, PilotsSaved ) + self:T( { From, Event, To, HeliName, HeliUnit } ) self.rescues = self.rescues + 1 self.rescuedpilots = self.rescuedpilots + PilotsSaved return self @@ -2166,8 +2170,8 @@ end -- @param #number Frequency Beacon frequency in kHz. -- @param #string Leadername Name of the #UNIT of the downed pilot. -- @param #string CoordinatesText String of the position of the pilot. Format determined by self.coordtype. -function CSAR:onbeforePilotDown(From, Event, To, Group, Frequency, Leadername, CoordinatesText) - self:T({From, Event, To, Group, Frequency, Leadername, CoordinatesText}) +function CSAR:onbeforePilotDown( From, Event, To, Group, Frequency, Leadername, CoordinatesText ) + self:T( { From, Event, To, Group, Frequency, Leadername, CoordinatesText } ) return self end -------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 55cee46a8da3e170e1efd7f560148e94b3b9c3cf Mon Sep 17 00:00:00 2001 From: TommyC81 Date: Sun, 19 Dec 2021 11:47:07 +0400 Subject: [PATCH 042/200] Core code formatting and typo fixes. (#1669) * Update Beacon.lua Code formatting and minor typo/text fixes. * Update Database.lua Code formatting and minor typo/text fixes. * Update Event.lua Code formatting and minor typo/text fixes. --- Moose Development/Moose/Core/Beacon.lua | 303 +++++---- Moose Development/Moose/Core/Database.lua | 714 ++++++++++------------ Moose Development/Moose/Core/Event.lua | 189 +++--- 3 files changed, 571 insertions(+), 635 deletions(-) diff --git a/Moose Development/Moose/Core/Beacon.lua b/Moose Development/Moose/Core/Beacon.lua index 37dba8c76..c5203a6b3 100644 --- a/Moose Development/Moose/Core/Beacon.lua +++ b/Moose Development/Moose/Core/Beacon.lua @@ -1,9 +1,9 @@ --- **Core** - TACAN and other beacons. --- +-- -- === --- +-- -- ## Features: --- +-- -- * Provide beacon functionality to assist pilots. -- -- === @@ -14,34 +14,34 @@ -- @image Core_Radio.JPG --- *In order for the light to shine so brightly, the darkness must be present.* -- Francis Bacon --- +-- -- After attaching a @{#BEACON} to your @{Wrapper.Positionable#POSITIONABLE}, you need to select the right function to activate the kind of beacon you want. -- There are two types of BEACONs available : the AA TACAN Beacon and the general purpose Radio Beacon. -- Note that in both case, you can set an optional parameter : the `BeaconDuration`. This can be very usefull to simulate the battery time if your BEACON is --- attach to a cargo crate, for exemple. --- +-- attach to a cargo crate, for exemple. +-- -- ## AA TACAN Beacon usage --- +-- -- This beacon only works with airborne @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}. Use @{#BEACON:AATACAN}() to set the beacon parameters and start the beacon. -- Use @#BEACON:StopAATACAN}() to stop it. --- +-- -- ## General Purpose Radio Beacon usage --- +-- -- This beacon will work with any @{Wrapper.Positionable#POSITIONABLE}, but **it won't follow the @{Wrapper.Positionable#POSITIONABLE}** ! This means that you should only use it with -- @{Wrapper.Positionable#POSITIONABLE} that don't move, or move very slowly. Use @{#BEACON:RadioBeacon}() to set the beacon parameters and start the beacon. -- Use @{#BEACON:StopRadioBeacon}() to stop it. --- +-- -- @type BEACON -- @field #string ClassName Name of the class "BEACON". -- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will receive radio capabilities. -- @extends Core.Base#BASE BEACON = { - ClassName = "BEACON", + ClassName = "BEACON", Positionable = nil, - name = nil, + name = nil, } ---- Beacon types supported by DCS. +--- Beacon types supported by DCS. -- @type BEACON.Type -- @field #number NULL -- @field #number VOR @@ -65,19 +65,19 @@ BEACON = { -- @field #number ICLS_GLIDESLOPE -- @field #number NAUTICAL_HOMER BEACON.Type={ - NULL = 0, + NULL = 0, VOR = 1, DME = 2, - VOR_DME = 3, + VOR_DME = 3, TACAN = 4, - VORTAC = 5, + VORTAC = 5, RSBN = 128, - BROADCAST_STATION = 1024, + BROADCAST_STATION = 1024, HOMER = 8, - AIRPORT_HOMER = 4104, - AIRPORT_HOMER_WITH_MARKER = 4136, + AIRPORT_HOMER = 4104, + AIRPORT_HOMER_WITH_MARKER = 4136, ILS_FAR_HOMER = 16408, - ILS_NEAR_HOMER = 16424, + ILS_NEAR_HOMER = 16424, ILS_LOCALIZER = 16640, ILS_GLIDESLOPE = 16896, PRMG_LOCALIZER = 33024, @@ -95,11 +95,11 @@ BEACON.Type={ -- @field #number TACAN TACtical Air Navigation system on ground. -- @field #number TACAN_TANKER_X TACtical Air Navigation system for tankers on X band. -- @field #number TACAN_TANKER_Y TACtical Air Navigation system for tankers on Y band. --- @field #number VOR Very High Frequency Omni-Directional Range +-- @field #number VOR Very High Frequency Omnidirectional Range -- @field #number ILS_LOCALIZER ILS localizer --- @field #number ILS_GLIDESLOPE ILS glideslope. +-- @field #number ILS_GLIDESLOPE ILS glide slope. -- @field #number PRGM_LOCALIZER PRGM localizer. --- @field #number PRGM_GLIDESLOPE PRGM glideslope. +-- @field #number PRGM_GLIDESLOPE PRGM glide slope. -- @field #number BROADCAST_STATION Broadcast station. -- @field #number VORTAC Radio-based navigational aid for aircraft pilots consisting of a co-located VHF omnidirectional range (VOR) beacon and a tactical air navigation system (TACAN) beacon. -- @field #number TACAN_AA_MODE_X TACtical Air Navigation for aircraft on X band. @@ -108,13 +108,13 @@ BEACON.Type={ -- @field #number ICLS_LOCALIZER Carrier landing system. -- @field #number ICLS_GLIDESLOPE Carrier landing system. BEACON.System={ - PAR_10 = 1, - RSBN_5 = 2, - TACAN = 3, + PAR_10 = 1, + RSBN_5 = 2, + TACAN = 3, TACAN_TANKER_X = 4, TACAN_TANKER_Y = 5, - VOR = 6, - ILS_LOCALIZER = 7, + VOR = 6, + ILS_LOCALIZER = 7, ILS_GLIDESLOPE = 8, PRMG_LOCALIZER = 9, PRMG_GLIDESLOPE = 10, @@ -131,28 +131,27 @@ BEACON.System={ -- If you want to create a BEACON, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetBeacon}() instead. -- @param #BEACON self -- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities. --- @return #BEACON Beacon object or #nil if the positionable is invalid. -function BEACON:New(Positionable) +-- @return #BEACON Beacon object or #nil if the POSITIONABLE is invalid. +function BEACON:New( Positionable ) -- Inherit BASE. - local self=BASE:Inherit(self, BASE:New()) --#BEACON - + local self = BASE:Inherit( self, BASE:New() ) -- #BEACON + -- Debug. - self:F(Positionable) - + self:F( Positionable ) + -- Set positionable. - if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid + if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure POSITIONABLE is valid self.Positionable = Positionable - self.name=Positionable:GetName() - self:I(string.format("New BEACON %s", tostring(self.name))) + 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}) + + self:E( { "The passed POSITIONABLE is invalid, no BEACON created", Positionable } ) return nil end - --- Activates a TACAN BEACON. -- @param #BEACON self -- @param #number Channel TACAN channel, i.e. the "10" part in "10Y". @@ -162,53 +161,55 @@ end -- @param #number Duration How long will the beacon last in seconds. Omit for forever. -- @return #BEACON self -- @usage +-- -- -- Let's create a TACAN Beacon for a tanker --- local myUnit = UNIT:FindByName("MyUnit") +-- local myUnit = UNIT:FindByName("MyUnit") -- local myBeacon = myUnit:GetBeacon() -- Creates the beacon --- +-- -- myBeacon:ActivateTACAN(20, "Y", "TEXACO", true) -- Activate the beacon -function BEACON:ActivateTACAN(Channel, Mode, Message, Bearing, Duration) - self:T({channel=Channel, mode=Mode, callsign=Message, bearing=Bearing, duration=Duration}) - +-- +function BEACON:ActivateTACAN( Channel, Mode, Message, Bearing, Duration ) + self:T( { channel = Channel, mode = Mode, callsign = Message, bearing = Bearing, duration = Duration } ) + -- Get frequency. - local Frequency=UTILS.TACANToFrequency(Channel, Mode) - + local Frequency = UTILS.TACANToFrequency( Channel, Mode ) + -- Check. - if not Frequency then - self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"}) + if not Frequency then + self:E( { "The passed TACAN channel is invalid, the BEACON is not emitting" } ) return self end - + -- Beacon type. - local Type=BEACON.Type.TACAN - + local Type = BEACON.Type.TACAN + -- Beacon system. - local System=BEACON.System.TACAN - + local System = BEACON.System.TACAN + -- Check if unit is an aircraft and set system accordingly. - local AA=self.Positionable:IsAir() + local AA = self.Positionable:IsAir() if AA then - System=5 --NOTE: 5 is how you cat the correct tanker behaviour! --BEACON.System.TACAN_TANKER + System = 5 -- NOTE: 5 is how you cat the correct tanker behaviour! --BEACON.System.TACAN_TANKER -- Check if "Y" mode is selected for aircraft. - 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}) + 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 - + -- Attached unit. - local UnitID=self.Positionable:GetID() - + local UnitID = self.Positionable:GetID() + -- Debug. - 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: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 ) ) } ) + -- Start beacon. - self.Positionable:CommandActivateBeacon(Type, System, Frequency, UnitID, Channel, Mode, AA, Message, Bearing) - - -- Stop sheduler. + self.Positionable:CommandActivateBeacon( Type, System, Frequency, UnitID, Channel, Mode, AA, Message, Bearing ) + + -- Stop scheduler. if Duration then - self.Positionable:DeactivateBeacon(Duration) + self.Positionable:DeactivateBeacon( Duration ) end - + return self end @@ -218,23 +219,23 @@ end -- @param #string Callsign The Message that is going to be coded in Morse and broadcasted by the beacon. -- @param #number Duration How long will the beacon last in seconds. Omit for forever. -- @return #BEACON self -function BEACON:ActivateICLS(Channel, Callsign, Duration) - self:F({Channel=Channel, Callsign=Callsign, Duration=Duration}) - +function BEACON:ActivateICLS( Channel, Callsign, Duration ) + self:F( { Channel = Channel, Callsign = Callsign, Duration = Duration } ) + -- Attached unit. - local UnitID=self.Positionable:GetID() - + local UnitID = self.Positionable:GetID() + -- Debug - self:T2({"ICLS BEACON started!"}) - + self:T2( { "ICLS BEACON started!" } ) + -- Start beacon. - self.Positionable:CommandActivateICLS(Channel, UnitID, Callsign) - - -- Stop sheduler + self.Positionable:CommandActivateICLS( Channel, UnitID, Callsign ) + + -- Stop scheduler if Duration then -- Schedule the stop of the BEACON if asked by the MD - self.Positionable:DeactivateBeacon(Duration) + self.Positionable:DeactivateBeacon( Duration ) end - + return self end @@ -246,27 +247,29 @@ end -- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever. -- @return #BEACON self -- @usage +-- -- -- Let's create a TACAN Beacon for a tanker --- local myUnit = UNIT:FindByName("MyUnit") +-- local myUnit = UNIT:FindByName("MyUnit") -- local myBeacon = myUnit:GetBeacon() -- Creates the beacon --- +-- -- myBeacon:AATACAN(20, "TEXACO", true) -- Activate the beacon -function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration) - self:F({TACANChannel, Message, Bearing, BeaconDuration}) - +-- +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}) + 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"}) + + 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 - + -- I'm using the beacon type 4 (BEACON_TYPE_TACAN). For System, I'm using 5 (TACAN_TANKER_MODE_Y) if the bearing shows its bearing -- or 14 (TACAN_AA_MODE_Y) if it does not local System @@ -275,27 +278,26 @@ function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration) else System = 14 end - + if IsValid then -- Starts the BEACON - self:T2({"AA TACAN BEACON started !"}) - self.Positionable:SetCommand({ + self:T2( { "AA TACAN BEACON started !" } ) + self.Positionable:SetCommand( { id = "ActivateBeacon", params = { type = 4, system = System, callsign = Message, frequency = Frequency, - } - }) - + }, + } ) + if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD - SCHEDULER:New(nil, - function() + SCHEDULER:New( nil, function() self:StopAATACAN() - end, {}, BeaconDuration) + end, {}, BeaconDuration ) end end - + return self end @@ -305,21 +307,19 @@ end function BEACON:StopAATACAN() self:F() if not self.Positionable then - self:E({"Start the beacon first before stoping it !"}) + self:E( { "Start the beacon first before stopping it!" } ) else - self.Positionable:SetCommand({ - id = 'DeactivateBeacon', - params = { - } - }) + self.Positionable:SetCommand( { + id = 'DeactivateBeacon', + params = {}, + } ) end end - ---- Activates a general pupose Radio Beacon +--- Activates a general purpose Radio Beacon -- This uses the very generic singleton function "trigger.action.radioTransmission()" provided by DCS to broadcast a sound file on a specific frequency. -- Although any frequency could be used, only 2 DCS Modules can home on radio beacons at the time of writing : the Huey and the Mi-8. --- They can home in on these specific frequencies : +-- They can home in on these specific frequencies : -- * **Mi8** -- * R-828 -> 20-60MHz -- * ARKUD -> 100-150MHz (canal 1 : 114166, canal 2 : 114333, canal 3 : 114583, canal 4 : 121500, canal 5 : 123100, canal 6 : 124100) AM @@ -342,55 +342,54 @@ end -- -- -- Set the beacon and start it -- UnitBeacon:RadioBeacon("MySoundFileSOS.ogg", 40, radio.modulation.FM, 20, 5*60) -function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDuration) - self:F({FileName, Frequency, Modulation, Power, BeaconDuration}) +function BEACON:RadioBeacon( FileName, Frequency, Modulation, Power, BeaconDuration ) + self:F( { FileName, Frequency, Modulation, Power, BeaconDuration } ) local IsValid = false - + -- Check the filename - if type(FileName) == "string" then - if FileName:find(".ogg") or FileName:find(".wav") then - if not FileName:find("l10n/DEFAULT/") then + 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}) + self:E( { "File name invalid. Maybe something wrong with the extension? ", FileName } ) end - + -- Check the Frequency - if type(Frequency) ~= "number" and IsValid then - self:E({"Frequency invalid. ", Frequency}) + if type( Frequency ) ~= "number" and IsValid then + self:E( { "Frequency invalid. ", Frequency } ) IsValid = false end Frequency = Frequency * 1000000 -- Conversion to Hz - + -- Check the modulation - if Modulation ~= radio.modulation.AM and Modulation ~= radio.modulation.FM and IsValid then --TODO Maybe make this future proof if ED decides to add an other modulation ? - self:E({"Modulation is invalid. Use DCS's enum radio.modulation.", Modulation}) + if Modulation ~= radio.modulation.AM and Modulation ~= radio.modulation.FM and IsValid then -- TODO: Maybe make this future proof if ED decides to add an other modulation ? + self:E( { "Modulation is invalid. Use DCS's enum radio.modulation.", Modulation } ) IsValid = false end - + -- Check the Power - if type(Power) ~= "number" and IsValid then - self:E({"Power is invalid. ", Power}) + if type( Power ) ~= "number" and IsValid then + self:E( { "Power is invalid. ", Power } ) IsValid = false end - Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that - + Power = math.floor( math.abs( Power ) ) -- TODO: Find what is the maximum power allowed by DCS and limit power to that + if IsValid then - self:T2({"Activating Beacon on ", Frequency, Modulation}) + self:T2( { "Activating Beacon on ", Frequency, Modulation } ) -- Note that this is looped. I have to give this transmission a unique name, I use the class ID - trigger.action.radioTransmission(FileName, self.Positionable:GetPositionVec3(), Modulation, true, Frequency, Power, tostring(self.ID)) - - if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD - SCHEDULER:New( nil, - function() - self:StopRadioBeacon() - end, {}, BeaconDuration) - end - end + trigger.action.radioTransmission( FileName, self.Positionable:GetPositionVec3(), Modulation, true, Frequency, Power, tostring( self.ID ) ) + + if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD + SCHEDULER:New( nil, function() + self:StopRadioBeacon() + end, {}, BeaconDuration ) + end + end end --- Stops the AA TACAN BEACON @@ -399,7 +398,7 @@ end function BEACON:StopRadioBeacon() self:F() -- The unique name of the transmission is the class ID - trigger.action.stopRadioTransmission(tostring(self.ID)) + trigger.action.stopRadioTransmission( tostring( self.ID ) ) return self end @@ -407,26 +406,26 @@ end -- @param #BEACON self -- @param #number TACANChannel -- @param #string TACANMode --- @return #number Frequecy +-- @return #number Frequency -- @return #nil if parameters are invalid -function BEACON:_TACANToFrequency(TACANChannel, TACANMode) - self:F3({TACANChannel, TACANMode}) +function BEACON:_TACANToFrequency( TACANChannel, TACANMode ) + self:F3( { TACANChannel, TACANMode } ) - if type(TACANChannel) ~= "number" then + if type( TACANChannel ) ~= "number" then if TACANMode ~= "X" and TACANMode ~= "Y" then return nil -- error in arguments end end - --- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137. --- I have no idea what it does but it seems to work + + -- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137. + -- I have no idea what it does but it seems to work local A = 1151 -- 'X', channel >= 64 - local B = 64 -- channel >= 64 - + local B = 64 -- channel >= 64 + if TACANChannel < 64 then B = 1 end - + if TACANMode == 'Y' then A = 1025 if TACANChannel < 64 then @@ -437,6 +436,6 @@ function BEACON:_TACANToFrequency(TACANChannel, TACANMode) A = 962 end end - + return (A + TACANChannel - B) * 1000000 -end \ No newline at end of file +end diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 50031e81b..02a2d8b57 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -31,7 +31,6 @@ -- @module Core.Database -- @image Core_Database.JPG - --- @type DATABASE -- @field #string ClassName Name of the class. -- @field #table Templates Templates: Units, Groups, Statics, ClientsByName, ClientsByID. @@ -51,7 +50,7 @@ -- * PLAYERS -- * CARGOS -- --- On top, for internal MOOSE administration purposes, the DATBASE administers the Unit and Group TEMPLATES as defined within the Mission Editor. +-- On top, for internal MOOSE administration purposes, the DATABASE administers the Unit and Group TEMPLATES as defined within the Mission Editor. -- -- The singleton object **_DATABASE** is automatically created by MOOSE, that administers all objects within the mission. -- Moose refers to **_DATABASE** within the framework extensively, but you can also refer to the _DATABASE object within your missions if required. @@ -90,22 +89,19 @@ DATABASE = { FLIGHTCONTROLS = {}, } -local _DATABASECoalition = - { - [1] = "Red", - [2] = "Blue", - [3] = "Neutral", - } - -local _DATABASECategory = - { - ["plane"] = Unit.Category.AIRPLANE, - ["helicopter"] = Unit.Category.HELICOPTER, - ["vehicle"] = Unit.Category.GROUND_UNIT, - ["ship"] = Unit.Category.SHIP, - ["static"] = Unit.Category.STRUCTURE, - } +local _DATABASECoalition = { + [1] = "Red", + [2] = "Blue", + [3] = "Neutral", +} +local _DATABASECategory = { + ["plane"] = Unit.Category.AIRPLANE, + ["helicopter"] = Unit.Category.HELICOPTER, + ["vehicle"] = Unit.Category.GROUND_UNIT, + ["ship"] = Unit.Category.SHIP, + ["static"] = Unit.Category.STRUCTURE, +} --- Creates a new DATABASE object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. -- @param #DATABASE self @@ -129,15 +125,15 @@ function DATABASE:New() self:HandleEvent( EVENTS.DeleteCargo ) self:HandleEvent( EVENTS.NewZone ) self:HandleEvent( EVENTS.DeleteZone ) - --self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventOnPlayerEnterUnit ) -- This is not working anymore!, handling this through the birth event. + -- self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventOnPlayerEnterUnit ) -- This is not working anymore!, handling this through the birth event. self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventOnPlayerLeaveUnit ) self:_RegisterTemplates() self:_RegisterGroupsAndUnits() self:_RegisterClients() self:_RegisterStatics() - --self:_RegisterAirbases() - --self:_RegisterPlayers() + -- self:_RegisterAirbases() + -- self:_RegisterPlayers() self.UNITS_Position = 0 @@ -154,7 +150,6 @@ function DATABASE:FindUnit( UnitName ) return UnitFound end - --- Adds a Unit based on the Unit Name in the DATABASE. -- @param #DATABASE self -- @param #string DCSUnitName Unit name. @@ -162,23 +157,22 @@ end function DATABASE:AddUnit( DCSUnitName ) if not self.UNITS[DCSUnitName] then - + -- Debug info. self:T( { "Add UNIT:", DCSUnitName } ) - - --local UnitRegister = UNIT:Register( DCSUnitName ) - + + -- local UnitRegister = UNIT:Register( DCSUnitName ) + -- Register unit - self.UNITS[DCSUnitName]=UNIT:Register(DCSUnitName) + self.UNITS[DCSUnitName] = UNIT:Register( DCSUnitName ) -- This is not used anywhere in MOOSE as far as I can see so I remove it until there comes an error somewhere. - --table.insert(self.UNITS_Index, DCSUnitName ) + -- table.insert(self.UNITS_Index, DCSUnitName ) end return self.UNITS[DCSUnitName] end - --- Deletes a Unit from the DATABASE based on the Unit Name. -- @param #DATABASE self function DATABASE:DeleteUnit( DCSUnitName ) @@ -200,7 +194,6 @@ function DATABASE:AddStatic( DCSStaticName ) return nil end - --- Deletes a Static from the DATABASE based on the Static Name. -- @param #DATABASE self function DATABASE:DeleteStatic( DCSStaticName ) @@ -240,7 +233,6 @@ function DATABASE:AddAirbase( AirbaseName ) return self.AIRBASES[AirbaseName] end - --- Deletes a Airbase from the DATABASE based on the Airbase Name. -- @param #DATABASE self -- @param #string AirbaseName The name of the airbase @@ -259,7 +251,6 @@ function DATABASE:FindAirbase( AirbaseName ) return AirbaseFound end - do -- Zones --- Finds a @{Zone} based on the zone name. @@ -283,7 +274,6 @@ do -- Zones end end - --- Deletes a @{Zone} from the DATABASE based on the zone name. -- @param #DATABASE self -- @param #string ZoneName The name of the zone. @@ -292,83 +282,82 @@ do -- Zones self.ZONES[ZoneName] = nil end - --- Private method that registers new ZONE_BASE derived objects within the DATABASE Object. -- @param #DATABASE self -- @return #DATABASE self function DATABASE:_RegisterZones() - for ZoneID, ZoneData in pairs(env.mission.triggers.zones) do + for ZoneID, ZoneData in pairs( env.mission.triggers.zones ) do local ZoneName = ZoneData.name - + -- Color - local color=ZoneData.color or {1, 0, 0, 0.15} - + local color = ZoneData.color or { 1, 0, 0, 0.15 } + -- Create new Zone - local Zone=nil --Core.Zone#ZONE_BASE - - if ZoneData.type==0 then - + local Zone = nil -- Core.Zone#ZONE_BASE + + if ZoneData.type == 0 then + --- -- Circular zone --- - - self:I(string.format("Register ZONE: %s (Circular)", ZoneName)) - - Zone=ZONE:New(ZoneName) - + + self:I( string.format( "Register ZONE: %s (Circular)", ZoneName ) ) + + Zone = ZONE:New( ZoneName ) + else --- -- Quad-point zone --- - self:I(string.format("Register ZONE: %s (Polygon, Quad)", ZoneName)) - - Zone=ZONE_POLYGON_BASE:New(ZoneName, ZoneData.verticies) - - --for i,vec2 in pairs(ZoneData.verticies) do + self:I( string.format( "Register ZONE: %s (Polygon, Quad)", ZoneName ) ) + + Zone = ZONE_POLYGON_BASE:New( ZoneName, ZoneData.verticies ) + + -- for i,vec2 in pairs(ZoneData.verticies) do -- local coord=COORDINATE:NewFromVec2(vec2) -- coord:MarkToAll(string.format("%s Point %d", ZoneName, i)) - --end - + -- end + end - + if Zone then -- Store color of zone. - Zone.Color=color - + Zone.Color = color + -- Store in DB. self.ZONENAMES[ZoneName] = ZoneName - + -- Add zone. - self:AddZone(ZoneName, Zone) - + self:AddZone( ZoneName, Zone ) + end - + end -- Polygon zones defined by late activated groups. for ZoneGroupName, ZoneGroup in pairs( self.GROUPS ) do - if ZoneGroupName:match("#ZONE_POLYGON") then - - local ZoneName1 = ZoneGroupName:match("(.*)#ZONE_POLYGON") - local ZoneName2 = ZoneGroupName:match(".*#ZONE_POLYGON(.*)") - local ZoneName = ZoneName1 .. ( ZoneName2 or "" ) + if ZoneGroupName:match( "#ZONE_POLYGON" ) then + + local ZoneName1 = ZoneGroupName:match( "(.*)#ZONE_POLYGON" ) + local ZoneName2 = ZoneGroupName:match( ".*#ZONE_POLYGON(.*)" ) + local ZoneName = ZoneName1 .. (ZoneName2 or "") -- Debug output - self:I(string.format("Register ZONE: %s (Polygon)", ZoneName)) - + self:I( string.format( "Register ZONE: %s (Polygon)", ZoneName ) ) + -- Create a new polygon zone. local Zone_Polygon = ZONE_POLYGON:New( ZoneName, ZoneGroup ) - + -- Set color. - Zone_Polygon:SetColor({1, 0, 0}, 0.15) - + Zone_Polygon:SetColor( { 1, 0, 0 }, 0.15 ) + -- Store name in DB. self.ZONENAMES[ZoneName] = ZoneName - + -- Add zone to DB. self:AddZone( ZoneName, Zone_Polygon ) end @@ -376,7 +365,6 @@ do -- Zones end - end -- zone do -- Zone_Goal @@ -402,7 +390,6 @@ do -- Zone_Goal end end - --- Deletes a @{Zone} from the DATABASE based on the zone name. -- @param #DATABASE self -- @param #string ZoneName The name of the zone. @@ -424,7 +411,6 @@ do -- cargo end end - --- Deletes a Cargo from the DATABASE based on the Cargo Name. -- @param #DATABASE self -- @param #string CargoName The name of the airbase @@ -466,38 +452,38 @@ do -- cargo for CargoGroupName, CargoGroup in pairs( Groups ) do if self:IsCargo( CargoGroupName ) then - local CargoInfo = CargoGroupName:match("#CARGO(.*)") - local CargoParam = CargoInfo and CargoInfo:match( "%((.*)%)") - local CargoName1 = CargoGroupName:match("(.*)#CARGO%(.*%)") - local CargoName2 = CargoGroupName:match(".*#CARGO%(.*%)(.*)") - local CargoName = CargoName1 .. ( CargoName2 or "" ) - local Type = CargoParam and CargoParam:match( "T=([%a%d ]+),?") - local Name = CargoParam and CargoParam:match( "N=([%a%d]+),?") or CargoName - local LoadRadius = CargoParam and tonumber( CargoParam:match( "RR=([%a%d]+),?") ) - local NearRadius = CargoParam and tonumber( CargoParam:match( "NR=([%a%d]+),?") ) + local CargoInfo = CargoGroupName:match( "#CARGO(.*)" ) + local CargoParam = CargoInfo and CargoInfo:match( "%((.*)%)" ) + local CargoName1 = CargoGroupName:match( "(.*)#CARGO%(.*%)" ) + local CargoName2 = CargoGroupName:match( ".*#CARGO%(.*%)(.*)" ) + local CargoName = CargoName1 .. (CargoName2 or "") + local Type = CargoParam and CargoParam:match( "T=([%a%d ]+),?" ) + local Name = CargoParam and CargoParam:match( "N=([%a%d]+),?" ) or CargoName + local LoadRadius = CargoParam and tonumber( CargoParam:match( "RR=([%a%d]+),?" ) ) + local NearRadius = CargoParam and tonumber( CargoParam:match( "NR=([%a%d]+),?" ) ) - self:I({"Register CargoGroup:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius}) + self:I( { "Register CargoGroup:", Type = Type, Name = Name, LoadRadius = LoadRadius, NearRadius = NearRadius } ) CARGO_GROUP:New( CargoGroup, Type, Name, LoadRadius, NearRadius ) end end for CargoStaticName, CargoStatic in pairs( self.STATICS ) do if self:IsCargo( CargoStaticName ) then - local CargoInfo = CargoStaticName:match("#CARGO(.*)") - local CargoParam = CargoInfo and CargoInfo:match( "%((.*)%)") - local CargoName = CargoStaticName:match("(.*)#CARGO") - local Type = CargoParam and CargoParam:match( "T=([%a%d ]+),?") - local Category = CargoParam and CargoParam:match( "C=([%a%d ]+),?") - local Name = CargoParam and CargoParam:match( "N=([%a%d]+),?") or CargoName - local LoadRadius = CargoParam and tonumber( CargoParam:match( "RR=([%a%d]+),?") ) - local NearRadius = CargoParam and tonumber( CargoParam:match( "NR=([%a%d]+),?") ) + local CargoInfo = CargoStaticName:match( "#CARGO(.*)" ) + local CargoParam = CargoInfo and CargoInfo:match( "%((.*)%)" ) + local CargoName = CargoStaticName:match( "(.*)#CARGO" ) + local Type = CargoParam and CargoParam:match( "T=([%a%d ]+),?" ) + local Category = CargoParam and CargoParam:match( "C=([%a%d ]+),?" ) + local Name = CargoParam and CargoParam:match( "N=([%a%d]+),?" ) or CargoName + local LoadRadius = CargoParam and tonumber( CargoParam:match( "RR=([%a%d]+),?" ) ) + local NearRadius = CargoParam and tonumber( CargoParam:match( "NR=([%a%d]+),?" ) ) if Category == "SLING" then - self:I({"Register CargoSlingload:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius}) + self:I( { "Register CargoSlingload:", Type = Type, Name = Name, LoadRadius = LoadRadius, NearRadius = NearRadius } ) CARGO_SLINGLOAD:New( CargoStatic, Type, Name, LoadRadius, NearRadius ) else if Category == "CRATE" then - self:I({"Register CargoCrate:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius}) + self:I( { "Register CargoCrate:", Type = Type, Name = Name, LoadRadius = LoadRadius, NearRadius = NearRadius } ) CARGO_CRATE:New( CargoStatic, Type, Name, LoadRadius, NearRadius ) end end @@ -518,7 +504,6 @@ function DATABASE:FindClient( ClientName ) return ClientFound end - --- Adds a CLIENT based on the ClientName in the DATABASE. -- @param #DATABASE self -- @param #string ClientName Name of the Client unit. @@ -532,7 +517,6 @@ function DATABASE:AddClient( ClientName ) return self.CLIENTS[ClientName] end - --- Finds a GROUP based on the GroupName. -- @param #DATABASE self -- @param #string GroupName @@ -543,7 +527,6 @@ function DATABASE:FindGroup( GroupName ) return GroupFound end - --- Adds a GROUP based on the GroupName in the DATABASE. -- @param #DATABASE self function DATABASE:AddGroup( GroupName ) @@ -591,7 +574,6 @@ function DATABASE:GetPlayers() return self.PLAYERS end - --- Get the player table from the DATABASE, which contains all UNIT objects. -- The player table contains all UNIT objects of the player with the key the name of the player (PlayerName). -- @param #DATABASE self @@ -604,7 +586,6 @@ function DATABASE:GetPlayerUnits() return self.PLAYERUNITS end - --- Get the player table from the DATABASE which have joined in the mission historically. -- The player table contains all UNIT objects with the key the name of the player (PlayerName). -- @param #DATABASE self @@ -617,7 +598,6 @@ function DATABASE:GetPlayersJoined() return self.PLAYERSJOINED end - --- Instantiate new Groups within the DCSRTE. -- This method expects EXACTLY the same structure as a structure within the ME, and needs 2 additional fields defined: -- SpawnCountryID, SpawnCategoryID @@ -640,7 +620,7 @@ function DATABASE:Spawn( SpawnTemplate ) SpawnTemplate.CountryID = nil SpawnTemplate.CategoryID = nil - self:_RegisterGroupTemplate( SpawnTemplate, SpawnCoalitionID, SpawnCategoryID, SpawnCountryID ) + self:_RegisterGroupTemplate( SpawnTemplate, SpawnCoalitionID, SpawnCategoryID, SpawnCountryID ) self:T3( SpawnTemplate ) coalition.addGroup( SpawnCountryID, SpawnCategoryID, SpawnTemplate ) @@ -722,7 +702,7 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category for unit_num, UnitTemplate in pairs( GroupTemplate.units ) do - UnitTemplate.name = env.getValueDictByKey(UnitTemplate.name) + UnitTemplate.name = env.getValueDictByKey( UnitTemplate.name ) self.Templates.Units[UnitTemplate.name] = {} self.Templates.Units[UnitTemplate.name].UnitName = UnitTemplate.name @@ -742,17 +722,16 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category self.Templates.ClientsByID[UnitTemplate.unitId] = UnitTemplate end - UnitNames[#UnitNames+1] = self.Templates.Units[UnitTemplate.name].UnitName + UnitNames[#UnitNames + 1] = self.Templates.Units[UnitTemplate.name].UnitName end -- Debug info. - self:T( { Group = self.Templates.Groups[GroupTemplateName].GroupName, + self:T( { Group = self.Templates.Groups[GroupTemplateName].GroupName, Coalition = self.Templates.Groups[GroupTemplateName].CoalitionID, - Category = self.Templates.Groups[GroupTemplateName].CategoryID, - Country = self.Templates.Groups[GroupTemplateName].CountryID, - Units = UnitNames - } - ) + Category = self.Templates.Groups[GroupTemplateName].CategoryID, + Country = self.Templates.Groups[GroupTemplateName].CountryID, + Units = UnitNames, + } ) end --- Get group template. @@ -778,7 +757,7 @@ function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, Category local StaticTemplate = UTILS.DeepCopy( StaticTemplate ) - local StaticTemplateName = env.getValueDictByKey(StaticTemplate.name) + local StaticTemplateName = env.getValueDictByKey( StaticTemplate.name ) self.Templates.Statics[StaticTemplateName] = self.Templates.Statics[StaticTemplateName] or {} @@ -797,9 +776,8 @@ function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, Category self:T( { Static = self.Templates.Statics[StaticTemplateName].StaticName, Coalition = self.Templates.Statics[StaticTemplateName].CoalitionID, Category = self.Templates.Statics[StaticTemplateName].CategoryID, - Country = self.Templates.Statics[StaticTemplateName].CountryID - } - ) + Country = self.Templates.Statics[StaticTemplateName].CountryID, + } ) self:AddStatic( StaticTemplateName ) @@ -815,7 +793,7 @@ function DATABASE:GetStaticGroupTemplate( StaticName ) local StaticTemplate = self.Templates.Statics[StaticName].GroupTemplate return StaticTemplate, self.Templates.Statics[StaticName].CoalitionID, self.Templates.Statics[StaticName].CategoryID, self.Templates.Statics[StaticName].CountryID else - self:E("ERROR: Static group template does NOT exist for static "..tostring(StaticName)) + self:E( "ERROR: Static group template does NOT exist for static " .. tostring( StaticName ) ) return nil end end @@ -829,8 +807,8 @@ function DATABASE:GetStaticUnitTemplate( StaticName ) local UnitTemplate = self.Templates.Statics[StaticName].UnitTemplate return UnitTemplate, self.Templates.Statics[StaticName].CoalitionID, self.Templates.Statics[StaticName].CategoryID, self.Templates.Statics[StaticName].CountryID else - self:E("ERROR: Static unit template does NOT exist for static "..tostring(StaticName)) - return nil + self:E( "ERROR: Static unit template does NOT exist for static " .. tostring( StaticName ) ) + return nil end end @@ -842,7 +820,7 @@ function DATABASE:GetGroupNameFromUnitName( UnitName ) if self.Templates.Units[UnitName] then return self.Templates.Units[UnitName].GroupName else - self:E("ERROR: Unit template does not exist for unit "..tostring(UnitName)) + self:E( "ERROR: Unit template does not exist for unit " .. tostring( UnitName ) ) return nil end end @@ -855,8 +833,8 @@ function DATABASE:GetGroupTemplateFromUnitName( UnitName ) if self.Templates.Units[UnitName] then return self.Templates.Units[UnitName].GroupTemplate else - self:E("ERROR: Unit template does not exist for unit "..tostring(UnitName)) - return nil + self:E( "ERROR: Unit template does not exist for unit " .. tostring( UnitName ) ) + return nil end end @@ -902,8 +880,6 @@ function DATABASE:GetCategoryFromAirbase( AirbaseName ) return self.AIRBASES[AirbaseName]:GetCategory() end - - --- Private method that registers all alive players in the mission. -- @param #DATABASE self -- @return #DATABASE self @@ -927,25 +903,24 @@ function DATABASE:_RegisterPlayers() return self end - --- Private method that registers all Groups and Units within in the mission. -- @param #DATABASE self -- @return #DATABASE self function DATABASE:_RegisterGroupsAndUnits() - local CoalitionsData = { GroupsRed = coalition.getGroups( coalition.side.RED ), GroupsBlue = coalition.getGroups( coalition.side.BLUE ), GroupsNeutral = coalition.getGroups( coalition.side.NEUTRAL ) } - + local CoalitionsData = { GroupsRed = coalition.getGroups( coalition.side.RED ), GroupsBlue = coalition.getGroups( coalition.side.BLUE ), GroupsNeutral = coalition.getGroups( coalition.side.NEUTRAL ) } + for CoalitionId, CoalitionData in pairs( CoalitionsData ) do - + for DCSGroupId, DCSGroup in pairs( CoalitionData ) do if DCSGroup:isExist() then - + -- Group name. local DCSGroupName = DCSGroup:getName() -- Add group. - self:I(string.format("Register Group: %s", tostring(DCSGroupName))) + self:I( string.format( "Register Group: %s", tostring( DCSGroupName ) ) ) self:AddGroup( DCSGroupName ) -- Loop over units in group. @@ -953,14 +928,14 @@ function DATABASE:_RegisterGroupsAndUnits() -- Get unit name. local DCSUnitName = DCSUnit:getName() - + -- Add unit. - self:I(string.format("Register Unit: %s", tostring(DCSUnitName))) + self:I( string.format( "Register Unit: %s", tostring( DCSUnitName ) ) ) self:AddUnit( DCSUnitName ) - + end else - self:E({"Group does not exist: ", DCSGroup}) + self:E( { "Group does not exist: ", DCSGroup } ) end end @@ -975,7 +950,7 @@ end function DATABASE:_RegisterClients() for ClientName, ClientTemplate in pairs( self.Templates.ClientsByName ) do - self:I(string.format("Register Client: %s", tostring(ClientName))) + self:I( string.format( "Register Client: %s", tostring( ClientName ) ) ) self:AddClient( ClientName ) end @@ -985,7 +960,7 @@ end --- @param #DATABASE self function DATABASE:_RegisterStatics() - local CoalitionsData={GroupsRed=coalition.getStaticObjects(coalition.side.RED), GroupsBlue=coalition.getStaticObjects(coalition.side.BLUE), GroupsNeutral=coalition.getStaticObjects(coalition.side.NEUTRAL)} + local CoalitionsData = { GroupsRed = coalition.getStaticObjects( coalition.side.RED ), GroupsBlue = coalition.getStaticObjects( coalition.side.BLUE ), GroupsNeutral = coalition.getStaticObjects( coalition.side.NEUTRAL ) } for CoalitionId, CoalitionData in pairs( CoalitionsData ) do for DCSStaticId, DCSStatic in pairs( CoalitionData ) do @@ -993,10 +968,10 @@ function DATABASE:_RegisterStatics() if DCSStatic:isExist() then local DCSStaticName = DCSStatic:getName() - self:I(string.format("Register Static: %s", tostring(DCSStaticName))) + self:I( string.format( "Register Static: %s", tostring( DCSStaticName ) ) ) self:AddStatic( DCSStaticName ) else - self:E( { "Static does not exist: ", DCSStatic } ) + self:E( { "Static does not exist: ", DCSStatic } ) end end end @@ -1009,41 +984,40 @@ end -- @return #DATABASE self function DATABASE:_RegisterAirbases() - for DCSAirbaseId, DCSAirbase in pairs(world.getAirbases()) do - + for DCSAirbaseId, DCSAirbase in pairs( world.getAirbases() ) do + -- Get the airbase name. local DCSAirbaseName = DCSAirbase:getName() -- This gave the incorrect value to be inserted into the airdromeID for DCS 2.5.6. Is fixed now. - local airbaseID=DCSAirbase:getID() + local airbaseID = DCSAirbase:getID() -- Add and register airbase. - local airbase=self:AddAirbase( DCSAirbaseName ) - + local airbase = self:AddAirbase( DCSAirbaseName ) + -- Unique ID. - local airbaseUID=airbase:GetID(true) + local airbaseUID = airbase:GetID( true ) -- Debug output. - local text=string.format("Register %s: %s (ID=%d UID=%d), parking=%d [", AIRBASE.CategoryName[airbase.category], tostring(DCSAirbaseName), airbaseID, airbaseUID, airbase.NparkingTotal) - for _,terminalType in pairs(AIRBASE.TerminalType) do + local text = string.format( "Register %s: %s (ID=%d UID=%d), parking=%d [", AIRBASE.CategoryName[airbase.category], tostring( DCSAirbaseName ), airbaseID, airbaseUID, airbase.NparkingTotal ) + for _, terminalType in pairs( AIRBASE.TerminalType ) do if airbase.NparkingTerminal and airbase.NparkingTerminal[terminalType] then - text=text..string.format("%d=%d ", terminalType, airbase.NparkingTerminal[terminalType]) + text = text .. string.format( "%d=%d ", terminalType, airbase.NparkingTerminal[terminalType] ) end end - text=text.."]" - self:I(text) - + text = text .. "]" + self:I( text ) + -- Check for DCS bug IDs. - if airbaseID~=airbase:GetID() then - --self:E("WARNING: :getID does NOT match :GetID!") - end - + if airbaseID ~= airbase:GetID() then + -- self:E("WARNING: :getID does NOT match :GetID!") + end + end return self end - --- Events --- Handles the OnBirth event for the alive units set. @@ -1053,76 +1027,75 @@ function DATABASE:_EventOnBirth( Event ) self:F( { Event } ) if Event.IniDCSUnit then - + if Event.IniObjectCategory == 3 then - + self:AddStatic( Event.IniDCSUnitName ) - + else - + if Event.IniObjectCategory == 1 then - + self:AddUnit( Event.IniDCSUnitName ) self:AddGroup( Event.IniDCSGroupName ) - + -- Add airbase if it was spawned later in the mission. - local DCSAirbase = Airbase.getByName(Event.IniDCSUnitName) + local DCSAirbase = Airbase.getByName( Event.IniDCSUnitName ) if DCSAirbase then - self:I(string.format("Adding airbase %s", tostring(Event.IniDCSUnitName))) - self:AddAirbase(Event.IniDCSUnitName) + self:I( string.format( "Adding airbase %s", tostring( Event.IniDCSUnitName ) ) ) + self:AddAirbase( Event.IniDCSUnitName ) end - + end end - + if Event.IniObjectCategory == 1 then - + Event.IniUnit = self:FindUnit( Event.IniDCSUnitName ) Event.IniGroup = self:FindGroup( Event.IniDCSGroupName ) - + -- Client - local client=self.CLIENTS[Event.IniDCSUnitName] --Wrapper.Client#CLIENT - + local client = self.CLIENTS[Event.IniDCSUnitName] -- Wrapper.Client#CLIENT + if client then -- TODO: create event ClientAlive - end - + end + -- Get player name. local PlayerName = Event.IniUnit:GetPlayerName() - + if PlayerName then - + -- Debug info. - self:I(string.format("Player '%s' joint unit '%s' of group '%s'", tostring(PlayerName), tostring(Event.IniDCSUnitName), tostring(Event.IniDCSGroupName))) - + self:I( string.format( "Player '%s' joint unit '%s' of group '%s'", tostring( PlayerName ), tostring( Event.IniDCSUnitName ), tostring( Event.IniDCSGroupName ) ) ) + -- Add client in case it does not exist already. if not client then - client=self:AddClient(Event.IniDCSUnitName) + client = self:AddClient( Event.IniDCSUnitName ) end - + -- Add player. - client:AddPlayer(PlayerName) - + client:AddPlayer( PlayerName ) + -- Add player. if not self.PLAYERS[PlayerName] then self:AddPlayer( Event.IniUnitName, PlayerName ) end - + -- Player settings. local Settings = SETTINGS:Set( PlayerName ) - Settings:SetPlayerMenu(Event.IniUnit) - - -- Create an event. - self:CreateEventPlayerEnterAircraft(Event.IniUnit) - - end - - end - - end - -end + Settings:SetPlayerMenu( Event.IniUnit ) + -- Create an event. + self:CreateEventPlayerEnterAircraft( Event.IniUnit ) + + end + + end + + end + +end --- Handles the OnDead or OnCrash event for alive units set. -- @param #DATABASE self @@ -1130,55 +1103,54 @@ end function DATABASE:_EventOnDeadOrCrash( Event ) if Event.IniDCSUnit then - - local name=Event.IniDCSUnitName - + + local name = Event.IniDCSUnitName + if Event.IniObjectCategory == 3 then - + --- -- STATICS --- - + if self.STATICS[Event.IniDCSUnitName] then self:DeleteStatic( Event.IniDCSUnitName ) end - + else - + if Event.IniObjectCategory == 1 then - + --- -- UNITS --- - + -- Delete unit. if self.UNITS[Event.IniDCSUnitName] then - self:DeleteUnit(Event.IniDCSUnitName) + self:DeleteUnit( Event.IniDCSUnitName ) end - + -- Remove client players. - local client=self.CLIENTS[name] --Wrapper.Client#CLIENT - + local client = self.CLIENTS[name] -- Wrapper.Client#CLIENT + if client then client:RemovePlayers() end - + end end - + -- Add airbase if it was spawned later in the mission. - local airbase=self.AIRBASES[Event.IniDCSUnitName] --Wrapper.Airbase#AIRBASE + local airbase = self.AIRBASES[Event.IniDCSUnitName] -- Wrapper.Airbase#AIRBASE if airbase and (airbase:IsHelipad() or airbase:IsShip()) then - self:DeleteAirbase(Event.IniDCSUnitName) + self:DeleteAirbase( Event.IniDCSUnitName ) end - + end -- Account destroys. self:AccountDestroys( Event ) end - --- Handles the OnPlayerEnterUnit event to fill the active players table (with the unit filter applied). -- @param #DATABASE self -- @param Core.Event#EVENTDATA Event @@ -1187,36 +1159,35 @@ function DATABASE:_EventOnPlayerEnterUnit( Event ) if Event.IniDCSUnit then if Event.IniObjectCategory == 1 then - + -- Add unit. self:AddUnit( Event.IniDCSUnitName ) - + -- Ini unit. Event.IniUnit = self:FindUnit( Event.IniDCSUnitName ) - + -- Add group. self:AddGroup( Event.IniDCSGroupName ) - + -- Get player unit. local PlayerName = Event.IniDCSUnit:getPlayerName() - + if PlayerName then - + if not self.PLAYERS[PlayerName] then self:AddPlayer( Event.IniDCSUnitName, PlayerName ) end - + local Settings = SETTINGS:Set( PlayerName ) Settings:SetPlayerMenu( Event.IniUnit ) - + else - self:E("ERROR: getPlayerName() returned nil for event PlayerEnterUnit") + self:E( "ERROR: getPlayerName() returned nil for event PlayerEnterUnit" ) end end end end - --- Handles the OnPlayerLeaveUnit event to clean the active players table. -- @param #DATABASE self -- @param Core.Event#EVENTDATA Event @@ -1224,30 +1195,30 @@ function DATABASE:_EventOnPlayerLeaveUnit( Event ) self:F2( { Event } ) if Event.IniUnit then - + if Event.IniObjectCategory == 1 then - + -- Try to get the player name. This can be buggy for multicrew aircraft! local PlayerName = Event.IniUnit:GetPlayerName() - - if PlayerName then --and self.PLAYERS[PlayerName] then - + + if PlayerName then -- and self.PLAYERS[PlayerName] then + -- Debug info. - self:I(string.format("Player '%s' left unit %s", tostring(PlayerName), tostring(Event.IniUnitName))) - + self:I( string.format( "Player '%s' left unit %s", tostring( PlayerName ), tostring( Event.IniUnitName ) ) ) + -- Remove player menu. local Settings = SETTINGS:Set( PlayerName ) - Settings:RemovePlayerMenu(Event.IniUnit) - + Settings:RemovePlayerMenu( Event.IniUnit ) + -- Delete player. - self:DeletePlayer(Event.IniUnit, PlayerName) - + self:DeletePlayer( Event.IniUnit, PlayerName ) + -- Client stuff. - local client=self.CLIENTS[Event.IniDCSUnitName] --Wrapper.Client#CLIENT + local client = self.CLIENTS[Event.IniDCSUnitName] -- Wrapper.Client#CLIENT if client then - client:RemovePlayer(PlayerName) + client:RemovePlayer( PlayerName ) end - + end end end @@ -1265,22 +1236,22 @@ function DATABASE:ForEach( IteratorFunction, FinalizeFunction, arg, Set ) local function CoRoutine() local Count = 0 for ObjectID, Object in pairs( Set ) do - self:T2( Object ) - IteratorFunction( Object, unpack( arg ) ) - Count = Count + 1 --- if Count % 100 == 0 then --- coroutine.yield( false ) --- end + self:T2( Object ) + IteratorFunction( Object, unpack( arg ) ) + Count = Count + 1 + -- if Count % 100 == 0 then + -- coroutine.yield( false ) + -- end end return true end --- local co = coroutine.create( CoRoutine ) + -- local co = coroutine.create( CoRoutine ) local co = CoRoutine local function Schedule() --- local status, res = coroutine.resume( co ) + -- local status, res = coroutine.resume( co ) local status, res = co() self:T3( { status, res } ) @@ -1296,18 +1267,17 @@ function DATABASE:ForEach( IteratorFunction, FinalizeFunction, arg, Set ) return false end - --local Scheduler = SCHEDULER:New( self, Schedule, {}, 0.001, 0.001, 0 ) + -- local Scheduler = SCHEDULER:New( self, Schedule, {}, 0.001, 0.001, 0 ) Schedule() return self end - --- Iterate the DATABASE and call an iterator function for each **alive** STATIC, providing the STATIC and optional parameters. -- @param #DATABASE self -- @param #function IteratorFunction The function that will be called for each object in the database. The function needs to accept a STATIC parameter. -- @return #DATABASE self -function DATABASE:ForEachStatic( IteratorFunction, FinalizeFunction, ... ) --R2.1 +function DATABASE:ForEachStatic( IteratorFunction, FinalizeFunction, ... ) -- R2.1 self:F2( arg ) self:ForEach( IteratorFunction, FinalizeFunction, arg, self.STATICS ) @@ -1315,7 +1285,6 @@ function DATABASE:ForEachStatic( IteratorFunction, FinalizeFunction, ... ) --R2 return self end - --- Iterate the DATABASE and call an iterator function for each **alive** UNIT, providing the UNIT and optional parameters. -- @param #DATABASE self -- @param #function IteratorFunction The function that will be called for each object in the database. The function needs to accept a UNIT parameter. @@ -1328,7 +1297,6 @@ function DATABASE:ForEachUnit( IteratorFunction, FinalizeFunction, ... ) return self end - --- Iterate the DATABASE and call an iterator function for each **alive** GROUP, providing the GROUP and optional parameters. -- @param #DATABASE self -- @param #function IteratorFunction The function that will be called for each object in the database. The function needs to accept a GROUP parameter. @@ -1341,7 +1309,6 @@ function DATABASE:ForEachGroup( IteratorFunction, FinalizeFunction, ... ) return self end - --- Iterate the DATABASE and call an iterator function for each **ALIVE** player, providing the player name and optional parameters. -- @param #DATABASE self -- @param #function IteratorFunction The function that will be called for each object in the database. The function needs to accept the player name. @@ -1354,7 +1321,6 @@ function DATABASE:ForEachPlayer( IteratorFunction, FinalizeFunction, ... ) return self end - --- Iterate the DATABASE and call an iterator function for each player who has joined the mission, providing the Unit of the player and optional parameters. -- @param #DATABASE self -- @param #function IteratorFunction The function that will be called for each object in the database. The function needs to accept a UNIT parameter. @@ -1379,7 +1345,6 @@ function DATABASE:ForEachPlayerUnit( IteratorFunction, FinalizeFunction, ... ) return self end - --- Iterate the DATABASE and call an iterator function for each CLIENT, providing the CLIENT to the function and optional parameters. -- @param #DATABASE self -- @param #function IteratorFunction The function that will be called object in the database. The function needs to accept a CLIENT parameter. @@ -1404,7 +1369,6 @@ function DATABASE:ForEachCargo( IteratorFunction, ... ) return self end - --- Handles the OnEventNewCargo event. -- @param #DATABASE self -- @param Core.Event#EVENTDATA EventData @@ -1416,7 +1380,6 @@ function DATABASE:OnEventNewCargo( EventData ) end end - --- Handles the OnEventDeleteCargo. -- @param #DATABASE self -- @param Core.Event#EVENTDATA EventData @@ -1428,7 +1391,6 @@ function DATABASE:OnEventDeleteCargo( EventData ) end end - --- Handles the OnEventNewZone event. -- @param #DATABASE self -- @param Core.Event#EVENTDATA EventData @@ -1440,7 +1402,6 @@ function DATABASE:OnEventNewZone( EventData ) end end - --- Handles the OnEventDeleteZone. -- @param #DATABASE self -- @param Core.Event#EVENTDATA EventData @@ -1452,8 +1413,6 @@ function DATABASE:OnEventDeleteZone( EventData ) end end - - --- Gets the player settings -- @param #DATABASE self -- @param #string PlayerName @@ -1463,7 +1422,6 @@ function DATABASE:GetPlayerSettings( PlayerName ) return self.PLAYERSETTINGS[PlayerName] end - --- Sets the player settings -- @param #DATABASE self -- @param #string PlayerName @@ -1477,21 +1435,21 @@ end --- Add a flight group to the data base. -- @param #DATABASE self -- @param Ops.FlightGroup#FLIGHTGROUP flightgroup -function DATABASE:AddFlightGroup(flightgroup) - self:I({NewFlightGroup=flightgroup.groupname}) - self.FLIGHTGROUPS[flightgroup.groupname]=flightgroup +function DATABASE:AddFlightGroup( flightgroup ) + self:I( { NewFlightGroup = flightgroup.groupname } ) + self.FLIGHTGROUPS[flightgroup.groupname] = flightgroup end --- Get a flight group from the data base. -- @param #DATABASE self -- @param #string groupname Group name of the flight group. Can also be passed as GROUP object. -- @return Ops.FlightGroup#FLIGHTGROUP Flight group object. -function DATABASE:GetFlightGroup(groupname) +function DATABASE:GetFlightGroup( groupname ) -- Get group and group name. - if type(groupname)=="string" then + if type( groupname ) == "string" then else - groupname=groupname:GetName() + groupname = groupname:GetName() end return self.FLIGHTGROUPS[groupname] @@ -1500,16 +1458,16 @@ end --- Add a flight control to the data base. -- @param #DATABASE self -- @param Ops.FlightControl#FLIGHTCONTROL flightcontrol -function DATABASE:AddFlightControl(flightcontrol) +function DATABASE:AddFlightControl( flightcontrol ) self:F2( { flightcontrol } ) - self.FLIGHTCONTROLS[flightcontrol.airbasename]=flightcontrol + self.FLIGHTCONTROLS[flightcontrol.airbasename] = flightcontrol end --- Get a flight control object from the data base. -- @param #DATABASE self -- @param #string airbasename Name of the associated airbase. -- @return Ops.FlightControl#FLIGHTCONTROL The FLIGHTCONTROL object.s -function DATABASE:GetFlightControl(airbasename) +function DATABASE:GetFlightControl( airbasename ) return self.FLIGHTCONTROLS[airbasename] end @@ -1519,32 +1477,32 @@ function DATABASE:_RegisterTemplates() self.Navpoints = {} self.UNITS = {} - --Build routines.db.units and self.Navpoints - for CoalitionName, coa_data in pairs(env.mission.coalition) do - self:T({CoalitionName=CoalitionName}) + -- Build routines.db.units and self.Navpoints + for CoalitionName, coa_data in pairs( env.mission.coalition ) do + self:T( { CoalitionName = CoalitionName } ) - if (CoalitionName == 'red' or CoalitionName == 'blue' or CoalitionName == 'neutrals') and type(coa_data) == 'table' then - --self.Units[coa_name] = {} + if (CoalitionName == 'red' or CoalitionName == 'blue' or CoalitionName == 'neutrals') and type( coa_data ) == 'table' then + -- self.Units[coa_name] = {} - local CoalitionSide = coalition.side[string.upper(CoalitionName)] - if CoalitionName=="red" then - CoalitionSide=coalition.side.RED - elseif CoalitionName=="blue" then - CoalitionSide=coalition.side.BLUE + local CoalitionSide = coalition.side[string.upper( CoalitionName )] + if CoalitionName == "red" then + CoalitionSide = coalition.side.RED + elseif CoalitionName == "blue" then + CoalitionSide = coalition.side.BLUE else - CoalitionSide=coalition.side.NEUTRAL + CoalitionSide = coalition.side.NEUTRAL end -- build nav points DB self.Navpoints[CoalitionName] = {} - if coa_data.nav_points then --navpoints - for nav_ind, nav_data in pairs(coa_data.nav_points) do + if coa_data.nav_points then -- navpoints + for nav_ind, nav_data in pairs( coa_data.nav_points ) do - if type(nav_data) == 'table' then - self.Navpoints[CoalitionName][nav_ind] = routines.utils.deepCopy(nav_data) + if type( nav_data ) == 'table' then + self.Navpoints[CoalitionName][nav_ind] = routines.utils.deepCopy( nav_data ) - self.Navpoints[CoalitionName][nav_ind]['name'] = nav_data.callsignStr -- name is a little bit more self-explanatory. - self.Navpoints[CoalitionName][nav_ind]['point'] = {} -- point is used by SSE, support it. + self.Navpoints[CoalitionName][nav_ind]['name'] = nav_data.callsignStr -- name is a little bit more self-explanatory. + self.Navpoints[CoalitionName][nav_ind]['point'] = {} -- point is used by SSE, support it. self.Navpoints[CoalitionName][nav_ind]['point']['x'] = nav_data.x self.Navpoints[CoalitionName][nav_ind]['point']['y'] = 0 self.Navpoints[CoalitionName][nav_ind]['point']['z'] = nav_data.y @@ -1553,138 +1511,138 @@ function DATABASE:_RegisterTemplates() end ------------------------------------------------- - if coa_data.country then --there is a country table - for cntry_id, cntry_data in pairs(coa_data.country) do + if coa_data.country then -- there is a country table + for cntry_id, cntry_data in pairs( coa_data.country ) do - local CountryName = string.upper(cntry_data.name) + local CountryName = string.upper( cntry_data.name ) local CountryID = cntry_data.id self.COUNTRY_ID[CountryName] = CountryID self.COUNTRY_NAME[CountryID] = CountryName - --self.Units[coa_name][countryName] = {} - --self.Units[coa_name][countryName]["countryId"] = cntry_data.id + -- self.Units[coa_name][countryName] = {} + -- self.Units[coa_name][countryName]["countryId"] = cntry_data.id - if type(cntry_data) == 'table' then --just making sure + if type( cntry_data ) == 'table' then -- just making sure - for obj_type_name, obj_type_data in pairs(cntry_data) 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" or obj_type_name == "static" then --should be an unncessary check + if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" or obj_type_name == "static" then -- should be an unncessary check local CategoryName = obj_type_name - if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then --there's a group! + 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 -- there's a group! - --self.Units[coa_name][countryName][category] = {} + -- self.Units[coa_name][countryName][category] = {} - for group_num, Template in pairs(obj_type_data.group) do + 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 -- making sure again- this is a valid group + + self:_RegisterGroupTemplate( Template, CoalitionSide, _DATABASECategory[string.lower( CategoryName )], CountryID ) - if obj_type_name ~= "static" and Template and Template.units and type(Template.units) == 'table' then --making sure again- this is a valid group - - self:_RegisterGroupTemplate(Template, CoalitionSide, _DATABASECategory[string.lower(CategoryName)], CountryID) - else - - self:_RegisterStaticTemplate(Template, CoalitionSide, _DATABASECategory[string.lower(CategoryName)], CountryID) - - end --if GroupTemplate and GroupTemplate.units then - end --for group_num, GroupTemplate in pairs(obj_type_data.group) do - end --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 - end --if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" or obj_type_name == "static" then - end --for obj_type_name, obj_type_data in pairs(cntry_data) do - end --if type(cntry_data) == 'table' then - end --for cntry_id, cntry_data in pairs(coa_data.country) do - end --if coa_data.country then --there is a country table - end --if coa_name == 'red' or coa_name == 'blue' and type(coa_data) == 'table' then - end --for coa_name, coa_data in pairs(mission.coalition) do + + self:_RegisterStaticTemplate( Template, CoalitionSide, _DATABASECategory[string.lower( CategoryName )], CountryID ) + + end -- if GroupTemplate and GroupTemplate.units then + end -- for group_num, GroupTemplate in pairs(obj_type_data.group) do + end -- 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 + end -- if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" or obj_type_name == "static" then + end -- for obj_type_name, obj_type_data in pairs(cntry_data) do + end -- if type(cntry_data) == 'table' then + end -- for cntry_id, cntry_data in pairs(coa_data.country) do + end -- if coa_data.country then --there is a country table + end -- if coa_name == 'red' or coa_name == 'blue' and type(coa_data) == 'table' then + end -- for coa_name, coa_data in pairs(mission.coalition) do return self end - --- Account the Hits of the Players. - -- @param #DATABASE self - -- @param Core.Event#EVENTDATA Event - function DATABASE:AccountHits( Event ) - self:F( { Event } ) +--- Account the Hits of the Players. +-- @param #DATABASE self +-- @param Core.Event#EVENTDATA Event +function DATABASE:AccountHits( Event ) + self:F( { Event } ) - if Event.IniPlayerName ~= nil then -- It is a player that is hitting something - self:T( "Hitting Something" ) + if Event.IniPlayerName ~= nil then -- It is a player that is hitting something + self:T( "Hitting Something" ) - -- What is he hitting? - if Event.TgtCategory then + -- What is he hitting? + if Event.TgtCategory then + -- A target got hit + 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 + + -- It is a weapon initiated by a player, that is hitting something + -- This seems to occur only with scenery and static objects. + if Event.WeaponPlayerName ~= nil then + self:T( "Hitting Scenery" ) + + -- What is he hitting? + if Event.TgtCategory then + + if Event.WeaponCoalition then -- A coalition object was hit, probably a static. -- A target got hit 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 - - -- It is a weapon initiated by a player, that is hitting something - -- This seems to occur only with scenery and static objects. - if Event.WeaponPlayerName ~= nil then - self:T( "Hitting Scenery" ) - - -- What is he hitting? - if Event.TgtCategory then - - if Event.WeaponCoalition then -- A coalition object was hit, probably a static. - -- A target got hit - 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 -- A scenery object was hit. - end + Hit.Players[Event.WeaponPlayerName] = true + else -- A scenery object was hit. end end end +end - --- Account the destroys. - -- @param #DATABASE self - -- @param Core.Event#EVENTDATA Event - function DATABASE:AccountDestroys( Event ) - self:F( { Event } ) +--- Account the destroys. +-- @param #DATABASE self +-- @param Core.Event#EVENTDATA Event +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 + 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 + if Event.IniDCSUnit then - TargetUnit = Event.IniUnit - TargetUnitName = Event.IniDCSUnitName - TargetGroup = Event.IniDCSGroup - TargetGroupName = Event.IniDCSGroupName - TargetPlayerName = Event.IniPlayerName + TargetUnit = Event.IniUnit + TargetUnitName = Event.IniDCSUnitName + TargetGroup = Event.IniDCSGroup + TargetGroupName = Event.IniDCSGroupName + TargetPlayerName = Event.IniPlayerName - TargetCoalition = Event.IniCoalition - --TargetCategory = TargetUnit:getCategory() - --TargetCategory = TargetUnit:getDesc().category -- Workaround - TargetCategory = Event.IniCategory - TargetType = Event.IniTypeName + TargetCoalition = Event.IniCoalition + -- TargetCategory = TargetUnit:getCategory() + -- TargetCategory = TargetUnit:getDesc().category -- Workaround + TargetCategory = Event.IniCategory + TargetType = Event.IniTypeName - TargetUnitType = TargetType + TargetUnitType = TargetType - self:T( { TargetUnitName, TargetGroupName, TargetPlayerName, TargetCoalition, TargetCategory, TargetType } ) - end - - local Destroyed = false - - -- What is the player destroying? - if self.HITS[Event.IniUnitName] then -- Was there a hit for this unit for this player before registered??? - self.DESTROYS[Event.IniUnitName] = self.DESTROYS[Event.IniUnitName] or {} - self.DESTROYS[Event.IniUnitName] = true - end + self:T( { TargetUnitName, TargetGroupName, TargetPlayerName, TargetCoalition, TargetCategory, TargetType } ) end + + local Destroyed = false + + -- What is the player destroying? + if self.HITS[Event.IniUnitName] then -- Was there a hit for this unit for this player before registered??? + self.DESTROYS[Event.IniUnitName] = self.DESTROYS[Event.IniUnitName] or {} + self.DESTROYS[Event.IniUnitName] = true + end +end diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 02dab0afc..ea8b7575a 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -14,7 +14,7 @@ -- ![Objects](..\Presentations\EVENT\Dia2.JPG) -- -- Within a running mission, various DCS events occur. Units are dynamically created, crash, die, shoot stuff, get hit etc. --- This module provides a mechanism to dispatch those events occuring within your running mission, to the different objects orchestrating your mission. +-- This module provides a mechanism to dispatch those events occurring within your running mission, to the different objects orchestrating your mission. -- -- ![Objects](..\Presentations\EVENT\Dia3.JPG) -- @@ -141,7 +141,6 @@ -- EventData.IniUnit:SmokeGreen() -- end -- --- -- Find below an overview which events populate which information categories: -- -- ![Objects](..\Presentations\EVENT\Dia14.JPG) @@ -172,7 +171,6 @@ -- @module Core.Event -- @image Core_Event.JPG - --- @type EVENT -- @field #EVENT.Events Events -- @extends Core.Base#BASE @@ -194,7 +192,6 @@ 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 - --- The different types of events supported by MOOSE. -- Use this structure to subscribe to events using the @{Core.Base#BASE.HandleEvent}() method. -- @type EVENTS @@ -227,13 +224,13 @@ EVENTS = { MarkChange = world.event.S_EVENT_MARK_CHANGE, MarkRemoved = world.event.S_EVENT_MARK_REMOVED, -- Moose Events - 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, + 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, -- Added with DCS 2.5.6 DetailedFailure = world.event.S_EVENT_DETAILED_FAILURE or -1, --We set this to -1 for backward compatibility to DCS 2.5.5 and earlier @@ -304,8 +301,6 @@ EVENTS = { -- @field Core.ZONE#ZONE Zone The zone object. -- @field #string ZoneName The name of the zone. - - local _EVENTMETA = { [world.event.S_EVENT_SHOT] = { Order = 1, @@ -562,7 +557,6 @@ local _EVENTMETA = { }, } - --- The Events structure -- @type EVENT.Events -- @field #number IniUnit @@ -576,12 +570,11 @@ function EVENT:New() local self = BASE:Inherit( self, BASE:New() ) -- Add world event handler. - self.EventHandler = world.addEventHandler(self) + self.EventHandler = world.addEventHandler( self ) return self end - --- Initializes the Events structure for the event. -- @param #EVENT self -- @param DCS#world.event EventID Event ID. @@ -595,7 +588,7 @@ function EVENT:Init( EventID, EventClass ) self.Events[EventID] = {} end - -- Each event has a subtable of EventClasses, ordered by EventPriority. + -- Each event has a sub-table of EventClasses, ordered by EventPriority. local EventPriority = EventClass:GetEventPriority() if not self.Events[EventID][EventPriority] then @@ -603,7 +596,7 @@ function EVENT:Init( EventID, EventClass ) end if not self.Events[EventID][EventPriority][EventClass] then - self.Events[EventID][EventPriority][EventClass] = {} + self.Events[EventID][EventPriority][EventClass] = {} end return self.Events[EventID][EventPriority][EventClass] @@ -614,7 +607,7 @@ end -- @param Core.Base#BASE EventClass The self instance of the class for which the event is. -- @param DCS#world.event EventID Event ID. -- @return #EVENT self -function EVENT:RemoveEvent( EventClass, EventID ) +function EVENT:RemoveEvent( EventClass, EventID ) -- Debug info. self:F2( { "Removing subscription for class: ", EventClass:GetClassNameAndID() } ) @@ -638,7 +631,7 @@ end -- @param Core.Base#BASE EventClass The self instance of the class for which the event is. -- @param DCS#world.event EventID Event ID. -- @return #EVENT.Events -function EVENT:Reset( EventObject ) --R2.1 +function EVENT:Reset( EventObject ) -- R2.1 self:F( { "Resetting subscriptions for class: ", EventObject:GetClassNameAndID() } ) @@ -657,12 +650,11 @@ function EVENT:Reset( EventObject ) --R2.1 end end - --- Clears all event subscriptions for a @{Core.Base#BASE} derived object. -- @param #EVENT self -- @param Core.Base#BASE EventClass The self class object for which the events are removed. -- @return #EVENT self -function EVENT:RemoveAll(EventClass) +function EVENT:RemoveAll( EventClass ) local EventClassName = EventClass:GetClassNameAndID() @@ -676,8 +668,6 @@ function EVENT:RemoveAll(EventClass) return self end - - --- Create an OnDead event handler for a group -- @param #EVENT self -- @param #table EventTemplate @@ -709,7 +699,6 @@ function EVENT:OnEventGeneric( EventFunction, EventClass, EventID ) return self end - --- Set a new listener for an `S_EVENT_X` event for a UNIT. -- @param #EVENT self -- @param #string UnitName The name of the UNIT. @@ -797,7 +786,6 @@ do -- OnDead end - do -- OnLand --- Create an OnLand event handler for a group @@ -864,7 +852,7 @@ do -- Event Creation id = EVENTS.NewCargo, time = timer.getTime(), cargo = Cargo, - } + } world.onEvent( Event ) end @@ -879,7 +867,7 @@ do -- Event Creation id = EVENTS.DeleteCargo, time = timer.getTime(), cargo = Cargo, - } + } world.onEvent( Event ) end @@ -894,7 +882,7 @@ do -- Event Creation id = EVENTS.NewZone, time = timer.getTime(), zone = Zone, - } + } world.onEvent( Event ) end @@ -909,7 +897,7 @@ do -- Event Creation id = EVENTS.DeleteZone, time = timer.getTime(), zone = Zone, - } + } world.onEvent( Event ) end @@ -924,12 +912,11 @@ do -- Event Creation id = EVENTS.NewZoneGoal, time = timer.getTime(), ZoneGoal = ZoneGoal, - } + } world.onEvent( Event ) end - --- Creation of a ZoneGoal Deletion Event. -- @param #EVENT self -- @param Core.ZoneGoal#ZONE_GOAL ZoneGoal The ZoneGoal created. @@ -940,12 +927,11 @@ do -- Event Creation id = EVENTS.DeleteZoneGoal, time = timer.getTime(), ZoneGoal = ZoneGoal, - } + } world.onEvent( Event ) end - --- Creation of a S_EVENT_PLAYER_ENTER_UNIT Event. -- @param #EVENT self -- @param Wrapper.Unit#UNIT PlayerUnit. @@ -955,8 +941,8 @@ do -- Event Creation local Event = { id = EVENTS.PlayerEnterUnit, time = timer.getTime(), - initiator = PlayerUnit:GetDCSObject() - } + initiator = PlayerUnit:GetDCSObject(), + } world.onEvent( Event ) end @@ -970,8 +956,8 @@ do -- Event Creation local Event = { id = EVENTS.PlayerEnterAircraft, time = timer.getTime(), - initiator = PlayerUnit:GetDCSObject() - } + initiator = PlayerUnit:GetDCSObject(), + } world.onEvent( Event ) end @@ -993,14 +979,13 @@ function EVENT:onEvent( Event ) return errmsg end - -- Get event meta data. local EventMeta = _EVENTMETA[Event.id] -- Check if this is a known event? 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 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 @@ -1024,9 +1009,9 @@ function EVENT:onEvent( Event ) if Event.IniDCSGroup and Event.IniDCSGroup:isExist() then Event.IniDCSGroupName = Event.IniDCSGroup:getName() Event.IniGroup = GROUP:FindByName( Event.IniDCSGroupName ) - --if Event.IniGroup then - Event.IniGroupName = Event.IniDCSGroupName - --end + -- if Event.IniGroup then + Event.IniGroupName = Event.IniDCSGroupName + -- end end Event.IniPlayerName = Event.IniDCSUnit:getPlayerName() Event.IniCoalition = Event.IniDCSUnit:getCoalition() @@ -1036,23 +1021,23 @@ function EVENT:onEvent( Event ) if Event.IniObjectCategory == Object.Category.STATIC then - if Event.id==31 then + if Event.id == 31 then -- Event.initiator is a Static object representing the pilot. But getName() errors due to DCS bug. Event.IniDCSUnit = Event.initiator - local ID=Event.initiator.id_ - Event.IniDCSUnitName = string.format("Ejected Pilot ID %s", tostring(ID)) + 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.IniCategory = 0 Event.IniTypeName = "Ejected Pilot" - elseif Event.id == 33 then -- ejection seat discarded + elseif Event.id == 33 then -- ejection seat discarded Event.IniDCSUnit = Event.initiator - local ID=Event.initiator.id_ - Event.IniDCSUnitName = string.format("Ejection Seat ID %s", tostring(ID)) + 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.IniCategory = 0 Event.IniTypeName = "Ejection Seat" else Event.IniDCSUnit = Event.initiator @@ -1088,7 +1073,7 @@ function EVENT:onEvent( Event ) Event.IniDCSUnit = Event.initiator Event.IniDCSUnitName = Event.IniDCSUnit:getName() Event.IniUnitName = Event.IniDCSUnitName - Event.IniUnit = AIRBASE:FindByName(Event.IniDCSUnitName) + Event.IniUnit = AIRBASE:FindByName( Event.IniDCSUnitName ) Event.IniCoalition = Event.IniDCSUnit:getCoalition() Event.IniCategory = Event.IniDCSUnit:getDesc().category Event.IniTypeName = Event.IniDCSUnit:getTypeName() @@ -1109,9 +1094,9 @@ function EVENT:onEvent( Event ) if Event.TgtDCSGroup and Event.TgtDCSGroup:isExist() then Event.TgtDCSGroupName = Event.TgtDCSGroup:getName() Event.TgtGroup = GROUP:FindByName( Event.TgtDCSGroupName ) - --if Event.TgtGroup then - Event.TgtGroupName = Event.TgtDCSGroupName - --end + -- if Event.TgtGroup then + Event.TgtGroupName = Event.TgtDCSGroupName + -- end end Event.TgtPlayerName = Event.TgtDCSUnit:getPlayerName() Event.TgtCoalition = Event.TgtDCSUnit:getCoalition() @@ -1130,18 +1115,18 @@ function EVENT:onEvent( Event ) 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.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.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.TgtDCSUnitName = string.format( "Ejection Seat ID %s", tostring( Event.IniDCSUnitName ) ) Event.TgtUnitName = Event.TgtDCSUnitName else Event.TgtTypeName = "Static" @@ -1167,29 +1152,29 @@ function EVENT:onEvent( Event ) Event.WeaponCoalition = Event.WeaponUNIT and Event.Weapon:getCoalition() Event.WeaponCategory = Event.WeaponUNIT and Event.Weapon:getDesc().category Event.WeaponTypeName = Event.WeaponUNIT and Event.Weapon:getTypeName() - --Event.WeaponTgtDCSUnit = Event.Weapon:getTarget() + -- Event.WeaponTgtDCSUnit = Event.Weapon:getTarget() end -- Place should be given for takeoff and landing events as well as base captured. It should be a DCS airbase. if Event.place then - if Event.id==EVENTS.LandingAfterEjection then + if Event.id == EVENTS.LandingAfterEjection then -- Place is here the UNIT of which the pilot ejected. - --local name=Event.place:getName() -- This returns a DCS error "Airbase doesn't exit" :( + -- local name=Event.place:getName() -- This returns a DCS error "Airbase doesn't exit" :( -- However, this is not a big thing, as the aircraft the pilot ejected from is usually long crashed before the ejected pilot touches the ground. - --Event.Place=UNIT:Find(Event.place) + -- Event.Place=UNIT:Find(Event.place) else - Event.Place=AIRBASE:Find(Event.place) - Event.PlaceName=Event.Place:GetName() + Event.Place = AIRBASE:Find( Event.place ) + Event.PlaceName = Event.Place:GetName() end end -- Mark points. 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.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 @@ -1218,9 +1203,9 @@ function EVENT:onEvent( Event ) -- Okay, we got the event from DCS. Now loop the SORTED self.EventSorted[] table for the received Event.id, and for each EventData registered, check if a function needs to be called. for EventClass, EventData in pairs( self.Events[Event.id][EventPriority] ) do - --if Event.IniObjectCategory ~= Object.Category.STATIC then + -- if Event.IniObjectCategory ~= Object.Category.STATIC then -- self:E( { "Evaluating: ", EventClass:GetClassNameAndID() } ) - --end + -- end Event.IniGroup = GROUP:FindByName( Event.IniDCSGroupName ) Event.TgtGroup = GROUP:FindByName( Event.TgtDCSGroupName ) @@ -1237,8 +1222,8 @@ function EVENT:onEvent( Event ) local UnitName = EventClass:GetName() - if ( EventMeta.Side == "I" and UnitName == Event.IniDCSUnitName ) or - ( EventMeta.Side == "T" and UnitName == Event.TgtDCSUnitName ) then + if (EventMeta.Side == "I" and UnitName == Event.IniDCSUnitName) or + (EventMeta.Side == "T" and UnitName == Event.TgtDCSUnitName) then -- First test if a EventFunction is Set, otherwise search for the default function if EventData.EventFunction then @@ -1247,15 +1232,14 @@ function EVENT:onEvent( Event ) 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 ) + local Result, Value = xpcall( function() + return EventData.EventFunction( EventClass, Event ) + end, ErrorHandler ) else -- There is no EventFunction defined, so try to find if a default OnEvent function is defined on the object. - local EventFunction = EventClass[ EventMeta.Event ] + local EventFunction = EventClass[EventMeta.Event] if EventFunction and type( EventFunction ) == "function" then -- Now call the default event function. @@ -1263,10 +1247,9 @@ function EVENT:onEvent( Event ) self:F( { "Calling " .. EventMeta.Event .. " for Class ", EventClass:GetClassNameAndID(), EventPriority } ) end - local Result, Value = xpcall( - function() - return EventFunction( EventClass, Event ) - end, ErrorHandler ) + local Result, Value = xpcall( function() + return EventFunction( EventClass, Event ) + end, ErrorHandler ) end end end @@ -1290,8 +1273,8 @@ function EVENT:onEvent( Event ) -- We can get the name of the EventClass, which is now always a GROUP object. local GroupName = EventClass:GetName() - if ( EventMeta.Side == "I" and GroupName == Event.IniDCSGroupName ) or - ( EventMeta.Side == "T" and GroupName == Event.TgtDCSGroupName ) then + if (EventMeta.Side == "I" and GroupName == Event.IniDCSGroupName) or + (EventMeta.Side == "T" and GroupName == Event.TgtDCSGroupName) then -- First test if a EventFunction is Set, otherwise search for the default function if EventData.EventFunction then @@ -1300,15 +1283,14 @@ function EVENT:onEvent( Event ) 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 ) + local Result, Value = xpcall( function() + return EventData.EventFunction( EventClass, Event, unpack( EventData.Params ) ) + end, ErrorHandler ) else -- There is no EventFunction defined, so try to find if a default OnEvent function is defined on the object. - local EventFunction = EventClass[ EventMeta.Event ] + local EventFunction = EventClass[EventMeta.Event] if EventFunction and type( EventFunction ) == "function" then -- Now call the default event function. @@ -1316,16 +1298,15 @@ function EVENT:onEvent( Event ) 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 ) + local Result, Value = xpcall( function() + return EventFunction( EventClass, Event, unpack( EventData.Params ) ) + end, ErrorHandler ) end end end else -- The EventClass is not alive anymore, we remove it from the EventHandlers... - --self:RemoveEvent( EventClass, Event.id ) + -- self:RemoveEvent( EventClass, Event.id ) end else @@ -1340,14 +1321,13 @@ function EVENT:onEvent( Event ) 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 ) + local Result, Value = xpcall( function() + return EventData.EventFunction( EventClass, Event ) + end, ErrorHandler ) else -- There is no EventFunction defined, so try to find if a default OnEvent function is defined on the object. - local EventFunction = EventClass[ EventMeta.Event ] + local EventFunction = EventClass[EventMeta.Event] if EventFunction and type( EventFunction ) == "function" then -- Now call the default event function. @@ -1355,11 +1335,10 @@ function EVENT:onEvent( Event ) 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 ) + local Result, Value = xpcall( function() + local Result, Value = EventFunction( EventClass, Event ) + return Result, Value + end, ErrorHandler ) end end @@ -1383,7 +1362,7 @@ function EVENT:onEvent( Event ) 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))) + 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 From 4a406604bd4bd17cdb6176963f9c8abfe1b8d470 Mon Sep 17 00:00:00 2001 From: TommyC81 Date: Mon, 20 Dec 2021 15:59:56 +0400 Subject: [PATCH 043/200] Core modules formatting (#1670) * Update Fsm.lua Code formatting and minor typo/documentation fixes. * Update Goal.lua Code formatting and minor typo/documentation fixes. * Update Menu.lua Code formatting and minor typo/documentation fixes. * Update Message.lua Code formatting and minor typo/documentation fixes. * Update Report.lua Code formatting and minor typo/documentation fixes. * Update ScheduleDispatcher.lua Code formatting and minor typo/documentation fixes. * Update Scheduler.lua Code formatting and minor typo/documentation fixes. * Update Settings.lua Code formatting and minor typo/documentation fixes. * Update Spawn.lua Code formatting and minor typo/documentation fixes. --- Moose Development/Moose/Core/Fsm.lua | 905 +++-- Moose Development/Moose/Core/Goal.lua | 89 +- Moose Development/Moose/Core/Menu.lua | 444 ++- Moose Development/Moose/Core/Message.lua | 318 +- Moose Development/Moose/Core/Report.lua | 27 +- .../Moose/Core/ScheduleDispatcher.lua | 199 +- Moose Development/Moose/Core/Scheduler.lua | 218 +- Moose Development/Moose/Core/Settings.lua | 235 +- Moose Development/Moose/Core/Spawn.lua | 2964 ++++++++--------- 9 files changed, 2646 insertions(+), 2753 deletions(-) diff --git a/Moose Development/Moose/Core/Fsm.lua b/Moose Development/Moose/Core/Fsm.lua index 644a04d63..e34e39eac 100644 --- a/Moose Development/Moose/Core/Fsm.lua +++ b/Moose Development/Moose/Core/Fsm.lua @@ -1,9 +1,9 @@ --- **Core** - FSM (Finite State Machine) are objects that model and control long lasting business processes and workflow. --- +-- -- === --- +-- -- ## Features: --- +-- -- * Provide a base class to model your own state machines. -- * Trigger events synchronously. -- * Trigger events asynchronously. @@ -13,66 +13,65 @@ -- - to handle controllables (groups and units). -- - to handle tasks. -- - to handle processes. --- +-- -- === --- +-- -- A Finite State Machine (FSM) models a process flow that transitions between various **States** through triggered **Events**. --- +-- -- A FSM can only be in one of a finite number of states. --- The machine is in only one state at a time; the state it is in at any given time is called the **current state**. --- It can change from one state to another when initiated by an **__internal__ or __external__ triggering event**, which is called a **transition**. +-- The machine is in only one state at a time; the state it is in at any given time is called the **current state**. +-- It can change from one state to another when initiated by an **__internal__ or __external__ triggering event**, which is called a **transition**. -- An **FSM implementation** is defined by **a list of its states**, **its initial state**, and **the triggering events** for **each possible transition**. -- An FSM implementation is composed out of **two parts**, a set of **state transition rules**, and an implementation set of **state transition handlers**, implementing those transitions. --- --- The FSM class supports a **hierarchical implementation of a Finite State Machine**, +-- +-- The FSM class supports a **hierarchical implementation of a Finite State Machine**, -- that is, it allows to **embed existing FSM implementations in a master FSM**. -- FSM hierarchies allow for efficient FSM re-use, **not having to re-invent the wheel every time again** when designing complex processes. --- +-- -- ![Workflow Example](..\Presentations\FSM\Dia2.JPG) --- +-- -- The above diagram shows a graphical representation of a FSM implementation for a **Task**, which guides a Human towards a Zone, -- orders him to destroy x targets and account the results. --- Other examples of ready made FSM could be: --- +-- Other examples of ready made FSM could be: +-- -- * route a plane to a zone flown by a human -- * detect targets by an AI and report to humans -- * account for destroyed targets by human players -- * handle AI infantry to deploy from or embark to a helicopter or airplane or vehicle -- * let an AI patrol a zone --- --- The **MOOSE framework** uses extensively the FSM class and derived FSM\_ classes, +-- +-- The **MOOSE framework** uses extensively the FSM class and derived FSM\_ classes, -- because **the goal of MOOSE is to simplify mission design complexity for mission building**. -- By efficiently utilizing the FSM class and derived classes, MOOSE allows mission designers to quickly build processes. --- **Ready made FSM-based implementations classes** exist within the MOOSE framework that **can easily be re-used, +-- **Ready made FSM-based implementations classes** exist within the MOOSE framework that **can easily be re-used, -- and tailored** by mission designers through **the implementation of Transition Handlers**. -- Each of these FSM implementation classes start either with: --- +-- -- * an acronym **AI\_**, which indicates an FSM implementation directing **AI controlled** @{GROUP} and/or @{UNIT}. These AI\_ classes derive the @{#FSM_CONTROLLABLE} class. -- * an acronym **TASK\_**, which indicates an FSM implementation executing a @{TASK} executed by Groups of players. These TASK\_ classes derive the @{#FSM_TASK} class. -- * an acronym **ACT\_**, which indicates an Sub-FSM implementation, directing **Humans actions** that need to be done in a @{TASK}, seated in a @{CLIENT} (slot) or a @{UNIT} (CA join). These ACT\_ classes derive the @{#FSM_PROCESS} class. --- +-- -- Detailed explanations and API specifics are further below clarified and FSM derived class specifics are described in those class documentation sections. --- --- ##__Dislaimer:__ +-- +-- ##__Disclaimer:__ -- The FSM class development is based on a finite state machine implementation made by Conroy Kyle. -- The state machine can be found on [github](https://github.com/kyleconroy/lua-state-machine) -- I've reworked this development (taken the concept), and created a **hierarchical state machine** out of it, embedded within the DCS simulator. -- Additionally, I've added extendability and created an API that allows seamless FSM implementation. --- +-- -- The following derived classes are available in the MOOSE framework, that implement a specialised form of a FSM: --- +-- -- * @{#FSM_TASK}: Models Finite State Machines for @{Task}s. -- * @{#FSM_PROCESS}: Models Finite State Machines for @{Task} actions, which control @{Client}s. -- * @{#FSM_CONTROLLABLE}: Models Finite State Machines for @{Wrapper.Controllable}s, which are @{Wrapper.Group}s, @{Wrapper.Unit}s, @{Client}s. -- * @{#FSM_SET}: Models Finite State Machines for @{Set}s. Note that these FSMs control multiple objects!!! So State concerns here -- for multiple objects or the position of the state machine in the process. --- +-- -- === --- --- +-- -- ### Author: **FlightControl** -- ### Contributions: **funkyfranky** --- +-- -- === -- -- @module Core.Fsm @@ -88,195 +87,194 @@ do -- FSM -- @field #table Scores Scores. -- @field #string current Current state name. -- @extends Core.Base#BASE - - + --- A Finite State Machine (FSM) models a process flow that transitions between various **States** through triggered **Events**. - -- + -- -- A FSM can only be in one of a finite number of states. - -- The machine is in only one state at a time; the state it is in at any given time is called the **current state**. - -- It can change from one state to another when initiated by an **__internal__ or __external__ triggering event**, which is called a **transition**. + -- The machine is in only one state at a time; the state it is in at any given time is called the **current state**. + -- It can change from one state to another when initiated by an **__internal__ or __external__ triggering event**, which is called a **transition**. -- An **FSM implementation** is defined by **a list of its states**, **its initial state**, and **the triggering events** for **each possible transition**. -- An FSM implementation is composed out of **two parts**, a set of **state transition rules**, and an implementation set of **state transition handlers**, implementing those transitions. - -- - -- The FSM class supports a **hierarchical implementation of a Finite State Machine**, + -- + -- The FSM class supports a **hierarchical implementation of a Finite State Machine**, -- that is, it allows to **embed existing FSM implementations in a master FSM**. -- FSM hierarchies allow for efficient FSM re-use, **not having to re-invent the wheel every time again** when designing complex processes. - -- + -- -- ![Workflow Example](..\Presentations\FSM\Dia2.JPG) - -- + -- -- The above diagram shows a graphical representation of a FSM implementation for a **Task**, which guides a Human towards a Zone, -- orders him to destroy x targets and account the results. - -- Other examples of ready made FSM could be: - -- + -- Other examples of ready made FSM could be: + -- -- * route a plane to a zone flown by a human -- * detect targets by an AI and report to humans -- * account for destroyed targets by human players - -- * handle AI infantry to deploy from or embark to a helicopter or airplane or vehicle + -- * handle AI infantry to deploy from or embark to a helicopter or airplane or vehicle -- * let an AI patrol a zone - -- - -- The **MOOSE framework** uses extensively the FSM class and derived FSM\_ classes, + -- + -- The **MOOSE framework** uses extensively the FSM class and derived FSM\_ classes, -- because **the goal of MOOSE is to simplify mission design complexity for mission building**. -- By efficiently utilizing the FSM class and derived classes, MOOSE allows mission designers to quickly build processes. - -- **Ready made FSM-based implementations classes** exist within the MOOSE framework that **can easily be re-used, + -- **Ready made FSM-based implementations classes** exist within the MOOSE framework that **can easily be re-used, -- and tailored** by mission designers through **the implementation of Transition Handlers**. -- Each of these FSM implementation classes start either with: - -- + -- -- * an acronym **AI\_**, which indicates an FSM implementation directing **AI controlled** @{GROUP} and/or @{UNIT}. These AI\_ classes derive the @{#FSM_CONTROLLABLE} class. -- * an acronym **TASK\_**, which indicates an FSM implementation executing a @{TASK} executed by Groups of players. These TASK\_ classes derive the @{#FSM_TASK} class. -- * an acronym **ACT\_**, which indicates an Sub-FSM implementation, directing **Humans actions** that need to be done in a @{TASK}, seated in a @{CLIENT} (slot) or a @{UNIT} (CA join). These ACT\_ classes derive the @{#FSM_PROCESS} class. - -- + -- -- ![Transition Rules and Transition Handlers and Event Triggers](..\Presentations\FSM\Dia3.JPG) - -- + -- -- The FSM class is the base class of all FSM\_ derived classes. It implements the main functionality to define and execute Finite State Machines. -- The derived FSM\_ classes extend the Finite State Machine functionality to run a workflow process for a specific purpose or component. - -- + -- -- Finite State Machines have **Transition Rules**, **Transition Handlers** and **Event Triggers**. - -- - -- The **Transition Rules** define the "Process Flow Boundaries", that is, + -- + -- The **Transition Rules** define the "Process Flow Boundaries", that is, -- the path that can be followed hopping from state to state upon triggered events. - -- If an event is triggered, and there is no valid path found for that event, + -- If an event is triggered, and there is no valid path found for that event, -- an error will be raised and the FSM will stop functioning. - -- + -- -- The **Transition Handlers** are special methods that can be defined by the mission designer, following a defined syntax. -- If the FSM object finds a method of such a handler, then the method will be called by the FSM, passing specific parameters. -- The method can then define its own custom logic to implement the FSM workflow, and to conduct other actions. - -- + -- -- The **Event Triggers** are methods that are defined by the FSM, which the mission designer can use to implement the workflow. -- Most of the time, these Event Triggers are used within the Transition Handler methods, so that a workflow is created running through the state machine. - -- + -- -- As explained above, a FSM supports **Linear State Transitions** and **Hierarchical State Transitions**, and both can be mixed to make a comprehensive FSM implementation. - -- The below documentation has a seperate chapter explaining both transition modes, taking into account the **Transition Rules**, **Transition Handlers** and **Event Triggers**. - -- + -- The below documentation has a separate chapter explaining both transition modes, taking into account the **Transition Rules**, **Transition Handlers** and **Event Triggers**. + -- -- ## FSM Linear Transitions - -- + -- -- Linear Transitions are Transition Rules allowing an FSM to transition from one or multiple possible **From** state(s) towards a **To** state upon a Triggered **Event**. - -- The Lineair transition rule evaluation will always be done from the **current state** of the FSM. + -- The Linear transition rule evaluation will always be done from the **current state** of the FSM. -- If no valid Transition Rule can be found in the FSM, the FSM will log an error and stop. - -- + -- -- ### FSM Transition Rules - -- - -- The FSM has transition rules that it follows and validates, as it walks the process. + -- + -- The FSM has transition rules that it follows and validates, as it walks the process. -- These rules define when an FSM can transition from a specific state towards an other specific state upon a triggered event. - -- - -- The method @{#FSM.AddTransition}() specifies a new possible Transition Rule for the FSM. - -- + -- + -- The method @{#FSM.AddTransition}() specifies a new possible Transition Rule for the FSM. + -- -- The initial state can be defined using the method @{#FSM.SetStartState}(). The default start state of an FSM is "None". - -- + -- -- Find below an example of a Linear Transition Rule definition for an FSM. - -- + -- -- local Fsm3Switch = FSM:New() -- #FsmDemo -- FsmSwitch:SetStartState( "Off" ) -- FsmSwitch:AddTransition( "Off", "SwitchOn", "On" ) -- FsmSwitch:AddTransition( "Off", "SwitchMiddle", "Middle" ) -- FsmSwitch:AddTransition( "On", "SwitchOff", "Off" ) -- FsmSwitch:AddTransition( "Middle", "SwitchOff", "Off" ) - -- + -- -- The above code snippet models a 3-way switch Linear Transition: - -- + -- -- * It can be switched **On** by triggering event **SwitchOn**. -- * It can be switched to the **Middle** position, by triggering event **SwitchMiddle**. -- * It can be switched **Off** by triggering event **SwitchOff**. -- * Note that once the Switch is **On** or **Middle**, it can only be switched **Off**. - -- + -- -- #### Some additional comments: - -- + -- -- Note that Linear Transition Rules **can be declared in a few variations**: - -- + -- -- * The From states can be **a table of strings**, indicating that the transition rule will be valid **if the current state** of the FSM will be **one of the given From states**. -- * The From state can be a **"*"**, indicating that **the transition rule will always be valid**, regardless of the current state of the FSM. - -- - -- The below code snippet shows how the two last lines can be rewritten and consensed. - -- + -- + -- The below code snippet shows how the two last lines can be rewritten and condensed. + -- -- FsmSwitch:AddTransition( { "On", "Middle" }, "SwitchOff", "Off" ) - -- + -- -- ### Transition Handling - -- + -- -- ![Transition Handlers](..\Presentations\FSM\Dia4.JPG) - -- - -- An FSM transitions in **4 moments** when an Event is being triggered and processed. - -- The mission designer can define for each moment specific logic within methods implementations following a defined API syntax. + -- + -- An FSM transitions in **4 moments** when an Event is being triggered and processed. + -- The mission designer can define for each moment specific logic within methods implementations following a defined API syntax. -- These methods define the flow of the FSM process; because in those methods the FSM Internal Events will be triggered. -- -- * To handle **State** transition moments, create methods starting with OnLeave or OnEnter concatenated with the State name. -- * To handle **Event** transition moments, create methods starting with OnBefore or OnAfter concatenated with the Event name. - -- + -- -- **The OnLeave and OnBefore transition methods may return false, which will cancel the transition!** - -- + -- -- Transition Handler methods need to follow the above specified naming convention, but are also passed parameters from the FSM. -- These parameters are on the correct order: From, Event, To: - -- + -- -- * From = A string containing the From state. -- * Event = A string containing the Event name that was triggered. -- * To = A string containing the To state. - -- + -- -- On top, each of these methods can have a variable amount of parameters passed. See the example in section [1.1.3](#1.1.3\)-event-triggers). - -- + -- -- ### Event Triggers - -- + -- -- ![Event Triggers](..\Presentations\FSM\Dia5.JPG) - -- - -- The FSM creates for each Event two **Event Trigger methods**. + -- + -- The FSM creates for each Event two **Event Trigger methods**. -- There are two modes how Events can be triggered, which is **synchronous** and **asynchronous**: - -- + -- -- * The method **FSM:Event()** triggers an Event that will be processed **synchronously** or **immediately**. -- * The method **FSM:__Event( __seconds__ )** triggers an Event that will be processed **asynchronously** over time, waiting __x seconds__. - -- - -- The destinction between these 2 Event Trigger methods are important to understand. An asynchronous call will "log" the Event Trigger to be executed at a later time. + -- + -- The distinction between these 2 Event Trigger methods are important to understand. An asynchronous call will "log" the Event Trigger to be executed at a later time. -- Processing will just continue. Synchronous Event Trigger methods are useful to change states of the FSM immediately, but may have a larger processing impact. - -- + -- -- The following example provides a little demonstration on the difference between synchronous and asynchronous Event Triggering. - -- + -- -- function FSM:OnAfterEvent( From, Event, To, Amount ) - -- self:T( { Amount = Amount } ) + -- self:T( { Amount = Amount } ) -- end - -- + -- -- local Amount = 1 - -- FSM:__Event( 5, Amount ) - -- + -- FSM:__Event( 5, Amount ) + -- -- Amount = Amount + 1 -- FSM:Event( Text, Amount ) - -- + -- -- In this example, the **:OnAfterEvent**() Transition Handler implementation will get called when **Event** is being triggered. - -- Before we go into more detail, let's look at the last 4 lines of the example. + -- Before we go into more detail, let's look at the last 4 lines of the example. -- The last line triggers synchronously the **Event**, and passes Amount as a parameter. - -- The 3rd last line of the example triggers asynchronously **Event**. + -- The 3rd last line of the example triggers asynchronously **Event**. -- Event will be processed after 5 seconds, and Amount is given as a parameter. - -- + -- -- The output of this little code fragment will be: - -- + -- -- * Amount = 2 -- * Amount = 2 - -- + -- -- Because ... When Event was asynchronously processed after 5 seconds, Amount was set to 2. So be careful when processing and passing values and objects in asynchronous processing! - -- + -- -- ### Linear Transition Example - -- + -- -- This example is fully implemented in the MOOSE test mission on GITHUB: [FSM-100 - Transition Explanation](https://github.com/FlightControl-Master/MOOSE/blob/master/Moose%20Test%20Missions/FSM%20-%20Finite%20State%20Machine/FSM-100%20-%20Transition%20Explanation/FSM-100%20-%20Transition%20Explanation.lua) - -- + -- -- It models a unit standing still near Batumi, and flaring every 5 seconds while switching between a Green flare and a Red flare. -- The purpose of this example is not to show how exciting flaring is, but it demonstrates how a Linear Transition FSM can be build. -- Have a look at the source code. The source code is also further explained below in this section. - -- + -- -- The example creates a new FsmDemo object from class FSM. -- It will set the start state of FsmDemo to state **Green**. -- Two Linear Transition Rules are created, where upon the event **Switch**, -- the FsmDemo will transition from state **Green** to **Red** and from **Red** back to **Green**. - -- + -- -- ![Transition Example](..\Presentations\FSM\Dia6.JPG) - -- + -- -- local FsmDemo = FSM:New() -- #FsmDemo -- FsmDemo:SetStartState( "Green" ) -- FsmDemo:AddTransition( "Green", "Switch", "Red" ) -- FsmDemo:AddTransition( "Red", "Switch", "Green" ) - -- + -- -- In the above example, the FsmDemo could flare every 5 seconds a Green or a Red flare into the air. -- The next code implements this through the event handling method **OnAfterSwitch**. - -- + -- -- ![Transition Flow](..\Presentations\FSM\Dia7.JPG) - -- + -- -- function FsmDemo:OnAfterSwitch( From, Event, To, FsmUnit ) -- self:T( { From, Event, To, FsmUnit } ) - -- + -- -- if From == "Green" then -- FsmUnit:Flare(FLARECOLOR.Green) -- else @@ -286,22 +284,22 @@ do -- FSM -- end -- self:__Switch( 5, FsmUnit ) -- Trigger the next Switch event to happen in 5 seconds. -- end - -- + -- -- FsmDemo:__Switch( 5, FsmUnit ) -- Trigger the first Switch event to happen in 5 seconds. - -- + -- -- The OnAfterSwitch implements a loop. The last line of the code fragment triggers the Switch Event within 5 seconds. -- Upon the event execution (after 5 seconds), the OnAfterSwitch method is called of FsmDemo (cfr. the double point notation!!! ":"). - -- The OnAfterSwitch method receives from the FSM the 3 transition parameter details ( From, Event, To ), + -- The OnAfterSwitch method receives from the FSM the 3 transition parameter details ( From, Event, To ), -- and one additional parameter that was given when the event was triggered, which is in this case the Unit that is used within OnSwitchAfter. - -- + -- -- function FsmDemo:OnAfterSwitch( From, Event, To, FsmUnit ) - -- + -- -- For debugging reasons the received parameters are traced within the DCS.log. - -- + -- -- self:T( { From, Event, To, FsmUnit } ) - -- + -- -- The method will check if the From state received is either "Green" or "Red" and will flare the respective color from the FsmUnit. - -- + -- -- if From == "Green" then -- FsmUnit:Flare(FLARECOLOR.Green) -- else @@ -309,77 +307,75 @@ do -- FSM -- FsmUnit:Flare(FLARECOLOR.Red) -- end -- end - -- + -- -- It is important that the Switch event is again triggered, otherwise, the FsmDemo would stop working after having the first Event being handled. - -- + -- -- FsmDemo:__Switch( 5, FsmUnit ) -- Trigger the next Switch event to happen in 5 seconds. - -- + -- -- The below code fragment extends the FsmDemo, demonstrating multiple **From states declared as a table**, adding a **Linear Transition Rule**. -- The new event **Stop** will cancel the Switching process. -- The transition for event Stop can be executed if the current state of the FSM is either "Red" or "Green". - -- + -- -- local FsmDemo = FSM:New() -- #FsmDemo -- FsmDemo:SetStartState( "Green" ) -- FsmDemo:AddTransition( "Green", "Switch", "Red" ) -- FsmDemo:AddTransition( "Red", "Switch", "Green" ) -- FsmDemo:AddTransition( { "Red", "Green" }, "Stop", "Stopped" ) - -- + -- -- The transition for event Stop can also be simplified, as any current state of the FSM is valid. - -- + -- -- FsmDemo:AddTransition( "*", "Stop", "Stopped" ) - -- + -- -- So... When FsmDemo:Stop() is being triggered, the state of FsmDemo will transition from Red or Green to Stopped. -- And there is no transition handling method defined for that transition, thus, no new event is being triggered causing the FsmDemo process flow to halt. - -- + -- -- ## FSM Hierarchical Transitions - -- + -- -- Hierarchical Transitions allow to re-use readily available and implemented FSMs. - -- This becomes in very useful for mission building, where mission designers build complex processes and workflows, + -- This becomes in very useful for mission building, where mission designers build complex processes and workflows, -- combining smaller FSMs to one single FSM. - -- - -- The FSM can embed **Sub-FSMs** that will execute and return **multiple possible Return (End) States**. + -- + -- The FSM can embed **Sub-FSMs** that will execute and return **multiple possible Return (End) States**. -- Depending upon **which state is returned**, the main FSM can continue the flow **triggering specific events**. - -- - -- The method @{#FSM.AddProcess}() adds a new Sub-FSM to the FSM. + -- + -- The method @{#FSM.AddProcess}() adds a new Sub-FSM to the FSM. -- -- === - -- + -- -- @field #FSM - -- FSM = { ClassName = "FSM", } - + --- Creates a new FSM object. -- @param #FSM self -- @return #FSM function FSM:New() - + -- Inherits from BASE 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 - - + --- Sets the start state of the FSM. -- @param #FSM self -- @param #string State A string defining the start state. @@ -387,15 +383,14 @@ do -- FSM self._StartState = State self.current = State end - - + --- Returns the start state of the FSM. -- @param #FSM self -- @return #string A string containing the start state. function FSM:GetStartState() return self._StartState or {} end - + --- Add a new transition rule to the FSM. -- A transition rule defines when and if the FSM can transition from a state towards another state upon a triggered event. -- @param #FSM self @@ -403,27 +398,26 @@ do -- FSM -- @param #string Event The Event name. -- @param #string To The To state. function FSM:AddTransition( From, Event, To ) - + local Transition = {} Transition.From = From Transition.Event = Event Transition.To = To - + -- Debug message. self:T2( Transition ) - + self._Transitions[Transition] = Transition self:_eventmap( self.Events, Transition ) end - --- Returns a table of the transition rules defined within the FSM. -- @param #FSM self -- @return #table Transitions. - function FSM:GetTransitions() + function FSM:GetTransitions() return self._Transitions or {} end - + --- Set the default @{Process} template with key ProcessName providing the ProcessClass and the process object when it is assigned to a @{Wrapper.Controllable} by the task. -- @param #FSM self -- @param #table From Can contain a string indicating the From state or a table of strings containing multiple From states. @@ -433,73 +427,71 @@ do -- FSM -- @return Core.Fsm#FSM_PROCESS The SubFSM. 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 - - + --- Returns a table of the SubFSM rules defined within the FSM. -- @param #FSM self -- @return #table Sub processes. 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 - + --- Adds an End state. -- @param #FSM self -- @param #string State The FSM state. - function FSM:AddEndState( State ) + function FSM:AddEndState( State ) self._EndStates[State] = State self.endstates[State] = State end - + --- Returns the End states. -- @param #FSM self -- @return #table End states. - function FSM:GetEndStates() + function FSM:GetEndStates() return self._EndStates or {} end - - + --- Adds a score for the FSM to be achieved. -- @param #FSM self -- @param #string State is the state of the process when the score needs to be given. (See the relevant state descriptions of the process). @@ -508,14 +500,14 @@ do -- FSM -- @return #FSM self 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 - + --- Adds a score for the FSM_PROCESS to be achieved. -- @param #FSM self -- @param #string From is the From State of the main process. @@ -526,85 +518,85 @@ do -- FSM -- @return #FSM self 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 - + --- Returns a table with the scores defined. -- @param #FSM self -- @return #table Scores. - function FSM:GetScores() + function FSM:GetScores() return self._Scores or {} end - + --- Returns a table with the Subs defined. -- @param #FSM self -- @return #table Sub processes. - function FSM:GetSubs() + function FSM:GetSubs() return self.options.subs end - + --- Load call backs. -- @param #FSM self - -- @param #table CallBackTable Table of call backs. + -- @param #table CallBackTable Table of call backs. function FSM:LoadCallBacks( CallBackTable ) - + for name, callback in pairs( CallBackTable or {} ) do self[name] = callback end - + end - - --- Event map. + + --- Event map. -- @param #FSM self -- @param #table Events Events. -- @param #table EventStructure Event structure. 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) - - -- Debug message. - self:T2( "Added methods: " .. Event .. ", " .. __Event ) - - Events[Event] = self.Events[Event] or { map = {} } - self:_add_to_map( Events[Event].map, 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 ) + + -- Debug message. + self:T2( "Added methods: " .. Event .. ", " .. __Event ) + + Events[Event] = self.Events[Event] or { map = {} } + self:_add_to_map( Events[Event].map, EventStructure ) + end - --- Sub maps. + --- Sub maps. -- @param #FSM self -- @param #table subs Subs. -- @param #table sub Sub. - -- @param #string name Name. + -- @param #string name Name. function FSM:_submap( subs, sub, name ) - + subs[sub.From] = subs[sub.From] or {} subs[sub.From][sub.Event] = subs[sub.From][sub.Event] or {} - + -- Make the reference table weak. -- setmetatable( subs[sub.From][sub.Event], { __mode = "k" } ) - + 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 {} -- these events need to be given to find the correct continue event ... if none given, the processing will stop. subs[sub.From][sub.Event][sub].name = name subs[sub.From][sub.Event][sub].fsmparent = self - + end - + --- Call handler. -- @param #FSM self -- @param #string step Step "onafter", "onbefore", "onenter", "onleave". @@ -613,12 +605,12 @@ do -- FSM -- @param #string EventName Event name. -- @return Value. function FSM:_call_handler( step, trigger, params, EventName ) - --env.info(string.format("FF T=%.3f _call_handler step=%s, trigger=%s, event=%s", timer.getTime(), step, trigger, EventName)) + -- env.info(string.format("FF T=%.3f _call_handler step=%s, trigger=%s, event=%s", timer.getTime(), step, trigger, EventName)) local handler = step .. trigger - + if self[handler] then - + --[[ if step == "onafter" or step == "OnAfter" then self:T( ":::>" .. step .. params[2] .. " : " .. params[1] .. " >> " .. params[2] .. ">" .. step .. params[2] .. "()" .. " >> " .. params[3] ) @@ -632,7 +624,7 @@ do -- FSM self:T( ":::>" .. step .. " : " .. params[1] .. " >> " .. params[2] .. " >> " .. params[3] ) end ]] - + self._EventSchedules[EventName] = nil -- Error handler. @@ -640,49 +632,50 @@ do -- FSM env.info( "Error in SCHEDULER function:" .. errmsg ) if BASE.Debug ~= nil then env.info( BASE.Debug.traceback() ) - end + end return errmsg end - - --return self[handler](self, unpack( params )) - + + -- return self[handler](self, unpack( params )) + -- Protected call. - local Result, Value = xpcall( function() return self[handler]( self, unpack( params ) ) end, ErrorHandler ) - return Value + local Result, Value = xpcall( function() + return self[handler]( self, unpack( params ) ) + end, ErrorHandler ) + return Value end - + end - + --- Handler. -- @param #FSM self -- @param #string EventName Event name. -- @param ... Arguments. function FSM._handler( self, EventName, ... ) - + local Can, To = self:can( EventName ) - + if To == "*" then To = self.current end - + if Can then - + -- From state. local From = self.current - + -- Parameters. - local Params = { From, EventName, To, ... } + 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["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 @@ -691,7 +684,7 @@ do -- FSM self:T( "*** FSM *** Cancel" .. " *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** OnBefore" .. EventName ) return false else - if self:_call_handler( "onleave", From, Params, EventName ) == false then + if self:_call_handler( "onleave", From, Params, EventName ) == false then self:T( "*** FSM *** Cancel" .. " *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** onleave" .. From ) return false else @@ -699,147 +692,149 @@ do -- FSM self:T( "*** FSM *** Cancel" .. " *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** OnLeave" .. From ) return false end - 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 - + 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 + self:T( "*** FSM *** Transit *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** Task: " .. self.Task:GetName() .. ", TaskUnit: " .. self.Controllable.ControllableName .. " *** " ) + end end - + -- New current state. self.current = To - + local execute = true - + local subtable = self:_gosub( From, EventName ) - + for _, sub in pairs( subtable ) do - - --if sub.nextevent then + + -- if sub.nextevent then -- self:F2( "nextevent = " .. sub.nextevent ) -- self[sub.nextevent]( self ) - --end - + -- end + 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 ) - + + 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 ) - + + 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 --- Delayed transition. -- @param #FSM self - -- @param #string EventName Event name. + -- @param #string EventName Event name. -- @return #function Function. function FSM:_delayed_transition( EventName ) - + return function( self, DelaySeconds, ... ) - + -- Debug. self:T2( "Delayed Event: " .. EventName ) - + local CallID = 0 if DelaySeconds ~= nil then - + if DelaySeconds < 0 then -- Only call the event ONCE! - + DelaySeconds = math.abs( DelaySeconds ) - + if not self._EventSchedules[EventName] then - + -- Call _handler. CallID = self.CallScheduler:Schedule( self, self._handler, { EventName, ... }, DelaySeconds or 1, nil, nil, nil, 4, true ) - + -- Set call ID. self._EventSchedules[EventName] = CallID - + -- Debug output. - self:T2(string.format("NEGATIVE Event %s delayed by %.1f sec SCHEDULED with CallID=%s", EventName, DelaySeconds, tostring(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)) + self:T2( string.format( "NEGATIVE Event %s delayed by %.1f sec CANCELLED as we already have such an event in the queue.", EventName, DelaySeconds ) ) -- reschedule 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))) + + 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 - + -- Debug. self:T2( { CallID = CallID } ) end - + end --- Create transition. -- @param #FSM self - -- @param #string EventName Event name. - -- @return #function Function. + -- @param #string EventName Event name. + -- @return #function Function. function FSM:_create_transition( EventName ) - return function( self, ... ) return self._handler( self, EventName , ... ) end + return function( self, ... ) + return self._handler( self, EventName, ... ) + end end - + --- Go sub. - -- @param #FSM self + -- @param #FSM self -- @param #string ParentFrom Parent from state. -- @param #string ParentEvent Parent event name. -- @return #table Subs. @@ -860,21 +855,21 @@ do -- FSM -- @return #string Event name. function FSM:_isendstate( Current ) local FSMParent = self.fsmparent - + if FSMParent and self.endstates[Current] then - --self:T( { state = Current, endstates = self.endstates, endstate = self.endstates[Current] } ) + -- self:T( { state = Current, endstates = self.endstates, endstate = self.endstates[Current] } ) FSMParent.current = Current local ParentFrom = FSMParent.current - --self:T( { ParentFrom, self.ReturnEvents } ) + -- self:T( { ParentFrom, self.ReturnEvents } ) local Event = self.ReturnEvents[Current] - --self:T( { Event } ) + -- self:T( { Event } ) if Event then return FSMParent, Event else - --self:T( { "Could not find parent event name for state ", ParentFrom } ) + -- self:T( { "Could not find parent event name for state ", ParentFrom } ) end end - + return nil end @@ -883,17 +878,17 @@ do -- FSM -- @param #table Map Map. -- @param #table Event Event table. function FSM:_add_to_map( Map, Event ) - self:F3( { Map, Event } ) - - if type(Event.From) == 'string' then - Map[Event.From] = Event.To + 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 + for _, From in ipairs( Event.From ) do + Map[From] = Event.To end end - - self:T3( { Map, Event } ) + + self:T3( { Map, Event } ) end --- Get current state. @@ -905,11 +900,11 @@ do -- FSM --- Get current state. -- @param #FSM self - -- @return #string Current FSM state. + -- @return #string Current FSM state. function FSM:GetCurrentState() return self.current end - + --- Check if FSM is in state. -- @param #FSM self -- @param #string State State name. @@ -921,8 +916,8 @@ do -- FSM --- Check if FSM is in state. -- @param #FSM self -- @param #string State State name. - -- @param #boolean If true, FSM is in this state. - function FSM:is(state) + -- @param #boolean If true, FSM is in this state. + function FSM:is( state ) return self.current == state end @@ -931,14 +926,14 @@ do -- FSM -- @param #string e Event name. -- @return #boolean If true, FSM can do the event. -- @return #string To state. - function FSM:can(e) - + function FSM:can( e ) + local Event = self.Events[e] - - --self:F3( { self.current, Event } ) - + + -- self:F3( { self.current, Event } ) + local To = Event and Event.map[self.current] or Event.map['*'] - + return To ~= nil, To end @@ -946,8 +941,8 @@ do -- FSM -- @param #FSM self -- @param #string e Event name. -- @return #boolean If true, FSM cannot do the event. - function FSM:cannot(e) - return not self:can(e) + function FSM:cannot( e ) + return not self:can( e ) end end @@ -957,32 +952,32 @@ do -- FSM_CONTROLLABLE --- @type FSM_CONTROLLABLE -- @field Wrapper.Controllable#CONTROLLABLE Controllable -- @extends Core.Fsm#FSM - + --- Models Finite State Machines for @{Wrapper.Controllable}s, which are @{Wrapper.Group}s, @{Wrapper.Unit}s, @{Client}s. - -- + -- -- === - -- + -- -- @field #FSM_CONTROLLABLE FSM_CONTROLLABLE = { ClassName = "FSM_CONTROLLABLE", } - + --- Creates a new FSM_CONTROLLABLE object. -- @param #FSM_CONTROLLABLE self -- @param #table FSMT Finite State Machine Table -- @param Wrapper.Controllable#CONTROLLABLE Controllable (optional) The CONTROLLABLE object that the FSM_CONTROLLABLE governs. -- @return #FSM_CONTROLLABLE function FSM_CONTROLLABLE:New( Controllable ) - + -- Inherits from BASE local self = BASE:Inherit( self, FSM:New() ) -- Core.Fsm#FSM_CONTROLLABLE - + if Controllable then self:SetControllable( Controllable ) end - + self:AddTransition( "*", "Stop", "Stopped" ) - + --- OnBefore Transition Handler for Event Stop. -- @function [parent=#FSM_CONTROLLABLE] OnBeforeStop -- @param #FSM_CONTROLLABLE self @@ -991,7 +986,7 @@ do -- FSM_CONTROLLABLE -- @param #string Event The Event string. -- @param #string To The To State string. -- @return #boolean Return false to cancel Transition. - + --- OnAfter Transition Handler for Event Stop. -- @function [parent=#FSM_CONTROLLABLE] OnAfterStop -- @param #FSM_CONTROLLABLE self @@ -999,16 +994,16 @@ do -- FSM_CONTROLLABLE -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. - + --- Synchronous Event Trigger for Event Stop. -- @function [parent=#FSM_CONTROLLABLE] Stop -- @param #FSM_CONTROLLABLE self - + --- Asynchronous Event Trigger for Event Stop. -- @function [parent=#FSM_CONTROLLABLE] __Stop -- @param #FSM_CONTROLLABLE self -- @param #number Delay The delay in seconds. - + --- OnLeave Transition Handler for State Stopped. -- @function [parent=#FSM_CONTROLLABLE] OnLeaveStopped -- @param #FSM_CONTROLLABLE self @@ -1017,7 +1012,7 @@ do -- FSM_CONTROLLABLE -- @param #string Event The Event string. -- @param #string To The To State string. -- @return #boolean Return false to cancel Transition. - + --- OnEnter Transition Handler for State Stopped. -- @function [parent=#FSM_CONTROLLABLE] OnEnterStopped -- @param #FSM_CONTROLLABLE self @@ -1036,51 +1031,53 @@ do -- FSM_CONTROLLABLE -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. - function FSM_CONTROLLABLE:OnAfterStop(Controllable,From,Event,To) - + function FSM_CONTROLLABLE:OnAfterStop( Controllable, From, Event, To ) + -- Clear all pending schedules self.CallScheduler:Clear() end - + --- Sets the CONTROLLABLE object that the FSM_CONTROLLABLE governs. -- @param #FSM_CONTROLLABLE self -- @param Wrapper.Controllable#CONTROLLABLE FSMControllable -- @return #FSM_CONTROLLABLE function FSM_CONTROLLABLE:SetControllable( FSMControllable ) - --self:F( FSMControllable:GetName() ) + -- self:F( FSMControllable:GetName() ) self.Controllable = FSMControllable end - + --- Gets the CONTROLLABLE object that the FSM_CONTROLLABLE governs. -- @param #FSM_CONTROLLABLE self -- @return Wrapper.Controllable#CONTROLLABLE 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 ) + local Result, Value = xpcall( function() + return self[handler]( self, self.Controllable, unpack( params ) ) + end, ErrorHandler ) return Value - --return self[handler]( self, self.Controllable, unpack( params ) ) + -- return self[handler]( self, self.Controllable, unpack( params ) ) end end - + end do -- FSM_PROCESS @@ -1088,50 +1085,47 @@ do -- FSM_PROCESS --- @type FSM_PROCESS -- @field Tasking.Task#TASK Task -- @extends Core.Fsm#FSM_CONTROLLABLE - - + --- FSM_PROCESS class models Finite State Machines for @{Task} actions, which control @{Client}s. -- -- === -- -- @field #FSM_PROCESS FSM_PROCESS -- - FSM_PROCESS = { - ClassName = "FSM_PROCESS", - } - + FSM_PROCESS = { ClassName = "FSM_PROCESS" } + --- Creates a new FSM_PROCESS object. -- @param #FSM_PROCESS self -- @return #FSM_PROCESS function FSM_PROCESS:New( Controllable, Task ) - + local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- Core.Fsm#FSM_PROCESS - --self:F( Controllable ) - + -- self:F( Controllable ) + self:Assign( Controllable, Task ) - + return self end - + function FSM_PROCESS:Init( FsmProcess ) self:T( "No Initialisation" ) - end + 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() ) @@ -1139,53 +1133,54 @@ do -- FSM_PROCESS 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 ) + Result, Value = xpcall( function() + return self[handler]( self, self.Controllable, self.Task, unpack( params ) ) + end, ErrorHandler ) end return Value - --return self[handler]( self, self.Controllable, unpack( params ) ) + -- return self[handler]( self, self.Controllable, unpack( params ) ) end end - + --- Creates a new FSM_PROCESS object based on this FSM_PROCESS. -- @param #FSM_PROCESS self -- @return #FSM_PROCESS function FSM_PROCESS:Copy( Controllable, Task ) self:T( { self:GetClassNameAndID() } ) - local NewFsm = self:New( Controllable, Task ) -- Core.Fsm#FSM_PROCESS - + NewFsm:Assign( Controllable, Task ) -- Polymorphic call to initialize the new FSM_PROCESS based on self FSM_PROCESS NewFsm:Init( self ) - + -- Set Start State NewFsm:SetStartState( self:GetStartState() ) - + -- Copy Transitions for TransitionID, Transition in pairs( self:GetTransitions() ) do NewFsm:AddTransition( Transition.From, Transition.Event, Transition.To ) end - + -- Copy Processes for ProcessID, Process in pairs( self:GetProcesses() ) do - --self:E( { Process:GetName() } ) + -- self:E( { Process:GetName() } ) local FsmProcess = NewFsm:AddProcess( Process.From, Process.Event, Process.fsm:Copy( Controllable, Task ), Process.ReturnEvents ) end - + -- Copy End States for EndStateID, EndState in pairs( self:GetEndStates() ) do self:T( EndState ) NewFsm:AddEndState( EndState ) end - + -- Copy the score tables for ScoreID, Score in pairs( self:GetScores() ) do self:T( Score ) NewFsm:AddScore( ScoreID, Score.ScoreText, Score.Score ) end - + return NewFsm end @@ -1197,7 +1192,7 @@ do -- FSM_PROCESS self:F( "Clearing Schedules" ) self.CallScheduler:Clear() - + -- Copy Processes for ProcessID, Process in pairs( self:GetProcesses() ) do if Process.fsm then @@ -1205,98 +1200,94 @@ do -- FSM_PROCESS Process.fsm = nil end end - + return self end - + --- Sets the task of the process. -- @param #FSM_PROCESS self -- @param Tasking.Task#TASK Task -- @return #FSM_PROCESS function FSM_PROCESS:SetTask( Task ) - + self.Task = Task - + return self end - + --- Gets the task of the process. -- @param #FSM_PROCESS self -- @return Tasking.Task#TASK function FSM_PROCESS:GetTask() - + return self.Task end - + --- Gets the mission of the process. -- @param #FSM_PROCESS self -- @return Tasking.Mission#MISSION function FSM_PROCESS:GetMission() - + return self.Task.Mission end - + --- Gets the mission of the process. -- @param #FSM_PROCESS self -- @return Tasking.CommandCenter#COMMANDCENTER function FSM_PROCESS:GetCommandCenter() - + return self:GetTask():GetMission():GetCommandCenter() end - --- TODO: Need to check and fix that an FSM_PROCESS is only for a UNIT. Not for a GROUP. - + + -- TODO: Need to check and fix that an FSM_PROCESS is only for a UNIT. Not for a GROUP. + --- Send a message of the @{Task} to the Group of the Unit. -- @param #FSM_PROCESS self function FSM_PROCESS:Message( Message ) self:F( { Message = Message } ) - + local CC = self:GetCommandCenter() local TaskGroup = self.Controllable:GetGroup() - + local PlayerName = self.Controllable:GetPlayerName() -- Only for a unit PlayerName = PlayerName and " (" .. PlayerName .. ")" or "" -- If PlayerName is nil, then keep it nil, otherwise add brackets. local Callsign = self.Controllable:GetCallsign() local Prefix = Callsign and " @ " .. Callsign .. PlayerName or "" - + Message = Prefix .. ": " .. Message CC:MessageToGroup( Message, TaskGroup ) end - - - --- Assign the process to a @{Wrapper.Unit} and activate the process. -- @param #FSM_PROCESS self -- @param Task.Tasking#TASK Task -- @param Wrapper.Unit#UNIT ProcessUnit -- @return #FSM_PROCESS self function FSM_PROCESS:Assign( ProcessUnit, Task ) - --self:T( { Task:GetName(), ProcessUnit:GetName() } ) - + -- self:T( { Task:GetName(), ProcessUnit:GetName() } ) + self:SetControllable( ProcessUnit ) self:SetTask( Task ) - - --self.ProcessGroup = ProcessUnit:GetGroup() - + + -- self.ProcessGroup = ProcessUnit:GetGroup() + return self end - --- function FSM_PROCESS:onenterAssigned( ProcessUnit, Task, From, Event, To ) --- --- if From( "Planned" ) then --- self:T( "*** FSM *** Assign *** " .. Task:GetName() .. "/" .. ProcessUnit:GetName() .. " *** " .. From .. " --> " .. Event .. " --> " .. To ) --- self.Task:Assign() --- end --- end - + + -- function FSM_PROCESS:onenterAssigned( ProcessUnit, Task, From, Event, To ) + -- + -- if From( "Planned" ) then + -- self:T( "*** FSM *** Assign *** " .. Task:GetName() .. "/" .. ProcessUnit:GetName() .. " *** " .. From .. " --> " .. Event .. " --> " .. To ) + -- self.Task:Assign() + -- end + -- 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 - --- StateMachine callback function for a FSM_PROCESS -- @param #FSM_PROCESS self -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit @@ -1304,20 +1295,20 @@ do -- FSM_PROCESS -- @param #string From -- @param #string To 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:IsTrace() then --- MESSAGE:New( "@ Process " .. self:GetClassNameAndID() .. " : " .. Event .. " changed to state " .. To, 2 ):ToAll() --- self:F2( { Scores = self._Scores, To = To } ) --- end - + + -- if self:IsTrace() then + -- MESSAGE:New( "@ Process " .. self:GetClassNameAndID() .. " : " .. Event .. " changed to state " .. To, 2 ):ToAll() + -- self:F2( { Scores = self._Scores, To = To } ) + -- end + -- TODO: This needs to be reworked with a callback functions allocated within Task, and set within the mission script from the Task Objects... if self._Scores[To] then - - local Task = self.Task + + local Task = self.Task local Scoring = Task:GetScoring() if Scoring then Scoring:_AddMissionTaskScore( Task.Mission, ProcessUnit, self._Scores[To].ScoreText, self._Scores[To].Score ) @@ -1333,49 +1324,51 @@ do -- FSM_TASK -- @type FSM_TASK -- @field Tasking.Task#TASK Task -- @extends #FSM - + --- Models Finite State Machines for @{Tasking.Task}s. - -- + -- -- === - -- + -- -- @field #FSM_TASK FSM_TASK - -- + -- FSM_TASK = { ClassName = "FSM_TASK", } - + --- Creates a new FSM_TASK object. -- @param #FSM_TASK self -- @param #string TaskName The name of the task. -- @return #FSM_TASK function FSM_TASK:New( TaskName ) - + local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- Core.Fsm#FSM_TASK - + 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 - --return self[handler]( self, unpack( params ) ) - local Result, Value = xpcall( function() return self[handler]( self, unpack( params ) ) end, ErrorHandler ) + -- return self[handler]( self, unpack( params ) ) + local Result, Value = xpcall( function() + return self[handler]( self, unpack( params ) ) + end, ErrorHandler ) return Value end end @@ -1389,35 +1382,33 @@ do -- FSM_SET -- @field Core.Set#SET_BASE Set -- @extends Core.Fsm#FSM - --- FSM_SET class models Finite State Machines for @{Set}s. Note that these FSMs control multiple objects!!! So State concerns here -- for multiple objects or the position of the state machine in the process. - -- + -- -- === - -- + -- -- @field #FSM_SET FSM_SET - -- FSM_SET = { ClassName = "FSM_SET", } - + --- Creates a new FSM_SET object. -- @param #FSM_SET self -- @param #table FSMT Finite State Machine Table -- @param Set_SET_BASE FSMSet (optional) The Set object that the FSM_SET governs. -- @return #FSM_SET function FSM_SET:New( FSMSet ) - + -- Inherits from BASE self = BASE:Inherit( self, FSM:New() ) -- Core.Fsm#FSM_SET - + if FSMSet then self:Set( FSMSet ) end - + return self end - + --- Sets the SET_BASE object that the FSM_SET governs. -- @param #FSM_SET self -- @param Core.Set#SET_BASE FSMSet @@ -1426,16 +1417,16 @@ do -- FSM_SET self:F( FSMSet ) self.Set = FSMSet end - + --- Gets the SET_BASE object that the FSM_SET governs. -- @param #FSM_SET self -- @return Core.Set#SET_BASE function FSM_SET:Get() return self.Controllable end - - function FSM_SET:_call_handler( step, trigger, params, EventName ) - local handler = step .. trigger + + 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 diff --git a/Moose Development/Moose/Core/Goal.lua b/Moose Development/Moose/Core/Goal.lua index c145d6de4..bc33246f6 100644 --- a/Moose Development/Moose/Core/Goal.lua +++ b/Moose Development/Moose/Core/Goal.lua @@ -1,89 +1,87 @@ --- **Core** - Models the process to achieve goal(s). -- -- === --- +-- -- ## Features: --- +-- -- * Define the goal. -- * Monitor the goal achievement. -- * Manage goal contribution by players. --- +-- -- === --- +-- -- Classes that implement a goal achievement, will derive from GOAL to implement the ways how the achievements can be realized. --- +-- -- === --- +-- -- ### Author: **FlightControl** -- ### Contributions: **funkyfranky** --- +-- -- === --- +-- -- @module Core.Goal -- @image Core_Goal.JPG - do -- Goal --- @type GOAL -- @extends Core.Fsm#FSM - --- Models processes that have an objective with a defined achievement. Derived classes implement the ways how the achievements can be realized. - -- + -- -- # 1. GOAL constructor - -- + -- -- * @{#GOAL.New}(): Creates a new GOAL object. - -- + -- -- # 2. GOAL is a finite state machine (FSM). - -- + -- -- ## 2.1. GOAL States - -- + -- -- * **Pending**: The goal object is in progress. -- * **Achieved**: The goal objective is Achieved. - -- + -- -- ## 2.2. GOAL Events - -- + -- -- * **Achieved**: Set the goal objective to Achieved. - -- + -- -- # 3. Player contributions. - -- + -- -- Goals are most of the time achieved by players. These player achievements can be registered as part of the goal achievement. -- Use @{#GOAL.AddPlayerContribution}() to add a player contribution to the goal. -- The player contributions are based on a points system, an internal counter per player. - -- So once the goal has been achieved, the player contributions can be queried using @{#GOAL.GetPlayerContributions}(), + -- So once the goal has been achieved, the player contributions can be queried using @{#GOAL.GetPlayerContributions}(), -- that retrieves all contributions done by the players. For one player, the contribution can be queried using @{#GOAL.GetPlayerContribution}(). -- The total amount of player contributions can be queried using @{#GOAL.GetTotalContributions}(). - -- + -- -- # 4. Goal achievement. - -- + -- -- Once the goal is achieved, the mission designer will need to trigger the goal achievement using the **Achieved** event. -- The underlying 2 examples will achieve the goals for the `Goal` object: - -- + -- -- Goal:Achieved() -- Achieve the goal immediately. -- Goal:__Achieved( 30 ) -- Achieve the goal within 30 seconds. - -- + -- -- # 5. Check goal achievement. - -- + -- -- The method @{#GOAL.IsAchieved}() will return true if the goal is achieved (the trigger **Achieved** was executed). -- You can use this method to check asynchronously if a goal has been achieved, for example using a scheduler. - -- + -- -- @field #GOAL GOAL = { ClassName = "GOAL", } - + --- @field #table GOAL.Players GOAL.Players = {} --- @field #number GOAL.TotalContributions GOAL.TotalContributions = 0 - + --- GOAL Constructor. -- @param #GOAL self -- @return #GOAL function GOAL:New() - + local self = BASE:Inherit( self, FSM:New() ) -- #GOAL self:F( {} ) @@ -104,11 +102,10 @@ do -- Goal -- @param #string From -- @param #string Event -- @param #string To - - + self:SetStartState( "Pending" ) - self:AddTransition( "*", "Achieved", "Achieved" ) - + self:AddTransition( "*", "Achieved", "Achieved" ) + --- Achieved Handler OnBefore for GOAL -- @function [parent=#GOAL] OnBeforeAchieved -- @param #GOAL self @@ -116,47 +113,44 @@ do -- Goal -- @param #string Event -- @param #string To -- @return #boolean - + --- Achieved Handler OnAfter for GOAL -- @function [parent=#GOAL] OnAfterAchieved -- @param #GOAL self -- @param #string From -- @param #string Event -- @param #string To - + --- Achieved Trigger for GOAL -- @function [parent=#GOAL] Achieved -- @param #GOAL self - + --- Achieved Asynchronous Trigger for GOAL -- @function [parent=#GOAL] __Achieved -- @param #GOAL self -- @param #number Delay - + self:SetEventPriority( 5 ) return self end - - + --- Add a new contribution by a player. -- @param #GOAL self -- @param #string PlayerName The name of the player. function GOAL:AddPlayerContribution( PlayerName ) - self:F({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 - - + --- @param #GOAL self -- @param #number Player contribution. function GOAL:GetPlayerContribution( PlayerName ) - return self.Players[PlayerName] or 0 + return self.Players[PlayerName] or 0 end - --- Get the players who contributed to achieve the goal. -- The result is a list of players, sorted by the name of the players. -- @param #GOAL self @@ -165,7 +159,6 @@ do -- Goal return self.Players or {} end - --- Gets the total contributions that happened to achieve the goal. -- The result is a number. -- @param #GOAL self @@ -173,9 +166,7 @@ do -- Goal function GOAL:GetTotalContributions() return self.TotalContributions or 0 end - - - + --- Validates if the goal is achieved. -- @param #GOAL self -- @return #boolean true if the goal is achieved. @@ -183,4 +174,4 @@ do -- Goal return self:Is( "Achieved" ) end -end \ No newline at end of file +end diff --git a/Moose Development/Moose/Core/Menu.lua b/Moose Development/Moose/Core/Menu.lua index f0962380d..bd64eb773 100644 --- a/Moose Development/Moose/Core/Menu.lua +++ b/Moose Development/Moose/Core/Menu.lua @@ -1,9 +1,9 @@ --- **Core** - Manage hierarchical menu structures and commands for players within a mission. --- +-- -- === --- +-- -- ### Features: --- +-- -- * Setup mission sub menus. -- * Setup mission command menus. -- * Setup coalition sub menus. @@ -14,46 +14,45 @@ -- * Only create or delete menus when required, and keep existing menus persistent. -- * Update menu structures. -- * Refresh menu structures intelligently, based on a time stamp of updates. --- - Delete obscolete menus. +-- - Delete obsolete menus. -- - Create new one where required. -- - Don't touch the existing ones. -- * Provide a variable amount of parameters to menus. -- * Update the parameters and the receiving methods, without updating the menu within DCS! -- * Provide a great performance boost in menu management. -- * Provide a great tool to manage menus in your code. --- --- DCS Menus can be managed using the MENU classes. --- The advantage of using MENU classes is that it hides the complexity of dealing with menu management in more advanced scanerios where you need to +-- +-- DCS Menus can be managed using the MENU classes. +-- The advantage of using MENU classes is that it hides the complexity of dealing with menu management in more advanced scenarios where you need to -- set menus and later remove them, and later set them again. You'll find while using use normal DCS scripting functions, that setting and removing --- menus is not a easy feat if you have complex menu hierarchies defined. +-- menus is not a easy feat if you have complex menu hierarchies defined. -- Using the MOOSE menu classes, the removal and refreshing of menus are nicely being handled within these classes, and becomes much more easy. --- On top, MOOSE implements **variable parameter** passing for command menus. --- +-- On top, MOOSE implements **variable parameter** passing for command menus. +-- -- There are basically two different MENU class types that you need to use: --- +-- -- ### To manage **main menus**, the classes begin with **MENU_**: --- +-- -- * @{Core.Menu#MENU_MISSION}: Manages main menus for whole mission file. -- * @{Core.Menu#MENU_COALITION}: Manages main menus for whole coalition. -- * @{Core.Menu#MENU_GROUP}: Manages main menus for GROUPs. --- +-- -- ### To manage **command menus**, which are menus that allow the player to issue **functions**, the classes begin with **MENU_COMMAND_**: --- +-- -- * @{Core.Menu#MENU_MISSION_COMMAND}: Manages command menus for whole mission file. -- * @{Core.Menu#MENU_COALITION_COMMAND}: Manages command menus for whole coalition. -- * @{Core.Menu#MENU_GROUP_COMMAND}: Manages command menus for GROUPs. --- +-- -- === ---- +--- -- ### Author: **FlightControl** --- ### Contributions: --- +-- ### Contributions: +-- -- === --- +-- -- @module Core.Menu -- @image Core_Menu.JPG - MENU_INDEX = {} MENU_INDEX.MenuMission = {} MENU_INDEX.MenuMission.Menus = {} @@ -64,23 +63,21 @@ 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 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 } ) + 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 } ) + BASE:E( { Path = Path, Coalition = Coalition } ) error( "Parent path not found in menu index for coalition menu" ) return nil end @@ -92,35 +89,31 @@ function MENU_INDEX:ParentPath( ParentMenu, MenuText ) end end end - + Path = Path .. "@" .. MenuText return Path end - function MENU_INDEX:PrepareMission() - self.MenuMission.Menus = self.MenuMission.Menus or {} + 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 {} + self.Coalition[CoalitionSide] = self.Coalition[CoalitionSide] or {} + self.Coalition[CoalitionSide].Menus = self.Coalition[CoalitionSide].Menus or {} end --- -- @param Wrapper.Group#GROUP Group function MENU_INDEX:PrepareGroup( Group ) - if Group and Group:IsAlive() ~= nil then -- something was changed here! + if Group and Group:IsAlive() ~= nil then -- something was changed here! 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] @@ -136,8 +129,6 @@ function MENU_INDEX:ClearMissionMenu( Path ) self.MenuMission.Menus[Path] = nil end - - function MENU_INDEX:HasCoalitionMenu( Coalition, Path ) return self.Coalition[Coalition].Menus[Path] @@ -153,8 +144,6 @@ 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() @@ -166,7 +155,7 @@ end function MENU_INDEX:SetGroupMenu( Group, Path, Menu ) local MenuGroupName = Group:GetName() - Group:F({MenuGroupName=MenuGroupName,Path=Path}) + Group:F( { MenuGroupName = MenuGroupName, Path = Path } ) self.Group[MenuGroupName].Menus[Path] = Menu end @@ -178,32 +167,25 @@ end function MENU_INDEX:Refresh( Group ) - for MenuID, Menu in pairs( self.MenuMission.Menus ) do - Menu:Refresh() - end + 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.BLUE].Menus ) do + Menu:Refresh() + end - for MenuID, Menu in pairs( self.Coalition[coalition.side.RED].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 + local GroupName = Group:GetName() + for MenuID, Menu in pairs( self.Group[GroupName].Menus ) do + Menu:Refresh() + end end - - - - - - - do -- MENU_BASE --- @type MENU_BASE @@ -216,37 +198,37 @@ do -- MENU_BASE ClassName = "MENU_BASE", MenuPath = nil, MenuText = "", - MenuParentPath = nil + MenuParentPath = nil, } - - --- Consructor + + --- Constructor -- @param #MENU_BASE -- @return #MENU_BASE 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 + 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 + + return self end function MENU_BASE:SetParentMenu( MenuText, Menu ) @@ -262,7 +244,7 @@ do -- MENU_BASE self.ParentMenu.Menus[MenuText] = nil self.ParentMenu.MenuCount = self.ParentMenu.MenuCount - 1 if self.ParentMenu.MenuCount == 0 then - --self.ParentMenu:Remove() + -- self.ParentMenu:Remove() end end end @@ -272,12 +254,11 @@ do -- MENU_BASE -- @param #boolean RemoveParent If true, the parent menu is automatically removed when this menu is the last child menu of that parent @{Menu}. -- @return #MENU_BASE function MENU_BASE:SetRemoveParent( RemoveParent ) - --self:F( { RemoveParent } ) + -- self:F( { RemoveParent } ) self.MenuRemoveParent = RemoveParent return self end - --- Gets a @{Menu} from a parent @{Menu} -- @param #MENU_BASE self -- @param #string MenuText The text of the child menu. @@ -294,16 +275,14 @@ do -- MENU_BASE self.MenuStamp = MenuStamp return self end - - + --- Gets a menu stamp for later prevention of menu removal. -- @param #MENU_BASE self -- @return MenuStamp function MENU_BASE:GetStamp() return timer.getTime() end - - + --- Sets a time stamp for later prevention of menu removal. -- @param #MENU_BASE self -- @param MenuStamp @@ -312,7 +291,7 @@ do -- MENU_BASE self.MenuStamp = MenuStamp return self end - + --- Sets a tag for later selection of menu refresh. -- @param #MENU_BASE self -- @param #string MenuTag A Tag or Key that will filter only menu items set with this key. @@ -321,7 +300,7 @@ do -- MENU_BASE self.MenuTag = MenuTag return self end - + end do -- MENU_COMMAND_BASE @@ -329,10 +308,10 @@ do -- MENU_COMMAND_BASE --- @type MENU_COMMAND_BASE -- @field #function MenuCallHandler -- @extends Core.Menu#MENU_BASE - - --- Defines the main MENU class where other MENU COMMAND_ + + --- Defines the main MENU class where other MENU COMMAND_ -- classes are derived from, in order to set commands. - -- + -- -- @field #MENU_COMMAND_BASE MENU_COMMAND_BASE = { ClassName = "MENU_COMMAND_BASE", @@ -340,13 +319,13 @@ do -- MENU_COMMAND_BASE CommandMenuArgument = nil, MenuCallHandler = nil, } - + --- Constructor -- @param #MENU_COMMAND_BASE -- @return #MENU_COMMAND_BASE function MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, CommandMenuArguments ) - - local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) -- #MENU_COMMAND_BASE + + local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) -- #MENU_COMMAND_BASE -- When a menu function goes into error, DCS displays an obscure menu message. -- This error handler catches the menu error and displays the full call stack. @@ -357,20 +336,20 @@ do -- MENU_COMMAND_BASE end return errmsg end - + self:SetCommandMenuFunction( CommandMenuFunction ) self:SetCommandMenuArguments( CommandMenuArguments ) self.MenuCallHandler = function() - local function MenuFunction() + local function MenuFunction() return self.CommandMenuFunction( unpack( self.CommandMenuArguments ) ) end local Status, Result = xpcall( MenuFunction, ErrorHandler ) end - - return self + + return self end - - --- This sets the new command function of a menu, + + --- This sets the new command function of a menu, -- so that if a menu is regenerated, or if command function changes, -- that the function set for the menu is loosely coupled with the menu itself!!! -- If the function changes, no new menu needs to be generated if the menu text is the same!!! @@ -394,43 +373,42 @@ do -- MENU_COMMAND_BASE end - do -- MENU_MISSION --- @type MENU_MISSION -- @extends Core.Menu#MENU_BASE - --- Manages the main menus for a complete mission. - -- + --- Manages the main menus for a complete mission. + -- -- You can add menus with the @{#MENU_MISSION.New} method, which constructs a MENU_MISSION object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_MISSION.Remove}. -- @field #MENU_MISSION MENU_MISSION = { - ClassName = "MENU_MISSION" + ClassName = "MENU_MISSION", } - + --- MENU_MISSION constructor. Creates a new MENU_MISSION object and creates the menu for a complete mission file. -- @param #MENU_MISSION self -- @param #string MenuText The text for the menu. - -- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the perent menu of DCS world (under F10 other). + -- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the parent menu of DCS world (under F10 other). -- @return #MENU_MISSION function MENU_MISSION:New( MenuText, ParentMenu ) - + MENU_INDEX:PrepareMission() local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) - local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) + 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 --- Refreshes a radio item for a mission @@ -444,33 +422,33 @@ do -- MENU_MISSION end end - + --- Removes the sub menus recursively of this MENU_MISSION. Note that the main menu is kept! -- @param #MENU_MISSION self -- @return #MENU_MISSION function MENU_MISSION:RemoveSubMenus() - + for MenuID, Menu in pairs( self.Menus or {} ) do Menu:Remove() end - + self.Menus = nil - + end - + --- Removes the main menu and the sub menus recursively of this MENU_MISSION. -- @param #MENU_MISSION self -- @return #nil function MENU_MISSION:Remove( MenuStamp, MenuTag ) - + MENU_INDEX:PrepareMission() local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) - local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) + 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 + 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 ) @@ -483,29 +461,27 @@ do -- MENU_MISSION else BASE:E( { "Cannot Remove MENU_MISSION", Path = Path, ParentMenu = self.ParentMenu, MenuText = self.MenuText } ) end - + return self end - - end do -- MENU_MISSION_COMMAND - + --- @type MENU_MISSION_COMMAND -- @extends Core.Menu#MENU_COMMAND_BASE - - --- Manages the command menus for a complete mission, which allow players to execute functions during mission execution. - -- + + --- Manages the command menus for a complete mission, which allow players to execute functions during mission execution. + -- -- You can add menus with the @{#MENU_MISSION_COMMAND.New} method, which constructs a MENU_MISSION_COMMAND object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_MISSION_COMMAND.Remove}. - -- + -- -- @field #MENU_MISSION_COMMAND MENU_MISSION_COMMAND = { - ClassName = "MENU_MISSION_COMMAND" + ClassName = "MENU_MISSION_COMMAND", } - + --- MENU_MISSION constructor. Creates a new radio command item for a complete mission file, which can invoke a function with parameters. -- @param #MENU_MISSION_COMMAND self -- @param #string MenuText The text for the menu. @@ -514,10 +490,10 @@ do -- MENU_MISSION_COMMAND -- @param CommandMenuArgument An argument for the function. There can only be ONE argument given. So multiple arguments must be wrapped into a table. See the below example how to do this. -- @return #MENU_MISSION_COMMAND self function MENU_MISSION_COMMAND:New( MenuText, ParentMenu, CommandMenuFunction, ... ) - + MENU_INDEX:PrepareMission() local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) - local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) + local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) if MissionMenu then MissionMenu:SetCommandMenuFunction( CommandMenuFunction ) @@ -526,7 +502,7 @@ do -- MENU_MISSION_COMMAND 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 @@ -544,19 +520,19 @@ do -- MENU_MISSION_COMMAND end end - + --- Removes a radio command item for a coalition -- @param #MENU_MISSION_COMMAND self -- @return #nil function MENU_MISSION_COMMAND:Remove() - + MENU_INDEX:PrepareMission() local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) - local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) + 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 + 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 ) @@ -569,24 +545,22 @@ do -- MENU_MISSION_COMMAND else BASE:E( { "Cannot Remove MENU_MISSION_COMMAND", Path = Path, ParentMenu = self.ParentMenu, MenuText = self.MenuText } ) end - + return self end end - - do -- MENU_COALITION --- @type MENU_COALITION -- @extends Core.Menu#MENU_BASE - - --- Manages the main menus for @{DCS.coalition}s. - -- + + --- Manages the main menus for @{DCS.coalition}s. + -- -- You can add menus with the @{#MENU_COALITION.New} method, which constructs a MENU_COALITION object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_COALITION.Remove}. - -- + -- -- -- @usage -- -- This demo creates a menu structure for the planes within the red coalition. @@ -616,7 +590,7 @@ do -- MENU_COALITION -- end -- -- local function AddStatusMenu() - -- + -- -- -- This would create a menu for the red coalition under the MenuCoalitionRed menu object. -- MenuStatus = MENU_COALITION:New( coalition.side.RED, "Status for Planes" ) -- MenuStatusShow = MENU_COALITION_COMMAND:New( coalition.side.RED, "Show Status", MenuStatus, ShowStatus, "Status of planes is ok!", "Message to Red Coalition" ) @@ -627,20 +601,20 @@ do -- MENU_COALITION -- -- @field #MENU_COALITION MENU_COALITION = { - ClassName = "MENU_COALITION" + ClassName = "MENU_COALITION", } - + --- MENU_COALITION constructor. Creates a new MENU_COALITION object and creates the menu for a complete coalition. -- @param #MENU_COALITION self -- @param DCS#coalition.side Coalition The coalition owning the menu. -- @param #string MenuText The text for the menu. - -- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the perent menu of DCS world (under F10 other). + -- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the parent menu of DCS world (under F10 other). -- @return #MENU_COALITION self 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 ) + local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( Coalition, Path ) if CoalitionMenu then return CoalitionMenu @@ -648,9 +622,9 @@ do -- MENU_COALITION 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 @@ -668,32 +642,32 @@ do -- MENU_COALITION end end - + --- Removes the sub menus recursively of this MENU_COALITION. Note that the main menu is kept! -- @param #MENU_COALITION self -- @return #MENU_COALITION function MENU_COALITION:RemoveSubMenus() - + for MenuID, Menu in pairs( self.Menus or {} ) do Menu:Remove() end - + self.Menus = nil end - + --- Removes the main menu and the sub menus recursively of this MENU_COALITION. -- @param #MENU_COALITION self -- @return #nil 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 ) + 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 + 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 ) @@ -706,29 +680,27 @@ do -- MENU_COALITION 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 - + --- @type MENU_COALITION_COMMAND -- @extends Core.Menu#MENU_COMMAND_BASE - - --- Manages the command menus for coalitions, which allow players to execute functions during mission execution. - -- + + --- Manages the command menus for coalitions, which allow players to execute functions during mission execution. + -- -- You can add menus with the @{#MENU_COALITION_COMMAND.New} method, which constructs a MENU_COALITION_COMMAND object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_COALITION_COMMAND.Remove}. -- -- @field #MENU_COALITION_COMMAND MENU_COALITION_COMMAND = { - ClassName = "MENU_COALITION_COMMAND" + ClassName = "MENU_COALITION_COMMAND", } - + --- MENU_COALITION constructor. Creates a new radio command item for a coalition, which can invoke a function with parameters. -- @param #MENU_COALITION_COMMAND self -- @param DCS#coalition.side Coalition The coalition owning the menu. @@ -738,20 +710,20 @@ do -- MENU_COALITION_COMMAND -- @param CommandMenuArgument An argument for the function. There can only be ONE argument given. So multiple arguments must be wrapped into a table. See the below example how to do this. -- @return #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 ) + 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 ) @@ -760,7 +732,6 @@ do -- MENU_COALITION_COMMAND end - --- Refreshes a radio item for a coalition -- @param #MENU_COALITION_COMMAND self -- @return #MENU_COALITION_COMMAND @@ -772,19 +743,19 @@ do -- MENU_COALITION_COMMAND end end - + --- Removes a radio command item for a coalition -- @param #MENU_COALITION_COMMAND self -- @return #nil 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 ) + 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 + 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 ) @@ -797,13 +768,12 @@ do -- MENU_COALITION_COMMAND else BASE:E( { "Cannot Remove MENU_COALITION_COMMAND", Path = Path, ParentMenu = self.ParentMenu, MenuText = self.MenuText, Coalition = self.Coalition } ) end - + return self end end - --- MENU_GROUP do @@ -816,20 +786,19 @@ do --- @type MENU_GROUP -- @extends Core.Menu#MENU_BASE - - - --- Manages the main menus for @{Wrapper.Group}s. - -- + + --- Manages the main menus for @{Wrapper.Group}s. + -- -- You can add menus with the @{#MENU_GROUP.New} method, which constructs a MENU_GROUP object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_GROUP.Remove}. - -- + -- -- @usage -- -- This demo creates a menu structure for the two groups of planes. -- -- Each group will receive a different menu structure. -- -- To test, join the planes, then look at the other radio menus (Option F10). -- -- Then switch planes and check if the menu is still there. -- -- And play with the Add and Remove menu options. - -- + -- -- -- Note that in multi player, this will only work after the DCS groups bug is solved. -- -- local function ShowStatus( PlaneGroup, StatusText, Coalition ) @@ -875,9 +844,9 @@ do -- -- @field #MENU_GROUP MENU_GROUP = { - ClassName = "MENU_GROUP" + ClassName = "MENU_GROUP", } - + --- MENU_GROUP constructor. Creates a new radio menu item for a group. -- @param #MENU_GROUP self -- @param Wrapper.Group#GROUP Group The Group owning the menu. @@ -885,7 +854,7 @@ do -- @param #table ParentMenu The parent menu. -- @return #MENU_GROUP self 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 ) @@ -900,11 +869,11 @@ do self.GroupID = Group:GetID() self.MenuPath = missionCommands.addSubMenuForGroup( self.GroupID, MenuText, self.MenuParentPath ) - + self:SetParentMenu( self.MenuText, self ) return self end - + end --- Refreshes a new radio item for a group and submenus @@ -915,14 +884,14 @@ do 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 - + --- Removes the sub menus recursively of this MENU_GROUP. -- @param #MENU_GROUP self -- @param MenuStamp @@ -933,11 +902,10 @@ do for MenuText, Menu in pairs( self.Menus or {} ) do Menu:Remove( MenuStamp, MenuTag ) end - - self.Menus = nil - - end + self.Menus = nil + + end --- Removes the main menu and sub menus recursively of this MENU_GROUP. -- @param #MENU_GROUP self @@ -948,12 +916,12 @@ do MENU_INDEX:PrepareGroup( self.Group ) local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) - local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path ) + 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 (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 ) @@ -967,23 +935,22 @@ do BASE:E( { "Cannot Remove MENU_GROUP", Path = Path, ParentMenu = self.ParentMenu, MenuText = self.MenuText, Group = self.Group } ) return nil end - + return self end - - + --- @type MENU_GROUP_COMMAND -- @extends Core.Menu#MENU_COMMAND_BASE - - --- The @{Core.Menu#MENU_GROUP_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. + + --- The @{Core.Menu#MENU_GROUP_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. -- You can add menus with the @{#MENU_GROUP_COMMAND.New} method, which constructs a MENU_GROUP_COMMAND object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_GROUP_COMMAND.Remove}. -- -- @field #MENU_GROUP_COMMAND MENU_GROUP_COMMAND = { - ClassName = "MENU_GROUP_COMMAND" + ClassName = "MENU_GROUP_COMMAND", } - + --- Creates a new radio command item for a group -- @param #MENU_GROUP_COMMAND self -- @param Wrapper.Group#GROUP Group The Group owning the menu. @@ -996,7 +963,7 @@ do MENU_INDEX:PrepareGroup( Group ) local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) - local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path ) + local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path ) if GroupMenu then GroupMenu:SetCommandMenuFunction( CommandMenuFunction ) @@ -1006,12 +973,12 @@ do 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 @@ -1029,7 +996,7 @@ do end end - + --- Removes a menu structure for a group. -- @param #MENU_GROUP_COMMAND self -- @param MenuStamp @@ -1039,13 +1006,13 @@ do MENU_INDEX:PrepareGroup( self.Group ) local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) - local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path ) + 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 (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 } ) + self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } ) missionCommands.removeItemForGroup( self.GroupID, self.MenuPath ) end MENU_INDEX:ClearGroupMenu( self.Group, Path ) @@ -1056,7 +1023,7 @@ do else BASE:E( { "Cannot Remove MENU_GROUP_COMMAND", Path = Path, ParentMenu = self.ParentMenu, MenuText = self.MenuText, Group = self.Group } ) end - + return self end @@ -1068,9 +1035,8 @@ do --- @type MENU_GROUP_DELAYED -- @extends Core.Menu#MENU_BASE - - - --- The MENU_GROUP_DELAYED class manages the main menus for groups. + + --- The MENU_GROUP_DELAYED class manages the main menus for groups. -- You can add menus with the @{#MENU_GROUP.New} method, which constructs a MENU_GROUP object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_GROUP.Remove}. -- The creation of the menu item is delayed however, and must be created using the @{#MENU_GROUP.Set} method. @@ -1079,9 +1045,9 @@ do -- -- @field #MENU_GROUP_DELAYED MENU_GROUP_DELAYED = { - ClassName = "MENU_GROUP_DELAYED" + ClassName = "MENU_GROUP_DELAYED", } - + --- MENU_GROUP_DELAYED constructor. Creates a new radio menu item for a group. -- @param #MENU_GROUP_DELAYED self -- @param Wrapper.Group#GROUP Group The Group owning the menu. @@ -1089,7 +1055,7 @@ do -- @param #table ParentMenu The parent menu. -- @return #MENU_GROUP_DELAYED self 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 ) @@ -1109,13 +1075,12 @@ do self.MenuPath = {} end table.insert( self.MenuPath, self.MenuText ) - + self:SetParentMenu( self.MenuText, self ) return self end - - end + end --- Refreshes a new radio item for a group and submenus -- @param #MENU_GROUP_DELAYED self @@ -1127,7 +1092,7 @@ do missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath ) self.MenuSet = true end - + for MenuText, Menu in pairs( self.Menus or {} ) do Menu:Set() end @@ -1135,7 +1100,6 @@ do end - --- Refreshes a new radio item for a group and submenus -- @param #MENU_GROUP_DELAYED self -- @return #MENU_GROUP_DELAYED @@ -1144,14 +1108,14 @@ do 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 - + --- Removes the sub menus recursively of this MENU_GROUP_DELAYED. -- @param #MENU_GROUP_DELAYED self -- @param MenuStamp @@ -1162,11 +1126,10 @@ do for MenuText, Menu in pairs( self.Menus or {} ) do Menu:Remove( MenuStamp, MenuTag ) end - - self.Menus = nil - - end + self.Menus = nil + + end --- Removes the main menu and sub menus recursively of this MENU_GROUP. -- @param #MENU_GROUP_DELAYED self @@ -1177,12 +1140,12 @@ do MENU_INDEX:PrepareGroup( self.Group ) local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) - local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path ) + 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 (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 ) @@ -1196,24 +1159,23 @@ do BASE:E( { "Cannot Remove MENU_GROUP_DELAYED", Path = Path, ParentMenu = self.ParentMenu, MenuText = self.MenuText, Group = self.Group } ) return nil end - + return self end - - + --- @type MENU_GROUP_COMMAND_DELAYED -- @extends Core.Menu#MENU_COMMAND_BASE - - --- Manages the command menus for coalitions, which allow players to execute functions during mission execution. - -- + + --- Manages the command menus for coalitions, which allow players to execute functions during mission execution. + -- -- You can add menus with the @{#MENU_GROUP_COMMAND_DELAYED.New} method, which constructs a MENU_GROUP_COMMAND_DELAYED object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_GROUP_COMMAND_DELAYED.Remove}. -- -- @field #MENU_GROUP_COMMAND_DELAYED MENU_GROUP_COMMAND_DELAYED = { - ClassName = "MENU_GROUP_COMMAND_DELAYED" + ClassName = "MENU_GROUP_COMMAND_DELAYED", } - + --- Creates a new radio command item for a group -- @param #MENU_GROUP_COMMAND_DELAYED self -- @param Wrapper.Group#GROUP Group The Group owning the menu. @@ -1226,7 +1188,7 @@ do MENU_INDEX:PrepareGroup( Group ) local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) - local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path ) + local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path ) if GroupMenu then GroupMenu:SetCommandMenuFunction( CommandMenuFunction ) @@ -1236,17 +1198,17 @@ do 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 @@ -1266,7 +1228,7 @@ do end end - + --- Refreshes a radio item for a group -- @param #MENU_GROUP_COMMAND_DELAYED self -- @return #MENU_GROUP_COMMAND_DELAYED @@ -1278,7 +1240,7 @@ do end end - + --- Removes a menu structure for a group. -- @param #MENU_GROUP_COMMAND_DELAYED self -- @param MenuStamp @@ -1288,11 +1250,11 @@ do MENU_INDEX:PrepareGroup( self.Group ) local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) - local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path ) + 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 (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 ) @@ -1305,7 +1267,7 @@ do else BASE:E( { "Cannot Remove MENU_GROUP_COMMAND_DELAYED", Path = Path, ParentMenu = self.ParentMenu, MenuText = self.MenuText, Group = self.Group } ) end - + return self end diff --git a/Moose Development/Moose/Core/Message.lua b/Moose Development/Moose/Core/Message.lua index 8e1f8324f..7daf9c671 100644 --- a/Moose Development/Moose/Core/Message.lua +++ b/Moose Development/Moose/Core/Message.lua @@ -1,18 +1,18 @@ --- **Core** - Informs the players using messages during a simulation. --- +-- -- === --- +-- -- ## Features: --- +-- -- * A more advanced messaging system using the DCS message system. -- * Time messages. -- * Send messages based on a message type, which has a pre-defined duration that can be tweaked in SETTINGS. -- * Send message to all players. -- * Send messages to a coalition. -- * Send messages to a specific group. --- +-- -- === --- +-- -- @module Core.Message -- @image Core_Message.JPG @@ -23,14 +23,14 @@ --- Message System to display Messages to Clients, Coalitions or All. -- Messages are shown on the display panel for an amount of seconds, and will then disappear. -- Messages can contain a category which is indicating the category of the message. --- +-- -- ## MESSAGE construction --- +-- -- Messages are created with @{#MESSAGE.New}. Note that when the MESSAGE object is created, no message is sent yet. -- To send messages, you need to use the To functions. --- +-- -- ## Send messages to an audience --- +-- -- Messages are sent: -- -- * To a @{Client} using @{#MESSAGE.ToClient}(). @@ -39,26 +39,26 @@ -- * To the red coalition using @{#MESSAGE.ToRed}(). -- * To the blue coalition using @{#MESSAGE.ToBlue}(). -- * To all Players using @{#MESSAGE.ToAll}(). --- +-- -- ## Send conditionally to an audience --- +-- -- Messages can be sent conditionally to an audience (when a condition is true): --- +-- -- * To all players using @{#MESSAGE.ToAllIf}(). -- * To a coalition using @{#MESSAGE.ToCoalitionIf}(). --- +-- -- === --- +-- -- ### Author: **FlightControl** --- ### Contributions: --- +-- ### Contributions: +-- -- === --- +-- -- @field #MESSAGE MESSAGE = { - ClassName = "MESSAGE", - MessageCategory = 0, - MessageID = 0, + ClassName = "MESSAGE", + MessageCategory = 0, + MessageID = 0, } --- Message Types @@ -68,10 +68,9 @@ MESSAGE.Type = { Information = "Information", Briefing = "Briefing Report", Overview = "Overview Report", - Detailed = "Detailed Report" + Detailed = "Detailed Report", } - --- Creates a new MESSAGE object. Note that these MESSAGE objects are not yet displayed on the display panel. You must use the functions @{ToClient} or @{ToCoalition} or @{ToAll} to send these Messages to the respective recipients. -- @param self -- @param #string MessageText is the text of the Message. @@ -80,52 +79,52 @@ MESSAGE.Type = { -- @param #boolean ClearScreen (optional) Clear all previous messages if true. -- @return #MESSAGE -- @usage --- -- Create a series of new Messages. --- -- MessageAll is meant to be sent to all players, for 25 seconds, and is classified as "Score". --- -- MessageRED is meant to be sent to the RED players only, for 10 seconds, and is classified as "End of Mission", with ID "Win". --- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score". --- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score". --- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", 25, "End of Mission" ) --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty" ) --- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score" ) --- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score") +-- +-- -- Create a series of new Messages. +-- -- MessageAll is meant to be sent to all players, for 25 seconds, and is classified as "Score". +-- -- MessageRED is meant to be sent to the RED players only, for 10 seconds, and is classified as "End of Mission", with ID "Win". +-- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score". +-- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score". +-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", 25, "End of Mission" ) +-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty" ) +-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score" ) +-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score") +-- function MESSAGE:New( MessageText, MessageDuration, MessageCategory, ClearScreen ) - local self = BASE:Inherit( self, BASE:New() ) - self:F( { MessageText, MessageDuration, MessageCategory } ) - + local self = BASE:Inherit( self, BASE:New() ) + self:F( { MessageText, MessageDuration, MessageCategory } ) self.MessageType = nil - + -- When no MessageCategory is given, we don't show it as a title... - if MessageCategory and MessageCategory ~= "" then - if MessageCategory:sub(-1) ~= "\n" then + if MessageCategory and MessageCategory ~= "" then + if MessageCategory:sub( -1 ) ~= "\n" then self.MessageCategory = MessageCategory .. ": " else - self.MessageCategory = MessageCategory:sub( 1, -2 ) .. ":\n" + self.MessageCategory = MessageCategory:sub( 1, -2 ) .. ":\n" end else self.MessageCategory = "" end - - self.ClearScreen=false - if ClearScreen~=nil then - self.ClearScreen=ClearScreen + + 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 + self.MessageDuration = MessageDuration or 5 + self.MessageTime = timer.getTime() + self.MessageText = MessageText:gsub( "^\n", "", 1 ):gsub( "\n$", "", 1 ) - return self + self.MessageSent = false + self.MessageGroup = false + self.MessageCoalition = false + + return self end - ---- Creates a new MESSAGE object of a certain type. --- Note that these MESSAGE objects are not yet displayed on the display panel. +--- Creates a new MESSAGE object of a certain type. +-- Note that these MESSAGE objects are not yet displayed on the display panel. -- You must use the functions @{ToClient} or @{ToCoalition} or @{ToAll} to send these Messages to the respective recipients. -- The message display times are automatically defined based on the timing settings in the @{Settings} menu. -- @param self @@ -134,83 +133,83 @@ end -- @param #boolean ClearScreen (optional) Clear all previous messages. -- @return #MESSAGE -- @usage +-- -- MessageAll = MESSAGE:NewType( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", MESSAGE.Type.Information ) -- MessageRED = MESSAGE:NewType( "To the RED Players: You receive a penalty because you've killed one of your own units", MESSAGE.Type.Information ) -- MessageClient1 = MESSAGE:NewType( "Congratulations, you've just hit a target", MESSAGE.Type.Update ) -- MessageClient2 = MESSAGE:NewType( "Congratulations, you've just killed a target", MESSAGE.Type.Update ) +-- 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 + + self.ClearScreen = false + if ClearScreen ~= nil then + self.ClearScreen = ClearScreen end self.MessageTime = timer.getTime() - self.MessageText = MessageText:gsub("^\n","",1):gsub("\n$","",1) - + self.MessageText = MessageText:gsub( "^\n", "", 1 ):gsub( "\n$", "", 1 ) + return self end - - ---- Clears all previous messages from the screen before the new message is displayed. Not that this must come before all functions starting with ToX(), e.g. ToAll(), ToGroup() etc. +--- Clears all previous messages from the screen before the new message is displayed. Not that this must come before all functions starting with ToX(), e.g. ToAll(), ToGroup() etc. -- @param #MESSAGE self -- @return #MESSAGE function MESSAGE:Clear() self:F() - self.ClearScreen=true + self.ClearScreen = true return self end - - --- Sends a MESSAGE to a Client Group. Note that the Group needs to be defined within the ME with the skillset "Client" or "Player". -- @param #MESSAGE self -- @param Wrapper.Client#CLIENT Client is the Group of the Client. -- @param Core.Settings#SETTINGS Settings Settings used to display the message. -- @return #MESSAGE -- @usage --- -- Send the 2 messages created with the @{New} method to the Client Group. --- -- Note that the Message of MessageClient2 is overwriting the Message of MessageClient1. --- ClientGroup = Group.getByName( "ClientGroup" ) -- --- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup ) --- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup ) --- or --- MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup ) --- MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup ) --- or --- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ) --- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ) --- MessageClient1:ToClient( ClientGroup ) --- MessageClient2:ToClient( ClientGroup ) +-- -- Send the 2 messages created with the @{New} method to the Client Group. +-- -- Note that the Message of MessageClient2 is overwriting the Message of MessageClient1. +-- ClientGroup = Group.getByName( "ClientGroup" ) +-- +-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup ) +-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup ) +-- or +-- MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup ) +-- MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup ) +-- or +-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ) +-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ) +-- MessageClient1:ToClient( ClientGroup ) +-- MessageClient2:ToClient( ClientGroup ) +-- function MESSAGE:ToClient( Client, Settings ) - self:F( Client ) + self:F( Client ) - if Client and Client:GetClientGroupID() then + if Client and Client:GetClientGroupID() then if self.MessageType then - local Settings = Settings or ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS + local Settings = Settings or (Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() )) or _SETTINGS -- Core.Settings#SETTINGS self.MessageDuration = Settings:GetMessageTime( self.MessageType ) self.MessageCategory = "" -- self.MessageType .. ": " 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 + 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 ---- Sends a MESSAGE to a Group. +--- Sends a MESSAGE to a Group. -- @param #MESSAGE self -- @param Wrapper.Group#GROUP Group to which the message is displayed. -- @return #MESSAGE Message object. @@ -218,74 +217,80 @@ 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 -- Core.Settings#SETTINGS + local Settings = Settings or (Group and _DATABASE:GetPlayerSettings( Group:GetPlayerName() )) or _SETTINGS -- Core.Settings#SETTINGS self.MessageDuration = Settings:GetMessageTime( self.MessageType ) self.MessageCategory = "" -- self.MessageType .. ": " 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 ) + 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 --- Sends a MESSAGE to the Blue coalition. --- @param #MESSAGE self --- @return #MESSAGE --- @usage --- -- Send a message created with the @{New} method to the BLUE coalition. --- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue() --- or --- MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue() --- or --- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) --- MessageBLUE:ToBlue() -function MESSAGE:ToBlue() - self:F() - - self:ToCoalition( coalition.side.BLUE ) - - return self -end - ---- Sends a MESSAGE to the Red Coalition. -- @param #MESSAGE self -- @return #MESSAGE -- @usage --- -- Send a message created with the @{New} method to the RED coalition. --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed() --- or --- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed() --- or --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) --- MessageRED:ToRed() -function MESSAGE:ToRed( ) - self:F() +-- +-- -- Send a message created with the @{New} method to the BLUE coalition. +-- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue() +-- or +-- MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue() +-- or +-- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) +-- MessageBLUE:ToBlue() +-- +function MESSAGE:ToBlue() + self:F() - self:ToCoalition( coalition.side.RED ) - - return self + self:ToCoalition( coalition.side.BLUE ) + + return self end ---- Sends a MESSAGE to a Coalition. +--- Sends a MESSAGE to the Red Coalition. +-- @param #MESSAGE self +-- @return #MESSAGE +-- @usage +-- +-- -- Send a message created with the @{New} method to the RED coalition. +-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed() +-- or +-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed() +-- or +-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) +-- MessageRED:ToRed() +-- +function MESSAGE:ToRed() + self:F() + + self:ToCoalition( coalition.side.RED ) + + return self +end + +--- Sends a MESSAGE to a Coalition. -- @param #MESSAGE self -- @param #DCS.coalition.side CoalitionSide @{#DCS.coalition.side} to which the message is displayed. -- @param Core.Settings#SETTINGS Settings (Optional) Settings for message display. -- @return #MESSAGE Message object. -- @usage --- -- Send a message created with the @{New} method to the RED coalition. --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED ) --- or --- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED ) --- or --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) --- MessageRED:ToCoalition( coalition.side.RED ) +-- +-- -- Send a message created with the @{New} method to the RED coalition. +-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED ) +-- or +-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED ) +-- or +-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) +-- MessageRED:ToCoalition( coalition.side.RED ) +-- function MESSAGE:ToCoalition( CoalitionSide, Settings ) - self:F( CoalitionSide ) + self:F( CoalitionSide ) if self.MessageType then local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS @@ -293,20 +298,20 @@ function MESSAGE:ToCoalition( CoalitionSide, Settings ) self.MessageCategory = "" -- self.MessageType .. ": " end - if CoalitionSide then + 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 ) + 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 + + return self end ---- Sends a MESSAGE to a Coalition if the given Condition is true. +--- Sends a MESSAGE to a Coalition if the given Condition is true. -- @param #MESSAGE self -- @param CoalitionSide needs to be filled out by the defined structure of the standard scripting engine @{coalition.side}. --- @param #boolean Condition Sends the message only if the condition is true. +-- @param #boolean Condition Sends the message only if the condition is true. -- @return #MESSAGE self function MESSAGE:ToCoalitionIf( CoalitionSide, Condition ) self:F( CoalitionSide ) @@ -314,7 +319,7 @@ function MESSAGE:ToCoalitionIf( CoalitionSide, Condition ) if Condition and Condition == true then self:ToCoalition( CoalitionSide ) end - + return self end @@ -323,14 +328,16 @@ end -- @param Core.Settings#Settings Settings (Optional) Settings for message display. -- @return #MESSAGE -- @usage --- -- Send a message created to all players. --- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll() --- or --- MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll() --- or --- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ) --- MessageAll:ToAll() -function MESSAGE:ToAll(Settings) +-- +-- -- Send a message created to all players. +-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll() +-- or +-- MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll() +-- or +-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ) +-- MessageAll:ToAll() +-- +function MESSAGE:ToAll( Settings ) self:F() if self.MessageType then @@ -340,14 +347,13 @@ function MESSAGE:ToAll(Settings) 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 ) + 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 - --- Sends a MESSAGE to all players if the given Condition is true. -- @param #MESSAGE self -- @return #MESSAGE @@ -357,5 +363,5 @@ function MESSAGE:ToAllIf( Condition ) self:ToAll() end - return self + return self end diff --git a/Moose Development/Moose/Core/Report.lua b/Moose Development/Moose/Core/Report.lua index bd860996b..d8225adcc 100644 --- a/Moose Development/Moose/Core/Report.lua +++ b/Moose Development/Moose/Core/Report.lua @@ -1,13 +1,13 @@ --- **Core** - Provides a handy means to create messages and reports. -- -- === --- +-- -- ## Features: --- +-- -- * Create text blocks that are formatted. -- * Create automatic indents. -- * Variate the delimiters between reporting lines. --- +-- -- === -- -- ### Authors: FlightControl : Design & Programming @@ -15,7 +15,6 @@ -- @module Core.Report -- @image Core_Report.JPG - --- @type REPORT -- @extends Core.Base#BASE @@ -36,7 +35,7 @@ function REPORT:New( Title ) self.Report = {} - self:SetTitle( Title or "" ) + self:SetTitle( Title or "" ) self:SetIndent( 3 ) return self @@ -45,28 +44,26 @@ end --- Has the REPORT Text? -- @param #REPORT self -- @return #boolean -function REPORT:HasText() --R2.1 - +function REPORT:HasText() -- R2.1 + return #self.Report > 0 end - --- Set indent of a REPORT. -- @param #REPORT self -- @param #number Indent -- @return #REPORT -function REPORT:SetIndent( Indent ) --R2.1 +function REPORT:SetIndent( Indent ) -- R2.1 self.Indent = Indent return self end - --- Add a new line to a REPORT. -- @param #REPORT self -- @param #string Text -- @return #REPORT function REPORT:Add( Text ) - self.Report[#self.Report+1] = Text + self.Report[#self.Report + 1] = Text return self end @@ -76,17 +73,17 @@ end -- @param #string Separator (optional) The start of each report line can begin with an optional separator character. This can be a "-", or "#", or "*". You're free to choose what you find the best. -- @return #REPORT 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 ) ) + 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 ---- Produces the text of the report, taking into account an optional delimeter, which is \n by default. +--- Produces the text of the report, taking into account an optional delimiter, which is \n by default. -- @param #REPORT self -- @param #string Delimiter (optional) A delimiter text. -- @return #string The report text. 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 "" + local ReportText = (self.Title ~= "" and self.Title .. Delimiter or self.Title) .. table.concat( self.Report, Delimiter ) or "" return ReportText end @@ -95,7 +92,7 @@ end -- @param #string Title The title of the report. -- @return #REPORT function REPORT:SetTitle( Title ) - self.Title = Title + self.Title = Title return self end diff --git a/Moose Development/Moose/Core/ScheduleDispatcher.lua b/Moose Development/Moose/Core/ScheduleDispatcher.lua index bfd1dabb4..145759bca 100644 --- a/Moose Development/Moose/Core/ScheduleDispatcher.lua +++ b/Moose Development/Moose/Core/ScheduleDispatcher.lua @@ -1,36 +1,36 @@ --- **Core** -- SCHEDULEDISPATCHER dispatches the different schedules. --- +-- -- === --- +-- -- Takes care of the creation and dispatching of scheduled functions for SCHEDULER objects. --- +-- -- This class is tricky and needs some thorough explanation. -- SCHEDULE classes are used to schedule functions for objects, or as persistent objects. -- The SCHEDULEDISPATCHER class ensures that: --- +-- -- - Scheduled functions are planned according the SCHEDULER object parameters. -- - Scheduled functions are repeated when requested, according the SCHEDULER object parameters. -- - Scheduled functions are automatically removed when the schedule is finished, according the SCHEDULER object parameters. --- +-- -- The SCHEDULEDISPATCHER class will manage SCHEDULER object in memory during garbage collection: --- +-- -- - When a SCHEDULER object is not attached to another object (that is, it's first :Schedule() parameter is nil), then the SCHEDULER object is _persistent_ within memory. -- - When a SCHEDULER object *is* attached to another object, then the SCHEDULER object is _not persistent_ within memory after a garbage collection! --- --- The none persistency of SCHEDULERS attached to objects is required to allow SCHEDULER objects to be garbage collectged, when the parent object is also desroyed or nillified and garbage collected. --- Even when there are pending timer scheduled functions to be executed for the SCHEDULER object, +-- +-- The non-persistency of SCHEDULERS attached to objects is required to allow SCHEDULER objects to be garbage collected when the parent object is destroyed, or set to nil and garbage collected. +-- Even when there are pending timer scheduled functions to be executed for the SCHEDULER object, -- these will not be executed anymore when the SCHEDULER object has been destroyed. --- +-- -- The SCHEDULEDISPATCHER allows multiple scheduled functions to be planned and executed for one SCHEDULER object. -- The SCHEDULER object therefore keeps a table of "CallID's", which are returned after each planning of a new scheduled function by the SCHEDULEDISPATCHER. --- The SCHEDULER object plans new scheduled functions through the @{Core.Scheduler#SCHEDULER.Schedule}() method. +-- The SCHEDULER object plans new scheduled functions through the @{Core.Scheduler#SCHEDULER.Schedule}() method. -- The Schedule() method returns the CallID that is the reference ID for each planned schedule. --- +-- -- === --- +-- -- ### Contributions: - -- ### Authors: FlightControl : Design & Programming --- +-- -- @module Core.ScheduleDispatcher -- @image Core_Schedule_Dispatcher.JPG @@ -38,7 +38,7 @@ -- @type SCHEDULEDISPATCHER -- @field #string ClassName Name of the class. -- @field #number CallID Call ID counter. --- @field #table PersistentSchedulers Persistant schedulers. +-- @field #table PersistentSchedulers Persistent schedulers. -- @field #table ObjectSchedulers Schedulers that only exist as long as the master object exists. -- @field #table Schedule Meta table setmetatable( {}, { __mode = "k" } ). -- @extends Core.Base#BASE @@ -46,11 +46,11 @@ --- The SCHEDULEDISPATCHER structure -- @type SCHEDULEDISPATCHER SCHEDULEDISPATCHER = { - ClassName = "SCHEDULEDISPATCHER", - CallID = 0, - PersistentSchedulers = {}, - ObjectSchedulers = {}, - Schedule = nil, + ClassName = "SCHEDULEDISPATCHER", + CallID = 0, + PersistentSchedulers = {}, + ObjectSchedulers = {}, + Schedule = nil, } --- Player data table holding all important parameters of each player. @@ -58,7 +58,7 @@ SCHEDULEDISPATCHER = { -- @field #function Function The schedule function to be called. -- @field #table Arguments Schedule function arguments. -- @field #number Start Start time in seconds. --- @field #number Repeat Repeat time intervall in seconds. +-- @field #number Repeat Repeat time interval in seconds. -- @field #number Randomize Randomization factor [0,1]. -- @field #number Stop Stop time in seconds. -- @field #number StartTime Time in seconds when the scheduler is created. @@ -77,7 +77,7 @@ end --- Add a Schedule to the ScheduleDispatcher. -- The development of this method was really tidy. --- It is constructed as such that a garbage collection is executed on the weak tables, when the Scheduler is nillified. +-- It is constructed as such that a garbage collection is executed on the weak tables, when the Scheduler is set to nil. -- Nothing of this code should be modified without testing it thoroughly. -- @param #SCHEDULEDISPATCHER self -- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object. @@ -85,7 +85,7 @@ end -- @param #table ScheduleArguments Table of arguments passed to the ScheduleFunction. -- @param #number Start Start time in seconds. -- @param #number Repeat Repeat interval in seconds. --- @param #number Randomize Radomization factor [0,1]. +-- @param #number Randomize Randomization factor [0,1]. -- @param #number Stop Stop time in seconds. -- @param #number TraceLevel Trace level [0,3]. -- @param Core.Fsm#FSM Fsm Finite state model. @@ -95,39 +95,38 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr -- Increase counter. self.CallID = self.CallID + 1 - + -- Create ID. - 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)) + 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 ) ) -- Initialize PersistentSchedulers self.PersistentSchedulers = self.PersistentSchedulers or {} -- Initialize the ObjectSchedulers array, which is a weakly coupled table. -- If the object used as the key is nil, then the garbage collector will remove the item from the Functions array. - self.ObjectSchedulers = self.ObjectSchedulers or setmetatable( {}, { __mode = "v" } ) - + 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) } ) + 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] = {} --#SCHEDULEDISPATCHER.ScheduleData + self.Schedule[Scheduler][CallID] = {} -- #SCHEDULEDISPATCHER.ScheduleData 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].StartTime = timer.getTime() + (Start or 0) self.Schedule[Scheduler][CallID].Start = Start + 0.1 self.Schedule[Scheduler][CallID].Repeat = Repeat or 0 self.Schedule[Scheduler][CallID].Randomize = Randomize or 0 self.Schedule[Scheduler][CallID].Stop = Stop - - + -- This section handles the tracing of the scheduled calls. -- Because these calls will be executed with a delay, we inspect the place where these scheduled calls are initiated. -- The Info structure contains the output of the debug.getinfo() calls, which inspects the call stack for the function name, line number and source name. @@ -149,10 +148,10 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr -- Therefore, in the call stack, at the TraceLevel these functions are mentioned as "tail calls", and the Info.name field will be nil as a result. -- To obtain the correct function name for FSM object calls, the function is mentioned in the call stack at a higher stack level. -- So when function name stored in Info.name is nil, then I inspect the function name within the call stack one level higher. - -- So this little piece of code does its magic wonderfully, preformance overhead is neglectible, as scheduled calls don't happen that often. + -- So this little piece of code does its magic wonderfully, performance overhead is negligible, as scheduled calls don't happen that often. local Info = {} - + if debug then TraceLevel = TraceLevel or 2 Info = debug.getinfo( TraceLevel, "nlS" ) @@ -166,7 +165,7 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr --- Function passed to the DCS timer.scheduleFunction() self.Schedule[Scheduler][CallID].CallHandler = function( Params ) - + local CallID = Params.CallID local Info = Params.Info or {} local Source = Info.source or "?" @@ -180,27 +179,27 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr end return errmsg end - - -- Get object or persistant scheduler object. - local Scheduler = self.ObjectSchedulers[CallID] --Core.Scheduler#SCHEDULER + + -- Get object or persistent scheduler object. + local Scheduler = self.ObjectSchedulers[CallID] -- Core.Scheduler#SCHEDULER if not Scheduler then Scheduler = self.PersistentSchedulers[CallID] end - - --self:T3( { Scheduler = Scheduler } ) - + + -- self:T3( { Scheduler = Scheduler } ) + if Scheduler then - local MasterObject = tostring(Scheduler.MasterObject) - - -- Schedule object. - local Schedule = self.Schedule[Scheduler][CallID] --#SCHEDULEDISPATCHER.ScheduleData - - --self:T3( { Schedule = Schedule } ) + local MasterObject = tostring( Scheduler.MasterObject ) - local SchedulerObject = Scheduler.MasterObject --Scheduler.SchedulerObject Now is this the Maste or Scheduler object? + -- Schedule object. + local Schedule = self.Schedule[Scheduler][CallID] -- #SCHEDULEDISPATCHER.ScheduleData + + -- self:T3( { Schedule = Schedule } ) + + local SchedulerObject = Scheduler.MasterObject -- Scheduler.SchedulerObject Now is this the Master or Scheduler object? local ShowTrace = Scheduler.ShowTrace - + local ScheduleFunction = Schedule.Function local ScheduleArguments = Schedule.Arguments or {} local Start = Schedule.Start @@ -208,18 +207,17 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr local Randomize = Schedule.Randomize or 0 local Stop = Schedule.Stop or 0 local ScheduleID = Schedule.ScheduleID - - - local Prefix = ( Repeat == 0 ) and "--->" or "+++>" - + + local Prefix = (Repeat == 0) and "--->" or "+++>" + local Status, Result - --self:E( { SchedulerObject = SchedulerObject } ) + -- self:E( { SchedulerObject = SchedulerObject } ) if SchedulerObject then local function Timer() if ShowTrace then SchedulerObject:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" ) end - return ScheduleFunction( SchedulerObject, unpack( ScheduleArguments ) ) + return ScheduleFunction( SchedulerObject, unpack( ScheduleArguments ) ) end Status, Result = xpcall( Timer, ErrorHandler ) else @@ -227,40 +225,39 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr if ShowTrace then self:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" ) end - return ScheduleFunction( unpack( ScheduleArguments ) ) + return ScheduleFunction( unpack( ScheduleArguments ) ) end Status, Result = xpcall( Timer, ErrorHandler ) end - + local CurrentTime = timer.getTime() local StartTime = Schedule.StartTime -- Debug info. - 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 -- Accuracy - --self:T3( { Repeat = CallID, CurrentTime, ScheduleTime, ScheduleArguments } ) + 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 -- Accuracy + -- self:T3( { Repeat = CallID, CurrentTime, ScheduleTime, ScheduleArguments } ) return ScheduleTime -- returns the next time the function needs to be called. 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 @@ -284,33 +281,33 @@ end -- @param #string Info (Optional) Debug info. function SCHEDULEDISPATCHER:Start( Scheduler, CallID, Info ) self:F2( { Start = CallID, Scheduler = Scheduler } ) - + if CallID then - - local Schedule = self.Schedule[Scheduler][CallID] --#SCHEDULEDISPATCHER.ScheduleData - + + local Schedule = self.Schedule[Scheduler][CallID] -- #SCHEDULEDISPATCHER.ScheduleData + -- Only start when there is no ScheduleID defined! -- This prevents to "Start" the scheduler twice with the same CallID... if not Schedule.ScheduleID then - + -- Current time in seconds. - local Tnow=timer.getTime() - - Schedule.StartTime = Tnow -- Set the StartTime field to indicate when the scheduler started. - + local Tnow = timer.getTime() + + Schedule.StartTime = Tnow -- Set the StartTime field to indicate when the scheduler started. + -- Start DCS schedule function https://wiki.hoggitworld.com/view/DCS_func_scheduleFunction - 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))) + 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 - + -- Recursive. for CallID, Schedule in pairs( self.Schedule[Scheduler] or {} ) do self:Start( Scheduler, CallID, Info ) -- Recursive end - + end end @@ -322,29 +319,29 @@ function SCHEDULEDISPATCHER:Stop( Scheduler, CallID ) self:F2( { Stop = CallID, Scheduler = Scheduler } ) if CallID then - - local Schedule = self.Schedule[Scheduler][CallID] --#SCHEDULEDISPATCHER.ScheduleData - + + local Schedule = self.Schedule[Scheduler][CallID] -- #SCHEDULEDISPATCHER.ScheduleData + -- Only stop when there is a ScheduleID defined for the CallID. So, when the scheduler was stopped before, do nothing. if Schedule.ScheduleID then - - self:T(string.format("scheduledispatcher stopping scheduler CallID=%s, ScheduleID=%s", tostring(CallID), tostring(Schedule.ScheduleID))) - + + self:T( string.format( "SCHEDULEDISPATCHER stopping scheduler CallID=%s, ScheduleID=%s", tostring( CallID ), tostring( Schedule.ScheduleID ) ) ) + -- Remove schedule function https://wiki.hoggitworld.com/view/DCS_func_removeFunction - timer.removeFunction(Schedule.ScheduleID) - + timer.removeFunction( Schedule.ScheduleID ) + Schedule.ScheduleID = nil - + else - self:T(string.format("Error no ScheduleID for CallID=%s", tostring(CallID))) + 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 ) -- Recursive end - + end end @@ -359,7 +356,7 @@ function SCHEDULEDISPATCHER:Clear( Scheduler ) end end ---- Shopw tracing info. +--- Show tracing info. -- @param #SCHEDULEDISPATCHER self -- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object. function SCHEDULEDISPATCHER:ShowTrace( Scheduler ) diff --git a/Moose Development/Moose/Core/Scheduler.lua b/Moose Development/Moose/Core/Scheduler.lua index 781b90ebe..d0c200a1e 100644 --- a/Moose Development/Moose/Core/Scheduler.lua +++ b/Moose Development/Moose/Core/Scheduler.lua @@ -1,41 +1,41 @@ --- **Core** - Prepares and handles the execution of functions over scheduled time (intervals). -- -- === --- +-- -- ## Features: --- +-- -- * Schedule functions over time, --- * optionally in an optional specified time interval, --- * optionally **repeating** with a specified time repeat interval, --- * optionally **randomizing** with a specified time interval randomization factor, --- * optionally **stop** the repeating after a specified time interval. +-- * optionally in an optional specified time interval, +-- * optionally **repeating** with a specified time repeat interval, +-- * optionally **randomizing** with a specified time interval randomization factor, +-- * optionally **stop** the repeating after a specified time interval. -- -- === --- +-- -- # Demo Missions --- +-- -- ### [SCHEDULER Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/SCH%20-%20Scheduler) --- +-- -- ### [SCHEDULER Demo Missions, only for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SCH%20-%20Scheduler) -- -- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases) --- --- === --- --- # YouTube Channel --- --- ### [SCHEDULER YouTube Channel (none)]() --- +-- -- === -- --- ### Contributions: --- +-- # YouTube Channel +-- +-- ### [SCHEDULER YouTube Channel (none)]() +-- +-- === +-- +-- ### Contributions: +-- -- * FlightControl : Concept & Testing --- --- ### Authors: --- +-- +-- ### Authors: +-- -- * FlightControl : Design & Programming --- +-- -- === -- -- @module Core.Scheduler @@ -48,62 +48,61 @@ -- @field #boolean ShowTrace Trace info if true. -- @extends Core.Base#BASE - --- Creates and handles schedules over time, which allow to execute code at specific time intervals with randomization. --- +-- -- A SCHEDULER can manage **multiple** (repeating) schedules. Each planned or executing schedule has a unique **ScheduleID**. -- The ScheduleID is returned when the method @{#SCHEDULER.Schedule}() is called. -- It is recommended to store the ScheduleID in a variable, as it is used in the methods @{SCHEDULER.Start}() and @{SCHEDULER.Stop}(), -- which can start and stop specific repeating schedules respectively within a SCHEDULER object. -- -- ## SCHEDULER constructor --- +-- -- The SCHEDULER class is quite easy to use, but note that the New constructor has variable parameters: --- +-- -- The @{#SCHEDULER.New}() method returns 2 variables: --- +-- -- 1. The SCHEDULER object reference. -- 2. The first schedule planned in the SCHEDULER object. --- +-- -- To clarify the different appliances, lets have a look at the following examples: --- +-- -- ### Construct a SCHEDULER object without a persistent schedule. --- +-- -- * @{#SCHEDULER.New}( nil ): Setup a new SCHEDULER object, which is persistently executed after garbage collection. --- +-- -- MasterObject = SCHEDULER:New() -- SchedulerID = MasterObject:Schedule( nil, ScheduleFunction, {} ) --- +-- -- The above example creates a new MasterObject, but does not schedule anything. -- A separate schedule is created by using the MasterObject using the method :Schedule..., which returns a ScheduleID --- +-- -- ### Construct a SCHEDULER object without a volatile schedule, but volatile to the Object existence... --- --- * @{#SCHEDULER.New}( Object ): Setup a new SCHEDULER object, which is linked to the Object. When the Object is nillified or destroyed, the SCHEDULER object will also be destroyed and stopped after garbage collection. --- +-- +-- * @{#SCHEDULER.New}( Object ): Setup a new SCHEDULER object, which is linked to the Object. When the Object is set to nil or destroyed, the SCHEDULER object will also be destroyed and stopped after garbage collection. +-- -- ZoneObject = ZONE:New( "ZoneName" ) -- MasterObject = SCHEDULER:New( ZoneObject ) -- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {} ) -- ... -- ZoneObject = nil -- garbagecollect() --- +-- -- The above example creates a new MasterObject, but does not schedule anything, and is bound to the existence of ZoneObject, which is a ZONE. -- A separate schedule is created by using the MasterObject using the method :Schedule()..., which returns a ScheduleID -- Later in the logic, the ZoneObject is put to nil, and garbage is collected. -- As a result, the MasterObject will cancel any planned schedule. --- +-- -- ### Construct a SCHEDULER object with a persistent schedule. --- +-- -- * @{#SCHEDULER.New}( nil, Function, FunctionArguments, Start, ... ): Setup a new persistent SCHEDULER object, and start a new schedule for the Function with the defined FunctionArguments according the Start and sequent parameters. --- +-- -- MasterObject, SchedulerID = SCHEDULER:New( nil, ScheduleFunction, {} ) --- +-- -- The above example creates a new MasterObject, and does schedule the first schedule as part of the call. -- Note that 2 variables are returned here: MasterObject, ScheduleID... --- +-- -- ### Construct a SCHEDULER object without a schedule, but volatile to the Object existence... --- +-- -- * @{#SCHEDULER.New}( Object, Function, FunctionArguments, Start, ... ): Setup a new SCHEDULER object, linked to Object, and start a new schedule for the Function with the defined FunctionArguments according the Start and sequent parameters. -- -- ZoneObject = ZONE:New( "ZoneName" ) @@ -112,13 +111,13 @@ -- ... -- ZoneObject = nil -- garbagecollect() --- +-- -- The above example creates a new MasterObject, and schedules a method call (ScheduleFunction), -- and is bound to the existence of ZoneObject, which is a ZONE object (ZoneObject). -- Both a MasterObject and a SchedulerID variable are returned. -- Later in the logic, the ZoneObject is put to nil, and garbage is collected. -- As a result, the MasterObject will cancel the planned schedule. --- +-- -- ## SCHEDULER timer stopping and (re-)starting. -- -- The SCHEDULER can be stopped and restarted with the following methods: @@ -133,70 +132,70 @@ -- MasterObject:Stop( SchedulerID ) -- ... -- MasterObject:Start( SchedulerID ) --- +-- -- The above example creates a new MasterObject, and does schedule the first schedule as part of the call. --- Note that 2 variables are returned here: MasterObject, ScheduleID... --- Later in the logic, the repeating schedule with SchedulerID is stopped. --- A bit later, the repeating schedule with SchedulerId is (re)-started. --- +-- Note that 2 variables are returned here: MasterObject, ScheduleID... +-- Later in the logic, the repeating schedule with SchedulerID is stopped. +-- A bit later, the repeating schedule with SchedulerId is (re)-started. +-- -- ## Create a new schedule --- --- With the method @{#SCHEDULER.Schedule}() a new time event can be scheduled. +-- +-- With the method @{#SCHEDULER.Schedule}() a new time event can be scheduled. -- This method is used by the :New() constructor when a new schedule is planned. --- +-- -- Consider the following code fragment of the SCHEDULER object creation. --- +-- -- ZoneObject = ZONE:New( "ZoneName" ) -- MasterObject = SCHEDULER:New( ZoneObject ) --- --- Several parameters can be specified that influence the behaviour of a Schedule. --- +-- +-- Several parameters can be specified that influence the behavior of a Schedule. +-- -- ### A single schedule, immediately executed --- +-- -- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {} ) --- --- The above example schedules a new ScheduleFunction call to be executed asynchronously, within milleseconds ... --- +-- +-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within milliseconds ... +-- -- ### A single schedule, planned over time --- +-- -- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10 ) --- +-- -- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds ... --- +-- -- ### A schedule with a repeating time interval, planned over time --- +-- -- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60 ) --- --- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds, +-- +-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds, -- and repeating 60 every seconds ... --- +-- -- ### A schedule with a repeating time interval, planned over time, with time interval randomization --- +-- -- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60, 0.5 ) --- --- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds, +-- +-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds, -- and repeating 60 seconds, with a 50% time interval randomization ... --- So the repeating time interval will be randomized using the **0.5**, --- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat, +-- So the repeating time interval will be randomized using the **0.5**, +-- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat, -- which is in this example between **30** and **90** seconds. --- +-- -- ### A schedule with a repeating time interval, planned over time, with time interval randomization, and stop after a time interval --- +-- -- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60, 0.5, 300 ) --- --- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds, +-- +-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds, -- The schedule will repeat every 60 seconds. --- So the repeating time interval will be randomized using the **0.5**, --- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat, +-- So the repeating time interval will be randomized using the **0.5**, +-- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat, -- which is in this example between **30** and **90** seconds. -- The schedule will stop after **300** seconds. --- +-- -- @field #SCHEDULER SCHEDULER = { - ClassName = "SCHEDULER", - Schedules = {}, - MasterObject = nil, - ShowTrace = nil, + ClassName = "SCHEDULER", + Schedules = {}, + MasterObject = nil, + ShowTrace = nil, } --- SCHEDULER constructor. @@ -211,15 +210,15 @@ SCHEDULER = { -- @return #SCHEDULER self. -- @return #table The ScheduleID of the planned schedule. function SCHEDULER:New( MasterObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop ) - + local self = BASE:Inherit( self, BASE:New() ) -- #SCHEDULER 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 @@ -235,7 +234,7 @@ end -- @param #number Start Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called. -- @param #number Repeat Specifies the time interval in seconds when the scheduler will call the event function. -- @param #number RandomizeFactor Specifies a randomization factor between 0 and 1 to randomize the Repeat. --- @param #number Stop Time interval in seconds after which the scheduler will be stoppe. +-- @param #number Stop Time interval in seconds after which the scheduler will be stopped. -- @param #number TraceLevel Trace level [0,3]. Default 3. -- @param Core.Fsm#FSM Fsm Finite state model. -- @return #table The ScheduleID of the planned schedule. @@ -245,28 +244,27 @@ function SCHEDULER:Schedule( MasterObject, SchedulerFunction, SchedulerArguments -- Debug info. local ObjectName = "-" - if MasterObject and MasterObject.ClassName and MasterObject.ClassID then + 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:F3( { "Schedule :", ObjectName, tostring( MasterObject ), Start, Repeat, RandomizeFactor, Stop } ) + -- Set master object. self.MasterObject = MasterObject - + -- Add schedule. - local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule( - self, - SchedulerFunction, - SchedulerArguments, - Start, - Repeat, - RandomizeFactor, - Stop, - TraceLevel or 3, - Fsm - ) - - self.Schedules[#self.Schedules+1] = ScheduleID + local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule( self, + SchedulerFunction, + SchedulerArguments, + Start, + Repeat, + RandomizeFactor, + Stop, + TraceLevel or 3, + Fsm + ) + + self.Schedules[#self.Schedules + 1] = ScheduleID return ScheduleID end @@ -276,7 +274,7 @@ end -- @param #string ScheduleID (Optional) The ScheduleID of the planned (repeating) schedule. function SCHEDULER:Start( ScheduleID ) self:F3( { ScheduleID } ) - self:T(string.format("Starting scheduler ID=%s", tostring(ScheduleID))) + self:T( string.format( "Starting scheduler ID=%s", tostring( ScheduleID ) ) ) _SCHEDULEDISPATCHER:Start( self, ScheduleID ) end @@ -285,7 +283,7 @@ end -- @param #string ScheduleID (Optional) The ScheduleID of the planned (repeating) schedule. function SCHEDULER:Stop( ScheduleID ) self:F3( { ScheduleID } ) - self:T(string.format("Stopping scheduler ID=%s", tostring(ScheduleID))) + self:T( string.format( "Stopping scheduler ID=%s", tostring( ScheduleID ) ) ) _SCHEDULEDISPATCHER:Stop( self, ScheduleID ) end @@ -294,15 +292,15 @@ end -- @param #string ScheduleID (optional) The ScheduleID of the planned (repeating) schedule. function SCHEDULER:Remove( ScheduleID ) self:F3( { ScheduleID } ) - self:T(string.format("Removing scheduler ID=%s", tostring(ScheduleID))) + self:T( string.format( "Removing scheduler ID=%s", tostring( ScheduleID ) ) ) _SCHEDULEDISPATCHER:RemoveSchedule( self, ScheduleID ) end --- Clears all pending schedules. -- @param #SCHEDULER self function SCHEDULER:Clear() - self:F3( ) - self:T(string.format("Clearing scheduler")) + self:F3() + self:T( string.format( "Clearing scheduler" ) ) _SCHEDULEDISPATCHER:Clear( self ) end diff --git a/Moose Development/Moose/Core/Settings.lua b/Moose Development/Moose/Core/Settings.lua index 74b5fa54b..ea3ecffdd 100644 --- a/Moose Development/Moose/Core/Settings.lua +++ b/Moose Development/Moose/Core/Settings.lua @@ -29,15 +29,14 @@ -- @module Core.Settings -- @image Core_Settings.JPG - --- @type SETTINGS -- @extends Core.Base#BASE ---- Takes care of various settings that influence the behaviour of certain functionalities and classes within the MOOSE framework. +--- Takes care of various settings that influence the behavior of certain functionalities and classes within the MOOSE framework. -- -- === -- --- The SETTINGS class takes care of various settings that influence the behaviour of certain functionalities and classes within the MOOSE framework. +-- The SETTINGS class takes care of various settings that influence the behavior of certain functionalities and classes within the MOOSE framework. -- SETTINGS can work on 2 levels: -- -- - **Default settings**: A running mission has **Default settings**. @@ -59,7 +58,7 @@ -- -- A menu is created automatically per Command Center that allows to modify the **Default** settings. -- So, when joining a CC unit, a menu will be available that allows to change the settings parameters **FOR ALL THE PLAYERS**! --- Note that the **Default settings** will only be used when a player has not choosen its own settings. +-- Note that the **Default settings** will only be used when a player has not chosen its own settings. -- -- ## 2.2) Player settings menu -- @@ -69,7 +68,7 @@ -- -- ## 2.3) Show or Hide the Player Setting menus -- --- Of course, it may be requried not to show any setting menus. In this case, a method is available on the **\_SETTINGS object**. +-- Of course, it may be required not to show any setting menus. In this case, a method is available on the **\_SETTINGS object**. -- Use @{#SETTINGS.SetPlayerMenuOff}() to hide the player menus, and use @{#SETTINGS.SetPlayerMenuOn}() show the player menus. -- Note that when this method is used, any player already in a slot will not have its menus visibility changed. -- The option will only have effect when a player enters a new slot or changes a slot. @@ -94,8 +93,8 @@ -- -- - A2G BR: [Bearing Range](https://en.wikipedia.org/wiki/Bearing_(navigation)). -- - A2G MGRS: The [Military Grid Reference System](https://en.wikipedia.org/wiki/Military_Grid_Reference_System). The accuracy can also be adapted. --- - A2G LL DMS: Lattitude Longitude [Degrees Minutes Seconds](https://en.wikipedia.org/wiki/Geographic_coordinate_conversion). The accuracy can also be adapted. --- - A2G LL DDM: Lattitude Longitude [Decimal Degrees Minutes](https://en.wikipedia.org/wiki/Decimal_degrees). The accuracy can also be adapted. +-- - A2G LL DMS: Latitude Longitude [Degrees Minutes Seconds](https://en.wikipedia.org/wiki/Geographic_coordinate_conversion). The accuracy can also be adapted. +-- - A2G LL DDM: Latitude Longitude [Decimal Degrees Minutes](https://en.wikipedia.org/wiki/Decimal_degrees). The accuracy can also be adapted. -- -- ### 3.1.2) A2G coordinates setting **menu** -- @@ -183,7 +182,7 @@ -- The settings can be changed by using the **Default settings menu** on the Command Center or the **Player settings menu** on the Player Slot. -- -- Each Message Type has specific timings that will be applied when the message is displayed. --- The Settings Menu will provide for each Message Type a selection of proposed durations from which can be choosen. +-- The Settings Menu will provide for each Message Type a selection of proposed durations from which can be chosen. -- So the player can choose its own amount of seconds how long a message should be displayed of a certain type. -- Note that **Update** messages can be chosen not to be displayed at all! -- @@ -196,7 +195,7 @@ -- -- ## 3.5) **Era** of the battle -- --- The threat level metric is scaled according the era of the battle. A target that is AAA, will pose a much greather threat in WWII than on modern warfare. +-- The threat level metric is scaled according the era of the battle. A target that is AAA, will pose a much greater threat in WWII than on modern warfare. -- Therefore, there are 4 era that are defined within the settings: -- -- - **WWII** era: Use for warfare with equipment during the world war II time. @@ -213,8 +212,8 @@ SETTINGS = { ClassName = "SETTINGS", ShowPlayerMenu = true, - MenuShort = false, - MenuStatic = false, + MenuShort = false, + MenuStatic = false, } SETTINGS.__Enum = {} @@ -231,7 +230,6 @@ SETTINGS.__Enum.Era = { Modern = 4, } - do -- SETTINGS --- SETTINGS constructor. @@ -268,14 +266,14 @@ do -- SETTINGS -- Short text are better suited for, e.g., VR. -- @param #SETTINGS self -- @param #boolean onoff If *true* use short menu texts. If *false* long ones (default). - function SETTINGS:SetMenutextShort(onoff) + function SETTINGS:SetMenutextShort( onoff ) _SETTINGS.MenuShort = onoff end --- Set menu to be static. -- @param #SETTINGS self -- @param #boolean onoff If *true* menu is static. If *false* menu will be updated after changes (default). - function SETTINGS:SetMenuStatic(onoff) + function SETTINGS:SetMenuStatic( onoff ) _SETTINGS.MenuStatic = onoff end @@ -289,7 +287,7 @@ do -- SETTINGS -- @param #SETTINGS self -- @return #boolean true if metric. function SETTINGS:IsMetric() - return ( self.Metric ~= nil and self.Metric == true ) or ( self.Metric == nil and _SETTINGS:IsMetric() ) + return (self.Metric ~= nil and self.Metric == true) or (self.Metric == nil and _SETTINGS:IsMetric()) end --- Sets the SETTINGS imperial. @@ -302,7 +300,7 @@ do -- SETTINGS -- @param #SETTINGS self -- @return #boolean true if imperial. function SETTINGS:IsImperial() - return ( self.Metric ~= nil and self.Metric == false ) or ( self.Metric == nil and _SETTINGS:IsMetric() ) + return (self.Metric ~= nil and self.Metric == false) or (self.Metric == nil and _SETTINGS:IsMetric()) end --- Sets the SETTINGS LL accuracy. @@ -344,13 +342,12 @@ do -- SETTINGS self.MessageTypeTimings[MessageType] = MessageTime end - --- Gets the SETTINGS Message Display Timing of a MessageType -- @param #SETTINGS self -- @param Core.Message#MESSAGE MessageType The type of the message. -- @return #number function SETTINGS:GetMessageTime( MessageType ) - return ( self.MessageTypeTimings and self.MessageTypeTimings[MessageType] ) or _SETTINGS:GetMessageTime( MessageType ) + return (self.MessageTypeTimings and self.MessageTypeTimings[MessageType]) or _SETTINGS:GetMessageTime( MessageType ) end --- Sets A2G LL DMS @@ -371,14 +368,14 @@ do -- SETTINGS -- @param #SETTINGS self -- @return #boolean true if LL DMS function SETTINGS:IsA2G_LL_DMS() - return ( self.A2GSystem and self.A2GSystem == "LL DMS" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_LL_DMS() ) + return (self.A2GSystem and self.A2GSystem == "LL DMS") or (not self.A2GSystem and _SETTINGS:IsA2G_LL_DMS()) end --- Is LL DDM -- @param #SETTINGS self -- @return #boolean true if LL DDM function SETTINGS:IsA2G_LL_DDM() - return ( self.A2GSystem and self.A2GSystem == "LL DDM" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_LL_DDM() ) + return (self.A2GSystem and self.A2GSystem == "LL DDM") or (not self.A2GSystem and _SETTINGS:IsA2G_LL_DDM()) end --- Sets A2G MGRS @@ -392,7 +389,7 @@ do -- SETTINGS -- @param #SETTINGS self -- @return #boolean true if MGRS function SETTINGS:IsA2G_MGRS() - return ( self.A2GSystem and self.A2GSystem == "MGRS" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_MGRS() ) + return (self.A2GSystem and self.A2GSystem == "MGRS") or (not self.A2GSystem and _SETTINGS:IsA2G_MGRS()) end --- Sets A2G BRA @@ -406,7 +403,7 @@ do -- SETTINGS -- @param #SETTINGS self -- @return #boolean true if BRA function SETTINGS:IsA2G_BR() - return ( self.A2GSystem and self.A2GSystem == "BR" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_BR() ) + return (self.A2GSystem and self.A2GSystem == "BR") or (not self.A2GSystem and _SETTINGS:IsA2G_BR()) end --- Sets A2A BRA @@ -420,7 +417,7 @@ do -- SETTINGS -- @param #SETTINGS self -- @return #boolean true if BRA function SETTINGS:IsA2A_BRAA() - return ( self.A2ASystem and self.A2ASystem == "BRAA" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_BRAA() ) + return (self.A2ASystem and self.A2ASystem == "BRAA") or (not self.A2ASystem and _SETTINGS:IsA2A_BRAA()) end --- Sets A2A BULLS @@ -434,7 +431,7 @@ do -- SETTINGS -- @param #SETTINGS self -- @return #boolean true if BULLS function SETTINGS:IsA2A_BULLS() - return ( self.A2ASystem and self.A2ASystem == "BULLS" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_BULLS() ) + return (self.A2ASystem and self.A2ASystem == "BULLS") or (not self.A2ASystem and _SETTINGS:IsA2A_BULLS()) end --- Sets A2A LL DMS @@ -455,14 +452,14 @@ do -- SETTINGS -- @param #SETTINGS self -- @return #boolean true if LL DMS function SETTINGS:IsA2A_LL_DMS() - return ( self.A2ASystem and self.A2ASystem == "LL DMS" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_LL_DMS() ) + return (self.A2ASystem and self.A2ASystem == "LL DMS") or (not self.A2ASystem and _SETTINGS:IsA2A_LL_DMS()) end --- Is LL DDM -- @param #SETTINGS self -- @return #boolean true if LL DDM function SETTINGS:IsA2A_LL_DDM() - return ( self.A2ASystem and self.A2ASystem == "LL DDM" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_LL_DDM() ) + return (self.A2ASystem and self.A2ASystem == "LL DDM") or (not self.A2ASystem and _SETTINGS:IsA2A_LL_DDM()) end --- Sets A2A MGRS @@ -476,7 +473,7 @@ do -- SETTINGS -- @param #SETTINGS self -- @return #boolean true if MGRS function SETTINGS:IsA2A_MGRS() - return ( self.A2ASystem and self.A2ASystem == "MGRS" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_MGRS() ) + return (self.A2ASystem and self.A2ASystem == "MGRS") or (not self.A2ASystem and _SETTINGS:IsA2A_MGRS()) end --- @param #SETTINGS self @@ -495,37 +492,37 @@ do -- SETTINGS -- A2G Coordinate System ------- - local text="A2G Coordinate System" + local text = "A2G Coordinate System" if _SETTINGS.MenuShort then - text="A2G Coordinates" + text = "A2G Coordinates" end local A2GCoordinateMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime ) -- Set LL DMS if not self:IsA2G_LL_DMS() then - local text="Lat/Lon Degree Min Sec (LL DMS)" + local text = "Lat/Lon Degree Min Sec (LL DMS)" if _SETTINGS.MenuShort then - text="LL DMS" + text = "LL DMS" end MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "LL DMS" ):SetTime( MenuTime ) end -- Set LL DDM if not self:IsA2G_LL_DDM() then - local text="Lat/Lon Degree Dec Min (LL DDM)" + local text = "Lat/Lon Degree Dec Min (LL DDM)" if _SETTINGS.MenuShort then - text="LL DDM" + 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 -- Set LL DMS accuracy. if self:IsA2G_LL_DDM() then - local text1="LL DDM Accuracy 1" - local text2="LL DDM Accuracy 2" - local text3="LL DDM Accuracy 3" + 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" + 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 ) @@ -534,18 +531,18 @@ do -- SETTINGS -- Set BR. if not self:IsA2G_BR() then - local text="Bearing, Range (BR)" + local text = "Bearing, Range (BR)" if _SETTINGS.MenuShort then - text="BR" + text = "BR" end - MENU_GROUP_COMMAND:New( MenuGroup, text , A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "BR" ):SetTime( MenuTime ) + MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "BR" ):SetTime( MenuTime ) end -- Set MGRS. if not self:IsA2G_MGRS() then - local text="Military Grid (MGRS)" + local text = "Military Grid (MGRS)" if _SETTINGS.MenuShort then - text="MGRS" + text = "MGRS" end MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "MGRS" ):SetTime( MenuTime ) end @@ -563,24 +560,24 @@ do -- SETTINGS -- A2A Coordinate System ------- - local text="A2A Coordinate System" + local text = "A2A Coordinate System" if _SETTINGS.MenuShort then - text="A2A Coordinates" + 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)" + local text = "Lat/Lon Degree Min Sec (LL DMS)" if _SETTINGS.MenuShort then - text="LL DMS" + 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)" + local text = "Lat/Lon Degree Dec Min (LL DDM)" if _SETTINGS.MenuShort then - text="LL DDM" + text = "LL DDM" end MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "LL DDM" ):SetTime( MenuTime ) end @@ -593,25 +590,25 @@ do -- SETTINGS end if not self:IsA2A_BULLS() then - local text="Bullseye (BULLS)" + local text = "Bullseye (BULLS)" if _SETTINGS.MenuShort then - text="Bulls" + 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)" + local text = "Bearing Range Altitude Aspect (BRAA)" if _SETTINGS.MenuShort then - text="BRAA" + 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)" + local text = "Military Grid (MGRS)" if _SETTINGS.MenuShort then - text="MGRS" + text = "MGRS" end MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "MGRS" ):SetTime( MenuTime ) end @@ -624,31 +621,31 @@ do -- SETTINGS 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" + local text = "Measures and Weights System" if _SETTINGS.MenuShort then - text="Unit System" + text = "Unit System" end local MetricsMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime ) if self:IsMetric() then - local text="Imperial (Miles,Feet)" + local text = "Imperial (Miles,Feet)" if _SETTINGS.MenuShort then - text="Imperial" + 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)" + local text = "Metric (Kilometers,Meters)" if _SETTINGS.MenuShort then - text="Metric" + text = "Metric" end MENU_GROUP_COMMAND:New( MenuGroup, text, MetricsMenu, self.MenuMWSystem, self, MenuGroup, RootMenu, true ):SetTime( MenuTime ) end - local text="Messages and Reports" + local text = "Messages and Reports" if _SETTINGS.MenuShort then - text="Messages & Reports" + text = "Messages & Reports" end local MessagesMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime ) @@ -689,7 +686,6 @@ do -- SETTINGS 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 @@ -733,11 +729,11 @@ do -- SETTINGS self.PlayerMenu = PlayerMenu - self:I(string.format("Setting menu for player %s", tostring(PlayerName))) + 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 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 ) @@ -754,40 +750,40 @@ do -- SETTINGS -- A2G Coordinate System ------ - local text="A2G Coordinate System" + local text = "A2G Coordinate System" if _SETTINGS.MenuShort then - text="A2G Coordinates" + 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)" + local text = "Lat/Lon Degree Min Sec (LL DMS)" if _SETTINGS.MenuShort then - text="A2G LL DMS" + 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)" + local text = "Lat/Lon Degree Dec Min (LL DDM)" if _SETTINGS.MenuShort then - text="A2G LL DDM" + 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)" + local text = "Bearing, Range (BR)" if _SETTINGS.MenuShort then - text="A2G BR" + 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)" + local text = "Military Grid (MGRS)" if _SETTINGS.MenuShort then - text="A2G MGRS" + text = "A2G MGRS" end MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "MGRS" ) end @@ -796,49 +792,48 @@ do -- SETTINGS -- A2A Coordinates Menu ------ - local text="A2A Coordinate System" + local text = "A2A Coordinate System" if _SETTINGS.MenuShort then - text="A2A Coordinates" + 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)" + local text = "Lat/Lon Degree Min Sec (LL DMS)" if _SETTINGS.MenuShort then - text="A2A LL DMS" + 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)" + local text = "Lat/Lon Degree Dec Min (LL DDM)" if _SETTINGS.MenuShort then - text="A2A LL DDM" + 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)" + local text = "Bullseye (BULLS)" if _SETTINGS.MenuShort then - text="A2A BULLS" + 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)" + local text = "Bearing Range Altitude Aspect (BRAA)" if _SETTINGS.MenuShort then - text="A2A BRAA" + 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)" + local text = "Military Grid (MGRS)" if _SETTINGS.MenuShort then - text="A2A MGRS" + text = "A2A MGRS" end MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "MGRS" ) end @@ -847,24 +842,24 @@ do -- SETTINGS -- Unit system --- - local text="Measures and Weights System" + local text = "Measures and Weights System" if _SETTINGS.MenuShort then - text="Unit System" + text = "Unit System" end local MetricsMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu ) if self:IsMetric() or _SETTINGS.MenuStatic then - local text="Imperial (Miles,Feet)" + local text = "Imperial (Miles,Feet)" if _SETTINGS.MenuShort then - text="Imperial" + 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)" + local text = "Metric (Kilometers,Meters)" if _SETTINGS.MenuShort then - text="Metric" + text = "Metric" end MENU_GROUP_COMMAND:New( PlayerGroup, text, MetricsMenu, self.MenuGroupMWSystem, self, PlayerUnit, PlayerGroup, PlayerName, true ) end @@ -873,9 +868,9 @@ do -- SETTINGS -- Messages and Reports --- - local text="Messages and Reports" + local text = "Messages and Reports" if _SETTINGS.MenuShort then - text="Messages & Reports" + text = "Messages & Reports" end local MessagesMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu ) @@ -935,39 +930,38 @@ do -- SETTINGS return self end - --- @param #SETTINGS self 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() + MESSAGE:New( string.format( "Settings: Default A2G coordinate system set to %s for all players!", A2GSystem ), 5 ):ToAll() self:SetSystemMenu( MenuGroup, RootMenu ) end --- @param #SETTINGS self 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() + MESSAGE:New( string.format( "Settings: Default A2A coordinate system set to %s for all players!", A2ASystem ), 5 ):ToAll() self:SetSystemMenu( MenuGroup, RootMenu ) end --- @param #SETTINGS self 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() + MESSAGE:New( string.format( "Settings: Default LL accuracy set to %s for all players!", LL_Accuracy ), 5 ):ToAll() self:SetSystemMenu( MenuGroup, RootMenu ) end --- @param #SETTINGS self 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() + MESSAGE:New( string.format( "Settings: Default MGRS accuracy set to %s for all players!", MGRS_Accuracy ), 5 ):ToAll() self:SetSystemMenu( MenuGroup, RootMenu ) end --- @param #SETTINGS self 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() + 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 @@ -980,12 +974,12 @@ do -- SETTINGS do --- @param #SETTINGS self function SETTINGS:MenuGroupA2GSystem( PlayerUnit, PlayerGroup, PlayerName, A2GSystem ) - BASE:E( {self, PlayerUnit:GetName(), 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) + if _SETTINGS.MenuStatic == false then + self:RemovePlayerMenu( PlayerUnit ) + self:SetPlayerMenu( PlayerUnit ) end end @@ -993,9 +987,9 @@ do -- SETTINGS 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) + if _SETTINGS.MenuStatic == false then + self:RemovePlayerMenu( PlayerUnit ) + self:SetPlayerMenu( PlayerUnit ) end end @@ -1003,9 +997,9 @@ do -- SETTINGS 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) + if _SETTINGS.MenuStatic == false then + self:RemovePlayerMenu( PlayerUnit ) + self:SetPlayerMenu( PlayerUnit ) end end @@ -1013,9 +1007,9 @@ do -- SETTINGS 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) + if _SETTINGS.MenuStatic == false then + self:RemovePlayerMenu( PlayerUnit ) + self:SetPlayerMenu( PlayerUnit ) end end @@ -1023,9 +1017,9 @@ do -- SETTINGS 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) + if _SETTINGS.MenuStatic == false then + self:RemovePlayerMenu( PlayerUnit ) + self:SetPlayerMenu( PlayerUnit ) end end @@ -1055,7 +1049,6 @@ do -- SETTINGS end - --- Configures the era of the mission to be Cold war. -- @param #SETTINGS self -- @return #SETTINGS self @@ -1065,7 +1058,6 @@ do -- SETTINGS end - --- Configures the era of the mission to be Modern war. -- @param #SETTINGS self -- @return #SETTINGS self @@ -1075,7 +1067,4 @@ do -- SETTINGS end - - - end diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index d25186b66..a067cc680 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -1,16 +1,16 @@ --- **Core** - Spawn dynamically new groups of units in running missions. --- +-- -- === --- +-- -- ## Features: --- +-- -- * Spawn new groups in running missions. -- * Schedule spawning of new groups. -- * Put limits on the amount of groups that can be spawned, and the amount of units that can be alive at the same time. -- * Randomize the spawning location between different zones. -- * Randomize the initial positions within the zones. -- * Spawn in array formation. --- * Spawn uncontrolled (for planes or helos only). +-- * Spawn uncontrolled (for planes or helicopters only). -- * Clean up inactive helicopters that "crashed". -- * Place a hook to capture a spawn event, and tailor with customer code. -- * Spawn late activated. @@ -27,26 +27,25 @@ -- * Spawn and keep the unit names. -- * Spawn with a different coalition and country. -- * Enquiry methods to check on spawn status. --- +-- -- === --- +-- -- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/SPA%20-%20Spawning) --- +-- -- === --- +-- -- ### [YouTube Playlist](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl1jirWIo4t4YxqN-HxjqRkL) --- +-- -- === --- +-- -- ### Author: **FlightControl** -- ### Contributions: A lot of people within this community! --- +-- -- === --- +-- -- @module Core.Spawn -- @image Core_Spawn.JPG - --- SPAWN Class -- @type SPAWN -- @field ClassName @@ -59,81 +58,80 @@ -- @field #SPAWN.SpawnZoneTable SpawnZoneTable -- @extends Core.Base#BASE - ---- Allows to spawn dynamically new @{Core.Group}s. --- +--- Allows to spawn dynamically new @{Core.Group}s. +-- -- Each SPAWN object needs to be have related **template groups** setup in the Mission Editor (ME), --- which is a normal group with the **Late Activation** flag set. --- This template group will never be activated in your mission. --- SPAWN uses that **template group** to reference to all the characteristics --- (air, ground, livery, unit composition, formation, skill level etc) of each new group to be spawned. --- +-- which is a normal group with the **Late Activation** flag set. +-- This template group will never be activated in your mission. +-- SPAWN uses that **template group** to reference to all the characteristics +-- (air, ground, livery, unit composition, formation, skill level etc) of each new group to be spawned. +-- -- Therefore, when creating a SPAWN object, the @{#SPAWN.New} and @{#SPAWN.NewWithAlias} require --- **the name of the template group** to be given as a string to those constructor methods. --- --- Initialization settings can be applied on the SPAWN object, --- which modify the behaviour or the way groups are spawned. +-- **the name of the template group** to be given as a string to those constructor methods. +-- +-- Initialization settings can be applied on the SPAWN object, +-- which modify the behavior or the way groups are spawned. -- These initialization methods have the prefix **Init**. -- There are also spawn methods with the prefix **Spawn** and will spawn new groups in various ways. --- --- ### IMPORTANT! The methods with prefix **Init** must be used before any methods with prefix **Spawn** method are used, or unexpected results may appear!!! --- --- Because SPAWN can spawn multiple groups of a template group, --- SPAWN has an **internal index** that keeps track --- which was the latest group that was spawned. --- --- **Limits** can be set on how many groups can be spawn in each SPAWN object, +-- +-- ### IMPORTANT! The methods with prefix **Init** must be used before any methods with prefix **Spawn** method are used, or unexpected results may appear!!! +-- +-- Because SPAWN can spawn multiple groups of a template group, +-- SPAWN has an **internal index** that keeps track +-- which was the latest group that was spawned. +-- +-- **Limits** can be set on how many groups can be spawn in each SPAWN object, -- using the method @{#SPAWN.InitLimit}. SPAWN has 2 kind of limits: --- --- * The maximum amount of @{Wrapper.Unit}s that can be **alive** at the same time... +-- +-- * The maximum amount of @{Wrapper.Unit}s that can be **alive** at the same time... -- * The maximum amount of @{Wrapper.Group}s that can be **spawned**... This is more of a **resource**-type of limit. --- --- When new groups get spawned using the **Spawn** methods, +-- +-- When new groups get spawned using the **Spawn** methods, -- it will be evaluated whether any limits have been reached. --- When no spawn limit is reached, a new group will be created by the spawning methods, --- and the internal index will be increased with 1. --- --- These limits ensure that your mission does not accidentally get flooded with spawned groups. --- Additionally, it also guarantees that independent of the group composition, +-- When no spawn limit is reached, a new group will be created by the spawning methods, +-- and the internal index will be increased with 1. +-- +-- These limits ensure that your mission does not accidentally get flooded with spawned groups. +-- Additionally, it also guarantees that independent of the group composition, -- at any time, the most optimal amount of groups are alive in your mission. -- For example, if your template group has a group composition of 10 units, and you specify a limit of 100 units alive at the same time, -- with unlimited resources = :InitLimit( 100, 0 ) and 10 groups are alive, but two groups have only one unit alive in the group, -- then a sequent Spawn(Scheduled) will allow a new group to be spawned!!! --- --- ### IMPORTANT!! If a limit has been reached, it is possible that a **Spawn** method returns **nil**, meaning, no @{Wrapper.Group} had been spawned!!! --- --- Spawned groups get **the same name** as the name of the template group. --- Spawned units in those groups keep _by default_ **the same name** as the name of the template group. --- However, because multiple groups and units are created from the template group, +-- +-- ### IMPORTANT!! If a limit has been reached, it is possible that a **Spawn** method returns **nil**, meaning, no @{Wrapper.Group} had been spawned!!! +-- +-- Spawned groups get **the same name** as the name of the template group. +-- Spawned units in those groups keep _by default_ **the same name** as the name of the template group. +-- However, because multiple groups and units are created from the template group, -- a suffix is added to each spawned group and unit. --- +-- -- Newly spawned groups will get the following naming structure at run-time: --- --- 1. Spawned groups will have the name _GroupName_#_nnn_, where _GroupName_ is the name of the **template group**, +-- +-- 1. Spawned groups will have the name _GroupName_#_nnn_, where _GroupName_ is the name of the **template group**, -- and _nnn_ is a **counter from 0 to 999**. --- 2. Spawned units will have the name _GroupName_#_nnn_-_uu_, +-- 2. Spawned units will have the name _GroupName_#_nnn_-_uu_, -- where _uu_ is a **counter from 0 to 99** for each new spawned unit belonging to the group. --- --- That being said, there is a way to keep the same unit names! +-- +-- That being said, there is a way to keep the same unit names! -- The method @{#SPAWN.InitKeepUnitNames}() will keep the same unit names as defined within the template group, thus: --- --- 3. Spawned units will have the name _UnitName_#_nnn_-_uu_, --- where _UnitName_ is the **unit name as defined in the template group*, +-- +-- 3. Spawned units will have the name _UnitName_#_nnn_-_uu_, +-- where _UnitName_ is the **unit name as defined in the template group*, -- and _uu_ is a **counter from 0 to 99** for each new spawned unit belonging to the group. --- +-- -- Some **additional notes that need to be considered!!**: --- --- * templates are actually groups defined within the mission editor, with the flag "Late Activation" set. +-- +-- * templates are actually groups defined within the mission editor, with the flag "Late Activation" set. -- As such, these groups are never used within the mission, but are used by the @{#SPAWN} module. -- * It is important to defined BEFORE you spawn new groups, -- a proper initialization of the SPAWN instance is done with the options you want to use. --- * When designing a mission, NEVER name groups using a "#" within the name of the group Spawn template(s), +-- * When designing a mission, NEVER name groups using a "#" within the name of the group Spawn template(s), -- or the SPAWN module logic won't work anymore. --- +-- -- ## SPAWN construction methods --- +-- -- Create a new SPAWN object with the @{#SPAWN.New}() or the @{#SPAWN.NewWithAlias}() methods: --- +-- -- * @{#SPAWN.New}(): Creates a new SPAWN object taking the name of the group that represents the GROUP template (definition). -- * @{#SPAWN.NewWithAlias}(): Creates a new SPAWN object taking the name of the group that represents the GROUP template (definition), and gives each spawned @{Wrapper.Group} an different name. -- @@ -142,63 +140,62 @@ -- So in principle, the group list will contain all parameters and configurations after initialization, and when groups get actually spawned, this spawning can be done quickly and efficient. -- -- ## SPAWN **Init**ialization methods --- --- A spawn object will behave differently based on the usage of **initialization** methods, which all start with the **Init** prefix: --- +-- +-- A spawn object will behave differently based on the usage of **initialization** methods, which all start with the **Init** prefix: +-- -- ### Unit Names --- +-- -- * @{#SPAWN.InitKeepUnitNames}(): Keeps the unit names as defined within the mission editor, but note that anything after a # mark is ignored, and any spaces before and after the resulting name are removed. IMPORTANT! This method MUST be the first used after :New !!! --- +-- -- ### Route randomization --- +-- -- * @{#SPAWN.InitRandomizeRoute}(): Randomize the routes of spawned groups, and for air groups also optionally the height. --- --- ### Group composition randomization --- +-- +-- ### Group composition randomization +-- -- * @{#SPAWN.InitRandomizeTemplate}(): Randomize the group templates so that when a new group is spawned, a random group template is selected from one of the templates defined. --- +-- -- ### Uncontrolled --- +-- -- * @{#SPAWN.InitUnControlled}(): Spawn plane groups uncontrolled. --- +-- -- ### Array formation --- --- * @{#SPAWN.InitArray}(): Make groups visible before they are actually activated, and order these groups like a batallion in an array. --- +-- +-- * @{#SPAWN.InitArray}(): Make groups visible before they are actually activated, and order these groups like a battalion in an array. +-- -- ### Position randomization --- +-- -- * @{#SPAWN.InitRandomizePosition}(): Randomizes the position of @{Wrapper.Group}s that are spawned within a **radius band**, given an Outer and Inner radius, from the point that the spawn happens. -- * @{#SPAWN.InitRandomizeUnits}(): Randomizes the @{Wrapper.Unit}s in the @{Wrapper.Group} that is spawned within a **radius band**, given an Outer and Inner radius. -- * @{#SPAWN.InitRandomizeZones}(): Randomizes the spawning between a predefined list of @{Zone}s that are declared using this function. Each zone can be given a probability factor. --- +-- -- ### Enable / Disable AI when spawning a new @{Wrapper.Group} --- +-- -- * @{#SPAWN.InitAIOn}(): Turns the AI On when spawning the new @{Wrapper.Group} object. -- * @{#SPAWN.InitAIOff}(): Turns the AI Off when spawning the new @{Wrapper.Group} object. -- * @{#SPAWN.InitAIOnOff}(): Turns the AI On or Off when spawning the new @{Wrapper.Group} object. --- --- ### Limit scheduled spawning --- +-- +-- ### Limit scheduled spawning +-- -- * @{#SPAWN.InitLimit}(): Limits the amount of groups that can be alive at the same time and that can be dynamically spawned. --- +-- -- ### Delay initial scheduled spawn --- --- * @{#SPAWN.InitDelayOnOff}(): Turns the inital delay On/Off when scheduled spawning the first @{Wrapper.Group} object. --- * @{#SPAWN.InitDelayOn}(): Turns the inital delay On when scheduled spawning the first @{Wrapper.Group} object. --- * @{#SPAWN.InitDelayOff}(): Turns the inital delay Off when scheduled spawning the first @{Wrapper.Group} object. --- +-- +-- * @{#SPAWN.InitDelayOnOff}(): Turns the initial delay On/Off when scheduled spawning the first @{Wrapper.Group} object. +-- * @{#SPAWN.InitDelayOn}(): Turns the initial delay On when scheduled spawning the first @{Wrapper.Group} object. +-- * @{#SPAWN.InitDelayOff}(): Turns the initial delay Off when scheduled spawning the first @{Wrapper.Group} object. +-- -- ### Repeat spawned @{Wrapper.Group}s upon landing --- +-- -- * @{#SPAWN.InitRepeat}() or @{#SPAWN.InitRepeatOnLanding}(): This method is used to re-spawn automatically the same group after it has landed. -- * @{#SPAWN.InitRepeatOnEngineShutDown}(): This method is used to re-spawn automatically the same group after it has landed and it shuts down the engines at the ramp. --- --- +-- -- ## SPAWN **Spawn** methods --- +-- -- Groups can be spawned at different times and methods: --- +-- -- ### **Single** spawning methods --- +-- -- * @{#SPAWN.Spawn}(): Spawn one new group based on the last spawned index. -- * @{#SPAWN.ReSpawn}(): Re-spawn a group based on a given index. -- * @{#SPAWN.SpawnFromVec3}(): Spawn a new group from a Vec3 coordinate. (The group will can be spawned at a point in the air). @@ -207,70 +204,66 @@ -- * @{#SPAWN.SpawnFromUnit}(): Spawn a new group taking the position of a @{Wrapper.Unit}. -- * @{#SPAWN.SpawnInZone}(): Spawn a new group in a @{Zone}. -- * @{#SPAWN.SpawnAtAirbase}(): Spawn a new group at an @{Wrapper.Airbase}, which can be an airdrome, ship or helipad. --- --- Note that @{#SPAWN.Spawn} and @{#SPAWN.ReSpawn} return a @{Wrapper.Group#GROUP.New} object, that contains a reference to the DCSGroup object. +-- +-- Note that @{#SPAWN.Spawn} and @{#SPAWN.ReSpawn} return a @{Wrapper.Group#GROUP.New} object, that contains a reference to the DCSGroup object. -- You can use the @{GROUP} object to do further actions with the DCSGroup. --- +-- -- ### **Scheduled** spawning methods --- --- * @{#SPAWN.SpawnScheduled}(): Spawn groups at scheduled but randomized intervals. ---- * @{#SPAWN.SpawnScheduleStart}(): Start or continue to spawn groups at scheduled time intervals. --- * @{#SPAWN.SpawnScheduleStop}(): Stop the spawning of groups at scheduled time intervals. --- --- +-- +-- * @{#SPAWN.SpawnScheduled}(): Spawn groups at scheduled but randomized intervals. +--- * @{#SPAWN.SpawnScheduleStart}(): Start or continue to spawn groups at scheduled time intervals. +-- * @{#SPAWN.SpawnScheduleStop}(): Stop the spawning of groups at scheduled time intervals. +-- -- ## Retrieve alive GROUPs spawned by the SPAWN object --- +-- -- The SPAWN class administers which GROUPS it has reserved (in stock) or has created during mission execution. -- Every time a SPAWN object spawns a new GROUP object, a reference to the GROUP object is added to an internal table of GROUPS. -- SPAWN provides methods to iterate through that internal GROUP object reference table: --- +-- -- * @{#SPAWN.GetFirstAliveGroup}(): Will find the first alive GROUP it has spawned, and return the alive GROUP object and the first Index where the first alive GROUP object has been found. -- * @{#SPAWN.GetNextAliveGroup}(): Will find the next alive GROUP object from a given Index, and return a reference to the alive GROUP object and the next Index where the alive GROUP has been found. -- * @{#SPAWN.GetLastAliveGroup}(): Will find the last alive GROUP object, and will return a reference to the last live GROUP object and the last Index where the last alive GROUP object has been found. --- +-- -- You can use the methods @{#SPAWN.GetFirstAliveGroup}() and sequently @{#SPAWN.GetNextAliveGroup}() to iterate through the alive GROUPS within the SPAWN object, and to actions... See the respective methods for an example. -- The method @{#SPAWN.GetGroupFromIndex}() will return the GROUP object reference from the given Index, dead or alive... --- +-- -- ## Spawned cleaning of inactive groups --- --- Sometimes, it will occur during a mission run-time, that ground or especially air objects get damaged, and will while being damged stop their activities, while remaining alive. --- In such cases, the SPAWN object will just sit there and wait until that group gets destroyed, but most of the time it won't, +-- +-- Sometimes, it will occur during a mission run-time, that ground or especially air objects get damaged, and will while being damaged stop their activities, while remaining alive. +-- In such cases, the SPAWN object will just sit there and wait until that group gets destroyed, but most of the time it won't, -- and it may occur that no new groups are or can be spawned as limits are reached. -- To prevent this, a @{#SPAWN.InitCleanUp}() initialization method has been defined that will silently monitor the status of each spawned group. --- Once a group has a velocity = 0, and has been waiting for a defined interval, that group will be cleaned or removed from run-time. --- There is a catch however :-) If a damaged group has returned to an airbase within the coalition, that group will not be considered as "lost"... --- In such a case, when the inactive group is cleaned, a new group will Re-spawned automatically. --- This models AI that has succesfully returned to their airbase, to restart their combat activities. +-- Once a group has a velocity = 0, and has been waiting for a defined interval, that group will be cleaned or removed from run-time. +-- There is a catch however :-) If a damaged group has returned to an airbase within the coalition, that group will not be considered as "lost"... +-- In such a case, when the inactive group is cleaned, a new group will Re-spawned automatically. +-- This models AI that has successfully returned to their airbase, to restart their combat activities. -- Check the @{#SPAWN.InitCleanUp}() for further info. --- +-- -- ## Catch the @{Wrapper.Group} Spawn Event in a callback function! --- +-- -- When using the @{#SPAWN.SpawnScheduled)() method, new @{Wrapper.Group}s are created following the spawn time interval parameters. -- When a new @{Wrapper.Group} is spawned, you maybe want to execute actions with that group spawned at the spawn event. --- The SPAWN class supports this functionality through the method @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ), --- which takes a function as a parameter that you can define locally. +-- The SPAWN class supports this functionality through the method @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ), +-- which takes a function as a parameter that you can define locally. -- Whenever a new @{Wrapper.Group} is spawned, the given function is called, and the @{Wrapper.Group} that was just spawned, is given as a parameter. --- As a result, your spawn event handling function requires one parameter to be declared, which will contain the spawned @{Wrapper.Group} object. +-- As a result, your spawn event handling function requires one parameter to be declared, which will contain the spawned @{Wrapper.Group} object. -- A coding example is provided at the description of the @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ) method. --- +-- -- ## Delay the initial spawning --- --- When using the @{#SPAWN.SpawnScheduled)() method, the default behaviour of this method will be that it will spawn the initial (first) @{Wrapper.Group} +-- +-- When using the @{#SPAWN.SpawnScheduled)() method, the default behavior of this method will be that it will spawn the initial (first) @{Wrapper.Group} -- immediately when :SpawnScheduled() is initiated. The methods @{#SPAWN.InitDelayOnOff}() and @{#SPAWN.InitDelayOn}() can be used to -- activate a delay before the first @{Wrapper.Group} is spawned. For completeness, a method @{#SPAWN.InitDelayOff}() is also available, that --- can be used to switch off the initial delay. Because there is no delay by default, this method would only be used when a +-- can be used to switch off the initial delay. Because there is no delay by default, this method would only be used when a -- @{#SPAWN.SpawnScheduleStop}() ; @{#SPAWN.SpawnScheduleStart}() sequence would have been used. --- --- +-- -- @field #SPAWN SPAWN --- SPAWN = { ClassName = "SPAWN", SpawnTemplatePrefix = nil, SpawnAliasPrefix = nil, } - --- Enumerator for spawns at airbases -- @type SPAWN.Takeoff -- @extends Wrapper.Group#GROUP.Takeoff @@ -286,7 +279,6 @@ SPAWN.Takeoff = { --- @type SPAWN.SpawnZoneTable -- @list SpawnZone - --- Creates the main object to spawn a @{Wrapper.Group} defined in the DCS ME. -- @param #SPAWN self -- @param #string SpawnTemplatePrefix is the name of the Group in the ME that defines the Template. Each new group will have the name starting with SpawnTemplatePrefix. @@ -296,47 +288,47 @@ SPAWN.Takeoff = { -- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ) -- @usage local Plane = SPAWN:New( "Plane" ) -- Creates a new local variable that can initiate new planes with the name "Plane#ddd" using the template "Plane" as defined within the ME. function SPAWN:New( SpawnTemplatePrefix ) - local self = BASE:Inherit( self, BASE:New() ) -- #SPAWN - self:F( { SpawnTemplatePrefix } ) - - local TemplateGroup = GROUP:FindByName( SpawnTemplatePrefix ) - if TemplateGroup then - self.SpawnTemplatePrefix = SpawnTemplatePrefix - self.SpawnIndex = 0 - self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart. - self.AliveUnits = 0 -- Contains the counter how many units are currently alive - self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not. - self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!! - self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning. - self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts. - self.SpawnInitLimit = false -- By default, no InitLimit - self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. - self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned. - self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false. - self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned. - self.AIOnOff = true -- The AI is on by default when spawning a group. + local self = BASE:Inherit( self, BASE:New() ) -- #SPAWN + self:F( { SpawnTemplatePrefix } ) + + local TemplateGroup = GROUP:FindByName( SpawnTemplatePrefix ) + if TemplateGroup then + self.SpawnTemplatePrefix = SpawnTemplatePrefix + self.SpawnIndex = 0 + self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart. + self.AliveUnits = 0 -- Contains the counter how many units are currently alive + self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not. + self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!! + self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning. + self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts. + self.SpawnInitLimit = false -- By default, no InitLimit + self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. + self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned. + self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false. + self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned. + self.AIOnOff = true -- The AI is on by default when spawning a group. self.SpawnUnControlled = false - self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name. - self.DelayOnOff = false -- No intial delay when spawning the first group. - self.SpawnGrouping = nil -- No grouping. - self.SpawnInitLivery = nil -- No special livery. - self.SpawnInitSkill = nil -- No special skill. - self.SpawnInitFreq = nil -- No special frequency. - self.SpawnInitModu = nil -- No special modulation. - self.SpawnInitRadio = nil -- No radio comms setting. + self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name. + self.DelayOnOff = false -- No intial delay when spawning the first group. + self.SpawnGrouping = nil -- No grouping. + self.SpawnInitLivery = nil -- No special livery. + self.SpawnInitSkill = nil -- No special skill. + self.SpawnInitFreq = nil -- No special frequency. + self.SpawnInitModu = nil -- No special modulation. + self.SpawnInitRadio = nil -- No radio comms setting. self.SpawnInitModex = nil self.SpawnInitAirbase = nil - self.TweakedTemplate = false -- Check if the user is using self made template. + self.TweakedTemplate = false -- Check if the user is using self made template. - self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned. - else - error( "SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" ) - end + self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned. + 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 + return self end --- Creates a new SPAWN instance to create new groups based on the defined template and using a new alias for each new group. @@ -349,50 +341,49 @@ end -- Spawn_BE_KA50 = SPAWN:NewWithAlias( 'BE KA-50@RAMP-Ground Defense', 'Helicopter Attacking a City' ) -- @usage local PlaneWithAlias = SPAWN:NewWithAlias( "Plane", "Bomber" ) -- Creates a new local variable that can instantiate new planes with the name "Bomber#ddd" using the template "Plane" as defined within the ME. 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 -- The internal counter of the amount of spawning the has happened since SpawnStart. - self.AliveUnits = 0 -- Contains the counter how many units are currently alive - self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not. - self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!! - self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning. - self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts. - self.SpawnInitLimit = false -- By default, no InitLimit - self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. - self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned. - self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false. - self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned. - self.AIOnOff = true -- The AI is on by default when spawning a group. + 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 -- The internal counter of the amount of spawning the has happened since SpawnStart. + self.AliveUnits = 0 -- Contains the counter how many units are currently alive + self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not. + self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!! + self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning. + self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts. + self.SpawnInitLimit = false -- By default, no InitLimit + self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. + self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned. + self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false. + self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned. + self.AIOnOff = true -- The AI is on by default when spawning a group. self.SpawnUnControlled = false - self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name. - self.DelayOnOff = false -- No intial delay when spawning the first group. - self.SpawnGrouping = nil -- No grouping. - self.SpawnInitLivery = nil -- No special livery. - self.SpawnInitSkill = nil -- No special skill. - self.SpawnInitFreq = nil -- No special frequency. - self.SpawnInitModu = nil -- No special modulation. - self.SpawnInitRadio = nil -- No radio comms setting. + self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name. + self.DelayOnOff = false -- No initial delay when spawning the first group. + self.SpawnGrouping = nil -- No grouping. + self.SpawnInitLivery = nil -- No special livery. + self.SpawnInitSkill = nil -- No special skill. + self.SpawnInitFreq = nil -- No special frequency. + self.SpawnInitModu = nil -- No special modulation. + self.SpawnInitRadio = nil -- No radio communication setting. self.SpawnInitModex = nil self.SpawnInitAirbase = nil - self.TweakedTemplate = false -- Check if the user is using self made template. + self.TweakedTemplate = false -- Check if the user is using self made template. + + self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned. + else + error( "SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" ) + end - self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned. - 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 + return self +end --- Creates a new SPAWN instance to create new groups based on the provided template. -- @param #SPAWN self @@ -403,86 +394,89 @@ end -- @usage -- -- Create a new SPAWN object based on a Group Template defined from scratch. -- Spawn_BE_KA50 = SPAWN:NewWithAlias( 'BE KA-50@RAMP-Ground Defense', 'Helicopter Attacking a City' ) --- @usage --- -- Create a new CSAR_Spawn object based on a normal Group Template to spawn a soldier. --- local CSAR_Spawn = SPAWN:NewWithFromTemplate( Template, "CSAR", "Pilot" ) +-- @usage +-- +-- -- Create a new CSAR_Spawn object based on a normal Group Template to spawn a soldier. +-- local CSAR_Spawn = SPAWN:NewWithFromTemplate( Template, "CSAR", "Pilot" ) +-- 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") + BASE:I( "ERROR: in function NewFromTemplate, required paramter SpawnAliasPrefix is not set" ) return nil end if SpawnTemplate then - self.SpawnTemplate = SpawnTemplate -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!! + self.SpawnTemplate = SpawnTemplate -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!! self.SpawnTemplatePrefix = SpawnTemplatePrefix self.SpawnAliasPrefix = SpawnAliasPrefix self.SpawnIndex = 0 - self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart. - self.AliveUnits = 0 -- Contains the counter how many units are currently alive - self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not. - self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning. - self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts. - self.SpawnInitLimit = false -- By default, no InitLimit. - self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. - self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned. - self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false. - self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned. - self.AIOnOff = true -- The AI is on by default when spawning a group. + self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart. + self.AliveUnits = 0 -- Contains the counter how many units are currently alive + self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not. + self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning. + self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts. + self.SpawnInitLimit = false -- By default, no InitLimit. + self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. + self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned. + self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false. + self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned. + self.AIOnOff = true -- The AI is on by default when spawning a group. self.SpawnUnControlled = false - self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name. - self.DelayOnOff = false -- No intial delay when spawning the first group. - self.Grouping = nil -- No grouping. - self.SpawnInitLivery = nil -- No special livery. - self.SpawnInitSkill = nil -- No special skill. - self.SpawnInitFreq = nil -- No special frequency. - self.SpawnInitModu = nil -- No special modulation. - self.SpawnInitRadio = nil -- No radio comms setting. + self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name. + self.DelayOnOff = false -- No initial delay when spawning the first group. + self.Grouping = nil -- No grouping. + self.SpawnInitLivery = nil -- No special livery. + self.SpawnInitSkill = nil -- No special skill. + self.SpawnInitFreq = nil -- No special frequency. + self.SpawnInitModu = nil -- No special modulation. + self.SpawnInitRadio = nil -- No radio communication setting. self.SpawnInitModex = nil self.SpawnInitAirbase = nil - self.TweakedTemplate = true -- Check if the user is using self made template. - - self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned. + self.TweakedTemplate = true -- Check if the user is using self made template. + + self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned. else error( "There is no template provided for SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" ) end - + self:SetEventPriority( 5 ) self.SpawnHookScheduler = SCHEDULER:New( nil ) - + return self end - --- Stops any more repeat spawns from happening once the UNIT count of Alive units, spawned by the same SPAWN object, exceeds the first parameter. Also can stop spawns from happening once a total GROUP still alive is met. -- Exceptionally powerful when combined with SpawnSchedule for Respawning. -- Note that this method is exceptionally important to balance the performance of the mission. Depending on the machine etc, a mission can only process a maximum amount of units. -- If the time interval must be short, but there should not be more Units or Groups alive than a maximum amount of units, then this method should be used... -- When a @{#SPAWN.New} is executed and the limit of the amount of units alive is reached, then no new spawn will happen of the group, until some of these units of the spawn object will be destroyed. -- @param #SPAWN self --- @param #number SpawnMaxUnitsAlive The maximum amount of units that can be alive at runtime. --- @param #number SpawnMaxGroups The maximum amount of groups that can be spawned. When the limit is reached, then no more actual spawns will happen of the group. --- This parameter is useful to define a maximum amount of airplanes, ground troops, helicopters, ships etc within a supply area. +-- @param #number SpawnMaxUnitsAlive The maximum amount of units that can be alive at runtime. +-- @param #number SpawnMaxGroups The maximum amount of groups that can be spawned. When the limit is reached, then no more actual spawns will happen of the group. +-- This parameter is useful to define a maximum amount of airplanes, ground troops, helicopters, ships etc within a supply area. -- This parameter accepts the value 0, which defines that there are no maximum group limits, but there are limits on the maximum of units that can be alive at the same time. -- @return #SPAWN self -- @usage --- -- NATO helicopters engaging in the battle field. --- -- This helicopter group consists of one Unit. So, this group will SPAWN maximum 2 groups simultaneously within the DCSRTE. --- -- There will be maximum 24 groups spawned during the whole mission lifetime. --- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitLimit( 2, 24 ) +-- +-- -- NATO helicopters engaging in the battle field. +-- -- This helicopter group consists of one Unit. So, this group will SPAWN maximum 2 groups simultaneously within the DCSRTE. +-- -- There will be maximum 24 groups spawned during the whole mission lifetime. +-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitLimit( 2, 24 ) +-- function SPAWN:InitLimit( SpawnMaxUnitsAlive, SpawnMaxGroups ) - self:F( { self.SpawnTemplatePrefix, SpawnMaxUnitsAlive, SpawnMaxGroups } ) + self:F( { self.SpawnTemplatePrefix, SpawnMaxUnitsAlive, SpawnMaxGroups } ) self.SpawnInitLimit = true - self.SpawnMaxUnitsAlive = SpawnMaxUnitsAlive -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. - self.SpawnMaxGroups = SpawnMaxGroups -- The maximum amount of groups that can be spawned. - - for SpawnGroupID = 1, self.SpawnMaxGroups do - self:_InitializeSpawnGroups( SpawnGroupID ) - end + self.SpawnMaxUnitsAlive = SpawnMaxUnitsAlive -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. + self.SpawnMaxGroups = SpawnMaxGroups -- The maximum amount of groups that can be spawned. - return self + for SpawnGroupID = 1, self.SpawnMaxGroups do + self:_InitializeSpawnGroups( SpawnGroupID ) + end + + return self end --- Keeps the unit names as defined within the mission editor, @@ -493,23 +487,22 @@ end -- @param #boolean KeepUnitNames (optional) If true, the unit names are kept, false or not provided to make new unit names. -- @return #SPAWN self function SPAWN:InitKeepUnitNames( KeepUnitNames ) - self:F( ) + self:F() self.SpawnInitKeepUnitNames = KeepUnitNames or true - + return self end - --- Flags that the spawned groups must be spawned late activated. -- @param #SPAWN self -- @param #boolean LateActivated (optional) If true, the spawned groups are late activated. -- @return #SPAWN self function SPAWN:InitLateActivated( LateActivated ) - self:F( ) + self:F() self.LateActivated = LateActivated or true - + return self end @@ -517,47 +510,45 @@ end -- @param #SPAWN self -- @param #string AirbaseName Name of the airbase. -- @param #number Takeoff (Optional) Takeoff type. Can be SPAWN.Takeoff.Hot (default), SPAWN.Takeoff.Cold or SPAWN.Takeoff.Runway. --- @param #number TerminalTyple (Optional) The terminal type. +-- @param #number TerminalType (Optional) The terminal type. -- @return #SPAWN self function SPAWN:InitAirbase( AirbaseName, Takeoff, TerminalType ) - self:F( ) + self:F() + + self.SpawnInitAirbase = AIRBASE:FindByName( AirbaseName ) + + self.SpawnInitTakeoff = Takeoff or SPAWN.Takeoff.Hot + + self.SpawnInitTerminalType = TerminalType - self.SpawnInitAirbase=AIRBASE:FindByName(AirbaseName) - - self.SpawnInitTakeoff=Takeoff or SPAWN.Takeoff.Hot - - self.SpawnInitTerminalType=TerminalType - return self end - ---- Defines the Heading for the new spawned units. +--- Defines the Heading for the new spawned units. -- The heading can be given as one fixed degree, or can be randomized between minimum and maximum degrees. -- @param #SPAWN self -- @param #number HeadingMin The minimum or fixed heading in degrees. -- @param #number HeadingMax (optional) The maximum heading in degrees. This there is no maximum heading, then the heading will be fixed for all units using minimum heading. -- @return #SPAWN self -- @usage --- --- Spawn = SPAWN:New( ... ) --- --- -- Spawn the units pointing to 100 degrees. --- Spawn:InitHeading( 100 ) --- --- -- Spawn the units pointing between 100 and 150 degrees. --- Spawn:InitHeading( 100, 150 ) --- +-- +-- Spawn = SPAWN:New( ... ) +-- +-- -- Spawn the units pointing to 100 degrees. +-- Spawn:InitHeading( 100 ) +-- +-- -- Spawn the units pointing between 100 and 150 degrees. +-- Spawn:InitHeading( 100, 150 ) +-- function SPAWN:InitHeading( HeadingMin, HeadingMax ) - self:F( ) + self:F() self.SpawnInitHeadingMin = HeadingMin self.SpawnInitHeadingMax = HeadingMax - + return self end - --- Defines the heading of the overall formation of the new spawned group. -- The heading can be given as one fixed degree, or can be randomized between minimum and maximum degrees. -- The Group's formation as laid out in its template will be rotated around the first unit in the group @@ -569,116 +560,114 @@ end -- @param #number unitVar (optional) Individual units within the group will have their heading randomized by +/- unitVar degrees. Default is zero. -- @return #SPAWN self -- @usage --- +-- -- mySpawner = SPAWN:New( ... ) --- --- -- Spawn the Group with the formation rotated +100 degrees around unit #1, compared to the mission template. --- mySpawner:InitGroupHeading( 100 ) --- --- Spawn the Group with the formation rotated units between +100 and +150 degrees around unit #1, compared to the mission template, and with individual units varying by +/- 10 degrees from their templated facing. --- mySpawner:InitGroupHeading( 100, 150, 10 ) --- --- Spawn the Group with the formation rotated -60 degrees around unit #1, compared to the mission template, but with all units facing due north regardless of how they were laid out in the template. --- mySpawner:InitGroupHeading(-60):InitHeading(0) --- or --- mySpawner:InitHeading(0):InitGroupHeading(-60) --- +-- +-- -- Spawn the Group with the formation rotated +100 degrees around unit #1, compared to the mission template. +-- mySpawner:InitGroupHeading( 100 ) +-- +-- -- Spawn the Group with the formation rotated units between +100 and +150 degrees around unit #1, compared to the mission template, and with individual units varying by +/- 10 degrees from their templated facing. +-- mySpawner:InitGroupHeading( 100, 150, 10 ) +-- +-- -- Spawn the Group with the formation rotated -60 degrees around unit #1, compared to the mission template, but with all units facing due north regardless of how they were laid out in the template. +-- mySpawner:InitGroupHeading(-60):InitHeading(0) +-- -- or +-- mySpawner:InitHeading(0):InitGroupHeading(-60) +-- function SPAWN:InitGroupHeading( HeadingMin, HeadingMax, unitVar ) - self:F({HeadingMin=HeadingMin, HeadingMax=HeadingMax, unitVar=unitVar}) + self:F( { HeadingMin = HeadingMin, HeadingMax = HeadingMax, unitVar = unitVar } ) self.SpawnInitGroupHeadingMin = HeadingMin self.SpawnInitGroupHeadingMax = HeadingMax - self.SpawnInitGroupUnitVar = unitVar + self.SpawnInitGroupUnitVar = unitVar return self end - --- Sets the coalition of the spawned group. Note that it might be necessary to also set the country explicitly! --- @param #SPAWN self +-- @param #SPAWN self -- @param DCS#coalition.side Coalition Coalition of the group as number of enumerator: --- --- * @{DCS#coaliton.side.NEUTRAL} --- * @{DCS#coaliton.side.RED} +-- +-- * @{DCS#coalition.side.NEUTRAL} +-- * @{DCS#coalition.side.RED} -- * @{DCS#coalition.side.BLUE} --- +-- -- @return #SPAWN self function SPAWN:InitCoalition( Coalition ) - self:F({coalition=Coalition}) + self:F( { coalition = Coalition } ) self.SpawnInitCoalition = Coalition - + return self end ---- Sets the country of the spawn group. Note that the country determins the coalition of the group depending on which country is defined to be on which side for each specific mission! --- @param #SPAWN self +--- Sets the country of the spawn group. Note that the country determines the coalition of the group depending on which country is defined to be on which side for each specific mission! +-- @param #SPAWN self -- @param #number Country Country id as number or enumerator: --- +-- -- * @{DCS#country.id.RUSSIA} -- * @{DCS#county.id.USA} --- +-- -- @return #SPAWN self function SPAWN:InitCountry( Country ) - self:F( ) + self:F() self.SpawnInitCountry = Country - + return self end - --- Sets category ID of the group. --- @param #SPAWN self +-- @param #SPAWN self -- @param #number Category Category id. -- @return #SPAWN self function SPAWN:InitCategory( Category ) - self:F( ) + self:F() self.SpawnInitCategory = Category - + return self end --- Sets livery of the group. --- @param #SPAWN self --- @param #string Livery Livery name. Note that this is not necessarily the same name as displayed in the mission edior. +-- @param #SPAWN self +-- @param #string Livery Livery name. Note that this is not necessarily the same name as displayed in the mission editor. -- @return #SPAWN self function SPAWN:InitLivery( Livery ) - self:F({livery=Livery} ) + self:F( { livery = Livery } ) self.SpawnInitLivery = Livery - + return self end --- Sets skill of the group. --- @param #SPAWN self +-- @param #SPAWN self -- @param #string Skill Skill, possible values "Average", "Good", "High", "Excellent" or "Random". -- @return #SPAWN self 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" + 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" + self.SpawnInitSkill = "High" end - + return self end ---- Sets the radio comms on or off. Same as checking/unchecking the COMM box in the mission editor. --- @param #SPAWN self --- @param #number switch If true (or nil), enables the radio comms. If false, disables the radio for the spawned group. +--- Sets the radio communication on or off. Same as checking/unchecking the COMM box in the mission editor. +-- @param #SPAWN self +-- @param #number switch If true (or nil), enables the radio communication. If false, disables the radio for the spawned group. -- @return #SPAWN self -function SPAWN:InitRadioCommsOnOff(switch) - self:F({switch=switch} ) - self.SpawnInitRadio=switch or true +function SPAWN:InitRadioCommsOnOff( switch ) + self:F( { switch = switch } ) + self.SpawnInitRadio = switch or true return self end @@ -686,11 +675,11 @@ end -- @param #SPAWN self -- @param #number frequency The frequency in MHz. -- @return #SPAWN self -function SPAWN:InitRadioFrequency(frequency) - self:F({frequency=frequency} ) +function SPAWN:InitRadioFrequency( frequency ) + self:F( { frequency = frequency } ) + + self.SpawnInitFreq = frequency - self.SpawnInitFreq=frequency - return self end @@ -698,64 +687,65 @@ end -- @param #SPAWN self -- @param #string modulation Either "FM" or "AM". If no value is given, modulation is set to AM. -- @return #SPAWN self -function SPAWN:InitRadioModulation(modulation) - self:F({modulation=modulation}) - if modulation and modulation:lower()=="fm" then - self.SpawnInitModu=radio.modulation.FM +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 + self.SpawnInitModu = radio.modulation.AM end return self end --- Sets the modex of the first unit of the group. If more units are in the group, the number is increased by one with every unit. --- @param #SPAWN self +-- @param #SPAWN self -- @param #number modex Modex of the first unit. -- @return #SPAWN self -function SPAWN:InitModex(modex) +function SPAWN:InitModex( modex ) if modex then - self.SpawnInitModex=tonumber(modex) + self.SpawnInitModex = tonumber( modex ) end - + return self end - ---- Randomizes the defined route of the SpawnTemplatePrefix group in the ME. This is very useful to define extra variation of the behaviour of groups. +--- Randomizes the defined route of the SpawnTemplatePrefix group in the ME. This is very useful to define extra variation of the behavior of groups. -- @param #SPAWN self --- @param #number SpawnStartPoint is the waypoint where the randomization begins. +-- @param #number SpawnStartPoint is the waypoint where the randomization begins. -- Note that the StartPoint = 0 equaling the point where the group is spawned. --- @param #number SpawnEndPoint is the waypoint where the randomization ends counting backwards. +-- @param #number SpawnEndPoint is the waypoint where the randomization ends counting backwards. -- This parameter is useful to avoid randomization to end at a waypoint earlier than the last waypoint on the route. -- @param #number SpawnRadius is the radius in meters in which the randomization of the new waypoints, with the original waypoint of the original template located in the middle ... -- @param #number SpawnHeight (optional) Specifies the **additional** height in meters that can be added to the base height specified at each waypoint in the ME. -- @return #SPAWN -- @usage --- -- NATO helicopters engaging in the battle field. --- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP). --- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter. --- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters. --- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 ) +-- +-- -- NATO helicopters engaging in the battle field. +-- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP). +-- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter. +-- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters. +-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 ) +-- function SPAWN:InitRandomizeRoute( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight ) - self:F( { self.SpawnTemplatePrefix, 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 + 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 + for GroupID = 1, self.SpawnMaxGroups do + self:_RandomizeRoute( GroupID ) + end + + return self end --- Randomizes the position of @{Wrapper.Group}s that are spawned within a **radius band**, given an Outer and Inner radius, from the point that the spawn happens. -- @param #SPAWN self --- @param #boolean RandomizePosition If true, SPAWN will perform the randomization of the @{Wrapper.Group}s position between a given outer and inner radius. +-- @param #boolean RandomizePosition If true, SPAWN will perform the randomization of the @{Wrapper.Group}s position between a given outer and inner radius. -- @param DCS#Distance OuterRadius (optional) The outer radius in meters where the new group will be spawned. -- @param DCS#Distance InnerRadius (optional) The inner radius in meters where the new group will NOT be spawned. -- @return #SPAWN @@ -769,23 +759,24 @@ function SPAWN:InitRandomizePosition( RandomizePosition, OuterRadius, InnerRadiu for GroupID = 1, self.SpawnMaxGroups do self:_RandomizeRoute( GroupID ) end - + return self end - --- Randomizes the UNITs that are spawned within a radius band given an Outer and Inner radius. -- @param #SPAWN self --- @param #boolean RandomizeUnits If true, SPAWN will perform the randomization of the @{UNIT}s position within the group between a given outer and inner radius. +-- @param #boolean RandomizeUnits If true, SPAWN will perform the randomization of the @{UNIT}s position within the group between a given outer and inner radius. -- @param DCS#Distance OuterRadius (optional) The outer radius in meters where the new group will be spawned. -- @param DCS#Distance InnerRadius (optional) The inner radius in meters where the new group will NOT be spawned. -- @return #SPAWN -- @usage --- -- NATO helicopters engaging in the battle field. --- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP). --- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter. --- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters. --- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 ) +-- +-- -- NATO helicopters engaging in the battle field. +-- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP). +-- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter. +-- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters. +-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 ) +-- function SPAWN:InitRandomizeUnits( RandomizeUnits, OuterRadius, InnerRadius ) self:F( { self.SpawnTemplatePrefix, RandomizeUnits, OuterRadius, InnerRadius } ) @@ -796,65 +787,67 @@ function SPAWN:InitRandomizeUnits( RandomizeUnits, OuterRadius, InnerRadius ) for GroupID = 1, self.SpawnMaxGroups do self:_RandomizeRoute( GroupID ) end - + return self end --- This method is rather complicated to understand. But I'll try to explain. --- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor, +-- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor, -- but they will all follow the same Template route and have the same prefix name. -- In other words, this method randomizes between a defined set of groups the template to be used for each new spawn of a group. -- @param #SPAWN self --- @param #string SpawnTemplatePrefixTable A table with the names of the groups defined within the mission editor, from which one will be choosen when a new group will be spawned. +-- @param #string SpawnTemplatePrefixTable A table with the names of the groups defined within the mission editor, from which one will be chosen when a new group will be spawned. -- @return #SPAWN -- @usage --- -- NATO Tank Platoons invading Gori. --- -- Choose between 13 different 'US Tank Platoon' configurations for each new SPAWN the Group to be spawned for the --- -- 'US Tank Platoon Left', 'US Tank Platoon Middle' and 'US Tank Platoon Right' SpawnTemplatePrefixes. --- -- Each new SPAWN will randomize the route, with a defined time interval of 200 seconds with 40% time variation (randomization) and --- -- with a limit set of maximum 12 Units alive simulteneously and 150 Groups to be spawned during the whole mission. --- Spawn_US_Platoon = { 'US Tank Platoon 1', 'US Tank Platoon 2', 'US Tank Platoon 3', 'US Tank Platoon 4', 'US Tank Platoon 5', --- 'US Tank Platoon 6', 'US Tank Platoon 7', 'US Tank Platoon 8', 'US Tank Platoon 9', 'US Tank Platoon 10', --- 'US Tank Platoon 11', 'US Tank Platoon 12', 'US Tank Platoon 13' } --- Spawn_US_Platoon_Left = SPAWN:New( 'US Tank Platoon Left' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) --- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) --- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) +-- +-- -- NATO Tank Platoons invading Gori. +-- -- Choose between 13 different 'US Tank Platoon' configurations for each new SPAWN the Group to be spawned for the +-- -- 'US Tank Platoon Left', 'US Tank Platoon Middle' and 'US Tank Platoon Right' SpawnTemplatePrefixes. +-- -- Each new SPAWN will randomize the route, with a defined time interval of 200 seconds with 40% time variation (randomization) and +-- -- with a limit set of maximum 12 Units alive simultaneously and 150 Groups to be spawned during the whole mission. +-- Spawn_US_Platoon = { 'US Tank Platoon 1', 'US Tank Platoon 2', 'US Tank Platoon 3', 'US Tank Platoon 4', 'US Tank Platoon 5', +-- 'US Tank Platoon 6', 'US Tank Platoon 7', 'US Tank Platoon 8', 'US Tank Platoon 9', 'US Tank Platoon 10', +-- 'US Tank Platoon 11', 'US Tank Platoon 12', 'US Tank Platoon 13' } +-- Spawn_US_Platoon_Left = SPAWN:New( 'US Tank Platoon Left' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) +-- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) +-- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) function SPAWN:InitRandomizeTemplate( SpawnTemplatePrefixTable ) - self:F( { self.SpawnTemplatePrefix, SpawnTemplatePrefixTable } ) + self:F( { self.SpawnTemplatePrefix, SpawnTemplatePrefixTable } ) - self.SpawnTemplatePrefixTable = SpawnTemplatePrefixTable - self.SpawnRandomizeTemplate = true + self.SpawnTemplatePrefixTable = SpawnTemplatePrefixTable + self.SpawnRandomizeTemplate = true - for SpawnGroupID = 1, self.SpawnMaxGroups do - self:_RandomizeTemplate( SpawnGroupID ) - end - - return self + for SpawnGroupID = 1, self.SpawnMaxGroups do + self:_RandomizeTemplate( SpawnGroupID ) + end + + return self end - --- Randomize templates to be used as the unit representatives for the Spawned group, defined using a SET_GROUP object. --- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor, +-- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor, -- but they will all follow the same Template route and have the same prefix name. -- In other words, this method randomizes between a defined set of groups the template to be used for each new spawn of a group. -- @param #SPAWN self --- @param Core.Set#SET_GROUP SpawnTemplateSet A SET_GROUP object set, that contains the groups that are possible unit representatives of the group to be spawned. +-- @param Core.Set#SET_GROUP SpawnTemplateSet A SET_GROUP object set, that contains the groups that are possible unit representatives of the group to be spawned. -- @return #SPAWN -- @usage --- -- NATO Tank Platoons invading Gori. --- --- -- Choose between different 'US Tank Platoon Template' configurations to be spawned for the --- -- 'US Tank Platoon Left', 'US Tank Platoon Middle' and 'US Tank Platoon Right' SPAWN objects. --- --- -- Each new SPAWN will randomize the route, with a defined time interval of 200 seconds with 40% time variation (randomization) and --- -- with a limit set of maximum 12 Units alive simulteneously and 150 Groups to be spawned during the whole mission. --- --- Spawn_US_PlatoonSet = SET_GROUP:New():FilterPrefixes( "US Tank Platoon Templates" ):FilterOnce() --- --- --- Now use the Spawn_US_PlatoonSet to define the templates using InitRandomizeTemplateSet. --- Spawn_US_Platoon_Left = SPAWN:New( 'US Tank Platoon Left' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplateSet( Spawn_US_PlatoonSet ):InitRandomizeRoute( 3, 3, 2000 ) --- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplateSet( Spawn_US_PlatoonSet ):InitRandomizeRoute( 3, 3, 2000 ) --- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplateSet( Spawn_US_PlatoonSet ):InitRandomizeRoute( 3, 3, 2000 ) +-- +-- -- NATO Tank Platoons invading Gori. +-- +-- -- Choose between different 'US Tank Platoon Template' configurations to be spawned for the +-- -- 'US Tank Platoon Left', 'US Tank Platoon Middle' and 'US Tank Platoon Right' SPAWN objects. +-- +-- -- Each new SPAWN will randomize the route, with a defined time interval of 200 seconds with 40% time variation (randomization) and +-- -- with a limit set of maximum 12 Units alive simultaneously and 150 Groups to be spawned during the whole mission. +-- +-- Spawn_US_PlatoonSet = SET_GROUP:New():FilterPrefixes( "US Tank Platoon Templates" ):FilterOnce() +-- +-- -- Now use the Spawn_US_PlatoonSet to define the templates using InitRandomizeTemplateSet. +-- Spawn_US_Platoon_Left = SPAWN:New( 'US Tank Platoon Left' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplateSet( Spawn_US_PlatoonSet ):InitRandomizeRoute( 3, 3, 2000 ) +-- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplateSet( Spawn_US_PlatoonSet ):InitRandomizeRoute( 3, 3, 2000 ) +-- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplateSet( Spawn_US_PlatoonSet ):InitRandomizeRoute( 3, 3, 2000 ) +-- function SPAWN:InitRandomizeTemplateSet( SpawnTemplateSet ) -- R2.3 self:F( { self.SpawnTemplatePrefix } ) @@ -864,44 +857,44 @@ function SPAWN:InitRandomizeTemplateSet( SpawnTemplateSet ) -- R2.3 for SpawnGroupID = 1, self.SpawnMaxGroups do self:_RandomizeTemplate( SpawnGroupID ) end - + return self end - --- Randomize templates to be used as the unit representatives for the Spawned group, defined by specifying the prefix names. --- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor, +-- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor, -- but they will all follow the same Template route and have the same prefix name. -- In other words, this method randomizes between a defined set of groups the template to be used for each new spawn of a group. -- @param #SPAWN self -- @param #string SpawnTemplatePrefixes A string or a list of string that contains the prefixes of the groups that are possible unit representatives of the group to be spawned. -- @return #SPAWN -- @usage --- -- NATO Tank Platoons invading Gori. --- --- -- Choose between different 'US Tank Platoon Templates' configurations to be spawned for the --- -- 'US Tank Platoon Left', 'US Tank Platoon Middle' and 'US Tank Platoon Right' SPAWN objects. --- --- -- Each new SPAWN will randomize the route, with a defined time interval of 200 seconds with 40% time variation (randomization) and --- -- with a limit set of maximum 12 Units alive simulteneously and 150 Groups to be spawned during the whole mission. --- --- Spawn_US_Platoon_Left = SPAWN:New( 'US Tank Platoon Left' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplatePrefixes( "US Tank Platoon Templates" ):InitRandomizeRoute( 3, 3, 2000 ) --- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplatePrefixes( "US Tank Platoon Templates" ):InitRandomizeRoute( 3, 3, 2000 ) --- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplatePrefixes( "US Tank Platoon Templates" ):InitRandomizeRoute( 3, 3, 2000 ) -function SPAWN:InitRandomizeTemplatePrefixes( SpawnTemplatePrefixes ) --R2.3 +-- +-- -- NATO Tank Platoons invading Gori. +-- +-- -- Choose between different 'US Tank Platoon Templates' configurations to be spawned for the +-- -- 'US Tank Platoon Left', 'US Tank Platoon Middle' and 'US Tank Platoon Right' SPAWN objects. +-- +-- -- Each new SPAWN will randomize the route, with a defined time interval of 200 seconds with 40% time variation (randomization) and +-- -- with a limit set of maximum 12 Units alive simultaneously and 150 Groups to be spawned during the whole mission. +-- +-- Spawn_US_Platoon_Left = SPAWN:New( 'US Tank Platoon Left' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplatePrefixes( "US Tank Platoon Templates" ):InitRandomizeRoute( 3, 3, 2000 ) +-- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplatePrefixes( "US Tank Platoon Templates" ):InitRandomizeRoute( 3, 3, 2000 ) +-- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplatePrefixes( "US Tank Platoon Templates" ):InitRandomizeRoute( 3, 3, 2000 ) +-- +function SPAWN:InitRandomizeTemplatePrefixes( SpawnTemplatePrefixes ) -- R2.3 self:F( { self.SpawnTemplatePrefix } ) local SpawnTemplateSet = SET_GROUP:New():FilterPrefixes( SpawnTemplatePrefixes ):FilterOnce() self:InitRandomizeTemplateSet( SpawnTemplateSet ) - + return self end - --- When spawning a new group, make the grouping of the units according the InitGrouping setting. -- @param #SPAWN self --- @param #number Grouping Indicates the maximum amount of units in the group. +-- @param #number Grouping Indicates the maximum amount of units in the group. -- @return #SPAWN function SPAWN:InitGrouping( Grouping ) -- R2.2 self:F( { self.SpawnTemplatePrefix, Grouping } ) @@ -911,22 +904,21 @@ function SPAWN:InitGrouping( Grouping ) -- R2.2 return self end - - --- This method provides the functionality to randomize the spawning of the Groups at a given list of zones of different types. -- @param #SPAWN self --- @param #table SpawnZoneTable A table with @{Zone} objects. If this table is given, then each spawn will be executed within the given list of @{Zone}s objects. +-- @param #table SpawnZoneTable A table with @{Zone} objects. If this table is given, then each spawn will be executed within the given list of @{Zone}s objects. -- @return #SPAWN -- @usage --- -- Create a zone table of the 2 zones. --- ZoneTable = { ZONE:New( "Zone1" ), ZONE:New( "Zone2" ) } --- --- Spawn_Vehicle_1 = SPAWN:New( "Spawn Vehicle 1" ) --- :InitLimit( 10, 10 ) --- :InitRandomizeRoute( 1, 1, 200 ) --- :InitRandomizeZones( ZoneTable ) --- :SpawnScheduled( 5, .5 ) --- +-- +-- -- Create a zone table of the 2 zones. +-- ZoneTable = { ZONE:New( "Zone1" ), ZONE:New( "Zone2" ) } +-- +-- Spawn_Vehicle_1 = SPAWN:New( "Spawn Vehicle 1" ) +-- :InitLimit( 10, 10 ) +-- :InitRandomizeRoute( 1, 1, 200 ) +-- :InitRandomizeZones( ZoneTable ) +-- :SpawnScheduled( 5, .5 ) +-- function SPAWN:InitRandomizeZones( SpawnZoneTable ) self:F( { self.SpawnTemplatePrefix, SpawnZoneTable } ) @@ -936,111 +928,106 @@ function SPAWN:InitRandomizeZones( SpawnZoneTable ) for SpawnGroupID = 1, self.SpawnMaxGroups do self:_RandomizeZones( SpawnGroupID ) end - + return self end - - - - ---- For planes and helicopters, when these groups go home and land on their home airbases and farps, they normally would taxi to the parking spot, shut-down their engines and wait forever until the Group is removed by the runtime environment. --- This method is used to re-spawn automatically (so no extra call is needed anymore) the same group after it has landed. +--- For planes and helicopters, when these groups go home and land on their home airbases and FARPs, they normally would taxi to the parking spot, shut-down their engines and wait forever until the Group is removed by the runtime environment. +-- This method is used to re-spawn automatically (so no extra call is needed anymore) the same group after it has landed. -- This will enable a spawned group to be re-spawned after it lands, until it is destroyed... --- Note: When the group is respawned, it will re-spawn from the original airbase where it took off. +-- Note: When the group is respawned, it will re-spawn from the original airbase where it took off. -- So ensure that the routes for groups that respawn, always return to the original airbase, or players may get confused ... -- @param #SPAWN self -- @return #SPAWN self -- @usage --- -- RU Su-34 - AI Ship Attack --- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically. --- SpawnRU_SU34 = SPAWN --- :New( 'Su-34' ) --- :Schedule( 2, 3, 1800, 0.4 ) --- :SpawnUncontrolled() --- :InitRandomizeRoute( 1, 1, 3000 ) --- :InitRepeatOnEngineShutDown() --- +-- +-- -- RU Su-34 - AI Ship Attack +-- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically. +-- SpawnRU_SU34 = SPAWN:New( 'Su-34' ) +-- :Schedule( 2, 3, 1800, 0.4 ) +-- :SpawnUncontrolled() +-- :InitRandomizeRoute( 1, 1, 3000 ) +-- :InitRepeatOnEngineShutDown() +-- function SPAWN:InitRepeat() - self:F( { self.SpawnTemplatePrefix, self.SpawnIndex } ) + self:F( { self.SpawnTemplatePrefix, self.SpawnIndex } ) - self.Repeat = true - self.RepeatOnEngineShutDown = false - self.RepeatOnLanding = true + self.Repeat = true + self.RepeatOnEngineShutDown = false + self.RepeatOnLanding = true - return self + return self end --- Respawn group after landing. -- @param #SPAWN self -- @return #SPAWN self -- @usage --- -- RU Su-34 - AI Ship Attack --- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically. --- SpawnRU_SU34 = SPAWN --- :New( 'Su-34' ) --- :InitRandomizeRoute( 1, 1, 3000 ) --- :InitRepeatOnLanding() --- :Spawn() +-- +-- -- RU Su-34 - AI Ship Attack +-- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically. +-- SpawnRU_SU34 = SPAWN:New( 'Su-34' ) +-- :InitRandomizeRoute( 1, 1, 3000 ) +-- :InitRepeatOnLanding() +-- :Spawn() +-- function SPAWN:InitRepeatOnLanding() - self:F( { self.SpawnTemplatePrefix } ) + self:F( { self.SpawnTemplatePrefix } ) - self:InitRepeat() - self.RepeatOnEngineShutDown = false - self.RepeatOnLanding = true - - return self + self:InitRepeat() + self.RepeatOnEngineShutDown = false + self.RepeatOnLanding = true + + return self end - --- Respawn after landing when its engines have shut down. -- @param #SPAWN self -- @return #SPAWN self -- @usage --- -- RU Su-34 - AI Ship Attack --- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically. --- SpawnRU_SU34 = SPAWN --- :New( 'Su-34' ) --- :SpawnUncontrolled() --- :InitRandomizeRoute( 1, 1, 3000 ) --- :InitRepeatOnEngineShutDown() --- :Spawn() +-- +-- -- RU Su-34 - AI Ship Attack +-- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically. +-- SpawnRU_SU34 = SPAWN:New( 'Su-34' ) +-- :SpawnUncontrolled() +-- :InitRandomizeRoute( 1, 1, 3000 ) +-- :InitRepeatOnEngineShutDown() +-- :Spawn() function SPAWN:InitRepeatOnEngineShutDown() - self:F( { self.SpawnTemplatePrefix } ) + self:F( { self.SpawnTemplatePrefix } ) - self:InitRepeat() - self.RepeatOnEngineShutDown = true - self.RepeatOnLanding = false - - return self + self:InitRepeat() + self.RepeatOnEngineShutDown = true + self.RepeatOnLanding = false + + return self end - --- Delete groups that have not moved for X seconds - AIR ONLY!!! -- DO NOT USE ON GROUPS THAT DO NOT MOVE OR YOUR SERVER WILL BURN IN HELL (Pikes - April 2020) -- When groups are still alive and have become inactive due to damage and are unable to contribute anything, then this group will be removed at defined intervals in seconds. -- @param #SPAWN self -- @param #string SpawnCleanUpInterval The interval to check for inactive groups within seconds. -- @return #SPAWN self --- @usage --- Spawn_Helicopter:InitCleanUp( 20 ) -- CleanUp the spawning of the helicopters every 20 seconds when they become inactive. +-- @usage +-- +-- Spawn_Helicopter:InitCleanUp( 20 ) -- CleanUp the spawning of the helicopters every 20 seconds when they become inactive. +-- function SPAWN:InitCleanUp( SpawnCleanUpInterval ) - self:F( { self.SpawnTemplatePrefix, SpawnCleanUpInterval } ) + self:F( { self.SpawnTemplatePrefix, SpawnCleanUpInterval } ) - self.SpawnCleanUpInterval = SpawnCleanUpInterval - self.SpawnCleanUpTimeStamps = {} + self.SpawnCleanUpInterval = SpawnCleanUpInterval + self.SpawnCleanUpTimeStamps = {} local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup() self:T( { "CleanUp Scheduler:", SpawnGroup } ) - - --self.CleanUpFunction = routines.scheduleFunction( self._SpawnCleanUpScheduler, { self }, timer.getTime() + 1, SpawnCleanUpInterval ) - self.CleanUpScheduler = SCHEDULER:New( self, self._SpawnCleanUpScheduler, {}, 1, SpawnCleanUpInterval, 0.2 ) - return self + + -- self.CleanUpFunction = routines.scheduleFunction( self._SpawnCleanUpScheduler, { self }, timer.getTime() + 1, SpawnCleanUpInterval ) + self.CleanUpScheduler = SCHEDULER:New( self, self._SpawnCleanUpScheduler, {}, 1, SpawnCleanUpInterval, 0.2 ) + return self end - - ---- Makes the groups visible before start (like a batallion). +--- Makes the groups visible before start (like a battalion). -- The method will take the position of the group as the first position in the array. -- CAUTION: this directive will NOT work with OnSpawnGroup function. -- @param #SPAWN self @@ -1050,45 +1037,45 @@ end -- @param #number SpawnDeltaY The space between each Group on the Y-axis. -- @return #SPAWN self -- @usage --- -- Define an array of Groups. --- Spawn_BE_Ground = SPAWN --- :New( 'BE Ground' ) --- :InitLimit( 2, 24 ) --- :InitArray( 90, 10, 100, 50 ) --- +-- +-- -- Define an array of Groups. +-- Spawn_BE_Ground = SPAWN:New( 'BE Ground' ) +-- :InitLimit( 2, 24 ) +-- :InitArray( 90, 10, 100, 50 ) +-- function SPAWN:InitArray( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) - self:F( { self.SpawnTemplatePrefix, SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY } ) + self:F( { self.SpawnTemplatePrefix, SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY } ) - self.SpawnVisible = true -- When the first Spawn executes, all the Groups need to be made visible before start. - - 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.SpawnVisible = true -- When the first Spawn executes, all the Groups need to be made visible before start. - 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 SpawnX = 0 + local SpawnY = 0 + local SpawnXIndex = 0 + local SpawnYIndex = 0 - 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 + 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 ) @@ -1101,40 +1088,41 @@ function SPAWN:InitArray( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) 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 + self.SpawnGroups[SpawnGroupID].Group = _DATABASE:Spawn( self.SpawnGroups[SpawnGroupID].SpawnTemplate ) + + SpawnX = SpawnXIndex * SpawnDeltaX + SpawnY = SpawnYIndex * SpawnDeltaY + end + + return self end do -- AI methods + --- Turns the AI On or Off for the @{Wrapper.Group} when spawning. -- @param #SPAWN self -- @param #boolean AIOnOff A value of true sets the AI On, a value of false sets the AI Off. -- @return #SPAWN The SPAWN object function SPAWN:InitAIOnOff( AIOnOff ) - + self.AIOnOff = AIOnOff return self end - + --- Turns the AI On for the @{Wrapper.Group} when spawning. -- @param #SPAWN self -- @return #SPAWN The SPAWN object function SPAWN:InitAIOn() - + return self:InitAIOnOff( true ) end - + --- Turns the AI Off for the @{Wrapper.Group} when spawning. -- @param #SPAWN self -- @return #SPAWN The SPAWN object function SPAWN:InitAIOff() - + return self:InitAIOnOff( false ) end @@ -1147,24 +1135,24 @@ do -- Delay methods -- @param #boolean DelayOnOff A value of true sets the Delay On, a value of false sets the Delay Off. -- @return #SPAWN The SPAWN object function SPAWN:InitDelayOnOff( DelayOnOff ) - + self.DelayOnOff = DelayOnOff return self end - + --- Turns the Delay On for the @{Wrapper.Group} when spawning. -- @param #SPAWN self -- @return #SPAWN The SPAWN object function SPAWN:InitDelayOn() - + return self:InitDelayOnOff( true ) end - + --- Turns the Delay Off for the @{Wrapper.Group} when spawning. -- @param #SPAWN self -- @return #SPAWN The SPAWN object function SPAWN:InitDelayOff() - + return self:InitDelayOnOff( false ) end @@ -1175,14 +1163,14 @@ end -- Delay methods -- @param #SPAWN self -- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions. function SPAWN:Spawn() - self:F( { self.SpawnTemplatePrefix, self.SpawnIndex, self.AliveUnits } ) + self:F( { self.SpawnTemplatePrefix, self.SpawnIndex, self.AliveUnits } ) if self.SpawnInitAirbase then - return self:SpawnAtAirbase(self.SpawnInitAirbase, self.SpawnInitTakeoff, nil, self.SpawnInitTerminalType) + return self:SpawnAtAirbase( self.SpawnInitAirbase, self.SpawnInitTakeoff, nil, self.SpawnInitTerminalType ) else - return self:SpawnWithIndex( self.SpawnIndex + 1 ) - end - + return self:SpawnWithIndex( self.SpawnIndex + 1 ) + end + end --- Will re-spawn a group based on a given index. @@ -1191,38 +1179,37 @@ end -- @param #string SpawnIndex The index of the group to be spawned. -- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions. function SPAWN:ReSpawn( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) - - if not SpawnIndex then - SpawnIndex = 1 - end + self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) --- TODO: This logic makes DCS crash and i don't know why (yet). -- ED (Pikes -- not in the least bit scary to see this, right?) - 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 + if not SpawnIndex then + SpawnIndex = 1 end - local SpawnGroup = self:SpawnWithIndex( SpawnIndex ) - if SpawnGroup and WayPoints then - -- If there were WayPoints set, then Re-Execute those WayPoints! - SpawnGroup:WayPointInitialize( WayPoints ) - SpawnGroup:WayPointExecute( 1, 5 ) - end - - if SpawnGroup.ReSpawnFunction then - SpawnGroup:ReSpawnFunction() - end - - SpawnGroup:ResetEvents() - - return SpawnGroup -end + -- TODO: This logic makes DCS crash and i don't know why (yet). -- ED (Pikes -- not in the least bit scary to see this, right?) + 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 + -- If there were WayPoints set, then Re-Execute those WayPoints! + SpawnGroup:WayPointInitialize( WayPoints ) + SpawnGroup:WayPointExecute( 1, 5 ) + end + + if SpawnGroup.ReSpawnFunction then + SpawnGroup:ReSpawnFunction() + end + + SpawnGroup:ResetEvents() + + return SpawnGroup +end --- Set the spawn index to a specified index number. -- This method can be used to "reset" the spawn counter to a specific index number. @@ -1234,23 +1221,22 @@ function SPAWN:SetSpawnIndex( SpawnIndex ) self.SpawnIndex = SpawnIndex or 0 end - --- Will spawn a group with a specified index number. -- Uses @{DATABASE} global object defined in MOOSE. -- @param #SPAWN self -- @param #string SpawnIndex The index of the group to be spawned. -- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions. 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 + self:F2( { SpawnTemplatePrefix = self.SpawnTemplatePrefix, SpawnIndex = SpawnIndex, AliveUnits = self.AliveUnits, SpawnMaxGroups = self.SpawnMaxGroups } ) - local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate - self:T( SpawnTemplate.name ) + 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 @@ -1265,46 +1251,46 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) 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 ) + 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 RandomizeUnits, then Randomize the formation at the start point. 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 ) + self:T( 'SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) end end - - -- Get correct heading in Radians. - 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 + -- Get correct heading in Radians. + 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) + return -((2 * Rad180) - courseRad) end - end + end -- Generate a random value somewhere between two floating point values. - local function _RandomInRange ( min, max ) + local function _RandomInRange( min, max ) if min and max then - return min + ( math.random()*(max-min) ) + return min + (math.random() * (max - min)) else return min end @@ -1318,39 +1304,39 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) local pivotX = SpawnTemplate.units[1].x -- unit #1 is the pivot point 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) + 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 -- don't rotate position of unit #1 local unitXOff = SpawnTemplate.units[UnitID].x - pivotX -- rotate position offset around unit #1 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) + SpawnTemplate.units[UnitID].x = pivotX + (unitXOff * cosHeading) - (unitYOff * sinHeading) + SpawnTemplate.units[UnitID].y = pivotY + (unitYOff * cosHeading) + (unitXOff * sinHeading) end - + -- adjust heading of all units, including unit #1 local unitHeading = SpawnTemplate.units[UnitID].heading + headingRad -- add group rotation to units default rotation - SpawnTemplate.units[UnitID].heading = _HeadingRad(_RandomInRange(unitHeading-unitVarRad, unitHeading+unitVarRad)) + SpawnTemplate.units[UnitID].heading = _HeadingRad( _RandomInRange( unitHeading - unitVarRad, unitHeading + unitVarRad ) ) SpawnTemplate.units[UnitID].psi = -SpawnTemplate.units[UnitID].heading - + end end - + -- If Heading is given, point all the units towards the given Heading. Overrides any heading set in InitGroupHeading above. if self.SpawnInitHeadingMin then for UnitID = 1, #SpawnTemplate.units do - SpawnTemplate.units[UnitID].heading = _Heading(_RandomInRange(self.SpawnInitHeadingMin, self.SpawnInitHeadingMax)) + SpawnTemplate.units[UnitID].heading = _Heading( _RandomInRange( self.SpawnInitHeadingMin, self.SpawnInitHeadingMax ) ) SpawnTemplate.units[UnitID].psi = -SpawnTemplate.units[UnitID].heading end end - + -- Set livery. if self.SpawnInitLivery then for UnitID = 1, #SpawnTemplate.units do @@ -1368,39 +1354,38 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) -- Set tail number. if self.SpawnInitModex then for UnitID = 1, #SpawnTemplate.units do - SpawnTemplate.units[UnitID].onboard_num = string.format("%03d", self.SpawnInitModex+(UnitID-1)) + SpawnTemplate.units[UnitID].onboard_num = string.format( "%03d", self.SpawnInitModex + (UnitID - 1) ) end end - + -- Set radio comms on/off. if self.SpawnInitRadio then - SpawnTemplate.communication=self.SpawnInitRadio - end - + SpawnTemplate.communication = self.SpawnInitRadio + end + -- Set radio frequency. if self.SpawnInitFreq then - SpawnTemplate.frequency=self.SpawnInitFreq + SpawnTemplate.frequency = self.SpawnInitFreq end - + -- Set radio modulation. if self.SpawnInitModu then - SpawnTemplate.modulation=self.SpawnInitModu - end - + SpawnTemplate.modulation = self.SpawnInitModu + end + -- Set country, coaliton and categroy. - SpawnTemplate.CategoryID = self.SpawnInitCategory or SpawnTemplate.CategoryID - SpawnTemplate.CountryID = self.SpawnInitCountry or SpawnTemplate.CountryID - SpawnTemplate.CoalitionID = self.SpawnInitCoalition or SpawnTemplate.CoalitionID - - --- if SpawnTemplate.CategoryID == Group.Category.HELICOPTER or SpawnTemplate.CategoryID == Group.Category.AIRPLANE then --- if SpawnTemplate.route.points[1].type == "TakeOffParking" then --- SpawnTemplate.uncontrolled = self.SpawnUnControlled --- end --- end + SpawnTemplate.CategoryID = self.SpawnInitCategory or SpawnTemplate.CategoryID + SpawnTemplate.CountryID = self.SpawnInitCountry or SpawnTemplate.CountryID + SpawnTemplate.CoalitionID = self.SpawnInitCoalition or SpawnTemplate.CoalitionID + + -- if SpawnTemplate.CategoryID == Group.Category.HELICOPTER or SpawnTemplate.CategoryID == Group.Category.AIRPLANE then + -- if SpawnTemplate.route.points[1].type == "TakeOffParking" then + -- SpawnTemplate.uncontrolled = self.SpawnUnControlled + -- end + -- end end - - if not NoBirth then + + if not NoBirth then self:HandleEvent( EVENTS.Birth, self._OnBirth ) end self:HandleEvent( EVENTS.Dead, self._OnDeadOrCrash ) @@ -1414,37 +1399,36 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) self:HandleEvent( EVENTS.EngineShutdown, self._OnEngineShutDown ) end - self.SpawnGroups[self.SpawnIndex].Group = _DATABASE:Spawn( SpawnTemplate ) - - local SpawnGroup = self.SpawnGroups[self.SpawnIndex].Group -- Wrapper.Group#GROUP - - --TODO: Need to check if this function doesn't need to be scheduled, as the group may not be immediately there! + self.SpawnGroups[self.SpawnIndex].Group = _DATABASE:Spawn( SpawnTemplate ) + + local SpawnGroup = self.SpawnGroups[self.SpawnIndex].Group -- Wrapper.Group#GROUP + + -- TODO: Need to check if this function doesn't need to be scheduled, as the group may not be immediately there! if SpawnGroup then - - SpawnGroup:SetAIOnOff( self.AIOnOff ) - end + + SpawnGroup:SetAIOnOff( self.AIOnOff ) + end self:T3( SpawnTemplate.name ) - - -- If there is a SpawnFunction hook defined, call it. - if self.SpawnFunctionHook then - -- delay calling this for .1 seconds so that it hopefully comes after the BIRTH event of the group. - self.SpawnHookScheduler:Schedule( nil, self.SpawnFunctionHook, { self.SpawnGroups[self.SpawnIndex].Group, unpack( self.SpawnFunctionArguments)}, 0.1 ) - end - -- TODO: Need to fix this by putting an "R" in the name of the group when the group repeats. - --if self.Repeat then - -- _DATABASE:SetStatusGroup( SpawnTemplate.name, "ReSpawn" ) - --end - end - - - self.SpawnGroups[self.SpawnIndex].Spawned = true - return self.SpawnGroups[self.SpawnIndex].Group - else - --self:E( { self.SpawnTemplatePrefix, "No more Groups to Spawn:", SpawnIndex, self.SpawnMaxGroups } ) - end - return nil + -- If there is a SpawnFunction hook defined, call it. + if self.SpawnFunctionHook then + -- delay calling this for .1 seconds so that it hopefully comes after the BIRTH event of the group. + self.SpawnHookScheduler:Schedule( nil, self.SpawnFunctionHook, { self.SpawnGroups[self.SpawnIndex].Group, unpack( self.SpawnFunctionArguments ) }, 0.1 ) + end + -- TODO: Need to fix this by putting an "R" in the name of the group when the group repeats. + -- if self.Repeat then + -- _DATABASE:SetStatusGroup( SpawnTemplate.name, "ReSpawn" ) + -- end + end + + self.SpawnGroups[self.SpawnIndex].Spawned = true + return self.SpawnGroups[self.SpawnIndex].Group + else + -- self:E( { self.SpawnTemplatePrefix, "No more Groups to Spawn:", SpawnIndex, self.SpawnMaxGroups } ) + end + + return nil end --- Spawns new groups at varying time intervals. @@ -1452,29 +1436,29 @@ end -- @param #SPAWN self -- @param #number SpawnTime The time interval defined in seconds between each new spawn of new groups. -- @param #number SpawnTimeVariation The variation to be applied on the defined time interval between each new spawn. --- The variation is a number between 0 and 1, representing the %-tage of variation to be applied on the time interval. +-- The variation is a number between 0 and 1, representing the % of variation to be applied on the time interval. -- @return #SPAWN self -- @usage -- -- NATO helicopters engaging in the battle field. -- -- The time interval is set to SPAWN new helicopters between each 600 seconds, with a time variation of 50%. --- -- The time variation in this case will be between 450 seconds and 750 seconds. --- -- This is calculated as follows: --- -- Low limit: 600 * ( 1 - 0.5 / 2 ) = 450 +-- -- The time variation in this case will be between 450 seconds and 750 seconds. +-- -- This is calculated as follows: +-- -- Low limit: 600 * ( 1 - 0.5 / 2 ) = 450 -- -- High limit: 600 * ( 1 + 0.5 / 2 ) = 750 --- -- Between these two values, a random amount of seconds will be choosen for each new spawn of the helicopters. +-- -- Between these two values, a random amount of seconds will be chosen for each new spawn of the helicopters. -- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):SpawnScheduled( 600, 0.5 ) function SPAWN:SpawnScheduled( SpawnTime, SpawnTimeVariation ) - self:F( { 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 + 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 + end - return self + return self end --- Will re-start the spawning scheduler. @@ -1493,12 +1477,11 @@ end -- @return #SPAWN function SPAWN:SpawnScheduleStop() self:F( { self.SpawnTemplatePrefix } ) - + self.SpawnScheduler:Stop() return self end - --- Allows to place a CallFunction hook when a new group spawns. -- The provided method will be called when a new group is spawned, including its given parameters. -- The first parameter of the SpawnFunction is the @{Wrapper.Group#GROUP} that was spawned. @@ -1507,17 +1490,16 @@ end -- @param SpawnFunctionArguments A random amount of arguments to be provided to the function when the group spawns. -- @return #SPAWN -- @usage --- -- Declare SpawnObject and call a function when a new Group is spawned. --- local SpawnObject = SPAWN --- :New( "SpawnObject" ) --- :InitLimit( 2, 10 ) --- :OnSpawnGroup( --- function( SpawnGroup ) --- SpawnGroup:E( "I am spawned" ) --- end --- ) --- :SpawnScheduled( 300, 0.3 ) --- +-- +-- -- Declare SpawnObject and call a function when a new Group is spawned. +-- local SpawnObject = SPAWN:New( "SpawnObject" ) +-- :InitLimit( 2, 10 ) +-- :OnSpawnGroup( function( SpawnGroup ) +-- SpawnGroup:E( "I am spawned" ) +-- end +-- ) +-- :SpawnScheduled( 300, 0.3 ) +-- function SPAWN:OnSpawnGroup( SpawnCallBackFunction, ... ) self:F( "OnSpawnGroup" ) @@ -1525,109 +1507,110 @@ function SPAWN:OnSpawnGroup( SpawnCallBackFunction, ... ) self.SpawnFunctionArguments = {} if arg then self.SpawnFunctionArguments = arg - end + end return self end ---- Will spawn a group at an @{Wrapper.Airbase}. +--- Will spawn a group at an @{Wrapper.Airbase}. -- This method is mostly advisable to be used if you want to simulate spawning units at an airbase. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. -- You can use the returned group to further define the route to be followed. --- +-- -- The @{Wrapper.Airbase#AIRBASE} object must refer to a valid airbase known in the sim. -- You can use the following enumerations to search for the pre-defined airbases on the current known maps of DCS: --- --- * @{Wrapper.Airbase#AIRBASE.Caucasus}: The airbases on the Caucasus map. --- * @{Wrapper.Airbase#AIRBASE.Nevada}: The airbases on the Nevada (NTTR) map. --- * @{Wrapper.Airbase#AIRBASE.Normandy}: The airbases on the Normandy map. --- --- Use the method @{Wrapper.Airbase#AIRBASE.FindByName}() to retrieve the airbase object. +-- +-- * @{Wrapper.Airbase#AIRBASE.Caucasus}: The airbases on the Caucasus map. +-- * @{Wrapper.Airbase#AIRBASE.Nevada}: The airbases on the Nevada (NTTR) map. +-- * @{Wrapper.Airbase#AIRBASE.Normandy}: The airbases on the Normandy map. +-- +-- Use the method @{Wrapper.Airbase#AIRBASE.FindByName}() to retrieve the airbase object. -- The known AIRBASE objects are automatically imported at mission start by MOOSE. -- Therefore, there isn't any New() constructor defined for AIRBASE objects. --- --- Ships and Farps are added within the mission, and are therefore not known. +-- +-- Ships and FARPs are added within the mission, and are therefore not known. -- For these AIRBASE objects, there isn't an @{Wrapper.Airbase#AIRBASE} enumeration defined. -- You need to provide the **exact name** of the airbase as the parameter to the @{Wrapper.Airbase#AIRBASE.FindByName}() method! --- +-- -- @param #SPAWN self -- @param Wrapper.Airbase#AIRBASE SpawnAirbase The @{Wrapper.Airbase} where to spawn the group. -- @param #SPAWN.Takeoff Takeoff (optional) The location and takeoff method. Default is Hot. -- @param #number TakeoffAltitude (optional) The altitude above the ground. -- @param Wrapper.Airbase#AIRBASE.TerminalType TerminalType (optional) The terminal type the aircraft should be spawned at. See @{Wrapper.Airbase#AIRBASE.TerminalType}. -- @param #boolean EmergencyAirSpawn (optional) If true (default), groups are spawned in air if there is no parking spot at the airbase. If false, nothing is spawned if no parking spot is available. --- @param #table Parkingdata (optional) Table holding the coordinates and terminal ids for all units of the group. Spawning will be forced to happen at exactily these spots! +-- @param #table Parkingdata (optional) Table holding the coordinates and terminal ids for all units of the group. Spawning will be forced to happen at exactly these spots! -- @return Wrapper.Group#GROUP The group that was spawned or nil when nothing was spawned. -- @usage +-- -- Spawn_Plane = SPAWN:New( "Plane" ) -- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), SPAWN.Takeoff.Cold ) -- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), SPAWN.Takeoff.Hot ) -- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), SPAWN.Takeoff.Runway ) --- +-- -- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( "Carrier" ), SPAWN.Takeoff.Cold ) --- +-- -- Spawn_Heli = SPAWN:New( "Heli") --- +-- -- Spawn_Heli:SpawnAtAirbase( AIRBASE:FindByName( "FARP Cold" ), SPAWN.Takeoff.Cold ) -- Spawn_Heli:SpawnAtAirbase( AIRBASE:FindByName( "FARP Hot" ), SPAWN.Takeoff.Hot ) -- Spawn_Heli:SpawnAtAirbase( AIRBASE:FindByName( "FARP Runway" ), SPAWN.Takeoff.Runway ) -- Spawn_Heli:SpawnAtAirbase( AIRBASE:FindByName( "FARP Air" ), SPAWN.Takeoff.Air ) --- +-- -- Spawn_Heli:SpawnAtAirbase( AIRBASE:FindByName( "Carrier" ), SPAWN.Takeoff.Cold ) --- +-- -- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), SPAWN.Takeoff.Cold, nil, AIRBASE.TerminalType.OpenBig ) --- +-- function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalType, EmergencyAirSpawn, Parkingdata ) -- R2.2, R2.4 self:F( { self.SpawnTemplatePrefix, SpawnAirbase, Takeoff, TakeoffAltitude, TerminalType } ) -- Get position of airbase. local PointVec3 = SpawnAirbase:GetCoordinate() - self:T2(PointVec3) + self:T2( PointVec3 ) -- Set take off type. Default is hot. Takeoff = Takeoff or SPAWN.Takeoff.Hot - + -- By default, groups are spawned in air if no parking spot is available. - if EmergencyAirSpawn==nil then - EmergencyAirSpawn=true + if EmergencyAirSpawn == nil then + EmergencyAirSpawn = true end - + self:F( { SpawnIndex = self.SpawnIndex } ) - + if self:_GetSpawnIndex( self.SpawnIndex + 1 ) then - + -- Get group template. local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate - + self:F( { SpawnTemplate = SpawnTemplate } ) - + if SpawnTemplate then - + -- Check if the aircraft with the specified SpawnIndex is already spawned. -- If yes, ensure that the aircraft is spawned at the same aircraft spot. - + local GroupAlive = self:GetGroupFromIndex( self.SpawnIndex ) - + self:F( { GroupAlive = GroupAlive } ) -- Debug output self:T( { "Current point of ", self.SpawnTemplatePrefix, SpawnAirbase } ) - + -- Template group, unit and its attributes. - local TemplateGroup = GROUP:FindByName(self.SpawnTemplatePrefix) - local TemplateUnit=TemplateGroup:GetUnit(1) - + local TemplateGroup = GROUP:FindByName( self.SpawnTemplatePrefix ) + local TemplateUnit = TemplateGroup:GetUnit( 1 ) + -- General category of spawned group. - 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 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" ) + -- Number of units in the group. With grouping this can actually differ from the template group size! - local nunits=#SpawnTemplate.units + local nunits = #SpawnTemplate.units -- First waypoint of the group. local SpawnPoint = SpawnTemplate.route.points[1] @@ -1641,7 +1624,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT local AirbaseID = SpawnAirbase:GetID() local AirbaseCategory = SpawnAirbase:GetAirbaseCategory() self:F( { AirbaseCategory = AirbaseCategory } ) - + -- Set airdromeId. if AirbaseCategory == Airbase.Category.SHIP then SpawnPoint.linkUnit = AirbaseID @@ -1654,71 +1637,70 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT end -- Set waypoint type/action. - SpawnPoint.alt = 0 - SpawnPoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type + SpawnPoint.alt = 0 + SpawnPoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type SpawnPoint.action = GROUPTEMPLATE.Takeoff[Takeoff][2] -- action - + -- Check if we spawn on ground. - local spawnonground=not (Takeoff==SPAWN.Takeoff.Air) - self:T({spawnonground=spawnonground, TOtype=Takeoff, TOair=Takeoff==SPAWN.Takeoff.Air}) - + local spawnonground = not (Takeoff == SPAWN.Takeoff.Air) + self:T( { spawnonground = spawnonground, TOtype = Takeoff, TOair = Takeoff == SPAWN.Takeoff.Air } ) + -- Check where we actually spawn if we spawn on ground. - local spawnonship=false - local spawnonfarp=false - local spawnonrunway=false - local spawnonairport=false - if spawnonground then + local spawnonship = false + local spawnonfarp = false + local spawnonrunway = false + local spawnonairport = false + if spawnonground then if AirbaseCategory == Airbase.Category.SHIP then - spawnonship=true + spawnonship = true elseif AirbaseCategory == Airbase.Category.HELIPAD then - spawnonfarp=true + spawnonfarp = true elseif AirbaseCategory == Airbase.Category.AIRDROME then - spawnonairport=true + spawnonairport = true end - spawnonrunway=Takeoff==SPAWN.Takeoff.Runway + spawnonrunway = Takeoff == SPAWN.Takeoff.Runway end - + -- Array with parking spots coordinates. - local parkingspots={} - local parkingindex={} + local parkingspots = {} + local parkingindex = {} local spots - + -- Spawn happens on ground, i.e. at an airbase, a FARP or a ship. if spawnonground and not SpawnTemplate.parked then - - + -- Number of free parking spots. - local nfree=0 - + local nfree = 0 + -- Set terminal type. - local termtype=TerminalType - if spawnonrunway then + local termtype = TerminalType + if spawnonrunway then if spawnonship then -- Looks like there are no runway spawn spots on the stennis! if ishelo then - termtype=AIRBASE.TerminalType.HelicopterUsable + termtype = AIRBASE.TerminalType.HelicopterUsable else - termtype=AIRBASE.TerminalType.OpenMedOrBig + termtype = AIRBASE.TerminalType.OpenMedOrBig end else - termtype=AIRBASE.TerminalType.Runway - end + termtype = AIRBASE.TerminalType.Runway + end end - + -- Scan options. Might make that input somehow. - local scanradius=50 - local scanunits=true - local scanstatics=true - local scanscenery=false - local verysafe=false - + local scanradius = 50 + local scanunits = true + local scanstatics = true + local scanscenery = false + local verysafe = false + -- Number of free parking spots at the airbase. if spawnonship or spawnonfarp or spawnonrunway then -- These places work procedural and have some kind of build in queue ==> Less effort. - 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) - --[[ + 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 ) + --[[ elseif Parkingdata~=nil then -- Parking data explicitly set by user as input parameter. nfree=#Parkingdata @@ -1726,146 +1708,146 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT ]] else if ishelo then - if termtype==nil then + if termtype == nil then -- Helo is spawned. Try exclusive helo spots first. - 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 - + + -- On free spot required in these cases. + if nfree >= 1 then + -- All units get the same spot. DCS takes care of the rest. - for i=1,nunits do - table.insert(parkingspots, spots[1].Coordinate) - table.insert(parkingindex, spots[1].TerminalID) + for i = 1, nunits do + table.insert( parkingspots, spots[1].Coordinate ) + table.insert( parkingindex, spots[1].TerminalID ) end -- This is actually used... - PointVec3=spots[1].Coordinate - + PointVec3 = spots[1].Coordinate + else -- If there is absolutely no spot ==> air start! - _notenough=true + _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) + + if nfree >= nunits then + + for i = 1, nunits do + table.insert( parkingspots, spots[i].Coordinate ) + table.insert( parkingindex, spots[i].TerminalID ) end - + else -- Not enough spots for the whole group ==> air start! - _notenough=true - end + _notenough = true + end end - + -- Not enough spots ==> Prepare airstart. 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())) - + + 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() ) ) + -- Not enough parking spots at the airport ==> Spawn in air. - spawnonground=false - spawnonship=false - spawnonfarp=false - spawnonrunway=false - + spawnonground = false + spawnonship = false + spawnonfarp = false + spawnonrunway = false + -- Set waypoint type/action to turning point. - SpawnPoint.type = GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][1] -- type = Turning Point + SpawnPoint.type = GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][1] -- type = Turning Point SpawnPoint.action = GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][2] -- action = Turning Point - + -- Adjust altitude to be 500-1000 m above the airbase. - PointVec3.x=PointVec3.x+math.random(-500,500) - PointVec3.z=PointVec3.z+math.random(-500,500) + 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) + PointVec3.y = PointVec3:GetLandHeight() + math.random( 100, 1000 ) else -- Randomize position so that multiple AC wont be spawned on top even in air. - PointVec3.y=PointVec3:GetLandHeight()+math.random(500,2500) + PointVec3.y = PointVec3:GetLandHeight() + math.random( 500, 2500 ) end - - Takeoff=GROUP.Takeoff.Air + + 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())) + 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 - + -- Air start requested initially ==> Set altitude. if TakeoffAltitude then - PointVec3.y=TakeoffAltitude + PointVec3.y = TakeoffAltitude else if ishelo then - PointVec3.y=PointVec3:GetLandHeight()+math.random(100,1000) + PointVec3.y = PointVec3:GetLandHeight() + math.random( 100, 1000 ) else -- Randomize position so that multiple AC wont be spawned on top even in air. - PointVec3.y=PointVec3:GetLandHeight()+math.random(500,2500) + PointVec3.y = PointVec3:GetLandHeight() + math.random( 500, 2500 ) end end - + end if not SpawnTemplate.parked then @@ -1874,163 +1856,162 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT 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) - + self:T2( 'Before Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) + -- Template of the current unit. local UnitTemplate = SpawnTemplate.units[UnitID] - + -- Tranlate position and preserve the relative position/formation of all aircraft. local SX = UnitTemplate.x - local SY = UnitTemplate.y + 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) - + local TX = PointVec3.x + (SX - BX) + local TY = PointVec3.z + (SY - BY) + if spawnonground then - + -- Ships and FARPS seem to have a build in queue. if spawnonship or spawnonfarp or spawnonrunway then - - self:T(string.format("Group %s spawning at farp, ship or runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName())) - + + self:T( string.format( "Group %s spawning at farp, ship or runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) + -- Spawn on ship. We take only the position of the ship. - SpawnTemplate.units[UnitID].x = PointVec3.x --TX - SpawnTemplate.units[UnitID].y = PointVec3.z --TY + SpawnTemplate.units[UnitID].x = PointVec3.x -- TX + SpawnTemplate.units[UnitID].y = PointVec3.z -- TY 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])) - + + self:T( string.format( "Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID] ) ) + -- Get coordinates of parking spot. - SpawnTemplate.units[UnitID].x = parkingspots[UnitID].x - SpawnTemplate.units[UnitID].y = parkingspots[UnitID].z + SpawnTemplate.units[UnitID].x = parkingspots[UnitID].x + SpawnTemplate.units[UnitID].y = parkingspots[UnitID].z SpawnTemplate.units[UnitID].alt = parkingspots[UnitID].y - - --parkingspots[UnitID]:MarkToAll(string.format("Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID])) + + -- parkingspots[UnitID]:MarkToAll(string.format("Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID])) end - + else - - self:T(string.format("Group %s spawning in air at %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName())) - + + self:T( string.format( "Group %s spawning in air at %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) + -- Spawn in air as requested initially. Original template orientation is perserved, altitude is already correctly set. - SpawnTemplate.units[UnitID].x = TX - SpawnTemplate.units[UnitID].y = TY + SpawnTemplate.units[UnitID].x = TX + SpawnTemplate.units[UnitID].y = TY SpawnTemplate.units[UnitID].alt = PointVec3.y - + end - + -- Parking spot id. UnitTemplate.parking = nil UnitTemplate.parking_id = nil if parkingindex[UnitID] then UnitTemplate.parking = parkingindex[UnitID] end - + -- Debug output. - 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) + 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 - + -- Set gereral spawnpoint position. - SpawnPoint.x = PointVec3.x - SpawnPoint.y = PointVec3.z + SpawnPoint.x = PointVec3.x + SpawnPoint.y = PointVec3.z SpawnPoint.alt = PointVec3.y - + SpawnTemplate.x = PointVec3.x SpawnTemplate.y = PointVec3.z - + SpawnTemplate.uncontrolled = self.SpawnUnControlled - + -- Spawn group. local GroupSpawned = self:SpawnWithIndex( self.SpawnIndex ) - + -- When spawned in the air, we need to generate a Takeoff Event. 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 ) + SCHEDULER:New( nil, BASE.CreateEventTakeoff, { GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject() }, 5 ) end end - + -- Check if we accidentally spawned on the runway. Needs to be schedules, because group is not immidiately alive. - if Takeoff~=SPAWN.Takeoff.Runway and Takeoff~=SPAWN.Takeoff.Air and spawnonairport then - SCHEDULER:New(nil, AIRBASE.CheckOnRunWay, {SpawnAirbase, GroupSpawned, 75, true} , 1.0) + 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 + + return GroupSpawned end end - + return nil end ---- Spawn a group on an @{Wrapper.Airbase} at a specific parking spot. +--- Spawn a group on an @{Wrapper.Airbase} at a specific parking spot. -- @param #SPAWN self -- @param Wrapper.Airbase#AIRBASE Airbase The @{Wrapper.Airbase} where to spawn the group. -- @param #table Spots Table of parking spot IDs. Note that these in general are different from the numbering in the mission editor! -- @param #SPAWN.Takeoff Takeoff (Optional) Takeoff type, i.e. either SPAWN.Takeoff.Cold or SPAWN.Takeoff.Hot. Default is Hot. -- @return Wrapper.Group#GROUP The group that was spawned or nil when nothing was spawned. -function SPAWN:SpawnAtParkingSpot(Airbase, Spots, Takeoff) -- R2.5 - self:F({Airbase=Airbase, Spots=Spots, Takeoff=Takeoff}) +function SPAWN:SpawnAtParkingSpot( Airbase, Spots, Takeoff ) -- R2.5 + self:F( { Airbase = Airbase, Spots = Spots, Takeoff = Takeoff } ) -- Ensure that Spots parameter is a table. - if type(Spots)~="table" then - Spots={Spots} + if type( Spots ) ~= "table" then + Spots = { Spots } end -- Get template group. - local group=GROUP:FindByName(self.SpawnTemplatePrefix) - + local group = GROUP:FindByName( self.SpawnTemplatePrefix ) + -- Get number of units in group. - local nunits=self.SpawnGrouping or #group:GetUnits() + local nunits = self.SpawnGrouping or #group:GetUnits() -- Quick check. if nunits then - + -- Check that number of provided parking spots is large enough. - if #Spots=nunits then - return self:SpawnAtAirbase(Airbase, Takeoff, nil, nil, nil, Parkingdata) + + if #Parkingdata >= nunits then + return self:SpawnAtAirbase( Airbase, Takeoff, nil, nil, nil, Parkingdata ) else - self:E("ERROR: Could not find enough free parking spots!") + self:E( "ERROR: Could not find enough free parking spots!" ) end - - + else - self:E("ERROR: Could not get number of units in group!") + self:E( "ERROR: Could not get number of units in group!" ) end return nil end ---- Will park a group at an @{Wrapper.Airbase}. --- +--- Will park a group at an @{Wrapper.Airbase}. +-- -- @param #SPAWN self -- @param Wrapper.Airbase#AIRBASE SpawnAirbase The @{Wrapper.Airbase} where to spawn the group. -- @param Wrapper.Airbase#AIRBASE.TerminalType TerminalType (optional) The terminal type the aircraft should be spawned at. See @{Wrapper.Airbase#AIRBASE.TerminalType}. @@ -2042,34 +2023,34 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex -- Get position of airbase. local PointVec3 = SpawnAirbase:GetCoordinate() - self:T2(PointVec3) + self:T2( PointVec3 ) -- Set take off type. Default is hot. local Takeoff = SPAWN.Takeoff.Cold - + -- Get group template. local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate if SpawnTemplate then - + -- Check if the aircraft with the specified SpawnIndex is already spawned. -- If yes, ensure that the aircraft is spawned at the same aircraft spot. - + local GroupAlive = self:GetGroupFromIndex( SpawnIndex ) -- Debug output self:T( { "Current point of ", self.SpawnTemplatePrefix, SpawnAirbase } ) - + -- Template group, unit and its attributes. - 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 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" ) + -- Number of units in the group. With grouping this can actually differ from the template group size! - local nunits=#SpawnTemplate.units + local nunits = #SpawnTemplate.units -- First waypoint of the group. local SpawnPoint = SpawnTemplate.route.points[1] @@ -2083,7 +2064,7 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex local AirbaseID = SpawnAirbase:GetID() local AirbaseCategory = SpawnAirbase:GetAirbaseCategory() self:F( { AirbaseCategory = AirbaseCategory } ) - + -- Set airdromeId. if AirbaseCategory == Airbase.Category.SHIP then SpawnPoint.linkUnit = AirbaseID @@ -2096,59 +2077,58 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex end -- Set waypoint type/action. - SpawnPoint.alt = 0 - SpawnPoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type + SpawnPoint.alt = 0 + SpawnPoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type SpawnPoint.action = GROUPTEMPLATE.Takeoff[Takeoff][2] -- action - + -- Check if we spawn on ground. - local spawnonground=not (Takeoff==SPAWN.Takeoff.Air) - self:T({spawnonground=spawnonground, TOtype=Takeoff, TOair=Takeoff==SPAWN.Takeoff.Air}) - + local spawnonground = not (Takeoff == SPAWN.Takeoff.Air) + self:T( { spawnonground = spawnonground, TOtype = Takeoff, TOair = Takeoff == SPAWN.Takeoff.Air } ) + -- Check where we actually spawn if we spawn on ground. - local spawnonship=false - local spawnonfarp=false - local spawnonrunway=false - local spawnonairport=false - if spawnonground then + local spawnonship = false + local spawnonfarp = false + local spawnonrunway = false + local spawnonairport = false + if spawnonground then if AirbaseCategory == Airbase.Category.SHIP then - spawnonship=true + spawnonship = true elseif AirbaseCategory == Airbase.Category.HELIPAD then - spawnonfarp=true + spawnonfarp = true elseif AirbaseCategory == Airbase.Category.AIRDROME then - spawnonairport=true + spawnonairport = true end - spawnonrunway=Takeoff==SPAWN.Takeoff.Runway + spawnonrunway = Takeoff == SPAWN.Takeoff.Runway end - + -- Array with parking spots coordinates. - local parkingspots={} - local parkingindex={} + local parkingspots = {} + local parkingindex = {} local spots - + -- Spawn happens on ground, i.e. at an airbase, a FARP or a ship. if spawnonground and not SpawnTemplate.parked then - - + -- Number of free parking spots. - local nfree=0 - + local nfree = 0 + -- Set terminal type. - local termtype=TerminalType + local termtype = TerminalType -- Scan options. Might make that input somehow. - local scanradius=50 - local scanunits=true - local scanstatics=true - local scanscenery=false - local verysafe=false - + local scanradius = 50 + local scanunits = true + local scanstatics = true + local scanscenery = false + local verysafe = false + -- Number of free parking spots at the airbase. if spawnonship or spawnonfarp or spawnonrunway then -- These places work procedural and have some kind of build in queue ==> Less effort. - 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) - --[[ + 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 ) + --[[ elseif Parkingdata~=nil then -- Parking data explicitly set by user as input parameter. nfree=#Parkingdata @@ -2156,114 +2136,114 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex ]] else if ishelo then - if termtype==nil then + if termtype == nil then -- Helo is spawned. Try exclusive helo spots first. - 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 - + if nfree >= 1 then + -- All units get the same spot. DCS takes care of the rest. - for i=1,nunits do - table.insert(parkingspots, spots[1].Coordinate) - table.insert(parkingindex, spots[1].TerminalID) + for i = 1, nunits do + table.insert( parkingspots, spots[1].Coordinate ) + table.insert( parkingindex, spots[1].TerminalID ) end -- This is actually used... - PointVec3=spots[1].Coordinate - + PointVec3 = spots[1].Coordinate + else -- If there is absolutely no spot ==> air start! - _notenough=true + _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) + + if nfree >= nunits then + + for i = 1, nunits do + table.insert( parkingspots, spots[i].Coordinate ) + table.insert( parkingindex, spots[i].TerminalID ) end - + else -- Not enough spots for the whole group ==> air start! - _notenough=true - end + _notenough = true + end end - + -- Not enough spots ==> Prepare airstart. if _notenough then - - if not self.SpawnUnControlled 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())) + 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 @@ -2272,118 +2252,118 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex 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) - + self:F( 'Before Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) + -- Template of the current unit. local UnitTemplate = SpawnTemplate.units[UnitID] - + -- Tranlate position and preserve the relative position/formation of all aircraft. local SX = UnitTemplate.x - local SY = UnitTemplate.y + 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) - + local TX = PointVec3.x + (SX - BX) + local TY = PointVec3.z + (SY - BY) + if spawnonground then - + -- Ships and FARPS seem to have a build in queue. if spawnonship or spawnonfarp or spawnonrunway then - - self:T(string.format("Group %s spawning at farp, ship or runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName())) + + self:T( string.format( "Group %s spawning at farp, ship or runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) -- Spawn on ship. We take only the position of the ship. - SpawnTemplate.units[UnitID].x = PointVec3.x --TX - SpawnTemplate.units[UnitID].y = PointVec3.z --TY + SpawnTemplate.units[UnitID].x = PointVec3.x -- TX + SpawnTemplate.units[UnitID].y = PointVec3.z -- TY 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])) - + self:T( string.format( "Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID] ) ) + -- Get coordinates of parking spot. - SpawnTemplate.units[UnitID].x = parkingspots[UnitID].x - SpawnTemplate.units[UnitID].y = parkingspots[UnitID].z + SpawnTemplate.units[UnitID].x = parkingspots[UnitID].x + SpawnTemplate.units[UnitID].y = parkingspots[UnitID].z SpawnTemplate.units[UnitID].alt = parkingspots[UnitID].y - - --parkingspots[UnitID]:MarkToAll(string.format("Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID])) + + -- parkingspots[UnitID]:MarkToAll(string.format("Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID])) end - + else - - self:T(string.format("Group %s spawning in air at %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName())) - + + self:T( string.format( "Group %s spawning in air at %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) + -- Spawn in air as requested initially. Original template orientation is perserved, altitude is already correctly set. - SpawnTemplate.units[UnitID].x = TX - SpawnTemplate.units[UnitID].y = TY + SpawnTemplate.units[UnitID].x = TX + SpawnTemplate.units[UnitID].y = TY SpawnTemplate.units[UnitID].alt = PointVec3.y - + end - + -- Parking spot id. UnitTemplate.parking = nil UnitTemplate.parking_id = nil if parkingindex[UnitID] then UnitTemplate.parking = parkingindex[UnitID] end - + -- Debug output. - 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) + 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 - + -- Set general spawnpoint position. - SpawnPoint.x = PointVec3.x - SpawnPoint.y = PointVec3.z + SpawnPoint.x = PointVec3.x + SpawnPoint.y = PointVec3.z SpawnPoint.alt = PointVec3.y - + SpawnTemplate.x = PointVec3.x SpawnTemplate.y = PointVec3.z - + SpawnTemplate.uncontrolled = true - + -- Spawn group. local GroupSpawned = self:SpawnWithIndex( SpawnIndex, true ) - + -- When spawned in the air, we need to generate a Takeoff Event. 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 ) + SCHEDULER:New( nil, BASE.CreateEventTakeoff, { GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject() }, 5 ) end end - + -- Check if we accidentally spawned on the runway. Needs to be schedules, because group is not immidiately alive. - if Takeoff~=SPAWN.Takeoff.Runway and Takeoff~=SPAWN.Takeoff.Air and spawnonairport then - SCHEDULER:New(nil, AIRBASE.CheckOnRunWay, {SpawnAirbase, GroupSpawned, 75, true} , 1.0) + 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 ---- Will park a group at an @{Wrapper.Airbase}. +--- Will park a group at an @{Wrapper.Airbase}. -- This method is mostly advisable to be used if you want to simulate parking units at an airbase and be visible. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. --- +-- -- All groups that are in the spawn collection and that are alive, and not in the air, are parked. --- +-- -- The @{Wrapper.Airbase#AIRBASE} object must refer to a valid airbase known in the sim. -- You can use the following enumerations to search for the pre-defined airbases on the current known maps of DCS: --- --- * @{Wrapper.Airbase#AIRBASE.Caucasus}: The airbases on the Caucasus map. --- * @{Wrapper.Airbase#AIRBASE.Nevada}: The airbases on the Nevada (NTTR) map. --- * @{Wrapper.Airbase#AIRBASE.Normandy}: The airbases on the Normandy map. --- --- Use the method @{Wrapper.Airbase#AIRBASE.FindByName}() to retrieve the airbase object. +-- +-- * @{Wrapper.Airbase#AIRBASE.Caucasus}: The airbases on the Caucasus map. +-- * @{Wrapper.Airbase#AIRBASE.Nevada}: The airbases on the Nevada (NTTR) map. +-- * @{Wrapper.Airbase#AIRBASE.Normandy}: The airbases on the Normandy map. +-- +-- Use the method @{Wrapper.Airbase#AIRBASE.FindByName}() to retrieve the airbase object. -- The known AIRBASE objects are automatically imported at mission start by MOOSE. -- Therefore, there isn't any New() constructor defined for AIRBASE objects. --- +-- -- Ships and Farps are added within the mission, and are therefore not known. -- For these AIRBASE objects, there isn't an @{Wrapper.Airbase#AIRBASE} enumeration defined. -- You need to provide the **exact name** of the airbase as the parameter to the @{Wrapper.Airbase#AIRBASE.FindByName}() method! --- +-- -- @param #SPAWN self -- @param Wrapper.Airbase#AIRBASE SpawnAirbase The @{Wrapper.Airbase} where to spawn the group. -- @param Wrapper.Airbase#AIRBASE.TerminalType TerminalType (optional) The terminal type the aircraft should be spawned at. See @{Wrapper.Airbase#AIRBASE.TerminalType}. @@ -2392,15 +2372,15 @@ end -- @usage -- Spawn_Plane = SPAWN:New( "Plane" ) -- Spawn_Plane:ParkAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ) ) --- +-- -- Spawn_Heli = SPAWN:New( "Heli") --- +-- -- Spawn_Heli:ParkAtAirbase( AIRBASE:FindByName( "FARP Cold" ) ) --- +-- -- Spawn_Heli:ParkAtAirbase( AIRBASE:FindByName( "Carrier" ) ) --- +-- -- Spawn_Plane:ParkAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), AIRBASE.TerminalType.OpenBig ) --- +-- function SPAWN:ParkAtAirbase( SpawnAirbase, TerminalType, Parkingdata ) -- R2.2, R2.4, R2.5 self:F( { self.SpawnTemplatePrefix, SpawnAirbase, TerminalType } ) @@ -2408,15 +2388,15 @@ function SPAWN:ParkAtAirbase( SpawnAirbase, TerminalType, Parkingdata ) -- R2.2, for SpawnIndex = 2, self.SpawnMaxGroups do self:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex ) - --self:ScheduleOnce( SpawnIndex * 0.1, SPAWN.ParkAircraft, self, SpawnAirbase, TerminalType, Parkingdata, SpawnIndex ) + -- self:ScheduleOnce( SpawnIndex * 0.1, SPAWN.ParkAircraft, self, SpawnAirbase, TerminalType, Parkingdata, SpawnIndex ) end - + self:SetSpawnIndex( 0 ) - + return nil end ---- Will spawn a group from a Vec3 in 3D space. +--- Will spawn a group from a Vec3 in 3D space. -- This method is mostly advisable to be used if you want to simulate spawning units in the air, like helicopters or airplanes. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. -- You can use the returned group to further define the route to be followed. @@ -2429,21 +2409,21 @@ function SPAWN:SpawnFromVec3( Vec3, SpawnIndex ) self:F( { self.SpawnTemplatePrefix, Vec3, SpawnIndex } ) local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) - self:T2(PointVec3) + 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 {} @@ -2454,20 +2434,20 @@ function SPAWN:SpawnFromVec3( Vec3, SpawnIndex ) -- Translate the position of the Group Template to the Vec3. for UnitID = 1, #SpawnTemplate.units do - --self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) + -- self:T( '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 or 0 - local SY = UnitTemplate.y 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 ) + 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 ) + 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 @@ -2477,16 +2457,15 @@ function SPAWN:SpawnFromVec3( Vec3, SpawnIndex ) SpawnTemplate.x = Vec3.x SpawnTemplate.y = Vec3.z SpawnTemplate.alt = Vec3.y or TemplateHeight - + return self:SpawnWithIndex( self.SpawnIndex ) end end - + return nil end - ---- Will spawn a group from a Coordinate in 3D space. +--- Will spawn a group from a Coordinate in 3D space. -- This method is mostly advisable to be used if you want to simulate spawning units in the air, like helicopters or airplanes. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. -- You can use the returned group to further define the route to be followed. @@ -2501,8 +2480,6 @@ function SPAWN:SpawnFromCoordinate( Coordinate, SpawnIndex ) return self:SpawnFromVec3( Coordinate:GetVec3(), SpawnIndex ) end - - --- Will spawn a group from a PointVec3 in 3D space. -- This method is mostly advisable to be used if you want to simulate spawning units in the air, like helicopters or airplanes. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. @@ -2513,19 +2490,18 @@ end -- @return Wrapper.Group#GROUP that was spawned. -- @return #nil Nothing was spawned. -- @usage --- +-- -- local SpawnPointVec3 = ZONE:New( ZoneName ):GetPointVec3( 2000 ) -- Get the center of the ZONE object at 2000 meters from the ground. --- +-- -- -- Spawn at the zone center position at 2000 meters from the ground! --- SpawnAirplanes:SpawnFromPointVec3( SpawnPointVec3 ) --- +-- SpawnAirplanes:SpawnFromPointVec3( SpawnPointVec3 ) +-- function SPAWN:SpawnFromPointVec3( PointVec3, SpawnIndex ) self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) return self:SpawnFromVec3( PointVec3:GetVec3(), SpawnIndex ) end - --- Will spawn a group from a Vec2 in 3D space. -- This method is mostly advisable to be used if you want to simulate spawning groups on the ground from air units, like vehicles. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. @@ -2538,29 +2514,28 @@ end -- @return Wrapper.Group#GROUP that was spawned. -- @return #nil Nothing was spawned. -- @usage --- +-- -- local SpawnVec2 = ZONE:New( ZoneName ):GetVec2() --- +-- -- -- Spawn at the zone center position at the height specified in the ME of the group template! --- SpawnAirplanes:SpawnFromVec2( SpawnVec2 ) --- +-- SpawnAirplanes:SpawnFromVec2( SpawnVec2 ) +-- -- -- Spawn from the static position at the height randomized between 2000 and 4000 meters. --- SpawnAirplanes:SpawnFromVec2( SpawnVec2, 2000, 4000 ) --- +-- SpawnAirplanes:SpawnFromVec2( SpawnVec2, 2000, 4000 ) +-- 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) + Height = math.random( MinHeight, MaxHeight ) end - + return self:SpawnFromVec3( { x = Vec2.x, y = Height, z = Vec2.y }, SpawnIndex ) -- y can be nil. In this case, spawn on the ground for vehicles, and in the template altitude for air. end - ---- Will spawn a group from a POINT_VEC2 in 3D space. +--- Will spawn a group from a POINT_VEC2 in 3D space. -- This method is mostly advisable to be used if you want to simulate spawning groups on the ground from air units, like vehicles. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. -- You can use the returned group to further define the route to be followed. @@ -2572,23 +2547,21 @@ end -- @return Wrapper.Group#GROUP that was spawned. -- @return #nil Nothing was spawned. -- @usage --- +-- -- local SpawnPointVec2 = ZONE:New( ZoneName ):GetPointVec2() --- +-- -- -- Spawn at the zone center position at the height specified in the ME of the group template! --- SpawnAirplanes:SpawnFromPointVec2( SpawnPointVec2 ) --- +-- SpawnAirplanes:SpawnFromPointVec2( SpawnPointVec2 ) +-- -- -- Spawn from the static position at the height randomized between 2000 and 4000 meters. --- SpawnAirplanes:SpawnFromPointVec2( SpawnPointVec2, 2000, 4000 ) --- +-- SpawnAirplanes:SpawnFromPointVec2( SpawnPointVec2, 2000, 4000 ) +-- function SPAWN:SpawnFromPointVec2( PointVec2, MinHeight, MaxHeight, SpawnIndex ) self:F( { self.SpawnTemplatePrefix, self.SpawnIndex } ) return self:SpawnFromVec2( PointVec2:GetVec2(), MinHeight, MaxHeight, SpawnIndex ) end - - --- Will spawn a group from a hosting unit. This method is mostly advisable to be used if you want to simulate spawning from air units, like helicopters, which are dropping infantry into a defined Landing Zone. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. -- You can use the returned group to further define the route to be followed. @@ -2600,22 +2573,22 @@ end -- @return Wrapper.Group#GROUP that was spawned. -- @return #nil Nothing was spawned. -- @usage --- +-- -- local SpawnStatic = STATIC:FindByName( StaticName ) --- +-- -- -- Spawn from the static position at the height specified in the ME of the group template! --- SpawnAirplanes:SpawnFromUnit( SpawnStatic ) --- +-- SpawnAirplanes:SpawnFromUnit( SpawnStatic ) +-- -- -- Spawn from the static position at the height randomized between 2000 and 4000 meters. --- SpawnAirplanes:SpawnFromUnit( SpawnStatic, 2000, 4000 ) --- +-- SpawnAirplanes:SpawnFromUnit( SpawnStatic, 2000, 4000 ) +-- function SPAWN:SpawnFromUnit( HostUnit, MinHeight, MaxHeight, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, HostUnit, MinHeight, MaxHeight, SpawnIndex } ) + self:F( { self.SpawnTemplatePrefix, HostUnit, MinHeight, MaxHeight, SpawnIndex } ) if HostUnit and HostUnit:IsAlive() ~= nil then -- and HostUnit:getUnit(1):inAir() == false then return self:SpawnFromVec2( HostUnit:GetVec2(), MinHeight, MaxHeight, SpawnIndex ) end - + return nil end @@ -2629,22 +2602,22 @@ end -- @return Wrapper.Group#GROUP that was spawned. -- @return #nil Nothing was spawned. -- @usage --- +-- -- local SpawnStatic = STATIC:FindByName( StaticName ) --- +-- -- -- Spawn from the static position at the height specified in the ME of the group template! --- SpawnAirplanes:SpawnFromStatic( SpawnStatic ) --- +-- SpawnAirplanes:SpawnFromStatic( SpawnStatic ) +-- -- -- Spawn from the static position at the height randomized between 2000 and 4000 meters. --- SpawnAirplanes:SpawnFromStatic( SpawnStatic, 2000, 4000 ) --- +-- SpawnAirplanes:SpawnFromStatic( SpawnStatic, 2000, 4000 ) +-- 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 @@ -2661,27 +2634,27 @@ end -- @return Wrapper.Group#GROUP that was spawned. -- @return #nil when nothing was spawned. -- @usage --- +-- -- local SpawnZone = ZONE:New( ZoneName ) --- +-- -- -- Spawn at the zone center position at the height specified in the ME of the group template! --- SpawnAirplanes:SpawnInZone( SpawnZone ) --- +-- SpawnAirplanes:SpawnInZone( SpawnZone ) +-- -- -- Spawn in the zone at a random position at the height specified in the Me of the group template. --- SpawnAirplanes:SpawnInZone( SpawnZone, true ) --- +-- SpawnAirplanes:SpawnInZone( SpawnZone, true ) +-- -- -- Spawn in the zone at a random position at the height randomized between 2000 and 4000 meters. --- SpawnAirplanes:SpawnInZone( SpawnZone, true, 2000, 4000 ) --- +-- SpawnAirplanes:SpawnInZone( SpawnZone, true, 2000, 4000 ) +-- -- -- Spawn at the zone center position at the height randomized between 2000 and 4000 meters. --- SpawnAirplanes:SpawnInZone( SpawnZone, false, 2000, 4000 ) --- +-- SpawnAirplanes:SpawnInZone( SpawnZone, false, 2000, 4000 ) +-- -- -- Spawn at the zone center position at the height randomized between 2000 and 4000 meters. --- SpawnAirplanes:SpawnInZone( SpawnZone, nil, 2000, 4000 ) --- +-- SpawnAirplanes:SpawnInZone( SpawnZone, nil, 2000, 4000 ) +-- function SPAWN:SpawnInZone( Zone, RandomizeGroup, MinHeight, MaxHeight, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, 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 ) @@ -2689,11 +2662,11 @@ function SPAWN:SpawnInZone( Zone, RandomizeGroup, MinHeight, MaxHeight, SpawnInd return self:SpawnFromVec2( Zone:GetVec2(), MinHeight, MaxHeight, SpawnIndex ) end end - + return nil end ---- (**AIR**) Will spawn a plane group in UnControlled or Controlled mode... +--- (**AIR**) Will spawn a plane group in UnControlled or Controlled mode... -- This will be similar to the uncontrolled flag setting in the ME. -- You can use UnControlled mode to simulate planes startup and ready for take-off but aren't moving (yet). -- ReSpawn the plane in Controlled mode, and the plane will move... @@ -2701,17 +2674,16 @@ end -- @param #boolean UnControlled true if UnControlled, false if Controlled. -- @return #SPAWN self 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 + 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 --- Get the Coordinate of the Group that is Late Activated as the template for the SPAWN object. -- @param #SPAWN self @@ -2722,32 +2694,31 @@ function SPAWN:GetCoordinate() if LateGroup then return LateGroup:GetCoordinate() end - + return nil end - --- Will return the SpawnGroupName either with with a specific count number or without any count. -- @param #SPAWN self -- @param #number SpawnIndex Is the number of the Group that is to be spawned. -- @return #string SpawnGroupName function SPAWN:SpawnGroupName( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) + self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) - local SpawnPrefix = self.SpawnTemplatePrefix - if self.SpawnAliasPrefix then - SpawnPrefix = self.SpawnAliasPrefix - end + 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 - if SpawnIndex then - local SpawnName = string.format( '%s#%03d', SpawnPrefix, SpawnIndex ) - self:T( SpawnName ) - return SpawnName - else - self:T( SpawnPrefix ) - return SpawnPrefix - end - end --- Will find the first alive @{Wrapper.Group} it has spawned, and return the alive @{Wrapper.Group} object and the first Index where the first alive @{Wrapper.Group} object has been found. @@ -2755,14 +2726,16 @@ end -- @return Wrapper.Group#GROUP, #number The @{Wrapper.Group} object found, the new Index where the group was found. -- @return #nil, #nil When no group is found, #nil is returned. -- @usage --- -- Find the first alive @{Wrapper.Group} object of the SpawnPlanes SPAWN object @{Wrapper.Group} collection that it has spawned during the mission. --- local GroupPlane, Index = SpawnPlanes:GetFirstAliveGroup() --- while GroupPlane ~= nil do --- -- Do actions with the GroupPlane object. --- GroupPlane, Index = SpawnPlanes:GetNextAliveGroup( Index ) --- end +-- +-- -- Find the first alive @{Wrapper.Group} object of the SpawnPlanes SPAWN object @{Wrapper.Group} collection that it has spawned during the mission. +-- local GroupPlane, Index = SpawnPlanes:GetFirstAliveGroup() +-- while GroupPlane ~= nil do +-- -- Do actions with the GroupPlane object. +-- GroupPlane, Index = SpawnPlanes:GetNextAliveGroup( Index ) +-- end +-- function SPAWN:GetFirstAliveGroup() - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) + self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) for SpawnIndex = 1, self.SpawnCount do local SpawnGroup = self:GetGroupFromIndex( SpawnIndex ) @@ -2770,25 +2743,26 @@ function SPAWN:GetFirstAliveGroup() return SpawnGroup, SpawnIndex end end - + return nil, nil end - --- Will find the next alive @{Wrapper.Group} object from a given Index, and return a reference to the alive @{Wrapper.Group} object and the next Index where the alive @{Wrapper.Group} has been found. -- @param #SPAWN self -- @param #number SpawnIndexStart A Index holding the start position to search from. This method can also be used to find the first alive @{Wrapper.Group} object from the given Index. -- @return Wrapper.Group#GROUP, #number The next alive @{Wrapper.Group} object found, the next Index where the next alive @{Wrapper.Group} object was found. -- @return #nil, #nil When no alive @{Wrapper.Group} object is found from the start Index position, #nil is returned. -- @usage --- -- Find the first alive @{Wrapper.Group} object of the SpawnPlanes SPAWN object @{Wrapper.Group} collection that it has spawned during the mission. --- local GroupPlane, Index = SpawnPlanes:GetFirstAliveGroup() --- while GroupPlane ~= nil do --- -- Do actions with the GroupPlane object. --- GroupPlane, Index = SpawnPlanes:GetNextAliveGroup( Index ) --- end +-- +-- -- Find the first alive @{Wrapper.Group} object of the SpawnPlanes SPAWN object @{Wrapper.Group} collection that it has spawned during the mission. +-- local GroupPlane, Index = SpawnPlanes:GetFirstAliveGroup() +-- while GroupPlane ~= nil do +-- -- Do actions with the GroupPlane object. +-- GroupPlane, Index = SpawnPlanes:GetNextAliveGroup( Index ) +-- end +-- function SPAWN:GetNextAliveGroup( SpawnIndexStart ) - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndexStart } ) + self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndexStart } ) SpawnIndexStart = SpawnIndexStart + 1 for SpawnIndex = SpawnIndexStart, self.SpawnCount do @@ -2797,7 +2771,7 @@ function SPAWN:GetNextAliveGroup( SpawnIndexStart ) return SpawnGroup, SpawnIndex end end - + return nil, nil end @@ -2806,14 +2780,16 @@ end -- @return Wrapper.Group#GROUP, #number The last alive @{Wrapper.Group} object found, the last Index where the last alive @{Wrapper.Group} object was found. -- @return #nil, #nil When no alive @{Wrapper.Group} object is found, #nil is returned. -- @usage --- -- Find the last alive @{Wrapper.Group} object of the SpawnPlanes SPAWN object @{Wrapper.Group} collection that it has spawned during the mission. --- local GroupPlane, Index = SpawnPlanes:GetLastAliveGroup() --- if GroupPlane then -- GroupPlane can be nil!!! --- -- Do actions with the GroupPlane object. --- end +-- +-- -- Find the last alive @{Wrapper.Group} object of the SpawnPlanes SPAWN object @{Wrapper.Group} collection that it has spawned during the mission. +-- local GroupPlane, Index = SpawnPlanes:GetLastAliveGroup() +-- if GroupPlane then -- GroupPlane can be nil!!! +-- -- Do actions with the GroupPlane object. +-- end +-- function SPAWN:GetLastAliveGroup() - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) - + self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) + for SpawnIndex = self.SpawnCount, 1, -1 do -- Added local SpawnGroup = self:GetGroupFromIndex( SpawnIndex ) if SpawnGroup and SpawnGroup:IsAlive() then @@ -2826,8 +2802,6 @@ function SPAWN:GetLastAliveGroup() return nil end - - --- Get the group from an index. -- Returns the group from the SpawnGroups list. -- If no index is given, it will return the first group in the list. @@ -2835,20 +2809,19 @@ end -- @param #number SpawnIndex The index of the group to return. -- @return Wrapper.Group#GROUP self 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 + 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 --- Return the prefix of a SpawnUnit. -- The method will search for a #-mark, and will return the text before the #-mark. @@ -2868,90 +2841,87 @@ function SPAWN:_GetPrefixFromGroup( SpawnGroup ) end return SpawnPrefix end - + return nil end - --- Get the index from a given group. -- The function will search the name of the group for a #, and will return the number behind the #-mark. 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 - + 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 --- Return the last maximum index that can be used. function SPAWN:_GetLastIndex() - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) + self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) - return self.SpawnMaxGroups + return self.SpawnMaxGroups end --- Initalize the SpawnGroups collection. -- @param #SPAWN self function SPAWN:_InitializeSpawnGroups( SpawnIndex ) - self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, 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 ) - --self:_TranslateRotate( SpawnIndex ) - - return self.SpawnGroups[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 ) + -- self:_TranslateRotate( SpawnIndex ) + + return self.SpawnGroups[SpawnIndex] end - - --- Gets the CategoryID of the Group with the given SpawnPrefix function SPAWN:_GetGroupCategoryID( SpawnPrefix ) - local TemplateGroup = Group.getByName( SpawnPrefix ) - - if TemplateGroup then - return TemplateGroup:getCategory() - else - return nil - end + local TemplateGroup = Group.getByName( SpawnPrefix ) + + if TemplateGroup then + return TemplateGroup:getCategory() + else + return nil + end end --- Gets the CoalitionID of the Group with the given SpawnPrefix function SPAWN:_GetGroupCoalitionID( SpawnPrefix ) - local TemplateGroup = Group.getByName( SpawnPrefix ) - - if TemplateGroup then - return TemplateGroup:getCoalition() - else - return nil - end + local TemplateGroup = Group.getByName( SpawnPrefix ) + + if TemplateGroup then + return TemplateGroup:getCoalition() + else + return nil + end end --- Gets the CountryID of the Group with the given SpawnPrefix 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 + 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 --- Gets the Group Template from the ME environment definition. @@ -2960,25 +2930,25 @@ end -- @param #string SpawnTemplatePrefix -- @return @SPAWN self function SPAWN:_GetTemplate( SpawnTemplatePrefix ) - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnTemplatePrefix } ) + self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnTemplatePrefix } ) - local SpawnTemplate = nil + 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 + SpawnTemplate = UTILS.DeepCopy( _DATABASE.Templates.Groups[SpawnTemplatePrefix].Template ) - --SpawnTemplate.SpawnCoalitionID = self:_GetGroupCoalitionID( SpawnTemplatePrefix ) - --SpawnTemplate.SpawnCategoryID = self:_GetGroupCategoryID( SpawnTemplatePrefix ) - --SpawnTemplate.SpawnCountryID = self:_GetGroupCountryID( SpawnTemplatePrefix ) - - self:T3( { SpawnTemplate } ) - return SpawnTemplate + if SpawnTemplate == nil then + error( 'No Template returned for SpawnTemplatePrefix = ' .. SpawnTemplatePrefix ) + end + + -- SpawnTemplate.SpawnCoalitionID = self:_GetGroupCoalitionID( SpawnTemplatePrefix ) + -- SpawnTemplate.SpawnCategoryID = self:_GetGroupCategoryID( SpawnTemplatePrefix ) + -- SpawnTemplate.SpawnCountryID = self:_GetGroupCountryID( SpawnTemplatePrefix ) + + self:T3( { SpawnTemplate } ) + return SpawnTemplate end --- Prepares the new Group Template. @@ -2986,36 +2956,35 @@ end -- @param #string SpawnTemplatePrefix -- @param #number SpawnIndex -- @return #SPAWN self -function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) --R2.2 - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) - --- if not self.SpawnTemplate then --- self.SpawnTemplate = self:_GetTemplate( SpawnTemplatePrefix ) --- end - +function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2 + self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) + + -- if not self.SpawnTemplate then + -- self.SpawnTemplate = self:_GetTemplate( SpawnTemplatePrefix ) + -- end + local SpawnTemplate if self.TweakedTemplate ~= nil and self.TweakedTemplate == true then - BASE:I("WARNING: You are using a tweaked template.") + 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 = false - SpawnTemplate.lateActivation = self.LateActivated or false + SpawnTemplate.groupId = nil + -- SpawnTemplate.lateActivation = false + 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 + 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 @@ -3028,17 +2997,17 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) --R2.2 end end end - + if self.SpawnInitKeepUnitNames == false then - for UnitID = 1, #SpawnTemplate.units do - SpawnTemplate.units[UnitID].name = string.format( SpawnTemplate.name .. '-%02d', UnitID ) - SpawnTemplate.units[UnitID].unitId = nil - end + for UnitID = 1, #SpawnTemplate.units do + SpawnTemplate.units[UnitID].name = string.format( SpawnTemplate.name .. '-%02d', UnitID ) + SpawnTemplate.units[UnitID].unitId = nil + end else for UnitID = 1, #SpawnTemplate.units do local UnitPrefix, Rest = string.match( SpawnTemplate.units[UnitID].name, "^([^#]+)#?" ):gsub( "^%s*(.-)%s*$", "%1" ) self:T( { UnitPrefix, Rest } ) - + SpawnTemplate.units[UnitID].name = string.format( '%s#%03d-%02d', UnitPrefix, SpawnIndex, UnitID ) SpawnTemplate.units[UnitID].unitId = nil end @@ -3048,20 +3017,20 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) --R2.2 for UnitID = 1, #SpawnTemplate.units do local Callsign = SpawnTemplate.units[UnitID].callsign if Callsign then - if type(Callsign) ~= "number" then -- blue callsign - Callsign[2] = ( ( SpawnIndex - 1 ) % 10 ) + 1 + if type( Callsign ) ~= "number" then -- blue callsign + Callsign[2] = ((SpawnIndex - 1) % 10) + 1 local CallsignName = SpawnTemplate.units[UnitID].callsign["name"] -- #string local CallsignLen = CallsignName:len() - SpawnTemplate.units[UnitID].callsign["name"] = CallsignName:sub(1,CallsignLen) .. SpawnTemplate.units[UnitID].callsign[2] .. SpawnTemplate.units[UnitID].callsign[3] + SpawnTemplate.units[UnitID].callsign["name"] = CallsignName:sub( 1, CallsignLen ) .. SpawnTemplate.units[UnitID].callsign[2] .. SpawnTemplate.units[UnitID].callsign[3] else SpawnTemplate.units[UnitID].callsign = Callsign + SpawnIndex end end end - - self:T3( { "Template:", SpawnTemplate } ) - return SpawnTemplate - + + self:T3( { "Template:", SpawnTemplate } ) + return SpawnTemplate + end --- Private method randomizing the routes. @@ -3069,17 +3038,17 @@ end -- @param #number SpawnIndex The index of the group to be spawned. -- @return #SPAWN function SPAWN:_RandomizeRoute( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeRoute, self.SpawnRandomizeRouteStartPoint, self.SpawnRandomizeRouteEndPoint, self.SpawnRandomizeRouteRadius } ) + self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeRoute, self.SpawnRandomizeRouteStartPoint, self.SpawnRandomizeRouteEndPoint, self.SpawnRandomizeRouteRadius } ) if self.SpawnRandomizeRoute then local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate local RouteCount = #SpawnTemplate.route.points - - for t = self.SpawnRandomizeRouteStartPoint + 1, ( RouteCount - self.SpawnRandomizeRouteEndPoint ) do - + + for t = self.SpawnRandomizeRouteStartPoint + 1, (RouteCount - self.SpawnRandomizeRouteEndPoint) do + SpawnTemplate.route.points[t].x = SpawnTemplate.route.points[t].x + math.random( self.SpawnRandomizeRouteRadius * -1, self.SpawnRandomizeRouteRadius ) SpawnTemplate.route.points[t].y = SpawnTemplate.route.points[t].y + math.random( self.SpawnRandomizeRouteRadius * -1, self.SpawnRandomizeRouteRadius ) - + -- Manage randomization of altitude for airborne units ... if SpawnTemplate.CategoryID == Group.Category.AIRPLANE or SpawnTemplate.CategoryID == Group.Category.HELICOPTER then if SpawnTemplate.route.points[t].alt and self.SpawnRandomizeRouteHeight then @@ -3088,13 +3057,13 @@ function SPAWN:_RandomizeRoute( SpawnIndex ) else SpawnTemplate.route.points[t].alt = nil end - + self:T( 'SpawnTemplate.route.points[' .. t .. '].x = ' .. SpawnTemplate.route.points[t].x .. ', SpawnTemplate.route.points[' .. t .. '].y = ' .. SpawnTemplate.route.points[t].y ) end end - + self:_RandomizeZones( SpawnIndex ) - + return self end @@ -3103,10 +3072,10 @@ end -- @param #number SpawnIndex -- @return #SPAWN self function SPAWN:_RandomizeTemplate( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeTemplate } ) + self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeTemplate } ) if self.SpawnRandomizeTemplate then - self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix = self.SpawnTemplatePrefixTable[ math.random( 1, #self.SpawnTemplatePrefixTable ) ] + self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix = self.SpawnTemplatePrefixTable[math.random( 1, #self.SpawnTemplatePrefixTable )] self.SpawnGroups[SpawnIndex].SpawnTemplate = self:_Prepare( self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix, SpawnIndex ) self.SpawnGroups[SpawnIndex].SpawnTemplate.route = UTILS.DeepCopy( self.SpawnTemplate.route ) self.SpawnGroups[SpawnIndex].SpawnTemplate.x = self.SpawnTemplate.x @@ -3116,14 +3085,14 @@ function SPAWN:_RandomizeTemplate( SpawnIndex ) local OldY = self.SpawnGroups[SpawnIndex].SpawnTemplate.units[1].y for UnitID = 1, #self.SpawnGroups[SpawnIndex].SpawnTemplate.units do self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].heading = self.SpawnTemplate.units[1].heading - self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].x = self.SpawnTemplate.units[1].x + ( self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].x - OldX ) - self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y = self.SpawnTemplate.units[1].y + ( self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y - OldY ) + self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].x = self.SpawnTemplate.units[1].x + (self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].x - OldX) + self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y = self.SpawnTemplate.units[1].y + (self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y - OldY) self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].alt = self.SpawnTemplate.units[1].alt end end - + self:_RandomizeRoute( SpawnIndex ) - + return self end @@ -3140,84 +3109,79 @@ function SPAWN:_RandomizeZones( SpawnIndex ) self:T( { SpawnZoneTableCount = #self.SpawnZoneTable, self.SpawnZoneTable } ) local ZoneID = math.random( #self.SpawnZoneTable ) self:T( ZoneID ) - SpawnZone = self.SpawnZoneTable[ ZoneID ]:GetZoneMaybe() + SpawnZone = self.SpawnZoneTable[ZoneID]:GetZoneMaybe() end - + self:T( "Preparing Spawn in Zone", SpawnZone:GetName() ) - + local SpawnVec2 = SpawnZone:GetRandomVec2() - + self:T( { SpawnVec2 = SpawnVec2 } ) - + local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate - + self:T( { Route = SpawnTemplate.route } ) - + for UnitID = 1, #SpawnTemplate.units do local UnitTemplate = SpawnTemplate.units[UnitID] - self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y ) + self:T( 'Before Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y ) local SX = UnitTemplate.x - local SY = UnitTemplate.y + local SY = UnitTemplate.y local BX = SpawnTemplate.route.points[1].x local BY = SpawnTemplate.route.points[1].y - local TX = SpawnVec2.x + ( SX - BX ) - local TY = SpawnVec2.y + ( SY - BY ) + local TX = SpawnVec2.x + (SX - BX) + local TY = SpawnVec2.y + (SY - BY) UnitTemplate.x = TX UnitTemplate.y = TY -- TODO: Manage altitude based on landheight... - --SpawnTemplate.units[UnitID].alt = SpawnVec2: - self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y ) + -- SpawnTemplate.units[UnitID].alt = SpawnVec2: + self:T( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y ) end SpawnTemplate.x = SpawnVec2.x SpawnTemplate.y = SpawnVec2.y SpawnTemplate.route.points[1].x = SpawnVec2.x SpawnTemplate.route.points[1].y = SpawnVec2.y end - + return self - + end function SPAWN:_TranslateRotate( SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle } ) - + self:F( { self.SpawnTemplatePrefix, SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle } ) + -- Translate local TranslatedX = SpawnX local TranslatedY = SpawnY - + -- Rotate -- From Wikipedia: https://en.wikipedia.org/wiki/Rotation_matrix#Common_rotations -- x' = x \cos \theta - y \sin \theta\ -- y' = x \sin \theta + y \cos \theta\ - local RotatedX = - TranslatedX * math.cos( math.rad( SpawnAngle ) ) - + TranslatedY * math.sin( math.rad( SpawnAngle ) ) - local RotatedY = TranslatedX * math.sin( math.rad( SpawnAngle ) ) - + TranslatedY * math.cos( math.rad( SpawnAngle ) ) - + local RotatedX = -TranslatedX * math.cos( math.rad( SpawnAngle ) ) + TranslatedY * math.sin( math.rad( SpawnAngle ) ) + local RotatedY = TranslatedX * math.sin( math.rad( SpawnAngle ) ) + TranslatedY * math.cos( math.rad( SpawnAngle ) ) + -- Assign self.SpawnGroups[SpawnIndex].SpawnTemplate.x = SpawnRootX - RotatedX self.SpawnGroups[SpawnIndex].SpawnTemplate.y = SpawnRootY + RotatedY - local SpawnUnitCount = table.getn( self.SpawnGroups[SpawnIndex].SpawnTemplate.units ) for u = 1, SpawnUnitCount do - + -- Translate - local TranslatedX = SpawnX - local TranslatedY = SpawnY - 10 * ( u - 1 ) - + local TranslatedX = SpawnX + local TranslatedY = SpawnY - 10 * (u - 1) + -- Rotate - local RotatedX = - TranslatedX * math.cos( math.rad( SpawnAngle ) ) - + TranslatedY * math.sin( math.rad( SpawnAngle ) ) - local RotatedY = TranslatedX * math.sin( math.rad( SpawnAngle ) ) - + TranslatedY * math.cos( math.rad( SpawnAngle ) ) - + local RotatedX = -TranslatedX * math.cos( math.rad( SpawnAngle ) ) + TranslatedY * math.sin( math.rad( SpawnAngle ) ) + local RotatedY = TranslatedX * math.sin( math.rad( SpawnAngle ) ) + TranslatedY * math.cos( math.rad( SpawnAngle ) ) + -- Assign self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].x = SpawnRootX - RotatedX self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].y = SpawnRootY + RotatedY self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].heading = self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].heading + math.rad( SpawnAngle ) end - + return self end @@ -3226,10 +3190,10 @@ end -- @param #number SpawnIndex Spawn index. -- @return #number self.SpawnIndex function SPAWN:_GetSpawnIndex( SpawnIndex ) - self:F2( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive, self.AliveUnits, #self.SpawnTemplate.units } ) - - if ( self.SpawnMaxGroups == 0 ) or ( SpawnIndex <= self.SpawnMaxGroups ) then - if ( self.SpawnMaxUnitsAlive == 0 ) or ( self.AliveUnits + #self.SpawnTemplate.units <= self.SpawnMaxUnitsAlive ) or self.UnControlled == true then + self:F2( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive, self.AliveUnits, #self.SpawnTemplate.units } ) + + if (self.SpawnMaxGroups == 0) or (SpawnIndex <= self.SpawnMaxGroups) then + if (self.SpawnMaxUnitsAlive == 0) or (self.AliveUnits + #self.SpawnTemplate.units <= self.SpawnMaxUnitsAlive) or self.UnControlled == true then self:F( { SpawnCount = self.SpawnCount, SpawnIndex = SpawnIndex } ) if SpawnIndex and SpawnIndex >= self.SpawnCount + 1 then self.SpawnCount = self.SpawnCount + 1 @@ -3245,11 +3209,10 @@ function SPAWN:_GetSpawnIndex( SpawnIndex ) else return nil end - + return self.SpawnIndex end - -- TODO Need to delete this... _DATABASE does this now ... --- @param #SPAWN self @@ -3258,17 +3221,17 @@ function SPAWN:_OnBirth( EventData ) self:F( self.SpawnTemplatePrefix ) local SpawnGroup = EventData.IniGroup - + if SpawnGroup then local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup ) if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! - self:T( { "Birth Event:", EventPrefix, self.SpawnTemplatePrefix } ) - if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then - self.AliveUnits = self.AliveUnits + 1 - self:T( "Alive Units: " .. self.AliveUnits ) - end + self:T( { "Birth Event:", EventPrefix, self.SpawnTemplatePrefix } ) + if EventPrefix == self.SpawnTemplatePrefix or (self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix) then + self.AliveUnits = self.AliveUnits + 1 + self:T( "Alive Units: " .. self.AliveUnits ) + end end - end + end end @@ -3281,17 +3244,17 @@ function SPAWN:_OnDeadOrCrash( EventData ) self:F( self.SpawnTemplatePrefix ) local SpawnGroup = EventData.IniGroup - - if SpawnGroup then - local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup ) - if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! + + if SpawnGroup then + local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup ) + if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! self:T( { "Dead event: " .. EventPrefix } ) - if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then - self.AliveUnits = self.AliveUnits - 1 - self:T( "Alive Units: " .. self.AliveUnits ) - end + if EventPrefix == self.SpawnTemplatePrefix or (self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix) then + self.AliveUnits = self.AliveUnits - 1 + self:T( "Alive Units: " .. self.AliveUnits ) + end end - end + end end --- Will detect AIR Units taking off... When the event takes place, the spawned Group is registered as airborne... @@ -3306,12 +3269,12 @@ function SPAWN:_OnTakeOff( EventData ) local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup ) if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! self:T( { "TakeOff event: " .. EventPrefix } ) - if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then - self:T( "self.Landed = false" ) - SpawnGroup:SetState( SpawnGroup, "Spawn_Landed", false ) + if EventPrefix == self.SpawnTemplatePrefix or (self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix) then + self:T( "self.Landed = false" ) + SpawnGroup:SetState( SpawnGroup, "Spawn_Landed", false ) end end - end + end end --- Will detect AIR Units landing... When the event takes place, the spawned Group is registered as landed. @@ -3326,20 +3289,20 @@ function SPAWN:_OnLand( EventData ) local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup ) if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! self:T( { "Land event: " .. EventPrefix } ) - if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then - -- TODO: Check if this is the last unit of the group that lands. - SpawnGroup:SetState( SpawnGroup, "Spawn_Landed", true ) - if self.RepeatOnLanding then - local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup ) - self:T( { "Landed:", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } ) - --self:ReSpawn( SpawnGroupIndex ) - -- Delay respawn by three seconds due to DCS 2.5.4.26368 OB bug https://github.com/FlightControl-Master/MOOSE/issues/1076 - -- Bug was initially only for engine shutdown event but after ED "fixed" it, it now happens on landing events. - SCHEDULER:New(nil, self.ReSpawn, {self, SpawnGroupIndex}, 3) - end - end + if EventPrefix == self.SpawnTemplatePrefix or (self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix) then + -- TODO: Check if this is the last unit of the group that lands. + SpawnGroup:SetState( SpawnGroup, "Spawn_Landed", true ) + if self.RepeatOnLanding then + local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup ) + self:T( { "Landed:", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } ) + -- self:ReSpawn( SpawnGroupIndex ) + -- Delay respawn by three seconds due to DCS 2.5.4.26368 OB bug https://github.com/FlightControl-Master/MOOSE/issues/1076 + -- Bug was initially only for engine shutdown event but after ED "fixed" it, it now happens on landing events. + SCHEDULER:New( nil, self.ReSpawn, { self, SpawnGroupIndex }, 3 ) + end + end end - end + end end --- Will detect AIR Units shutting down their engines ... @@ -3355,76 +3318,75 @@ function SPAWN:_OnEngineShutDown( EventData ) local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup ) if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! self:T( { "EngineShutdown event: " .. EventPrefix } ) - if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then - -- todo: test if on the runway - local Landed = SpawnGroup:GetState( SpawnGroup, "Spawn_Landed" ) - if Landed and self.RepeatOnEngineShutDown then - local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup ) - self:T( { "EngineShutDown: ", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } ) - --self:ReSpawn( SpawnGroupIndex ) - -- Delay respawn by three seconds due to DCS 2.5.4 OB bug https://github.com/FlightControl-Master/MOOSE/issues/1076 - SCHEDULER:New(nil, self.ReSpawn, {self, SpawnGroupIndex}, 3) - end - end + if EventPrefix == self.SpawnTemplatePrefix or (self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix) then + -- todo: test if on the runway + local Landed = SpawnGroup:GetState( SpawnGroup, "Spawn_Landed" ) + if Landed and self.RepeatOnEngineShutDown then + local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup ) + self:T( { "EngineShutDown: ", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } ) + -- self:ReSpawn( SpawnGroupIndex ) + -- Delay respawn by three seconds due to DCS 2.5.4 OB bug https://github.com/FlightControl-Master/MOOSE/issues/1076 + SCHEDULER:New( nil, self.ReSpawn, { self, SpawnGroupIndex }, 3 ) + end + end end - end + end end --- This function is called automatically by the Spawning scheduler. -- It is the internal worker method SPAWNing new Groups on the defined time intervals. -- @param #SPAWN self function SPAWN:_Scheduler() - self:F2( { "_Scheduler", self.SpawnTemplatePrefix, self.SpawnAliasPrefix, self.SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive } ) - - -- Validate if there are still groups left in the batch... - self:Spawn() - - return true + self:F2( { "_Scheduler", self.SpawnTemplatePrefix, self.SpawnAliasPrefix, self.SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive } ) + + -- Validate if there are still groups left in the batch... + self:Spawn() + + return true end --- Schedules the CleanUp of Groups -- @param #SPAWN self -- @return #boolean True = Continue Scheduler function SPAWN:_SpawnCleanUpScheduler() - self:F( { "CleanUp Scheduler:", self.SpawnTemplatePrefix } ) + self:F( { "CleanUp Scheduler:", self.SpawnTemplatePrefix } ) - local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup() - self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) + local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup() + self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) - while SpawnGroup do + while SpawnGroup do local SpawnUnits = SpawnGroup:GetUnits() - - for UnitID, UnitData in pairs( SpawnUnits ) do - - local SpawnUnit = UnitData -- Wrapper.Unit#UNIT - local SpawnUnitName = SpawnUnit:GetName() - - - self.SpawnCleanUpTimeStamps[SpawnUnitName] = self.SpawnCleanUpTimeStamps[SpawnUnitName] or {} - local Stamp = self.SpawnCleanUpTimeStamps[SpawnUnitName] + + for UnitID, UnitData in pairs( SpawnUnits ) do + + local SpawnUnit = UnitData -- Wrapper.Unit#UNIT + local SpawnUnitName = SpawnUnit:GetName() + + self.SpawnCleanUpTimeStamps[SpawnUnitName] = self.SpawnCleanUpTimeStamps[SpawnUnitName] or {} + local Stamp = self.SpawnCleanUpTimeStamps[SpawnUnitName] self:T( { SpawnUnitName, Stamp } ) - - if Stamp.Vec2 then - if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1 then - local NewVec2 = SpawnUnit:GetVec2() - if (Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y) or (SpawnUnit:GetLife() <= 1) then - -- If the plane is not moving or dead , and is on the ground, assign it with a timestamp... - if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then - self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } ) - self:ReSpawn( SpawnCursor ) + + if Stamp.Vec2 then + if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1 then + local NewVec2 = SpawnUnit:GetVec2() + if (Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y) or (SpawnUnit:GetLife() <= 1) then + -- If the plane is not moving or dead , and is on the ground, assign it with a timestamp... + if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then + self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } ) + self:ReSpawn( SpawnCursor ) Stamp.Vec2 = nil Stamp.Time = nil - end - else - Stamp.Time = timer.getTime() + end + else + Stamp.Time = timer.getTime() Stamp.Vec2 = SpawnUnit:GetVec2() - end - else - Stamp.Vec2 = nil - Stamp.Time = nil - end - else + end + else + Stamp.Vec2 = nil + Stamp.Time = nil + end + else if SpawnUnit:InAir() == false then Stamp.Vec2 = SpawnUnit:GetVec2() if (SpawnUnit:GetVelocityKMH() < 1) then @@ -3436,13 +3398,13 @@ function SPAWN:_SpawnCleanUpScheduler() end end end - - SpawnGroup, SpawnCursor = self:GetNextAliveGroup( SpawnCursor ) - - self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) - - end - - return true -- Repeat - + + SpawnGroup, SpawnCursor = self:GetNextAliveGroup( SpawnCursor ) + + self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) + + end + + return true -- Repeat + end From f62e3391e12377fd8ed12fb3e9cb5661c43fc3cb Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 28 Dec 2021 08:25:31 +0100 Subject: [PATCH 044/200] SCORING - make treason and fratricide switchable --- .../Moose/Functional/Scoring.lua | 45 +++++++++++++------ 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/Moose Development/Moose/Functional/Scoring.lua b/Moose Development/Moose/Functional/Scoring.lua index b5c8da81b..b914d445c 100644 --- a/Moose Development/Moose/Functional/Scoring.lua +++ b/Moose Development/Moose/Functional/Scoring.lua @@ -284,9 +284,11 @@ function SCORING:New( GameName ) -- Default fratricide penalty level (maximum penalty that can be assigned to a player before he gets kicked). self:SetFratricide( self.ScaleDestroyPenalty * 3 ) + self.penaltyonfratricide = true -- Default penalty when a player changes coalition. self:SetCoalitionChangePenalty( self.ScaleDestroyPenalty ) + self.penaltyoncoalitionchange = true self:SetDisplayMessagePrefix() @@ -582,6 +584,23 @@ function SCORING:SetFratricide( Fratricide ) return self end +--- Decide if fratricide is leading to penalties (true) or not (false) +-- @param #SCORING self +-- @param #boolean OnOff Switch for Fratricide +-- @return #SCORING +function SCORING:SwitchFratricide( OnOff ) + self.penaltyonfratricide = OnOff + return self +end + +--- Decide if a change of coalition is leading to penalties (true) or not (false) +-- @param #SCORING self +-- @param #boolean OnOff Switch for Coalition Changes. +-- @return #SCORING +function SCORING:SwitchTreason( OnOff ) + self.penaltyoncoalitionchange = OnOff + return self +end --- When a player changes the coalition, he can receive a penalty score. -- Use the method @{#SCORING.SetCoalitionChangePenalty}() to define the penalty when a player changes coalition. @@ -647,14 +666,15 @@ function SCORING:_AddPlayerFromUnit( UnitData ) 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 + -- TODO: switch for coalition changes, make penalty alterable + if self.Players[PlayerName].UnitCoalition ~= UnitCoalition and self.penaltyoncoalitionchange then + self.Players[PlayerName].Penalty = self.Players[PlayerName].Penalty + self.CoalitionChangePenalty or 50 self.Players[PlayerName].PenaltyCoalition = self.Players[PlayerName].PenaltyCoalition + 1 MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' changed coalition from " .. _SCORINGCoalition[self.Players[PlayerName].UnitCoalition] .. " to " .. _SCORINGCoalition[UnitCoalition] .. - "(changed " .. self.Players[PlayerName].PenaltyCoalition .. " times the coalition). 50 Penalty points added.", + "(changed " .. self.Players[PlayerName].PenaltyCoalition .. " times the coalition). ".. self.CoalitionChangePenalty .."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, + self:ScoreCSV( PlayerName, "", "COALITION_PENALTY", 1, -1*self.CoalitionChangePenalty, self.Players[PlayerName].UnitName, _SCORINGCoalition[self.Players[PlayerName].UnitCoalition], _SCORINGCategory[self.Players[PlayerName].UnitCategory], self.Players[PlayerName].UnitType, UnitName, _SCORINGCoalition[UnitCoalition], _SCORINGCategory[UnitCategory], UnitData:GetTypeName() ) end end @@ -666,10 +686,9 @@ function SCORING:_AddPlayerFromUnit( UnitData ) self.Players[PlayerName].UNIT = UnitData self.Players[PlayerName].ThreatLevel = UnitThreatLevel self.Players[PlayerName].ThreatType = UnitThreatType - - -- TODO: DCS bug concerning Units with skill level client don't get destroyed in multi player. This logic is deactivated until this bug gets fixed. - --[[ - if self.Players[PlayerName].Penalty > self.Fratricide * 0.50 then + + -- TODO: make fratricide switchable + if self.Players[PlayerName].Penalty > self.Fratricide * 0.50 and self.penaltyonfratricide then if self.Players[PlayerName].PenaltyWarning < 1 then MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "': WARNING! If you continue to commit FRATRICIDE and have a PENALTY score higher than " .. self.Fratricide .. ", you will be COURT MARTIALED and DISMISSED from this mission! \nYour total penalty is: " .. self.Players[PlayerName].Penalty, MESSAGE.Type.Information @@ -678,14 +697,12 @@ function SCORING:_AddPlayerFromUnit( UnitData ) end end - if self.Players[PlayerName].Penalty > self.Fratricide then + if self.Players[PlayerName].Penalty > self.Fratricide and self.penaltyonfratricide then MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' committed FRATRICIDE, he will be COURT MARTIALED and is DISMISSED from this mission!", MESSAGE.Type.Information ):ToAll() UnitData:GetGroup():Destroy() end - --]] - end end @@ -1125,9 +1142,9 @@ function SCORING:_EventOnHit( Event ) if InitCoalition then -- A coalition object was hit, probably a static. if InitCoalition == TargetCoalition then -- TODO: Penalty according scale - Player.Penalty = Player.Penalty + 10 - PlayerHit.Penalty = PlayerHit.Penalty + 10 - PlayerHit.PenaltyHit = PlayerHit.PenaltyHit + 1 + Player.Penalty = Player.Penalty + 10 --* self.ScaleDestroyPenalty + PlayerHit.Penalty = PlayerHit.Penalty + 10 --* self.ScaleDestroyPenalty + PlayerHit.PenaltyHit = PlayerHit.PenaltyHit + 1 * self.ScaleDestroyPenalty MESSAGE :NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit friendly target " .. From 00d1aec210225ec2b69e50792cb1cfe1aafe5e6d Mon Sep 17 00:00:00 2001 From: TommyC81 Date: Tue, 28 Dec 2021 14:01:05 +0400 Subject: [PATCH 045/200] General fixes (#1673) * General minor Code formatting and minor typo/document fixes. * Update Marker.lua Code formatting and minor typo/document fixes. Note specifically the correction of "self.tocoaliton" to "self.tocoalition". --- Moose Development/Moose/AI/AI_A2A_Cap.lua | 6 +- .../Moose/AI/AI_A2A_Dispatcher.lua | 698 ++++++-------- Moose Development/Moose/AI/AI_A2A_Gci.lua | 6 +- Moose Development/Moose/AI/AI_A2A_Patrol.lua | 12 +- .../Moose/AI/AI_A2G_Dispatcher.lua | 22 +- Moose Development/Moose/AI/AI_A2G_SEAD.lua | 4 +- Moose Development/Moose/AI/AI_Air.lua | 16 +- .../Moose/AI/AI_Air_Dispatcher.lua | 122 ++- Moose Development/Moose/AI/AI_Air_Engage.lua | 4 +- Moose Development/Moose/AI/AI_Air_Patrol.lua | 6 +- Moose Development/Moose/AI/AI_BAI.lua | 8 +- Moose Development/Moose/AI/AI_CAP.lua | 8 +- Moose Development/Moose/AI/AI_CAS.lua | 8 +- Moose Development/Moose/AI/AI_Patrol.lua | 28 +- Moose Development/Moose/Cargo/Cargo.lua | 4 +- Moose Development/Moose/Core/Database.lua | 7 +- Moose Development/Moose/Core/Event.lua | 9 +- Moose Development/Moose/Core/Fsm.lua | 24 +- Moose Development/Moose/Core/Menu.lua | 2 +- Moose Development/Moose/Core/Spawn.lua | 4 +- Moose Development/Moose/Core/Zone.lua | 492 +++++----- .../Moose/Functional/Artillery.lua | 2 +- Moose Development/Moose/Functional/RAT.lua | 6 +- .../Moose/Functional/Warehouse.lua | 12 +- .../Moose/Functional/ZoneCaptureCoalition.lua | 27 +- .../Moose/Functional/ZoneGoalCoalition.lua | 110 ++- Moose Development/Moose/Ops/Airboss.lua | 2 +- Moose Development/Moose/Tasking/Mission.lua | 2 +- Moose Development/Moose/Tasking/Task.lua | 2 +- Moose Development/Moose/Utilities/Utils.lua | 2 +- .../Moose/Wrapper/Controllable.lua | 881 ++++++++---------- .../Moose/Wrapper/Identifiable.lua | 2 +- Moose Development/Moose/Wrapper/Marker.lua | 353 ++++--- 33 files changed, 1329 insertions(+), 1562 deletions(-) diff --git a/Moose Development/Moose/AI/AI_A2A_Cap.lua b/Moose Development/Moose/AI/AI_A2A_Cap.lua index f86bed9c0..aa43344e6 100644 --- a/Moose Development/Moose/AI/AI_A2A_Cap.lua +++ b/Moose Development/Moose/AI/AI_A2A_Cap.lua @@ -40,8 +40,8 @@ -- -- ![Process](..\Presentations\AI_CAP\Dia10.JPG) -- --- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB. --- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. +-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB. +-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. -- -- ![Process](..\Presentations\AI_CAP\Dia13.JPG) -- @@ -71,7 +71,7 @@ -- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets. -- * **@{#AI_A2A_CAP.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}. -- * **@{#AI_A2A_CAP.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task. --- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB. +-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB. -- -- ## 3. Set the Range of Engagement -- diff --git a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua index 941ac29e5..6c5ac333c 100644 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua @@ -40,7 +40,7 @@ -- AI\_A2A\_DISPATCHER is the main A2A defense class that models the A2A defense system. -- AI\_A2A\_GCICAP derives or inherits from AI\_A2A\_DISPATCHER and is a more **noob** user friendly class, but is less flexible. -- --- Before you start using the AI\_A2A\_DISPATCHER or AI\_A2A\_GCICAP ask youself the following questions. +-- Before you start using the AI\_A2A\_DISPATCHER or AI\_A2A\_GCICAP ask yourself the following questions. -- -- ## 0. Do I need AI\_A2A\_DISPATCHER or do I need AI\_A2A\_GCICAP? -- @@ -82,7 +82,7 @@ -- -- A good functioning defense will have a "maximum range" evaluated to the enemy when CAP will be engaged or GCI will be spawned. -- --- ## 6. Which Airbases, Carrier Ships, Farps will take part in the defense system for the Coalition? +-- ## 6. Which Airbases, Carrier Ships, FARPs will take part in the defense system for the Coalition? -- -- Carefully plan which airbases will take part in the coalition. Color each airbase in the color of the coalition. -- @@ -91,7 +91,7 @@ -- The defense system works with Squadrons. Each Squadron must be given a unique name, that forms the **key** to the defense system. -- Several options and activities can be set per Squadron. -- --- ## 8. Where will the Squadrons be located? On Airbases? On Carrier Ships? On Farps? +-- ## 8. Where will the Squadrons be located? On Airbases? On Carrier Ships? On FARPs? -- -- Squadrons are placed as the "home base" on an airfield, carrier or farp. -- Carefully plan where each Squadron will be located as part of the defense system. @@ -149,7 +149,7 @@ -- * From a parking spot with running engines -- * From a parking spot with cold engines -- --- **The default takeoff method is staight in the air.** +-- **The default takeoff method is straight in the air.** -- -- ## 17. For each Squadron, which landing method will I use? -- @@ -185,8 +185,6 @@ -- @module AI.AI_A2A_Dispatcher -- @image AI_Air_To_Air_Dispatching.JPG - - do -- AI_A2A_DISPATCHER --- AI_A2A_DISPATCHER class. @@ -266,7 +264,7 @@ do -- AI_A2A_DISPATCHER -- **DetectionSetGroup** is then being configured to filter all active groups with a group name starting with **DF CCCP AWACS** or **DF CCCP EWR** to be included in the Set. -- **DetectionSetGroup** is then being ordered to start the dynamic filtering. Note that any destroy or new spawn of a group with the above names will be removed or added to the Set. -- - -- Then a new Detection object is created from the class DETECTION_AREAS. A grouping radius of 30000 is choosen, which is 30km. + -- Then a new Detection object is created from the class DETECTION_AREAS. A grouping radius of 30000 is chosen, which is 30km. -- The **Detection** object is then passed to the @{#AI_A2A_DISPATCHER.New}() method to indicate the EWR network configuration and setup the A2A defense detection mechanism. -- -- You could build a **mutual defense system** like this: @@ -474,7 +472,7 @@ do -- AI_A2A_DISPATCHER -- * @{#AI_A2A_DISPATCHER.SetSquadronLandingAtRunway}() will despawn the returning aircraft directly after landing at the runway. -- * @{#AI_A2A_DISPATCHER.SetSquadronLandingAtEngineShutdown}() will despawn the returning aircraft when the aircraft has returned to its parking spot and has turned off its engines. -- - -- You can use these methods to minimize the airbase coodination overhead and to increase the airbase efficiency. + -- You can use these methods to minimize the airbase coordination overhead and to increase the airbase efficiency. -- When there are lots of aircraft returning for landing, at the same airbase, the takeoff process will be halted, which can cause a complete failure of the -- A2A defense system, as no new CAP or GCI planes can takeoff. -- Note that the method @{#AI_A2A_DISPATCHER.SetSquadronLandingNearAirbase}() will only work for returning aircraft, not for damaged or out of fuel aircraft. @@ -502,7 +500,7 @@ do -- AI_A2A_DISPATCHER -- -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia12.JPG) -- - -- In the case of GCI, the @{#AI_A2A_DISPATCHER.SetSquadronGrouping}() method has additional behaviour. When there aren't enough CAP flights airborne, a GCI will be initiated for the remaining + -- In the case of GCI, the @{#AI_A2A_DISPATCHER.SetSquadronGrouping}() method has additional behavior. When there aren't enough CAP flights airborne, a GCI will be initiated for the remaining -- targets to be engaged. Depending on the grouping parameter, the spawned flights for GCI are grouped into this setting. -- For example with a group setting of 2, if 3 targets are detected and cannot be engaged by CAP or any airborne flight, -- a GCI needs to be started, the GCI flights will be grouped as follows: Group 1 of 2 flights and Group 2 of one flight! @@ -537,13 +535,13 @@ do -- AI_A2A_DISPATCHER -- -- The **overhead value is set for a Squadron**, and can be **dynamically adjusted** during mission execution, so to adjust the defense overhead when the tactical situation changes. -- - -- ## 6.5. Squadron fuel treshold. + -- ## 6.5. Squadron fuel threshold. -- - -- When an airplane gets **out of fuel** to a certain %-tage, which is by default **15% (0.15)**, there are two possible actions that can be taken: + -- When an airplane gets **out of fuel** to a certain %, which is by default **15% (0.15)**, there are two possible actions that can be taken: -- - The defender will go RTB, and will be replaced with a new defender if possible. -- - The defender will refuel at a tanker, if a tanker has been specified for the squadron. -- - -- Use the method @{#AI_A2A_DISPATCHER.SetSquadronFuelThreshold}() to set the **squadron fuel treshold** of spawned airplanes for all squadrons. + -- Use the method @{#AI_A2A_DISPATCHER.SetSquadronFuelThreshold}() to set the **squadron fuel threshold** of spawned airplanes for all squadrons. -- -- ## 7. Setup a squadron for CAP -- @@ -608,7 +606,7 @@ do -- AI_A2A_DISPATCHER -- * The minimum and maximum engage speed -- * The type of altitude measurement -- - -- These define how the squadron will perform the CAP while partrolling. Different terrain types requires different types of CAP. + -- These define how the squadron will perform the CAP while patrolling. Different terrain types requires different types of CAP. -- -- The @{#AI_A2A_DISPATCHER.SetSquadronCapInterval}() method specifies **how much** and **when** CAP flights will takeoff. -- @@ -676,7 +674,7 @@ do -- AI_A2A_DISPATCHER -- Essentially this controls how many flights of GCI aircraft can be active at any time. -- Note allowing large numbers of active GCI flights can adversely impact mission performance on low or medium specification hosts/servers. -- GCI needs to be setup at strategic airbases. Too far will mean that the aircraft need to fly a long way to reach the intruders, - -- too short will mean that the intruders may have alraedy passed the ideal interception point! + -- too short will mean that the intruders may have already passed the ideal interception point! -- -- For example, the following setup will create a GCI for squadron "Sochi": -- @@ -734,17 +732,17 @@ do -- AI_A2A_DISPATCHER -- -- Use the method @{#AI_A2A_DISPATCHER.SetDefaultGrouping}() to set the **default grouping** of spawned airplanes for all squadrons. -- - -- ## 10.5. Default RTB fuel treshold. + -- ## 10.5. Default RTB fuel threshold. -- - -- When an airplane gets **out of fuel** to a certain %-tage, which is **15% (0.15)**, it will go RTB, and will be replaced with a new airplane when applicable. + -- When an airplane gets **out of fuel** to a certain %, which is **15% (0.15)**, it will go RTB, and will be replaced with a new airplane when applicable. -- - -- Use the method @{#AI_A2A_DISPATCHER.SetDefaultFuelThreshold}() to set the **default fuel treshold** of spawned airplanes for all squadrons. + -- Use the method @{#AI_A2A_DISPATCHER.SetDefaultFuelThreshold}() to set the **default fuel threshold** of spawned airplanes for all squadrons. -- - -- ## 10.6. Default RTB damage treshold. + -- ## 10.6. Default RTB damage threshold. -- - -- When an airplane is **damaged** to a certain %-tage, which is **40% (0.40)**, it will go RTB, and will be replaced with a new airplane when applicable. + -- When an airplane is **damaged** to a certain %, which is **40% (0.40)**, it will go RTB, and will be replaced with a new airplane when applicable. -- - -- Use the method @{#AI_A2A_DISPATCHER.SetDefaultDamageThreshold}() to set the **default damage treshold** of spawned airplanes for all squadrons. + -- Use the method @{#AI_A2A_DISPATCHER.SetDefaultDamageThreshold}() to set the **default damage threshold** of spawned airplanes for all squadrons. -- -- ## 10.7. Default settings for CAP. -- @@ -773,7 +771,7 @@ do -- AI_A2A_DISPATCHER -- -- In the mission editor, setup a group with task Refuelling. A tanker unit of the correct coalition will be automatically selected. -- Then, use the method @{#AI_A2A_DISPATCHER.SetDefaultTanker}() to set the tanker for the dispatcher. - -- Use the method @{#AI_A2A_DISPATCHER.SetDefaultFuelThreshold}() to set the %-tage left in the defender airplane tanks when a refuel action is needed. + -- Use the method @{#AI_A2A_DISPATCHER.SetDefaultFuelThreshold}() to set the % left in the defender airplane tanks when a refuel action is needed. -- -- When the tanker specified is alive and in the air, the tanker will be used for refuelling. -- @@ -787,7 +785,7 @@ do -- AI_A2A_DISPATCHER -- A2ADispatcher:SetSquadronCapInterval("Sochi", 2, 30, 600, 1 ) -- A2ADispatcher:SetSquadronGci( "Sochi", 900, 1200 ) -- - -- -- Set the default tanker for refuelling to "Tanker", when the default fuel treshold has reached 90% fuel left. + -- -- Set the default tanker for refuelling to "Tanker", when the default fuel threshold has reached 90% fuel left. -- A2ADispatcher:SetDefaultFuelThreshold( 0.9 ) -- A2ADispatcher:SetDefaultTanker( "Tanker" ) -- @@ -852,7 +850,6 @@ do -- AI_A2A_DISPATCHER Detection = nil, } - --- Squadron data structure. -- @type AI_A2A_DISPATCHER.Squadron -- @field #string Name Name of the squadron. @@ -871,7 +868,7 @@ do -- AI_A2A_DISPATCHER -- @field #number FuelThreshold Fuel threshold [0,1] for RTB. -- @field #string TankerName Name of the refuelling tanker. -- @field #table Table of template group names of the squadron. - -- @field #table Spawn Table of spaws Core.Spawn#SPAWN. + -- @field #table Spawn Table of spawns Core.Spawn#SPAWN. -- @field #table TemplatePrefixes -- @field #boolean Racetrack If true, CAP flights will perform a racetrack pattern rather than randomly patrolling the zone. -- @field #number RacetrackLengthMin Min Length of race track in meters. Default 10,000 m. @@ -888,7 +885,7 @@ do -- AI_A2A_DISPATCHER --- @field #AI_A2A_DISPATCHER.Takeoff Takeoff AI_A2A_DISPATCHER.Takeoff = GROUP.Takeoff - --- Defnes Landing location. + --- Defines Landing type/location. -- @field Landing AI_A2A_DISPATCHER.Landing = { NearAirbase = 1, @@ -897,26 +894,26 @@ do -- AI_A2A_DISPATCHER } --- AI_A2A_DISPATCHER constructor. - -- This is defining the A2A DISPATCHER for one coaliton. + -- This is defining the A2A DISPATCHER for one coalition. -- The Dispatcher works with a @{Functional.Detection#DETECTION_BASE} object that is taking of the detection of targets using the EWR units. - -- The Detection object is polymorphic, depending on the type of detection object choosen, the detection will work differently. + -- The Detection object is polymorphic, depending on the type of detection object chosen, the detection will work differently. -- @param #AI_A2A_DISPATCHER self -- @param Functional.Detection#DETECTION_BASE Detection The DETECTION object that will detects targets using the the Early Warning Radar network. -- @return #AI_A2A_DISPATCHER self -- @usage -- - -- -- Setup the Detection, using DETECTION_AREAS. - -- -- First define the SET of GROUPs that are defining the EWR network. - -- -- Here with prefixes DF CCCP AWACS, DF CCCP EWR. - -- DetectionSetGroup = SET_GROUP:New() - -- DetectionSetGroup:FilterPrefixes( { "DF CCCP AWACS", "DF CCCP EWR" } ) - -- DetectionSetGroup:FilterStart() + -- -- Setup the Detection, using DETECTION_AREAS. + -- -- First define the SET of GROUPs that are defining the EWR network. + -- -- Here with prefixes DF CCCP AWACS, DF CCCP EWR. + -- DetectionSetGroup = SET_GROUP:New() + -- DetectionSetGroup:FilterPrefixes( { "DF CCCP AWACS", "DF CCCP EWR" } ) + -- DetectionSetGroup:FilterStart() -- - -- -- Define the DETECTION_AREAS, using the DetectionSetGroup, with a 30km grouping radius. - -- Detection = DETECTION_AREAS:New( DetectionSetGroup, 30000 ) + -- -- Define the DETECTION_AREAS, using the DetectionSetGroup, with a 30km grouping radius. + -- Detection = DETECTION_AREAS:New( DetectionSetGroup, 30000 ) -- - -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) -- + -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. + -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) -- function AI_A2A_DISPATCHER:New( Detection ) @@ -933,12 +930,12 @@ do -- AI_A2A_DISPATCHER -- TODO: Check detection through radar. self.Detection:FilterCategories( { Unit.Category.AIRPLANE, Unit.Category.HELICOPTER } ) - --self.Detection:InitDetectRadar( true ) + -- self.Detection:InitDetectRadar( true ) self.Detection:SetRefreshTimeInterval( 30 ) self:SetEngageRadius() self:SetGciRadius() - self:SetIntercept( 300 ) -- A default intercept delay time of 300 seconds. + self:SetIntercept( 300 ) -- A default intercept delay time of 300 seconds. self:SetDisengageRadius( 300000 ) -- The default Disengage Radius is 300 km. self:SetDefaultTakeoff( AI_A2A_DISPATCHER.Takeoff.Air ) @@ -951,7 +948,6 @@ do -- AI_A2A_DISPATCHER self:SetDefaultCapTimeInterval( 180, 600 ) -- Between 180 and 600 seconds. self:SetDefaultCapLimit( 1 ) -- Maximum one CAP per squadron. - self:AddTransition( "Started", "Assign", "Started" ) --- OnAfter Transition Handler for Event Assign. @@ -1059,17 +1055,14 @@ do -- AI_A2A_DISPATCHER -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item. -- @param #table Defenders Defenders table. - -- Subscribe to the CRASH event so that when planes are shot -- by a Unit from the dispatcher, they will be removed from the detection... -- This will avoid the detection to still "know" the shot unit until the next detection. -- Otherwise, a new intercept or engage may happen for an already shot plane! - self:HandleEvent( EVENTS.Crash, self.OnEventCrashOrDead ) self:HandleEvent( EVENTS.Dead, self.OnEventCrashOrDead ) - --self:HandleEvent( EVENTS.RemoveUnit, self.OnEventCrashOrDead ) - + -- self:HandleEvent( EVENTS.RemoveUnit, self.OnEventCrashOrDead ) self:HandleEvent( EVENTS.Land ) self:HandleEvent( EVENTS.EngineShutdown ) @@ -1086,7 +1079,6 @@ do -- AI_A2A_DISPATCHER return self end - --- On after "Start" event. -- @param #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:onafterStart( From, Event, To ) @@ -1094,8 +1086,8 @@ do -- AI_A2A_DISPATCHER self:GetParent( self, AI_A2A_DISPATCHER ).onafterStart( self, From, Event, To ) -- Spawn the resources. - for SquadronName,_DefenderSquadron in pairs( self.DefenderSquadrons ) do - local DefenderSquadron=_DefenderSquadron --#AI_A2A_DISPATCHER.Squadron + for SquadronName, _DefenderSquadron in pairs( self.DefenderSquadrons ) do + local DefenderSquadron = _DefenderSquadron -- #AI_A2A_DISPATCHER.Squadron DefenderSquadron.Resources = {} if DefenderSquadron.ResourceCount then for Resource = 1, DefenderSquadron.ResourceCount do @@ -1105,7 +1097,6 @@ do -- AI_A2A_DISPATCHER end end - --- Park defender. -- @param #AI_A2A_DISPATCHER self -- @param #AI_A2A_DISPATCHER.Squadron DefenderSquadron The squadron. @@ -1113,7 +1104,7 @@ do -- AI_A2A_DISPATCHER local TemplateID = math.random( 1, #DefenderSquadron.Spawn ) - local Spawn = DefenderSquadron.Spawn[ TemplateID ] -- Core.Spawn#SPAWN + local Spawn = DefenderSquadron.Spawn[TemplateID] -- Core.Spawn#SPAWN Spawn:InitGrouping( 1 ) @@ -1121,11 +1112,11 @@ do -- AI_A2A_DISPATCHER if self:IsSquadronVisible( DefenderSquadron.Name ) then - local Grouping=DefenderSquadron.Grouping or self.DefenderDefault.Grouping + local Grouping = DefenderSquadron.Grouping or self.DefenderDefault.Grouping - Grouping=1 + Grouping = 1 - Spawn:InitGrouping(Grouping) + Spawn:InitGrouping( Grouping ) SpawnGroup = Spawn:SpawnAtAirbase( DefenderSquadron.Airbase, SPAWN.Takeoff.Cold ) @@ -1137,15 +1128,14 @@ do -- AI_A2A_DISPATCHER DefenderSquadron.Resources[TemplateID][GroupName] = {} DefenderSquadron.Resources[TemplateID][GroupName] = SpawnGroup - self.uncontrolled=self.uncontrolled or {} - self.uncontrolled[DefenderSquadron.Name]=self.uncontrolled[DefenderSquadron.Name] or {} + 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}) + table.insert( self.uncontrolled[DefenderSquadron.Name], { group = SpawnGroup, name = GroupName, grouping = Grouping } ) end end - --- Event base captured. -- @param #AI_A2A_DISPATCHER self -- @param Core.Event#EVENTDATA EventData @@ -1210,8 +1200,7 @@ do -- AI_A2A_DISPATCHER 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 + if LandingMethod == AI_A2A_DISPATCHER.Landing.AtEngineShutdown and not DefenderUnit:InAir() then local DefenderSize = Defender:GetSize() if DefenderSize == 1 then self:RemoveDefenderFromSquadron( Squadron, Defender ) @@ -1266,7 +1255,7 @@ do -- AI_A2A_DISPATCHER -- A2ADispatcher:SetDisengageRadius( 50000 ) -- -- -- Set 100km as the Disengage Radius. - -- A2ADispatcher:SetDisngageRadius() -- 300000 is the default value. + -- A2ADispatcher:SetDisengageRadius() -- 300000 is the default value. -- function AI_A2A_DISPATCHER:SetDisengageRadius( DisengageRadius ) @@ -1275,7 +1264,6 @@ do -- AI_A2A_DISPATCHER return self end - --- Define the radius to check if a target can be engaged by an ground controlled intercept. -- When targets are detected that are still really far off, you don't want the AI_A2A_DISPATCHER to launch intercepts just yet. -- You want it to wait until a certain Gci range is reached, which is the **distance of the closest airbase to target** @@ -1310,8 +1298,6 @@ do -- AI_A2A_DISPATCHER return self end - - --- Define a border area to simulate a **cold war** scenario. -- A **cold war** is one where CAP aircraft patrol their territory but will not attack enemy aircraft or launch GCI aircraft unless enemy aircraft enter their territory. In other words the EWR may detect an enemy aircraft but will only send aircraft to attack it if it crosses the border. -- A **hot war** is one where CAP aircraft will intercept any detected enemy aircraft and GCI aircraft will launch against detected enemy aircraft without regard for territory. In other words if the ground radar can detect the enemy aircraft then it will send CAP and GCI aircraft to attack it. @@ -1369,18 +1355,17 @@ do -- AI_A2A_DISPATCHER return self end - - --- Set the default damage treshold when defenders will RTB. - -- The default damage treshold is by default set to 40%, which means that when the airplane is 40% damaged, it will go RTB. + --- Set the default damage threshold when defenders will RTB. + -- The default damage threshold is by default set to 40%, which means that when the airplane is 40% damaged, it will go RTB. -- @param #AI_A2A_DISPATCHER self - -- @param #number DamageThreshold A decimal number between 0 and 1, that expresses the %-tage of the damage treshold before going RTB. + -- @param #number DamageThreshold A decimal number between 0 and 1, that expresses the % of the damage threshold before going RTB. -- @return #AI_A2A_DISPATCHER self -- @usage -- -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) -- - -- -- Now Setup the default damage treshold. + -- -- Now Setup the default damage threshold. -- A2ADispatcher:SetDefaultDamageThreshold( 0.90 ) -- Go RTB when the airplane 90% damaged. -- function AI_A2A_DISPATCHER:SetDefaultDamageThreshold( DamageThreshold ) @@ -1390,7 +1375,6 @@ do -- AI_A2A_DISPATCHER return self end - --- Set the default CAP time interval for squadrons, which will be used to determine a random CAP timing. -- The default CAP time interval is between 180 and 600 seconds. -- @param #AI_A2A_DISPATCHER self @@ -1413,7 +1397,6 @@ do -- AI_A2A_DISPATCHER return self end - --- Set the default CAP limit for squadrons, which will be used to determine how many CAP can be airborne at the same time for the squadron. -- The default CAP limit is 1 CAP, which means one CAP group being spawned. -- @param #AI_A2A_DISPATCHER self @@ -1448,7 +1431,6 @@ do -- AI_A2A_DISPATCHER return self end - --- Calculates which AI friendlies are nearby the area -- @param #AI_A2A_DISPATCHER self -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem @@ -1508,7 +1490,7 @@ do -- AI_A2A_DISPATCHER 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 "" + Message = Message .. (Target and (" from " .. Target.Index .. " [" .. Target.Set:Count() .. "]")) or "" end self:F( { Target = Message } ) end @@ -1535,17 +1517,16 @@ do -- AI_A2A_DISPATCHER if Defender and DefenderTask and DefenderTask.Target then DefenderTask.Target = nil end --- if Defender and DefenderTask then --- if DefenderTask.Fsm:Is( "Fuel" ) --- or DefenderTask.Fsm:Is( "LostControl") --- or DefenderTask.Fsm:Is( "Damaged" ) then --- self:ClearDefenderTask( Defender ) --- end --- end + -- if Defender and DefenderTask then + -- if DefenderTask.Fsm:Is( "Fuel" ) + -- or DefenderTask.Fsm:Is( "LostControl") + -- or DefenderTask.Fsm:Is( "Damaged" ) then + -- self:ClearDefenderTask( Defender ) + -- end + -- end return self end - --- Set defender task. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName Name of the squadron. @@ -1556,7 +1537,7 @@ do -- AI_A2A_DISPATCHER -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetDefenderTask( SquadronName, Defender, Type, Fsm, Target ) - self:F( { SquadronName = SquadronName, Defender = Defender:GetName(), Type=Type, Target=Target } ) + self:F( { SquadronName = SquadronName, Defender = Defender:GetName(), Type = Type, Target = Target } ) self.DefenderTasks[Defender] = self.DefenderTasks[Defender] or {} self.DefenderTasks[Defender].Type = Type @@ -1569,7 +1550,6 @@ do -- AI_A2A_DISPATCHER return self end - --- Set defender task target. -- @param #AI_A2A_DISPATCHER self -- @param Wrapper.Group#GROUP Defender The defender group. @@ -1587,7 +1567,6 @@ do -- AI_A2A_DISPATCHER return self end - --- This is the main method to define Squadrons programmatically. -- Squadrons: -- @@ -1630,6 +1609,7 @@ do -- AI_A2A_DISPATCHER -- If you have only one prefix name for a squadron, you don't need to use the `{ }`, otherwise you need to use the brackets. -- -- @param #number ResourceCount (optional) A number that specifies how many resources are in stock of the squadron. If not specified, the squadron will have infinite resources available. + -- @return #AI_A2A_DISPATCHER self -- -- @usage -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. @@ -1656,14 +1636,11 @@ do -- AI_A2A_DISPATCHER -- A2ADispatcher:SetSquadron( "104th", "Batumi", "Mig-29" ) -- A2ADispatcher:SetSquadron( "23th", "Batumi", "Su-27" ) -- - -- - -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetSquadron( SquadronName, AirbaseName, TemplatePrefixes, ResourceCount ) - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - local DefenderSquadron = self.DefenderSquadrons[SquadronName] --#AI_A2A_DISPATCHER.Squadron + local DefenderSquadron = self.DefenderSquadrons[SquadronName] -- #AI_A2A_DISPATCHER.Squadron DefenderSquadron.Name = SquadronName DefenderSquadron.Airbase = AIRBASE:FindByName( AirbaseName ) @@ -1680,7 +1657,7 @@ do -- AI_A2A_DISPATCHER else for TemplateID, SpawnTemplate in pairs( TemplatePrefixes ) do self.DefenderSpawns[SpawnTemplate] = self.DefenderSpawns[SpawnTemplate] or SPAWN:New( SpawnTemplate ) -- :InitCleanUp( 180 ) - DefenderSquadron.Spawn[#DefenderSquadron.Spawn+1] = self.DefenderSpawns[SpawnTemplate] + DefenderSquadron.Spawn[#DefenderSquadron.Spawn + 1] = self.DefenderSpawns[SpawnTemplate] end end DefenderSquadron.ResourceCount = ResourceCount @@ -1689,7 +1666,7 @@ do -- AI_A2A_DISPATCHER self:SetSquadronLanguage( SquadronName, "EN" ) -- Squadrons speak English by default. - self:F( { Squadron = {SquadronName, AirbaseName, TemplatePrefixes, ResourceCount } } ) + self:F( { Squadron = { SquadronName, AirbaseName, TemplatePrefixes, ResourceCount } } ) return self end @@ -1708,7 +1685,6 @@ do -- AI_A2A_DISPATCHER return DefenderSquadron end - --- Set the Squadron visible before startup of the dispatcher. -- All planes will be spawned as uncontrolled on the parking spot. -- They will lock the parking spot. @@ -1717,33 +1693,33 @@ do -- AI_A2A_DISPATCHER -- @return #AI_A2A_DISPATCHER self -- @usage -- - -- -- Set the Squadron visible before startup of dispatcher. - -- A2ADispatcher:SetSquadronVisible( "Mineralnye" ) + -- -- Set the Squadron visible before startup of dispatcher. + -- A2ADispatcher:SetSquadronVisible( "Mineralnye" ) -- function AI_A2A_DISPATCHER:SetSquadronVisible( SquadronName ) self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - local DefenderSquadron = self:GetSquadron( SquadronName ) --#AI_A2A_DISPATCHER.Squadron + local DefenderSquadron = self:GetSquadron( SquadronName ) -- #AI_A2A_DISPATCHER.Squadron DefenderSquadron.Uncontrolled = true -- For now, grouping is forced to 1 due to other parts of the class which would not work well with grouping>1. - DefenderSquadron.Grouping=1 + DefenderSquadron.Grouping = 1 -- Get free parking for fighter aircraft. - local nfreeparking=DefenderSquadron.Airbase:GetFreeParkingSpotsNumber(AIRBASE.TerminalType.FighterAircraft, true) + local nfreeparking = DefenderSquadron.Airbase:GetFreeParkingSpotsNumber( AIRBASE.TerminalType.FighterAircraft, true ) -- Take number of free parking spots if no resource count was specifed. - DefenderSquadron.ResourceCount=DefenderSquadron.ResourceCount or nfreeparking + DefenderSquadron.ResourceCount = DefenderSquadron.ResourceCount or nfreeparking -- Check that resource count is not larger than free parking spots. - DefenderSquadron.ResourceCount=math.min(DefenderSquadron.ResourceCount, nfreeparking) + DefenderSquadron.ResourceCount = math.min( DefenderSquadron.ResourceCount, nfreeparking ) -- Set uncontrolled spawning option. - for SpawnTemplate,_DefenderSpawn in pairs( self.DefenderSpawns ) do - local DefenderSpawn=_DefenderSpawn --Core.Spawn#SPAWN - DefenderSpawn:InitUnControlled(true) + for SpawnTemplate, _DefenderSpawn in pairs( self.DefenderSpawns ) do + local DefenderSpawn = _DefenderSpawn -- Core.Spawn#SPAWN + DefenderSpawn:InitUnControlled( true ) end end @@ -1754,14 +1730,14 @@ do -- AI_A2A_DISPATCHER -- @return #boolean true if visible. -- @usage -- - -- -- Set the Squadron visible before startup of dispatcher. - -- local IsVisible = A2ADispatcher:IsSquadronVisible( "Mineralnye" ) + -- -- Set the Squadron visible before startup of dispatcher. + -- local IsVisible = A2ADispatcher:IsSquadronVisible( "Mineralnye" ) -- function AI_A2A_DISPATCHER:IsSquadronVisible( SquadronName ) self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - local DefenderSquadron = self:GetSquadron( SquadronName ) --#AI_A2A_DISPATCHER.Squadron + local DefenderSquadron = self:GetSquadron( SquadronName ) -- #AI_A2A_DISPATCHER.Squadron if DefenderSquadron then return DefenderSquadron.Uncontrolled == true @@ -1788,24 +1764,24 @@ do -- AI_A2A_DISPATCHER -- @return #AI_A2A_DISPATCHER -- @usage -- - -- -- CAP Squadron execution. - -- CAPZoneEast = ZONE_POLYGON:New( "CAP Zone East", GROUP:FindByName( "CAP Zone East" ) ) - -- -- Setup a CAP, engaging between 800 and 900 km/h, altitude 30 (above the sea), radio altitude measurement, - -- -- patrolling speed between 500 and 600 km/h, altitude between 4000 and 10000 meters, barometric altitude measurement. - -- A2ADispatcher:SetSquadronCapV2( "Mineralnye", 800, 900, 30, 30, "RADIO", CAPZoneEast, 500, 600, 4000, 10000, "BARO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Mineralnye", 2, 30, 60, 1 ) + -- -- CAP Squadron execution. + -- CAPZoneEast = ZONE_POLYGON:New( "CAP Zone East", GROUP:FindByName( "CAP Zone East" ) ) + -- -- Setup a CAP, engaging between 800 and 900 km/h, altitude 30 (above the sea), radio altitude measurement, + -- -- patrolling speed between 500 and 600 km/h, altitude between 4000 and 10000 meters, barometric altitude measurement. + -- A2ADispatcher:SetSquadronCapV2( "Mineralnye", 800, 900, 30, 30, "RADIO", CAPZoneEast, 500, 600, 4000, 10000, "BARO" ) + -- A2ADispatcher:SetSquadronCapInterval( "Mineralnye", 2, 30, 60, 1 ) -- - -- CAPZoneWest = ZONE_POLYGON:New( "CAP Zone West", GROUP:FindByName( "CAP Zone West" ) ) - -- -- Setup a CAP, engaging between 800 and 1200 km/h, altitude between 4000 and 10000 meters, radio altitude measurement, - -- -- patrolling speed between 600 and 800 km/h, altitude between 4000 and 8000, barometric altitude measurement. - -- A2ADispatcher:SetSquadronCapV2( "Sochi", 800, 1200, 2000, 3000, "RADIO", CAPZoneWest, 600, 800, 4000, 8000, "BARO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) + -- CAPZoneWest = ZONE_POLYGON:New( "CAP Zone West", GROUP:FindByName( "CAP Zone West" ) ) + -- -- Setup a CAP, engaging between 800 and 1200 km/h, altitude between 4000 and 10000 meters, radio altitude measurement, + -- -- patrolling speed between 600 and 800 km/h, altitude between 4000 and 8000, barometric altitude measurement. + -- A2ADispatcher:SetSquadronCapV2( "Sochi", 800, 1200, 2000, 3000, "RADIO", CAPZoneWest, 600, 800, 4000, 8000, "BARO" ) + -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) -- - -- CAPZoneMiddle = ZONE:New( "CAP Zone Middle") - -- -- Setup a CAP, engaging between 800 and 1200 km/h, altitude between 5000 and 8000 meters, barometric altitude measurement, - -- -- patrolling speed between 600 and 800 km/h, altitude between 4000 and 8000, radio altitude. - -- A2ADispatcher:SetSquadronCapV2( "Maykop", 800, 1200, 5000, 8000, "BARO", CAPZoneMiddle, 600, 800, 4000, 8000, "RADIO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Maykop", 2, 30, 120, 1 ) + -- CAPZoneMiddle = ZONE:New( "CAP Zone Middle") + -- -- Setup a CAP, engaging between 800 and 1200 km/h, altitude between 5000 and 8000 meters, barometric altitude measurement, + -- -- patrolling speed between 600 and 800 km/h, altitude between 4000 and 8000, radio altitude. + -- A2ADispatcher:SetSquadronCapV2( "Maykop", 800, 1200, 5000, 8000, "BARO", CAPZoneMiddle, 600, 800, 4000, 8000, "RADIO" ) + -- A2ADispatcher:SetSquadronCapInterval( "Maykop", 2, 30, 120, 1 ) -- function AI_A2A_DISPATCHER:SetSquadronCap2( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType, Zone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType ) @@ -1857,18 +1833,18 @@ do -- AI_A2A_DISPATCHER -- @return #AI_A2A_DISPATCHER -- @usage -- - -- -- CAP Squadron execution. - -- CAPZoneEast = ZONE_POLYGON:New( "CAP Zone East", GROUP:FindByName( "CAP Zone East" ) ) - -- A2ADispatcher:SetSquadronCap( "Mineralnye", CAPZoneEast, 4000, 10000, 500, 600, 800, 900 ) - -- A2ADispatcher:SetSquadronCapInterval( "Mineralnye", 2, 30, 60, 1 ) + -- -- CAP Squadron execution. + -- CAPZoneEast = ZONE_POLYGON:New( "CAP Zone East", GROUP:FindByName( "CAP Zone East" ) ) + -- A2ADispatcher:SetSquadronCap( "Mineralnye", CAPZoneEast, 4000, 10000, 500, 600, 800, 900 ) + -- A2ADispatcher:SetSquadronCapInterval( "Mineralnye", 2, 30, 60, 1 ) -- - -- CAPZoneWest = ZONE_POLYGON:New( "CAP Zone West", GROUP:FindByName( "CAP Zone West" ) ) - -- A2ADispatcher:SetSquadronCap( "Sochi", CAPZoneWest, 4000, 8000, 600, 800, 800, 1200, "BARO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) + -- CAPZoneWest = ZONE_POLYGON:New( "CAP Zone West", GROUP:FindByName( "CAP Zone West" ) ) + -- A2ADispatcher:SetSquadronCap( "Sochi", CAPZoneWest, 4000, 8000, 600, 800, 800, 1200, "BARO" ) + -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) -- - -- CAPZoneMiddle = ZONE:New( "CAP Zone Middle") - -- A2ADispatcher:SetSquadronCap( "Maykop", CAPZoneMiddle, 4000, 8000, 600, 800, 800, 1200, "RADIO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) + -- CAPZoneMiddle = ZONE:New( "CAP Zone Middle") + -- A2ADispatcher:SetSquadronCap( "Maykop", CAPZoneMiddle, 4000, 8000, 600, 800, 800, 1200, "RADIO" ) + -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) -- function AI_A2A_DISPATCHER:SetSquadronCap( SquadronName, Zone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, AltType ) @@ -1885,18 +1861,18 @@ do -- AI_A2A_DISPATCHER -- @return #AI_A2A_DISPATCHER -- @usage -- - -- -- CAP Squadron execution. - -- CAPZoneEast = ZONE_POLYGON:New( "CAP Zone East", GROUP:FindByName( "CAP Zone East" ) ) - -- A2ADispatcher:SetSquadronCap( "Mineralnye", CAPZoneEast, 4000, 10000, 500, 600, 800, 900 ) - -- A2ADispatcher:SetSquadronCapInterval( "Mineralnye", 2, 30, 60, 1 ) + -- -- CAP Squadron execution. + -- CAPZoneEast = ZONE_POLYGON:New( "CAP Zone East", GROUP:FindByName( "CAP Zone East" ) ) + -- A2ADispatcher:SetSquadronCap( "Mineralnye", CAPZoneEast, 4000, 10000, 500, 600, 800, 900 ) + -- A2ADispatcher:SetSquadronCapInterval( "Mineralnye", 2, 30, 60, 1 ) -- - -- CAPZoneWest = ZONE_POLYGON:New( "CAP Zone West", GROUP:FindByName( "CAP Zone West" ) ) - -- A2ADispatcher:SetSquadronCap( "Sochi", CAPZoneWest, 4000, 8000, 600, 800, 800, 1200, "BARO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) + -- CAPZoneWest = ZONE_POLYGON:New( "CAP Zone West", GROUP:FindByName( "CAP Zone West" ) ) + -- A2ADispatcher:SetSquadronCap( "Sochi", CAPZoneWest, 4000, 8000, 600, 800, 800, 1200, "BARO" ) + -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) -- - -- CAPZoneMiddle = ZONE:New( "CAP Zone Middle") - -- A2ADispatcher:SetSquadronCap( "Maykop", CAPZoneMiddle, 4000, 8000, 600, 800, 800, 1200, "RADIO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) + -- CAPZoneMiddle = ZONE:New( "CAP Zone Middle") + -- A2ADispatcher:SetSquadronCap( "Maykop", CAPZoneMiddle, 4000, 8000, 600, 800, 800, 1200, "RADIO" ) + -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) -- function AI_A2A_DISPATCHER:SetSquadronCapInterval( SquadronName, CapLimit, LowInterval, HighInterval, Probability ) @@ -1914,7 +1890,7 @@ do -- AI_A2A_DISPATCHER Cap.Scheduler = Cap.Scheduler or SCHEDULER:New( self ) local Scheduler = Cap.Scheduler -- Core.Scheduler#SCHEDULER local ScheduleID = Cap.ScheduleID - local Variance = ( Cap.HighInterval - Cap.LowInterval ) / 2 + local Variance = (Cap.HighInterval - Cap.LowInterval) / 2 local Repeat = Cap.LowInterval + Variance local Randomization = Variance / Repeat local Start = math.random( 1, Cap.HighInterval ) @@ -1954,7 +1930,7 @@ do -- AI_A2A_DISPATCHER -- @param #string SquadronName The squadron name. -- @return #AI_A2A_DISPATCHER.Squadron DefenderSquadron function AI_A2A_DISPATCHER:CanCAP( SquadronName ) - self:F({SquadronName = SquadronName}) + self:F( { SquadronName = SquadronName } ) self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} self.DefenderSquadrons[SquadronName].Cap = self.DefenderSquadrons[SquadronName].Cap or {} @@ -1963,7 +1939,7 @@ do -- AI_A2A_DISPATCHER if DefenderSquadron.Captured == false then -- We can only spawn new CAP if the base has not been captured. - if ( not DefenderSquadron.ResourceCount ) or ( DefenderSquadron.ResourceCount and DefenderSquadron.ResourceCount > 0 ) then -- And, if there are sufficient resources. + if (not DefenderSquadron.ResourceCount) or (DefenderSquadron.ResourceCount and DefenderSquadron.ResourceCount > 0) then -- And, if there are sufficient resources. local Cap = DefenderSquadron.Cap if Cap then @@ -1981,7 +1957,6 @@ do -- AI_A2A_DISPATCHER return nil end - --- Set race track pattern as default when any squadron is performing CAP. -- @param #AI_A2A_DISPATCHER self -- @param #number LeglengthMin Min length of the race track leg in meters. Default 10,000 m. @@ -1992,16 +1967,16 @@ do -- AI_A2A_DISPATCHER -- @param #number DurationMax (Optional) Max duration in seconds before switching the orbit position. Default is keep same orbit until RTB or engage. -- @param #table CapCoordinates Table of coordinates of first race track point. Second point is determined by leg length and heading. -- @return #AI_A2A_DISPATCHER self - function AI_A2A_DISPATCHER:SetDefaultCapRacetrack(LeglengthMin, LeglengthMax, HeadingMin, HeadingMax, DurationMin, DurationMax, CapCoordinates) + function AI_A2A_DISPATCHER:SetDefaultCapRacetrack( LeglengthMin, LeglengthMax, HeadingMin, HeadingMax, DurationMin, DurationMax, CapCoordinates ) - self.DefenderDefault.Racetrack=true - self.DefenderDefault.RacetrackLengthMin=LeglengthMin - self.DefenderDefault.RacetrackLengthMax=LeglengthMax - self.DefenderDefault.RacetrackHeadingMin=HeadingMin - self.DefenderDefault.RacetrackHeadingMax=HeadingMax - self.DefenderDefault.RacetrackDurationMin=DurationMin - self.DefenderDefault.RacetrackDurationMax=DurationMax - self.DefenderDefault.RacetrackCoordinates=CapCoordinates + self.DefenderDefault.Racetrack = true + self.DefenderDefault.RacetrackLengthMin = LeglengthMin + self.DefenderDefault.RacetrackLengthMax = LeglengthMax + self.DefenderDefault.RacetrackHeadingMin = HeadingMin + self.DefenderDefault.RacetrackHeadingMax = HeadingMax + self.DefenderDefault.RacetrackDurationMin = DurationMin + self.DefenderDefault.RacetrackDurationMax = DurationMax + self.DefenderDefault.RacetrackCoordinates = CapCoordinates return self end @@ -2017,31 +1992,30 @@ do -- AI_A2A_DISPATCHER -- @param #number DurationMax (Optional) Max duration in seconds before switching the orbit position. Default is keep same orbit until RTB or engage. -- @param #table CapCoordinates Table of coordinates of first race track point. Second point is determined by leg length and heading. -- @return #AI_A2A_DISPATCHER self - function AI_A2A_DISPATCHER:SetSquadronCapRacetrack(SquadronName, LeglengthMin, LeglengthMax, HeadingMin, HeadingMax, DurationMin, DurationMax, CapCoordinates) + function AI_A2A_DISPATCHER:SetSquadronCapRacetrack( SquadronName, LeglengthMin, LeglengthMax, HeadingMin, HeadingMax, DurationMin, DurationMax, CapCoordinates ) local DefenderSquadron = self:GetSquadron( SquadronName ) if DefenderSquadron then - DefenderSquadron.Racetrack=true - DefenderSquadron.RacetrackLengthMin=LeglengthMin - DefenderSquadron.RacetrackLengthMax=LeglengthMax - DefenderSquadron.RacetrackHeadingMin=HeadingMin - DefenderSquadron.RacetrackHeadingMax=HeadingMax - DefenderSquadron.RacetrackDurationMin=DurationMin - DefenderSquadron.RacetrackDurationMax=DurationMax - DefenderSquadron.RacetrackCoordinates=CapCoordinates + DefenderSquadron.Racetrack = true + DefenderSquadron.RacetrackLengthMin = LeglengthMin + DefenderSquadron.RacetrackLengthMax = LeglengthMax + DefenderSquadron.RacetrackHeadingMin = HeadingMin + DefenderSquadron.RacetrackHeadingMax = HeadingMax + DefenderSquadron.RacetrackDurationMin = DurationMin + DefenderSquadron.RacetrackDurationMax = DurationMax + DefenderSquadron.RacetrackCoordinates = CapCoordinates end return self end - --- Check if squadron can do GCI. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The squadron name. -- @return #table DefenderSquadron function AI_A2A_DISPATCHER:CanGCI( SquadronName ) - self:F({SquadronName = SquadronName}) + self:F( { SquadronName = SquadronName } ) self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} self.DefenderSquadrons[SquadronName].Gci = self.DefenderSquadrons[SquadronName].Gci or {} @@ -2050,7 +2024,7 @@ do -- AI_A2A_DISPATCHER if DefenderSquadron.Captured == false then -- We can only spawn new CAP if the base has not been captured. - if ( not DefenderSquadron.ResourceCount ) or ( DefenderSquadron.ResourceCount and DefenderSquadron.ResourceCount > 0 ) then -- And, if there are sufficient resources. + if (not DefenderSquadron.ResourceCount) or (DefenderSquadron.ResourceCount and DefenderSquadron.ResourceCount > 0) then -- And, if there are sufficient resources. local Gci = DefenderSquadron.Gci if Gci then return DefenderSquadron @@ -2068,14 +2042,14 @@ do -- AI_A2A_DISPATCHER -- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement. -- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement. -- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO". + -- @return #AI_A2A_DISPATCHER -- @usage -- - -- -- GCI Squadron execution. - -- A2ADispatcher:SetSquadronGci2( "Mozdok", 900, 1200, 5000, 5000, "BARO" ) - -- A2ADispatcher:SetSquadronGci2( "Novo", 900, 2100, 30, 30, "RADIO" ) - -- A2ADispatcher:SetSquadronGci2( "Maykop", 900, 1200, 100, 300, "RADIO" ) + -- -- GCI Squadron execution. + -- A2ADispatcher:SetSquadronGci2( "Mozdok", 900, 1200, 5000, 5000, "BARO" ) + -- A2ADispatcher:SetSquadronGci2( "Novo", 900, 2100, 30, 30, "RADIO" ) + -- A2ADispatcher:SetSquadronGci2( "Maykop", 900, 1200, 100, 300, "RADIO" ) -- - -- @return #AI_A2A_DISPATCHER function AI_A2A_DISPATCHER:SetSquadronGci2( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} @@ -2097,14 +2071,14 @@ do -- AI_A2A_DISPATCHER -- @param #string SquadronName The squadron name. -- @param #number EngageMinSpeed The minimum speed [km/h] at which the GCI can be executed. -- @param #number EngageMaxSpeed The maximum speed [km/h] at which the GCI can be executed. + -- @return #AI_A2A_DISPATCHER -- @usage -- - -- -- GCI Squadron execution. - -- A2ADispatcher:SetSquadronGci( "Mozdok", 900, 1200 ) - -- A2ADispatcher:SetSquadronGci( "Novo", 900, 2100 ) - -- A2ADispatcher:SetSquadronGci( "Maykop", 900, 1200 ) + -- -- GCI Squadron execution. + -- A2ADispatcher:SetSquadronGci( "Mozdok", 900, 1200 ) + -- A2ADispatcher:SetSquadronGci( "Novo", 900, 2100 ) + -- A2ADispatcher:SetSquadronGci( "Maykop", 900, 1200 ) -- - -- @return #AI_A2A_DISPATCHER function AI_A2A_DISPATCHER:SetSquadronGci( SquadronName, EngageMinSpeed, EngageMaxSpeed ) self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} @@ -2120,7 +2094,8 @@ do -- AI_A2A_DISPATCHER --- Defines the default amount of extra planes that will take-off as part of the defense system. -- @param #AI_A2A_DISPATCHER self - -- @param #number Overhead The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units. + -- @param #number Overhead The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units. + -- @return #AI_A2A_DISPATCHER -- The default overhead is 1, so equal balance. The @{#AI_A2A_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance A2A missiles payload, may still be less effective than a F-15C with short missiles... -- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes. @@ -2147,7 +2122,6 @@ do -- AI_A2A_DISPATCHER -- -- A2ADispatcher:SetDefaultOverhead( 1.5 ) -- - -- @return #AI_A2A_DISPATCHER function AI_A2A_DISPATCHER:SetDefaultOverhead( Overhead ) self.DefenderDefault.Overhead = Overhead @@ -2155,11 +2129,11 @@ do -- AI_A2A_DISPATCHER return self end - --- Defines the amount of extra planes that will take-off as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. - -- @param #number Overhead The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units. + -- @param #number Overhead The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units. + -- @return #AI_A2A_DISPATCHER self -- The default overhead is 1, so equal balance. The @{#AI_A2A_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance A2A missiles payload, may still be less effective than a F-15C with short missiles... -- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes. @@ -2186,7 +2160,6 @@ do -- AI_A2A_DISPATCHER -- -- A2ADispatcher:SetSquadronOverhead( "SquadronName", 1.5 ) -- - -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetSquadronOverhead( SquadronName, Overhead ) local DefenderSquadron = self:GetSquadron( SquadronName ) @@ -2195,11 +2168,11 @@ do -- AI_A2A_DISPATCHER return self end - --- Sets the default grouping of new airplanes spawned. -- Grouping will trigger how new airplanes will be grouped if more than one airplane is spawned for defense. -- @param #AI_A2A_DISPATCHER self -- @param #number Grouping The level of grouping that will be applied of the CAP or GCI defenders. + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2207,8 +2180,6 @@ do -- AI_A2A_DISPATCHER -- -- Set a grouping by default per 2 airplanes. -- A2ADispatcher:SetDefaultGrouping( 2 ) -- - -- - -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetDefaultGrouping( Grouping ) self.DefenderDefault.Grouping = Grouping @@ -2216,12 +2187,12 @@ do -- AI_A2A_DISPATCHER return self end - --- Sets the grouping of new airplanes spawned. -- Grouping will trigger how new airplanes will be grouped if more than one airplane is spawned for defense. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @param #number Grouping The level of grouping that will be applied of the CAP or GCI defenders. + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2229,8 +2200,6 @@ do -- AI_A2A_DISPATCHER -- -- Set a grouping per 2 airplanes. -- A2ADispatcher:SetSquadronGrouping( "SquadronName", 2 ) -- - -- - -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetSquadronGrouping( SquadronName, Grouping ) local DefenderSquadron = self:GetSquadron( SquadronName ) @@ -2239,10 +2208,10 @@ do -- AI_A2A_DISPATCHER return self end - --- Defines the default method at which new flights will spawn and take-off as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @param #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2259,9 +2228,6 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights by default take-off from the airbase cold. -- A2ADispatcher:SetDefaultTakeoff( AI_A2A_Dispatcher.Takeoff.Cold ) -- - -- - -- @return #AI_A2A_DISPATCHER self - -- function AI_A2A_DISPATCHER:SetDefaultTakeoff( Takeoff ) self.DefenderDefault.Takeoff = Takeoff @@ -2273,6 +2239,7 @@ do -- AI_A2A_DISPATCHER -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @param #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2289,9 +2256,6 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights take-off from the airbase cold. -- A2ADispatcher:SetSquadronTakeoff( "SquadronName", AI_A2A_Dispatcher.Takeoff.Cold ) -- - -- - -- @return #AI_A2A_DISPATCHER self - -- function AI_A2A_DISPATCHER:SetSquadronTakeoff( SquadronName, Takeoff ) local DefenderSquadron = self:GetSquadron( SquadronName ) @@ -2300,7 +2264,6 @@ do -- AI_A2A_DISPATCHER return self end - --- Gets the default method at which new flights will spawn and take-off as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @return #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. @@ -2314,7 +2277,7 @@ do -- AI_A2A_DISPATCHER -- ... -- end -- - function AI_A2A_DISPATCHER:GetDefaultTakeoff( ) + function AI_A2A_DISPATCHER:GetDefaultTakeoff() return self.DefenderDefault.Takeoff end @@ -2339,9 +2302,9 @@ do -- AI_A2A_DISPATCHER return DefenderSquadron.Takeoff or self.DefenderDefault.Takeoff end - --- Sets flights to default take-off in the air, as part of the defense system. -- @param #AI_A2A_DISPATCHER self + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2349,8 +2312,6 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights by default take-off in the air. -- A2ADispatcher:SetDefaultTakeoffInAir() -- - -- @return #AI_A2A_DISPATCHER self - -- function AI_A2A_DISPATCHER:SetDefaultTakeoffInAir() self:SetDefaultTakeoff( AI_A2A_DISPATCHER.Takeoff.Air ) @@ -2358,11 +2319,11 @@ do -- AI_A2A_DISPATCHER return self end - --- Sets flights to take-off in the air, as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @param #number TakeoffAltitude (optional) The altitude in meters above the ground. If not given, the default takeoff altitude will be used. + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2370,8 +2331,6 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights take-off in the air. -- A2ADispatcher:SetSquadronTakeoffInAir( "SquadronName" ) -- - -- @return #AI_A2A_DISPATCHER self - -- function AI_A2A_DISPATCHER:SetSquadronTakeoffInAir( SquadronName, TakeoffAltitude ) self:SetSquadronTakeoff( SquadronName, AI_A2A_DISPATCHER.Takeoff.Air ) @@ -2383,9 +2342,9 @@ do -- AI_A2A_DISPATCHER return self end - --- Sets flights by default to take-off from the runway, as part of the defense system. -- @param #AI_A2A_DISPATCHER self + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2393,8 +2352,6 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights by default take-off from the runway. -- A2ADispatcher:SetDefaultTakeoffFromRunway() -- - -- @return #AI_A2A_DISPATCHER self - -- function AI_A2A_DISPATCHER:SetDefaultTakeoffFromRunway() self:SetDefaultTakeoff( AI_A2A_DISPATCHER.Takeoff.Runway ) @@ -2402,10 +2359,10 @@ do -- AI_A2A_DISPATCHER return self end - --- Sets flights to take-off from the runway, as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2413,8 +2370,6 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights take-off from the runway. -- A2ADispatcher:SetSquadronTakeoffFromRunway( "SquadronName" ) -- - -- @return #AI_A2A_DISPATCHER self - -- function AI_A2A_DISPATCHER:SetSquadronTakeoffFromRunway( SquadronName ) self:SetSquadronTakeoff( SquadronName, AI_A2A_DISPATCHER.Takeoff.Runway ) @@ -2422,9 +2377,9 @@ do -- AI_A2A_DISPATCHER return self end - --- Sets flights by default to take-off from the airbase at a hot location, as part of the defense system. -- @param #AI_A2A_DISPATCHER self + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2432,8 +2387,6 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights by default take-off at a hot parking spot. -- A2ADispatcher:SetDefaultTakeoffFromParkingHot() -- - -- @return #AI_A2A_DISPATCHER self - -- function AI_A2A_DISPATCHER:SetDefaultTakeoffFromParkingHot() self:SetDefaultTakeoff( AI_A2A_DISPATCHER.Takeoff.Hot ) @@ -2444,6 +2397,7 @@ do -- AI_A2A_DISPATCHER --- Sets flights to take-off from the airbase at a hot location, as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2451,8 +2405,6 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights take-off in the air. -- A2ADispatcher:SetSquadronTakeoffFromParkingHot( "SquadronName" ) -- - -- @return #AI_A2A_DISPATCHER self - -- function AI_A2A_DISPATCHER:SetSquadronTakeoffFromParkingHot( SquadronName ) self:SetSquadronTakeoff( SquadronName, AI_A2A_DISPATCHER.Takeoff.Hot ) @@ -2460,9 +2412,9 @@ do -- AI_A2A_DISPATCHER return self end - --- Sets flights to by default take-off from the airbase at a cold location, as part of the defense system. -- @param #AI_A2A_DISPATCHER self + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2470,8 +2422,6 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights take-off from a cold parking spot. -- A2ADispatcher:SetDefaultTakeoffFromParkingCold() -- - -- @return #AI_A2A_DISPATCHER self - -- function AI_A2A_DISPATCHER:SetDefaultTakeoffFromParkingCold() self:SetDefaultTakeoff( AI_A2A_DISPATCHER.Takeoff.Cold ) @@ -2479,10 +2429,10 @@ do -- AI_A2A_DISPATCHER return self end - --- Sets flights to take-off from the airbase at a cold location, as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2490,8 +2440,6 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights take-off from a cold parking spot. -- A2ADispatcher:SetSquadronTakeoffFromParkingCold( "SquadronName" ) -- - -- @return #AI_A2A_DISPATCHER self - -- function AI_A2A_DISPATCHER:SetSquadronTakeoffFromParkingCold( SquadronName ) self:SetSquadronTakeoff( SquadronName, AI_A2A_DISPATCHER.Takeoff.Cold ) @@ -2499,10 +2447,10 @@ do -- AI_A2A_DISPATCHER return self end - --- Defines the default altitude where airplanes will spawn in the air and take-off as part of the defense system, when the take-off in the air method has been selected. -- @param #AI_A2A_DISPATCHER self -- @param #number TakeoffAltitude The altitude in meters above the ground. + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2510,8 +2458,6 @@ do -- AI_A2A_DISPATCHER -- -- Set the default takeoff altitude when taking off in the air. -- A2ADispatcher:SetDefaultTakeoffInAirAltitude( 2000 ) -- This makes planes start at 2000 meters above the ground. -- - -- @return #AI_A2A_DISPATCHER self - -- function AI_A2A_DISPATCHER:SetDefaultTakeoffInAirAltitude( TakeoffAltitude ) self.DefenderDefault.TakeoffAltitude = TakeoffAltitude @@ -2523,6 +2469,7 @@ do -- AI_A2A_DISPATCHER -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @param #number TakeoffAltitude The altitude in meters above the ground. + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2530,8 +2477,6 @@ do -- AI_A2A_DISPATCHER -- -- Set the default takeoff altitude when taking off in the air. -- A2ADispatcher:SetSquadronTakeoffInAirAltitude( "SquadronName", 2000 ) -- This makes planes start at 2000 meters above the ground. -- - -- @return #AI_A2A_DISPATCHER self - -- function AI_A2A_DISPATCHER:SetSquadronTakeoffInAirAltitude( SquadronName, TakeoffAltitude ) local DefenderSquadron = self:GetSquadron( SquadronName ) @@ -2540,10 +2485,10 @@ do -- AI_A2A_DISPATCHER return self end - --- Defines the default method at which flights will land and despawn as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @param #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2557,7 +2502,6 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights by default despawn after landing and parking, and after engine shutdown. -- A2ADispatcher:SetDefaultLanding( AI_A2A_Dispatcher.Landing.AtEngineShutdown ) -- - -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetDefaultLanding( Landing ) self.DefenderDefault.Landing = Landing @@ -2565,11 +2509,11 @@ do -- AI_A2A_DISPATCHER return self end - --- Defines the method at which flights will land and despawn as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @param #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2583,7 +2527,6 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights despawn after landing and parking, and after engine shutdown. -- A2ADispatcher:SetSquadronLanding( "SquadronName", AI_A2A_Dispatcher.Landing.AtEngineShutdown ) -- - -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetSquadronLanding( SquadronName, Landing ) local DefenderSquadron = self:GetSquadron( SquadronName ) @@ -2592,7 +2535,6 @@ do -- AI_A2A_DISPATCHER return self end - --- Gets the default method at which flights will land and despawn as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @return #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown @@ -2611,7 +2553,6 @@ do -- AI_A2A_DISPATCHER return self.DefenderDefault.Landing end - --- Gets the method at which flights will land and despawn as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. @@ -2632,9 +2573,9 @@ do -- AI_A2A_DISPATCHER return DefenderSquadron.Landing or self.DefenderDefault.Landing end - --- Sets flights by default to land and despawn near the airbase in the air, as part of the defense system. -- @param #AI_A2A_DISPATCHER self + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2642,7 +2583,6 @@ do -- AI_A2A_DISPATCHER -- -- Let flights by default to land near the airbase and despawn. -- A2ADispatcher:SetDefaultLandingNearAirbase() -- - -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetDefaultLandingNearAirbase() self:SetDefaultLanding( AI_A2A_DISPATCHER.Landing.NearAirbase ) @@ -2650,10 +2590,10 @@ do -- AI_A2A_DISPATCHER return self end - --- Sets flights to land and despawn near the airbase in the air, as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2661,7 +2601,6 @@ do -- AI_A2A_DISPATCHER -- -- Let flights to land near the airbase and despawn. -- A2ADispatcher:SetSquadronLandingNearAirbase( "SquadronName" ) -- - -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetSquadronLandingNearAirbase( SquadronName ) self:SetSquadronLanding( SquadronName, AI_A2A_DISPATCHER.Landing.NearAirbase ) @@ -2669,9 +2608,9 @@ do -- AI_A2A_DISPATCHER return self end - --- Sets flights by default to land and despawn at the runway, as part of the defense system. -- @param #AI_A2A_DISPATCHER self + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2679,7 +2618,6 @@ do -- AI_A2A_DISPATCHER -- -- Let flights by default land at the runway and despawn. -- A2ADispatcher:SetDefaultLandingAtRunway() -- - -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetDefaultLandingAtRunway() self:SetDefaultLanding( AI_A2A_DISPATCHER.Landing.AtRunway ) @@ -2687,10 +2625,10 @@ do -- AI_A2A_DISPATCHER return self end - --- Sets flights to land and despawn at the runway, as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2698,7 +2636,6 @@ do -- AI_A2A_DISPATCHER -- -- Let flights land at the runway and despawn. -- A2ADispatcher:SetSquadronLandingAtRunway( "SquadronName" ) -- - -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetSquadronLandingAtRunway( SquadronName ) self:SetSquadronLanding( SquadronName, AI_A2A_DISPATCHER.Landing.AtRunway ) @@ -2706,9 +2643,9 @@ do -- AI_A2A_DISPATCHER return self end - --- Sets flights by default to land and despawn at engine shutdown, as part of the defense system. -- @param #AI_A2A_DISPATCHER self + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2716,18 +2653,17 @@ do -- AI_A2A_DISPATCHER -- -- Let flights by default land and despawn at engine shutdown. -- A2ADispatcher:SetDefaultLandingAtEngineShutdown() -- - -- @return #AI_A2A_DISPATCHER self - function AI_A2A_DISPATCHER:SetDefaultLandingAtEngineShutdown() + function AI_A2A_DISPATCHER:SetDefaultLandingAtEngineShutdown() self:SetDefaultLanding( AI_A2A_DISPATCHER.Landing.AtEngineShutdown ) return self end - --- Sets flights to land and despawn at engine shutdown, as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2735,7 +2671,6 @@ do -- AI_A2A_DISPATCHER -- -- Let flights land and despawn at engine shutdown. -- A2ADispatcher:SetSquadronLandingAtEngineShutdown( "SquadronName" ) -- - -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetSquadronLandingAtEngineShutdown( SquadronName ) self:SetSquadronLanding( SquadronName, AI_A2A_DISPATCHER.Landing.AtEngineShutdown ) @@ -2743,17 +2678,17 @@ do -- AI_A2A_DISPATCHER return self end - --- Set the default fuel treshold when defenders will RTB or Refuel in the air. - -- The fuel treshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed. + --- Set the default fuel threshold when defenders will RTB or Refuel in the air. + -- The fuel threshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed. -- @param #AI_A2A_DISPATCHER self - -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the %-tage of the treshold of fuel remaining in the tank when the plane will go RTB or Refuel. + -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the threshold of fuel remaining in the tank when the plane will go RTB or Refuel. -- @return #AI_A2A_DISPATCHER self -- @usage -- -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) -- - -- -- Now Setup the default fuel treshold. + -- -- Now Setup the default fuel threshold. -- A2ADispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. -- function AI_A2A_DISPATCHER:SetDefaultFuelThreshold( FuelThreshold ) @@ -2763,19 +2698,18 @@ do -- AI_A2A_DISPATCHER return self end - - --- Set the fuel treshold for the squadron when defenders will RTB or Refuel in the air. - -- The fuel treshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed. + --- Set the fuel threshold for the squadron when defenders will RTB or Refuel in the air. + -- The fuel threshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. - -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the %-tage of the treshold of fuel remaining in the tank when the plane will go RTB or Refuel. + -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the threshold of fuel remaining in the tank when the plane will go RTB or Refuel. -- @return #AI_A2A_DISPATCHER self -- @usage -- -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) -- - -- -- Now Setup the default fuel treshold. + -- -- Now Setup the default fuel threshold. -- A2ADispatcher:SetSquadronFuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. -- function AI_A2A_DISPATCHER:SetSquadronFuelThreshold( SquadronName, FuelThreshold ) @@ -2795,11 +2729,12 @@ do -- AI_A2A_DISPATCHER -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) -- - -- -- Now Setup the default fuel treshold. + -- -- Now Setup the default fuel threshold. -- A2ADispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. -- -- -- Now Setup the default tanker. -- A2ADispatcher:SetDefaultTanker( "Tanker" ) -- The group name of the tanker is "Tanker" in the Mission Editor. + -- function AI_A2A_DISPATCHER:SetDefaultTanker( TankerName ) self.DefenderDefault.TankerName = TankerName @@ -2807,7 +2742,6 @@ do -- AI_A2A_DISPATCHER return self end - --- Set the squadron tanker where defenders will Refuel in the air. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. @@ -2818,11 +2752,12 @@ do -- AI_A2A_DISPATCHER -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) -- - -- -- Now Setup the squadron fuel treshold. + -- -- Now Setup the squadron fuel threshold. -- A2ADispatcher:SetSquadronFuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. -- -- -- Now Setup the squadron tanker. -- A2ADispatcher:SetSquadronTanker( "SquadronName", "Tanker" ) -- The group name of the tanker is "Tanker" in the Mission Editor. + -- function AI_A2A_DISPATCHER:SetSquadronTanker( SquadronName, TankerName ) local DefenderSquadron = self:GetSquadron( SquadronName ) @@ -2831,7 +2766,6 @@ do -- AI_A2A_DISPATCHER return self end - --- Set the squadron language. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. @@ -2859,7 +2793,6 @@ do -- AI_A2A_DISPATCHER return self end - --- Set the frequency of communication and the mode of communication for voice overs. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. @@ -2894,7 +2827,7 @@ do -- AI_A2A_DISPATCHER function AI_A2A_DISPATCHER:AddDefenderToSquadron( Squadron, Defender, Size ) self.Defenders = self.Defenders or {} local DefenderName = Defender:GetName() - self.Defenders[ DefenderName ] = Squadron + self.Defenders[DefenderName] = Squadron if Squadron.ResourceCount then Squadron.ResourceCount = Squadron.ResourceCount - Size end @@ -2911,7 +2844,7 @@ do -- AI_A2A_DISPATCHER if Squadron.ResourceCount then Squadron.ResourceCount = Squadron.ResourceCount + Defender:GetSize() end - self.Defenders[ DefenderName ] = nil + self.Defenders[DefenderName] = nil self:F( { DefenderName = DefenderName, SquadronResourceCount = Squadron.ResourceCount } ) end @@ -2924,13 +2857,12 @@ do -- AI_A2A_DISPATCHER if Defender ~= nil then local DefenderName = Defender:GetName() self:F( { DefenderName = DefenderName } ) - return self.Defenders[ DefenderName ] + return self.Defenders[DefenderName] else - return nil + return nil end end - --- Creates an SWEEP task when there are targets for it. -- @param #AI_A2A_DISPATCHER self -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem @@ -2941,7 +2873,6 @@ do -- AI_A2A_DISPATCHER local DetectedSet = DetectedItem.Set local DetectedZone = DetectedItem.Zone - if DetectedItem.IsDetected == false then -- Here we're doing something advanced... We're copying the DetectedSet. @@ -2971,9 +2902,9 @@ do -- AI_A2A_DISPATCHER if AIGroup and AIGroup:IsAlive() then -- Check if the CAP is patrolling or engaging. If not, this is not a valid CAP, even if it is alive! -- The CAP could be damaged, lost control, or out of fuel! - --env.info("FF fsm state "..tostring(DefenderTask.Fsm:GetState())) + -- env.info("FF fsm state "..tostring(DefenderTask.Fsm:GetState())) if DefenderTask.Fsm:Is( "Patrolling" ) or DefenderTask.Fsm:Is( "Engaging" ) or DefenderTask.Fsm:Is( "Refuelling" ) or DefenderTask.Fsm:Is( "Started" ) then - --env.info("FF capcount "..CapCount) + -- env.info("FF capcount "..CapCount) CapCount = CapCount + 1 end end @@ -2985,24 +2916,23 @@ do -- AI_A2A_DISPATCHER return CapCount end - --- Count number of engaging defender groups. -- @param #AI_A2A_DISPATCHER self -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detection object. -- @return #number Number of defender groups engaging. function AI_A2A_DISPATCHER:CountDefendersEngaged( AttackerDetection ) - -- First, count the active AIGroups Units, targetting the DetectedSet + -- First, count the active AIGroups Units, targeting the DetectedSet local DefenderCount = 0 local DetectedSet = AttackerDetection.Set - --DetectedSet:Flush() + -- DetectedSet:Flush() local DefenderTasks = self:GetDefenderTasks() for DefenderGroup, DefenderTask in pairs( DefenderTasks ) do local Defender = DefenderGroup -- Wrapper.Group#GROUP - local DefenderTaskTarget = DefenderTask.Target --Functional.Detection#DETECTION_BASE.DetectedItem + local DefenderTaskTarget = DefenderTask.Target -- Functional.Detection#DETECTION_BASE.DetectedItem local DefenderSquadronName = DefenderTask.SquadronName if DefenderTaskTarget and DefenderTaskTarget.Index == AttackerDetection.Index then @@ -3069,7 +2999,6 @@ do -- AI_A2A_DISPATCHER return Friendlies end - --- Activate resource. -- @param #AI_A2A_DISPATCHER self -- @param #AI_A2A_DISPATCHER.Squadron DefenderSquadron The defender squadron. @@ -3084,36 +3013,36 @@ do -- AI_A2A_DISPATCHER local DefenderGrouping = DefenderSquadron.Grouping or self.DefenderDefault.Grouping - DefenderGrouping = ( DefenderGrouping < DefendersNeeded ) and DefenderGrouping or DefendersNeeded + DefenderGrouping = (DefenderGrouping < DefendersNeeded) and DefenderGrouping or DefendersNeeded - --env.info(string.format("FF resource activate: Squadron=%s grouping=%d needed=%d visible=%s", SquadronName, DefenderGrouping, DefendersNeeded, tostring(self:IsSquadronVisible( SquadronName )))) + -- env.info(string.format("FF resource activate: Squadron=%s grouping=%d needed=%d visible=%s", SquadronName, DefenderGrouping, DefendersNeeded, tostring(self:IsSquadronVisible( SquadronName )))) if self:IsSquadronVisible( SquadronName ) then - local n=#self.uncontrolled[SquadronName] + local n = #self.uncontrolled[SquadronName] - if n>0 then + if n > 0 then -- Random number 1,...n - local id=math.random(n) + local id = math.random( n ) -- Pick a random defender group. - local Defender=self.uncontrolled[SquadronName][id].group --Wrapper.Group#GROUP + local Defender = self.uncontrolled[SquadronName][id].group -- Wrapper.Group#GROUP -- Start uncontrolled group. Defender:StartUncontrolled() -- Get grouping. - DefenderGrouping=self.uncontrolled[SquadronName][id].grouping + DefenderGrouping = self.uncontrolled[SquadronName][id].grouping -- Add defender to squadron. self:AddDefenderToSquadron( DefenderSquadron, Defender, DefenderGrouping ) -- Remove defender from uncontrolled table. - table.remove(self.uncontrolled[SquadronName], id) + table.remove( self.uncontrolled[SquadronName], id ) return Defender, DefenderGrouping else - return nil,0 + return nil, 0 end -- Here we CAP the new planes. @@ -3171,7 +3100,7 @@ do -- AI_A2A_DISPATCHER --- Squadron not visible --- ---------------------------- - local Spawn = DefenderSquadron.Spawn[ math.random( 1, #DefenderSquadron.Spawn ) ] -- Core.Spawn#SPAWN + local Spawn = DefenderSquadron.Spawn[math.random( 1, #DefenderSquadron.Spawn )] -- Core.Spawn#SPAWN if DefenderGrouping then Spawn:InitGrouping( DefenderGrouping ) @@ -3199,7 +3128,7 @@ do -- AI_A2A_DISPATCHER -- @param #string SquadronName Name of the squadron. function AI_A2A_DISPATCHER:onafterCAP( From, Event, To, SquadronName ) - self:F({SquadronName = SquadronName}) + self:F( { SquadronName = SquadronName } ) self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} self.DefenderSquadrons[SquadronName].Cap = self.DefenderSquadrons[SquadronName].Cap or {} @@ -3223,13 +3152,13 @@ do -- AI_A2A_DISPATCHER 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) + 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() @@ -3238,15 +3167,15 @@ do -- AI_A2A_DISPATCHER function AI_A2A_Fsm:onafterTakeoff( DefenderGroup, From, Event, To ) -- Issue GetCallsign() returns nil, see https://github.com/FlightControl-Master/MOOSE/issues/1228 if DefenderGroup and DefenderGroup:IsAlive() then - self:F({"CAP Takeoff", DefenderGroup:GetName()}) - --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) + self:F( { "CAP Takeoff", DefenderGroup:GetName() } ) + -- self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = AI_A2A_Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) if Squadron then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. " Wheels up.", DefenderGroup ) + Dispatcher:MessageToPlayers( Squadron, DefenderName .. " Wheels up.", DefenderGroup ) AI_A2A_Fsm:__Patrol( 2 ) -- Start Patrolling end end @@ -3254,14 +3183,14 @@ do -- AI_A2A_DISPATCHER 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 ) + self:F( { "CAP PatrolRoute", DefenderGroup:GetName() } ) + self:GetParent( self ).onafterPatrolRoute( self, DefenderGroup, From, Event, To ) local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) if Squadron then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", patrolling.", DefenderGroup ) + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", patrolling.", DefenderGroup ) end Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) @@ -3270,15 +3199,15 @@ do -- AI_A2A_DISPATCHER function AI_A2A_Fsm:onafterRTB( DefenderGroup, From, Event, To ) if DefenderGroup and DefenderGroup:IsAlive() then - self:F({"CAP RTB", DefenderGroup:GetName()}) + self:F( { "CAP RTB", DefenderGroup:GetName() } ) - self:GetParent(self).onafterRTB( self, DefenderGroup, From, Event, To ) + self:GetParent( self ).onafterRTB( self, DefenderGroup, From, Event, To ) local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) if Squadron then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. " returning to base.", DefenderGroup ) + Dispatcher:MessageToPlayers( Squadron, DefenderName .. " returning to base.", DefenderGroup ) end Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) end @@ -3287,8 +3216,8 @@ do -- AI_A2A_DISPATCHER --- @param #AI_A2A_DISPATCHER self 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 ) + self:F( { "CAP Home", Defender:GetName() } ) + self:GetParent( self ).onafterHome( self, Defender, From, Event, To ) local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) @@ -3311,7 +3240,6 @@ do -- AI_A2A_DISPATCHER end - --- On after "ENGAGE" event. -- @param #AI_A2A_DISPATCHER self -- @param #string From From state. @@ -3320,7 +3248,7 @@ do -- AI_A2A_DISPATCHER -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item. -- @param #table Defenders Defenders table. function AI_A2A_DISPATCHER:onafterENGAGE( From, Event, To, AttackerDetection, Defenders ) - self:F("ENGAGING Detection ID="..tostring(AttackerDetection.ID)) + self:F( "ENGAGING Detection ID=" .. tostring( AttackerDetection.ID ) ) if Defenders then @@ -3347,7 +3275,7 @@ do -- AI_A2A_DISPATCHER -- @param #table DefenderFriendlies Friendly defenders. function AI_A2A_DISPATCHER:onafterGCI( From, Event, To, AttackerDetection, DefendersMissing, DefenderFriendlies ) - self:F("GCI Detection ID="..tostring(AttackerDetection.ID)) + self:F( "GCI Detection ID=" .. tostring( AttackerDetection.ID ) ) self:F( { From, Event, To, AttackerDetection.Index, DefendersMissing, DefenderFriendlies } ) @@ -3376,7 +3304,7 @@ do -- AI_A2A_DISPATCHER local BreakLoop = false - while( DefenderCount > 0 and not BreakLoop ) do + while (DefenderCount > 0 and not BreakLoop) do self:F( { DefenderSquadrons = self.DefenderSquadrons } ) @@ -3422,7 +3350,7 @@ do -- AI_A2A_DISPATCHER local DefenderGrouping = DefenderSquadron.Grouping or self.DefenderDefault.Grouping local DefendersNeeded = math.ceil( DefenderCount * DefenderOverhead ) - self:F( { Overhead = DefenderOverhead, SquadronOverhead = DefenderSquadron.Overhead , DefaultOverhead = self.DefenderDefault.Overhead } ) + self:F( { Overhead = DefenderOverhead, SquadronOverhead = DefenderSquadron.Overhead, DefaultOverhead = self.DefenderDefault.Overhead } ) self:F( { Grouping = DefenderGrouping, SquadronGrouping = DefenderSquadron.Grouping, DefaultGrouping = self.DefenderDefault.Grouping } ) self:F( { DefendersCount = DefenderCount, DefendersNeeded = DefendersNeeded } ) @@ -3433,7 +3361,7 @@ do -- AI_A2A_DISPATCHER BreakLoop = true end - while ( DefendersNeeded > 0 ) do + while (DefendersNeeded > 0) do local DefenderGCI, DefenderGrouping = self:ResourceActivate( DefenderSquadron, DefendersNeeded ) @@ -3451,13 +3379,11 @@ do -- AI_A2A_DISPATCHER 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()}) - --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) + self:F( { "GCI Birth", DefenderGroup:GetName() } ) + -- self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER @@ -3466,17 +3392,17 @@ do -- AI_A2A_DISPATCHER if DefenderTarget then if Squadron.Language == "EN" then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. " wheels up.", DefenderGroup ) + Dispatcher:MessageToPlayers( Squadron, DefenderName .. " wheels up.", DefenderGroup ) elseif Squadron.Language == "RU" then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. " колеса вверх.", DefenderGroup ) + Dispatcher:MessageToPlayers( Squadron, DefenderName .. " колеса вверх.", DefenderGroup ) end - --Fsm:__Engage( 2, DefenderTarget.Set ) -- Engage on the TargetSetUnit + -- Fsm:__Engage( 2, DefenderTarget.Set ) -- Engage on the TargetSetUnit Fsm:EngageRoute( DefenderTarget.Set ) -- Engage on the TargetSetUnit end end function Fsm:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit ) - self:F({"GCI Route", DefenderGroup:GetName()}) + self:F( { "GCI Route", DefenderGroup:GetName() } ) local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER @@ -3487,18 +3413,18 @@ do -- AI_A2A_DISPATCHER local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE if Squadron.Language == "EN" then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", intercepting bogeys at " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup ) + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", intercepting bogeys at " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup ) elseif Squadron.Language == "RU" then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", перехват самолетов в " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup ) + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", перехват самолетов в " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup ) elseif Squadron.Language == "DE" then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", Eindringlinge abfangen bei" .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup ) + 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()}) + self:F( { "GCI Engage", DefenderGroup:GetName() } ) local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER @@ -3509,17 +3435,17 @@ do -- AI_A2A_DISPATCHER local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE if Squadron.Language == "EN" then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging bogeys at " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup ) + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging bogeys at " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup ) elseif Squadron.Language == "RU" then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", захватывающие самолеты в " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup ) + 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 ) + self:F( { "GCI RTB", DefenderGroup:GetName() } ) + self:GetParent( self ).onafterRTB( self, DefenderGroup, From, Event, To ) local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER @@ -3527,9 +3453,9 @@ do -- AI_A2A_DISPATCHER if Squadron then if Squadron.Language == "EN" then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. " returning to base.", DefenderGroup ) + Dispatcher:MessageToPlayers( Squadron, DefenderName .. " returning to base.", DefenderGroup ) elseif Squadron.Language == "RU" then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", возвращаясь на базу.", DefenderGroup ) + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", возвращаясь на базу.", DefenderGroup ) end end Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) @@ -3537,8 +3463,8 @@ do -- AI_A2A_DISPATCHER --- @param #AI_A2A_DISPATCHER self function Fsm:onafterLostControl( Defender, From, Event, To ) - self:F({"GCI LostControl", Defender:GetName()}) - self:GetParent(self).onafterHome( self, Defender, From, Event, To ) + self:F( { "GCI LostControl", Defender:GetName() } ) + self:GetParent( self ).onafterHome( self, Defender, From, Event, To ) local Dispatcher = Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) @@ -3550,18 +3476,18 @@ do -- AI_A2A_DISPATCHER --- @param #AI_A2A_DISPATCHER self function Fsm:onafterHome( DefenderGroup, From, Event, To, Action ) - self:F({"GCI Home", DefenderGroup:GetName()}) - self:GetParent(self).onafterHome( self, DefenderGroup, From, Event, To ) + self:F( { "GCI Home", DefenderGroup:GetName() } ) + self:GetParent( self ).onafterHome( self, DefenderGroup, From, Event, To ) local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - if Squadron.Language == "EN" then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. " landing at base.", DefenderGroup ) - elseif Squadron.Language == "RU" then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", захватывающие самолеты в посадка на базу.", DefenderGroup ) - end + if Squadron.Language == "EN" then + Dispatcher:MessageToPlayers( Squadron, DefenderName .. " landing at base.", DefenderGroup ) + elseif Squadron.Language == "RU" then + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", захватывающие самолеты в посадка на базу.", DefenderGroup ) + end if Action and Action == "Destroy" then Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) @@ -3575,8 +3501,8 @@ do -- AI_A2A_DISPATCHER end end - end -- if DefenderGCI then - end -- while ( DefendersNeeded > 0 ) do + end -- if DefenderGCI then + end -- while ( DefendersNeeded > 0 ) do end else -- No more resources, try something else. @@ -3592,8 +3518,6 @@ do -- AI_A2A_DISPATCHER end -- if AttackerUnit end - - --- Creates an ENGAGE task when there are human friendlies airborne near the targets. -- @param #AI_A2A_DISPATCHER self -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem The detected item. @@ -3601,7 +3525,7 @@ do -- AI_A2A_DISPATCHER function AI_A2A_DISPATCHER:EvaluateENGAGE( DetectedItem ) self:F( { DetectedItem.ItemID } ) - -- First, count the active AIGroups Units, targetting the DetectedSet + -- First, count the active AIGroups Units, targeting the DetectedSet local DefenderCount = self:CountDefendersEngaged( DetectedItem ) local DefenderGroups = self:CountDefendersToBeEngaged( DetectedItem, DefenderCount ) @@ -3630,7 +3554,7 @@ do -- AI_A2A_DISPATCHER local AttackerSet = DetectedItem.Set local AttackerCount = AttackerSet:Count() - -- First, count the active AIGroups Units, targetting the DetectedSet + -- First, count the active AIGroups Units, targeting the DetectedSet local DefenderCount = self:CountDefendersEngaged( DetectedItem ) local DefendersMissing = AttackerCount - DefenderCount self:F( { AttackerCount = AttackerCount, DefenderCount = DefenderCount, DefendersMissing = DefendersMissing } ) @@ -3645,12 +3569,11 @@ do -- AI_A2A_DISPATCHER return nil, nil end - --- Assigns A2G AI Tasks in relation to the detected items. -- @param #AI_A2G_DISPATCHER self function AI_A2A_DISPATCHER:Order( DetectedItem ) - local detection=self.Detection -- Functional.Detection#DETECTION_AREAS + local detection = self.Detection -- Functional.Detection#DETECTION_AREAS local ShortestDistance = 999999999 @@ -3679,7 +3602,6 @@ do -- AI_A2A_DISPATCHER return ShortestDistance end - --- Shows the tactical display. -- @param #AI_A2A_DISPATCHER self -- @param Functional.Detection#DETECTION_BASE Detection The detection created by the @{Functional.Detection#DETECTION_BASE} derived object. @@ -3696,8 +3618,10 @@ do -- AI_A2A_DISPATCHER local DefenderGroupCount = 0 -- Now that all obsolete tasks are removed, loop through the detected targets. - --for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do - for DetectedItemID, DetectedItem in UTILS.spairs( Detection:GetDetectedItems(), function( t, a, b ) return self:Order(t[a]) < self:Order(t[b]) end ) do + -- for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do + for DetectedItemID, DetectedItem in UTILS.spairs( Detection:GetDetectedItems(), function( t, a, b ) + return self:Order( t[a] ) < self:Order( t[b] ) + end ) do local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT @@ -3712,30 +3636,30 @@ do -- AI_A2A_DISPATCHER local DetectedItemChanged = DetectedItem.Changed -- Show tactical situation - Report:Add( string.format( "\n- Target %s (%s): (#%d) %s" , DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Set:GetObjectNames() ) ) + Report:Add( string.format( "\n- Target %s (%s): (#%d) %s", DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Set:GetObjectNames() ) ) for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do local Defender = Defender -- Wrapper.Group#GROUP - if DefenderTask.Target and DefenderTask.Target.Index == DetectedItem.Index then - if Defender and Defender:IsAlive() then - DefenderGroupCount = DefenderGroupCount + 1 - local Fuel = Defender:GetFuelMin() * 100 - local Damage = Defender:GetLife() / Defender:GetLife0() * 100 - Report:Add( string.format( " - %s*%d/%d (%s - %s): (#%d) F: %3d, D:%3d - %s", - Defender:GetName(), - Defender:GetSize(), - Defender:GetInitialSize(), - DefenderTask.Type, - DefenderTask.Fsm:GetState(), - Defender:GetSize(), - Fuel, - Damage, - Defender:HasTask() == true and "Executing" or "Idle" ) ) - end - end + if DefenderTask.Target and DefenderTask.Target.Index == DetectedItem.Index then + if Defender and Defender:IsAlive() then + DefenderGroupCount = DefenderGroupCount + 1 + local Fuel = Defender:GetFuelMin() * 100 + local Damage = Defender:GetLife() / Defender:GetLife0() * 100 + Report:Add( string.format( " - %s*%d/%d (%s - %s): (#%d) F: %3d, D:%3d - %s", + Defender:GetName(), + Defender:GetSize(), + Defender:GetInitialSize(), + DefenderTask.Type, + DefenderTask.Fsm:GetState(), + Defender:GetSize(), + Fuel, + Damage, + Defender:HasTask() == true and "Executing" or "Idle" ) ) + end + end end end - Report:Add( "\n- No Targets:") + Report:Add( "\n- No Targets:" ) local TaskCount = 0 for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do TaskCount = TaskCount + 1 @@ -3780,7 +3704,6 @@ do -- AI_A2A_DISPATCHER local TaskReport = REPORT:New() - for AIGroup, DefenderTask in pairs( self:GetDefenderTasks() ) do local AIGroup = AIGroup -- Wrapper.Group#GROUP if not AIGroup:IsAlive() then @@ -3814,8 +3737,10 @@ do -- AI_A2A_DISPATCHER -- Now that all obsolete tasks are removed, loop through the detected targets. -- Closest detected targets to be considered first! - --for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do - for DetectedItemID, DetectedItem in UTILS.spairs( Detection:GetDetectedItems(), function( t, a, b ) return self:Order(t[a]) < self:Order(t[b]) end ) do + -- for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do + for DetectedItemID, DetectedItem in UTILS.spairs( Detection:GetDetectedItems(), function( t, a, b ) + return self:Order( t[a] ) < self:Order( t[b] ) + end ) do local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT @@ -3874,7 +3799,7 @@ do for PlayerUnitName, PlayerUnitData in pairs( PlayersNearBy ) do local PlayerUnit = PlayerUnitData -- Wrapper.Unit#UNIT local PlayerName = PlayerUnit:GetPlayerName() - --self:F( { PlayerName = PlayerName, PlayerUnit = PlayerUnit } ) + -- self:F( { PlayerName = PlayerName, PlayerUnit = PlayerUnit } ) if PlayerUnit:IsAirPlane() and PlayerName ~= nil then local FriendlyUnitThreatLevel = PlayerUnit:GetThreatLevel() PlayersCount = PlayersCount + 1 @@ -3887,19 +3812,18 @@ do end - --self:F( { PlayersCount = PlayersCount } ) + -- self:F( { PlayersCount = PlayersCount } ) local PlayerTypesReport = REPORT:New() if PlayersCount > 0 then for PlayerName, PlayerType in pairs( PlayerTypes ) do - PlayerTypesReport:Add( string.format('"%s" in %s', PlayerName, PlayerType ) ) + PlayerTypesReport:Add( string.format( '"%s" in %s', PlayerName, PlayerType ) ) end else PlayerTypesReport:Add( "-" ) end - return PlayersCount, PlayerTypesReport end @@ -3923,7 +3847,7 @@ do local FriendlyUnitThreatLevel = FriendlyUnit:GetThreatLevel() FriendliesCount = FriendliesCount + 1 local FriendlyType = FriendlyUnit:GetTypeName() - FriendlyTypes[FriendlyType] = FriendlyTypes[FriendlyType] and ( FriendlyTypes[FriendlyType] + 1 ) or 1 + FriendlyTypes[FriendlyType] = FriendlyTypes[FriendlyType] and (FriendlyTypes[FriendlyType] + 1) or 1 if DetectedTreatLevel < FriendlyUnitThreatLevel + 2 then end end @@ -3931,19 +3855,18 @@ do end - --self:F( { FriendliesCount = FriendliesCount } ) + -- self:F( { FriendliesCount = FriendliesCount } ) local FriendlyTypesReport = REPORT:New() if FriendliesCount > 0 then for FriendlyType, FriendlyTypeCount in pairs( FriendlyTypes ) do - FriendlyTypesReport:Add( string.format("%d of %s", FriendlyTypeCount, FriendlyType ) ) + FriendlyTypesReport:Add( string.format( "%d of %s", FriendlyTypeCount, FriendlyType ) ) end else FriendlyTypesReport:Add( "-" ) end - return FriendliesCount, FriendlyTypesReport end @@ -4050,13 +3973,13 @@ do -- -- ![Mission Editor Action](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_GCICAP-ME_4.JPG) -- - -- **All airplane or helicopter groups that are starting with any of the choosen Template Prefixes will result in a squadron created at the airbase.** + -- **All airplane or helicopter groups that are starting with any of the chosen Template Prefixes will result in a squadron created at the airbase.** -- -- ### 1.4) Place floating helicopters to create the CAP zones defined by its route points. -- -- ![Mission Editor Action](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_GCICAP-ME_5.JPG) -- - -- **All airplane or helicopter groups that are starting with any of the choosen Template Prefixes will result in a squadron created at the airbase.** + -- **All airplane or helicopter groups that are starting with any of the chosen Template Prefixes will result in a squadron created at the airbase.** -- -- The helicopter indicates the start of the CAP zone. -- The route points define the form of the CAP zone polygon. @@ -4069,7 +3992,7 @@ do -- -- ### 2.1) Planes are taking off in the air from the airbases. -- - -- This prevents airbases to get cluttered with airplanes taking off, it also reduces the risk of human players colliding with taxiiing airplanes, + -- This prevents airbases to get cluttered with airplanes taking off, it also reduces the risk of human players colliding with taxiing airplanes, -- resulting in the airbase to halt operations. -- -- You can change the way how planes take off by using the inherited methods from AI\_A2A\_DISPATCHER: @@ -4093,7 +4016,7 @@ do -- -- ### 2.2) Planes return near the airbase or will land if damaged. -- - -- When damaged airplanes return to the airbase, they will be routed and will dissapear in the air when they are near the airbase. + -- When damaged airplanes return to the airbase, they will be routed and will disappear in the air when they are near the airbase. -- There are exceptions to this rule, airplanes that aren't "listening" anymore due to damage or out of fuel, will return to the airbase and land. -- -- You can change the way how planes land by using the inherited methods from AI\_A2A\_DISPATCHER: @@ -4103,7 +4026,7 @@ do -- * @{#AI_A2A_DISPATCHER.SetSquadronLandingAtRunway}() will despawn the returning aircraft directly after landing at the runway. -- * @{#AI_A2A_DISPATCHER.SetSquadronLandingAtEngineShutdown}() will despawn the returning aircraft when the aircraft has returned to its parking spot and has turned off its engines. -- - -- You can use these methods to minimize the airbase coodination overhead and to increase the airbase efficiency. + -- You can use these methods to minimize the airbase coordination overhead and to increase the airbase efficiency. -- When there are lots of aircraft returning for landing, at the same airbase, the takeoff process will be halted, which can cause a complete failure of the -- A2A defense system, as no new CAP or GCI planes can takeoff. -- Note that the method @{#AI_A2A_DISPATCHER.SetSquadronLandingNearAirbase}() will only work for returning aircraft, not for damaged or out of fuel aircraft. @@ -4126,7 +4049,7 @@ do -- * The minimum and maximum engage speed -- * The type of altitude measurement -- - -- These define how the squadron will perform the CAP while partrolling. Different terrain types requires different types of CAP. + -- These define how the squadron will perform the CAP while patrolling. Different terrain types requires different types of CAP. -- -- The @{#AI_A2A_DISPATCHER.SetSquadronCapInterval}() method specifies **how much** and **when** CAP flights will takeoff. -- @@ -4152,7 +4075,7 @@ do -- Essentially this controls how many flights of GCI aircraft can be active at any time. -- Note allowing large numbers of active GCI flights can adversely impact mission performance on low or medium specification hosts/servers. -- GCI needs to be setup at strategic airbases. Too far will mean that the aircraft need to fly a long way to reach the intruders, - -- too short will mean that the intruders may have alraedy passed the ideal interception point! + -- too short will mean that the intruders may have already passed the ideal interception point! -- -- For example, the following setup will create a GCI for squadron "Sochi": -- @@ -4191,8 +4114,7 @@ do -- These late activated Groups start with the name `SQUADRON CCCP`. Each Group object contains only one Unit, and defines the weapon payload, skin and skill level. -- * `"CAP CCCP"`: CAP Zones are defined using floating, late activated Helicopter Group objects, where the route points define the route of the polygon of the CAP Zone. -- These Helicopter Group objects start with the name `CAP CCCP`, and will be the locations wherein CAP will be performed. - -- * `2` Defines how many CAP airplanes are patrolling in each CAP zone defined simulateneously. - -- + -- * `2` Defines how many CAP airplanes are patrolling in each CAP zone defined simultaneously. -- -- ### 4.2) A more advanced setup: -- @@ -4209,7 +4131,7 @@ do -- * `{ "104th CAP" }`: An array of the names of the CAP zones are defined using floating, late activated helicopter group objects, -- where the route points define the route of the polygon of the CAP Zone. -- These Helicopter Group objects start with the name `104th CAP`, and will be the locations wherein CAP will be performed. - -- * `4` Defines how many CAP airplanes are patrolling in each CAP zone defined simulateneously. + -- * `4` Defines how many CAP airplanes are patrolling in each CAP zone defined simultaneously. -- -- @field #AI_A2A_GCICAP AI_A2A_GCICAP = { @@ -4217,7 +4139,6 @@ do Detection = nil, } - --- AI_A2A_GCICAP constructor. -- @param #AI_A2A_GCICAP self -- @param #string EWRPrefixes A list of prefixes that of groups that setup the Early Warning Radar network. @@ -4297,7 +4218,7 @@ do -- -- The CAP Zone prefix is nil. No CAP is created. -- -- The CAP Limit is nil. -- -- The Grouping Radius is nil. The default range of 6km radius will be grouped as a group of targets. - -- -- The Engage Radius is set nil. The default Engage Radius will be used to consider a defenser being assigned to a task. + -- -- The Engage Radius is set nil. The default Engage Radius will be used to consider a defender being assigned to a task. -- -- The GCI Radius is nil. Any target detected within the default GCI Radius will be considered for GCI engagement. -- -- The amount of resources for each squadron is set to 30. Thus about 30 resources are allocated to each squadron created. -- @@ -4309,7 +4230,7 @@ do EWRSetGroup:FilterPrefixes( EWRPrefixes ) EWRSetGroup:FilterStart() - local Detection = DETECTION_AREAS:New( EWRSetGroup, GroupingRadius or 30000 ) + local Detection = DETECTION_AREAS:New( EWRSetGroup, GroupingRadius or 30000 ) local self = BASE:Inherit( self, AI_A2A_DISPATCHER:New( Detection ) ) -- #AI_A2A_GCICAP @@ -4330,14 +4251,11 @@ do end end - self.Templates = SET_GROUP - :New() - :FilterPrefixes( TemplatePrefixes ) - :FilterOnce() + self.Templates = SET_GROUP:New():FilterPrefixes( TemplatePrefixes ):FilterOnce() -- Setup squadrons - self:I( { Airbases = AirbaseNames } ) + self:I( { Airbases = AirbaseNames } ) self:I( "Defining Templates for Airbases ..." ) for AirbaseID, AirbaseName in pairs( AirbaseNames ) do @@ -4415,7 +4333,7 @@ do self:HandleEvent( EVENTS.Crash, self.OnEventCrashOrDead ) self:HandleEvent( EVENTS.Dead, self.OnEventCrashOrDead ) - --self:HandleEvent( EVENTS.RemoveUnit, self.OnEventCrashOrDead ) + -- self:HandleEvent( EVENTS.RemoveUnit, self.OnEventCrashOrDead ) self:HandleEvent( EVENTS.Land ) self:HandleEvent( EVENTS.EngineShutdown ) @@ -4512,7 +4430,7 @@ do -- -- The CAP Zone prefix is nil. No CAP is created. -- -- The CAP Limit is nil. -- -- The Grouping Radius is nil. The default range of 6km radius will be grouped as a group of targets. - -- -- The Engage Radius is set nil. The default Engage Radius will be used to consider a defenser being assigned to a task. + -- -- The Engage Radius is set nil. The default Engage Radius will be used to consider a defender being assigned to a task. -- -- The GCI Radius is nil. Any target detected within the default GCI Radius will be considered for GCI engagement. -- -- The amount of resources for each squadron is set to 30. Thus about 30 resources are allocated to each squadron created. -- diff --git a/Moose Development/Moose/AI/AI_A2A_Gci.lua b/Moose Development/Moose/AI/AI_A2A_Gci.lua index 4a3f4570b..15653d1e0 100644 --- a/Moose Development/Moose/AI/AI_A2A_Gci.lua +++ b/Moose Development/Moose/AI/AI_A2A_Gci.lua @@ -42,8 +42,8 @@ -- -- ![Process](..\Presentations\AI_GCI\Dia10.JPG) -- --- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB. --- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. +-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB. +-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. -- -- ![Process](..\Presentations\AI_GCI\Dia13.JPG) -- @@ -73,7 +73,7 @@ -- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets. -- * **@{#AI_A2A_GCI.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}. -- * **@{#AI_A2A_GCI.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task. --- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB. +-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB. -- -- ## 3. Set the Range of Engagement -- diff --git a/Moose Development/Moose/AI/AI_A2A_Patrol.lua b/Moose Development/Moose/AI/AI_A2A_Patrol.lua index d0a01bdd6..f4252fac1 100644 --- a/Moose Development/Moose/AI/AI_A2A_Patrol.lua +++ b/Moose Development/Moose/AI/AI_A2A_Patrol.lua @@ -39,8 +39,8 @@ -- -- ![Process](..\Presentations\AI_PATROL\Dia10.JPG) -- --- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB. --- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. +-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB. +-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. -- -- ![Process](..\Presentations\AI_PATROL\Dia11.JPG) -- @@ -68,7 +68,7 @@ -- * **RTB** ( Group ): Route the AI to the home base. -- * **Detect** ( Group ): The AI is detecting targets. -- * **Detected** ( Group ): The AI has detected new targets. --- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB. +-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB. -- -- ## 3. Set or Get the AI controllable -- @@ -100,8 +100,8 @@ -- ## 6. Manage the "out of fuel" in the AI_A2A_PATROL -- -- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base. --- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated. --- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit, +-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated. +-- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit, -- while a new AI is targetted to the AI_A2A_PATROL. -- Once the time is finished, the old AI will return to the base. -- Use the method @{#AI_A2A_PATROL.ManageFuel}() to have this proces in place. @@ -109,7 +109,7 @@ -- ## 7. Manage "damage" behaviour of the AI in the AI_A2A_PATROL -- -- When the AI is damaged, it is required that a new Patrol is started. However, damage cannon be foreseen early on. --- Therefore, when the damage treshold is reached, the AI will return immediately to the home base (RTB). +-- Therefore, when the damage threshold is reached, the AI will return immediately to the home base (RTB). -- Use the method @{#AI_A2A_PATROL.ManageDamage}() to have this proces in place. -- -- === diff --git a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua index 597f60ac7..9d6fd1573 100644 --- a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua @@ -3243,17 +3243,17 @@ do -- AI_A2G_DISPATCHER return self end - --- Set the default fuel treshold when defenders will RTB or Refuel in the air. - -- The fuel treshold is by default set to 15%, which means that an aircraft will stay in the air until 15% of its fuel is remaining. + --- Set the default fuel threshold when defenders will RTB or Refuel in the air. + -- The fuel threshold is by default set to 15%, which means that an aircraft will stay in the air until 15% of its fuel is remaining. -- @param #AI_A2G_DISPATCHER self - -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the treshold of fuel remaining in the tank when the plane will go RTB or Refuel. + -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the threshold of fuel remaining in the tank when the plane will go RTB or Refuel. -- @return #AI_A2G_DISPATCHER -- @usage -- -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) -- - -- -- Now Setup the default fuel treshold. + -- -- Now Setup the default fuel threshold. -- A2GDispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. -- function AI_A2G_DISPATCHER:SetDefaultFuelThreshold( FuelThreshold ) @@ -3264,18 +3264,18 @@ do -- AI_A2G_DISPATCHER end - --- Set the fuel treshold for the squadron when defenders will RTB or Refuel in the air. - -- The fuel treshold is by default set to 15%, which means that an aircraft will stay in the air until 15% of its fuel is remaining. + --- Set the fuel threshold for the squadron when defenders will RTB or Refuel in the air. + -- The fuel threshold is by default set to 15%, which means that an aircraft will stay in the air until 15% of its fuel is remaining. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The name of the squadron. - -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the treshold of fuel remaining in the tank when the plane will go RTB or Refuel. + -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the threshold of fuel remaining in the tank when the plane will go RTB or Refuel. -- @return #AI_A2G_DISPATCHER -- @usage -- -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) -- - -- -- Now Setup the default fuel treshold. + -- -- Now Setup the default fuel threshold. -- A2GDispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. -- function AI_A2G_DISPATCHER:SetSquadronFuelThreshold( SquadronName, FuelThreshold ) @@ -3295,7 +3295,7 @@ do -- AI_A2G_DISPATCHER -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) -- - -- -- Now Setup the default fuel treshold. + -- -- Now Setup the default fuel threshold. -- A2GDispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. -- -- -- Now Setup the default tanker. @@ -3318,7 +3318,7 @@ do -- AI_A2G_DISPATCHER -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) -- - -- -- Now Setup the squadron fuel treshold. + -- -- Now Setup the squadron fuel threshold. -- A2GDispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. -- -- -- Now Setup the squadron tanker. @@ -3421,7 +3421,7 @@ do -- AI_A2G_DISPATCHER -- @param #AI_A2G_DISPATCHER self function AI_A2G_DISPATCHER:CountDefendersEngaged( AttackerDetection, AttackerCount ) - -- First, count the active AIGroups Units, targetting the DetectedSet + -- First, count the active AIGroups Units, targeting the DetectedSet local DefendersEngaged = 0 local DefendersTotal = 0 diff --git a/Moose Development/Moose/AI/AI_A2G_SEAD.lua b/Moose Development/Moose/AI/AI_A2G_SEAD.lua index 6a0a32e3c..49d7ac16c 100644 --- a/Moose Development/Moose/AI/AI_A2G_SEAD.lua +++ b/Moose Development/Moose/AI/AI_A2G_SEAD.lua @@ -42,8 +42,8 @@ -- -- ![Process](..\Presentations\AI_GCI\Dia10.JPG) -- --- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB. --- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. +-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB. +-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. -- -- ![Process](..\Presentations\AI_GCI\Dia13.JPG) -- diff --git a/Moose Development/Moose/AI/AI_Air.lua b/Moose Development/Moose/AI/AI_Air.lua index 3c0724585..9d5397073 100644 --- a/Moose Development/Moose/AI/AI_Air.lua +++ b/Moose Development/Moose/AI/AI_Air.lua @@ -370,11 +370,11 @@ end --- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base. --- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated. --- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_AIR. +-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated. +-- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_AIR. -- Once the time is finished, the old AI will return to the base. -- @param #AI_AIR self --- @param #number FuelThresholdPercentage The treshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel. +-- @param #number FuelThresholdPercentage The threshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel. -- @param #number OutOfFuelOrbitTime The amount of seconds the out of fuel AIControllable will orbit before returning to the base. -- @return #AI_AIR self function AI_AIR:SetFuelThreshold( FuelThresholdPercentage, OutOfFuelOrbitTime ) @@ -387,14 +387,14 @@ function AI_AIR:SetFuelThreshold( FuelThresholdPercentage, OutOfFuelOrbitTime ) return self end ---- When the AI is damaged beyond a certain treshold, it is required that the AI returns to the home base. +--- When the AI is damaged beyond a certain threshold, it is required that the AI returns to the home base. -- However, damage cannot be foreseen early on. --- Therefore, when the damage treshold is reached, +-- Therefore, when the damage threshold is reached, -- the AI will return immediately to the home base (RTB). -- Note that for groups, the average damage of the complete group will be calculated. --- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage treshold will be 0.25. +-- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage threshold will be 0.25. -- @param #AI_AIR self --- @param #number PatrolDamageThreshold The treshold in percentage (between 0 and 1) when the AI is considered to be damaged. +-- @param #number PatrolDamageThreshold The threshold in percentage (between 0 and 1) when the AI is considered to be damaged. -- @return #AI_AIR self function AI_AIR:SetDamageThreshold( PatrolDamageThreshold ) @@ -476,7 +476,7 @@ function AI_AIR:onafterStatus() local Fuel = self.Controllable:GetFuelMin() - -- If the fuel in the controllable is below the treshold percentage, + -- If the fuel in the controllable is below the threshold percentage, -- then send for refuel in case of a tanker, otherwise RTB. if Fuel < self.FuelThresholdPercentage then diff --git a/Moose Development/Moose/AI/AI_Air_Dispatcher.lua b/Moose Development/Moose/AI/AI_Air_Dispatcher.lua index 7565f7a64..58e4308e4 100644 --- a/Moose Development/Moose/AI/AI_Air_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_Air_Dispatcher.lua @@ -10,11 +10,11 @@ -- * Setup (CAS) Controlled Air Support squadrons, to attack closeby enemy ground units near friendly installations. -- * Setup (BAI) Battleground Air Interdiction squadrons to attack remote enemy ground units and targets. -- * Define and use a detection network controlled by recce. --- * Define AIR defense squadrons at airbases, farps and carriers. +-- * Define AIR defense squadrons at airbases, FARPs and carriers. -- * Enable airbases for AIR defenses. -- * Add different planes and helicopter templates to squadrons. -- * Assign squadrons to execute a specific engagement type depending on threat level of the detected ground enemy unit composition. --- * Add multiple squadrons to different airbases, farps or carriers. +-- * Add multiple squadrons to different airbases, FARPs or carriers. -- * Define different ranges to engage upon. -- * Establish an automatic in air refuel process for planes using refuel tankers. -- * Setup default settings for all squadrons and AIR defenses. @@ -40,7 +40,7 @@ -- -- AI_AIR_DISPATCHER is the main AIR defense class that models the AIR defense system. -- --- Before you start using the AI_AIR_DISPATCHER, ask youself the following questions. +-- Before you start using the AI_AIR_DISPATCHER, ask yourself the following questions. -- -- -- ## 1. Which coalition am I modeling an AIR defense system for? blue or red? @@ -128,7 +128,7 @@ -- Depending on the defense type, different payloads will be needed. See further points on squadron definition. -- -- --- ## 7. Where will the Squadrons be located? On Airbases? On Carrier Ships? On Farps? +-- ## 7. Where will the Squadrons be located? On Airbases? On Carrier Ships? On FARPs? -- -- Squadrons are placed at the **home base** on an **airfield**, **carrier** or **farp**. -- Carefully plan where each Squadron will be located as part of the defense system required for mission effective defenses. @@ -354,7 +354,7 @@ do -- AI_AIR_DISPATCHER -- **DetectionSetGroup** is then calling `FilterStart()`, which is starting the dynamic filtering or inclusion of these groups. -- Note that any destroy or new spawn of a group having a name, starting with the above prefix, will be removed or added to the set. -- - -- Then a new detection object is created from the class `DETECTION_AREAS`. A grouping radius of 1000 meters (1km) is choosen. + -- Then a new detection object is created from the class `DETECTION_AREAS`. A grouping radius of 1000 meters (1km) is chosen. -- -- The `Detection` object is then passed to the @{#AI_AIR_DISPATCHER.New}() method to indicate the reconnaissance network -- configuration and setup the AIR defense detection mechanism. @@ -647,7 +647,7 @@ do -- AI_AIR_DISPATCHER -- * @{#AI_AIR_DISPATCHER.SetSquadronLandingAtRunway}() will despawn the returning aircraft directly after landing at the runway. -- * @{#AI_AIR_DISPATCHER.SetSquadronLandingAtEngineShutdown}() will despawn the returning aircraft when the aircraft has returned to its parking spot and has turned off its engines. -- - -- You can use these methods to minimize the airbase coodination overhead and to increase the airbase efficiency. + -- You can use these methods to minimize the airbase coordination overhead and to increase the airbase efficiency. -- When there are lots of aircraft returning for landing, at the same airbase, the takeoff process will be halted, which can cause a complete failure of the -- A2A defense system, as no new CAP or GCI planes can takeoff. -- Note that the method @{#AI_AIR_DISPATCHER.SetSquadronLandingNearAirbase}() will only work for returning aircraft, not for damaged or out of fuel aircraft. @@ -724,13 +724,13 @@ do -- AI_AIR_DISPATCHER -- -- Use the method @{#AI_AIR_DISPATCHER.SetSquadronEngageLimit}() to limit the amount of aircraft that will engage with the enemy, per squadron. -- - -- ## 4. Set the **fuel treshold**. + -- ## 4. Set the **fuel threshold**. -- - -- When aircraft get **out of fuel** to a certain %-tage, which is by default **15% (0.15)**, there are two possible actions that can be taken: + -- When aircraft get **out of fuel** to a certain %, which is by default **15% (0.15)**, there are two possible actions that can be taken: -- - The aircraft will go RTB, and will be replaced with a new aircraft if possible. -- - The aircraft will refuel at a tanker, if a tanker has been specified for the squadron. -- - -- Use the method @{#AI_AIR_DISPATCHER.SetSquadronFuelThreshold}() to set the **squadron fuel treshold** of the aircraft for all squadrons. + -- Use the method @{#AI_AIR_DISPATCHER.SetSquadronFuelThreshold}() to set the **squadron fuel threshold** of the aircraft for all squadrons. -- -- ## 6. Other configuration options -- @@ -786,17 +786,17 @@ do -- AI_AIR_DISPATCHER -- -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultGrouping}() to set the **default grouping** of spawned airplanes for all squadrons. -- - -- ## 10.5. Default RTB fuel treshold. + -- ## 10.5. Default RTB fuel threshold. -- - -- When an airplane gets **out of fuel** to a certain %-tage, which is **15% (0.15)**, it will go RTB, and will be replaced with a new airplane when applicable. + -- When an airplane gets **out of fuel** to a certain %, which is **15% (0.15)**, it will go RTB, and will be replaced with a new airplane when applicable. -- - -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultFuelThreshold}() to set the **default fuel treshold** of spawned airplanes for all squadrons. + -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultFuelThreshold}() to set the **default fuel threshold** of spawned airplanes for all squadrons. -- - -- ## 10.6. Default RTB damage treshold. + -- ## 10.6. Default RTB damage threshold. -- - -- When an airplane is **damaged** to a certain %-tage, which is **40% (0.40)**, it will go RTB, and will be replaced with a new airplane when applicable. + -- When an airplane is **damaged** to a certain %, which is **40% (0.40)**, it will go RTB, and will be replaced with a new airplane when applicable. -- - -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultDamageThreshold}() to set the **default damage treshold** of spawned airplanes for all squadrons. + -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultDamageThreshold}() to set the **default damage threshold** of spawned airplanes for all squadrons. -- -- ## 10.7. Default settings for **patrol**. -- @@ -829,7 +829,7 @@ do -- AI_AIR_DISPATCHER -- -- In the mission editor, setup a group with task Refuelling. A tanker unit of the correct coalition will be automatically selected. -- Then, use the method @{#AI_AIR_DISPATCHER.SetDefaultTanker}() to set the tanker for the dispatcher. - -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultFuelThreshold}() to set the %-tage left in the defender airplane tanks when a refuel action is needed. + -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultFuelThreshold}() to set the % left in the defender airplane tanks when a refuel action is needed. -- -- When the tanker specified is alive and in the air, the tanker will be used for refuelling. -- @@ -843,7 +843,7 @@ do -- AI_AIR_DISPATCHER -- A2ADispatcher:SetSquadronCapInterval("Sochi", 2, 30, 600, 1 ) -- A2ADispatcher:SetSquadronGci( "Sochi", 900, 1200 ) -- - -- -- Set the default tanker for refuelling to "Tanker", when the default fuel treshold has reached 90% fuel left. + -- -- Set the default tanker for refuelling to "Tanker", when the default fuel threshold has reached 90% fuel left. -- A2ADispatcher:SetDefaultFuelThreshold( 0.9 ) -- A2ADispatcher:SetDefaultTanker( "Tanker" ) -- @@ -882,10 +882,7 @@ do -- AI_AIR_DISPATCHER -- As a result, the GCI and CAP will stop! -- However, the squadron will still stay alive. Any airplane that is airborne will continue its operations until all airborne airplanes -- of the squadron will be destroyed. This to keep consistency of air operations not to confuse the players. - -- - -- - -- - -- + -- -- @field #AI_AIR_DISPATCHER AI_AIR_DISPATCHER = { ClassName = "AI_AIR_DISPATCHER", @@ -914,10 +911,10 @@ do -- AI_AIR_DISPATCHER --- Enumerator for spawns at airbases -- @type AI_AIR_DISPATCHER.Takeoff -- @extends Wrapper.Group#GROUP.Takeoff - + --- @field #AI_AIR_DISPATCHER.Takeoff Takeoff AI_AIR_DISPATCHER.Takeoff = GROUP.Takeoff - + --- Defnes Landing location. -- @field #AI_AIR_DISPATCHER.Landing AI_AIR_DISPATCHER.Landing = { @@ -925,7 +922,7 @@ do -- AI_AIR_DISPATCHER AtRunway = 2, AtEngineShutdown = 3, } - + --- A defense queue item description -- @type AI_AIR_DISPATCHER.DefenseQueueItem -- @field Squadron @@ -936,7 +933,7 @@ do -- AI_AIR_DISPATCHER -- @field Functional.Detection#DETECTION_BASE AttackerDetection -- @field DefenderGrouping -- @field #string SquadronName The name of the squadron. - + --- Queue of planned defenses to be launched. -- This queue exists because defenses must be launched on FARPS, or in the air, or on an airbase, or on carriers. -- And some of these platforms have very limited amount of "launching" platforms. @@ -945,40 +942,39 @@ do -- AI_AIR_DISPATCHER -- This guarantees that launched defenders are also directly existing ... -- @type AI_AIR_DISPATCHER.DefenseQueue -- @list<#AI_AIR_DISPATCHER.DefenseQueueItem> DefenseQueueItem A list of all defenses being queued ... - + --- @field #AI_AIR_DISPATCHER.DefenseQueue DefenseQueue AI_AIR_DISPATCHER.DefenseQueue = {} - - + --- Defense approach types -- @type #AI_AIR_DISPATCHER.DefenseApproach AI_AIR_DISPATCHER.DefenseApproach = { Random = 1, Distance = 2, } - + --- AI_AIR_DISPATCHER constructor. - -- This is defining the AIR DISPATCHER for one coaliton. + -- This is defining the AIR DISPATCHER for one coalition. -- The Dispatcher works with a @{Functional.Detection#DETECTION_BASE} object that is taking of the detection of targets using the EWR units. - -- The Detection object is polymorphic, depending on the type of detection object choosen, the detection will work differently. + -- The Detection object is polymorphic, depending on the type of detection object chosen, the detection will work differently. -- @param #AI_AIR_DISPATCHER self -- @param Functional.Detection#DETECTION_BASE Detection The DETECTION object that will detects targets using the the Early Warning Radar network. -- @return #AI_AIR_DISPATCHER self -- @usage - -- - -- -- Setup the Detection, using DETECTION_AREAS. - -- -- First define the SET of GROUPs that are defining the EWR network. - -- -- Here with prefixes DF CCCP AWACS, DF CCCP EWR. - -- DetectionSetGroup = SET_GROUP:New() - -- DetectionSetGroup:FilterPrefixes( { "DF CCCP AWACS", "DF CCCP EWR" } ) - -- DetectionSetGroup:FilterStart() - -- - -- -- Define the DETECTION_AREAS, using the DetectionSetGroup, with a 30km grouping radius. - -- Detection = DETECTION_AREAS:New( DetectionSetGroup, 30000 ) - -- - -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. - -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) -- - -- + -- + -- -- Setup the Detection, using DETECTION_AREAS. + -- -- First define the SET of GROUPs that are defining the EWR network. + -- -- Here with prefixes DF CCCP AWACS, DF CCCP EWR. + -- DetectionSetGroup = SET_GROUP:New() + -- DetectionSetGroup:FilterPrefixes( { "DF CCCP AWACS", "DF CCCP EWR" } ) + -- DetectionSetGroup:FilterStart() + -- + -- -- Define the DETECTION_AREAS, using the DetectionSetGroup, with a 30km grouping radius. + -- Detection = DETECTION_AREAS:New( DetectionSetGroup, 30000 ) + -- + -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. + -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) + -- function AI_AIR_DISPATCHER:New( Detection ) -- Inherits from DETECTION_MANAGER @@ -1435,17 +1431,17 @@ do -- AI_AIR_DISPATCHER end - --- Set the default damage treshold when defenders will RTB. - -- The default damage treshold is by default set to 40%, which means that when the airplane is 40% damaged, it will go RTB. + --- Set the default damage threshold when defenders will RTB. + -- The default damage threshold is by default set to 40%, which means that when the airplane is 40% damaged, it will go RTB. -- @param #AI_AIR_DISPATCHER self - -- @param #number DamageThreshold A decimal number between 0 and 1, that expresses the %-tage of the damage treshold before going RTB. + -- @param #number DamageThreshold A decimal number between 0 and 1, that expresses the % of the damage threshold before going RTB. -- @return #AI_AIR_DISPATCHER -- @usage -- -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) -- - -- -- Now Setup the default damage treshold. + -- -- Now Setup the default damage threshold. -- AIRDispatcher:SetDefaultDamageThreshold( 0.90 ) -- Go RTB when the airplane 90% damaged. -- function AI_AIR_DISPATCHER:SetDefaultDamageThreshold( DamageThreshold ) @@ -1989,7 +1985,7 @@ do -- AI_AIR_DISPATCHER --- Defines the default amount of extra planes that will take-off as part of the defense system. -- @param #AI_AIR_DISPATCHER self - -- @param #number Overhead The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units. + -- @param #number Overhead The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units. -- The default overhead is 1, so equal balance. The @{#AI_AIR_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance AIR missiles payload, may still be less effective than a F-15C with short missiles... -- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes. @@ -2028,7 +2024,7 @@ do -- AI_AIR_DISPATCHER --- Defines the amount of extra planes that will take-off as part of the defense system. -- @param #AI_AIR_DISPATCHER self -- @param #string SquadronName The name of the squadron. - -- @param #number Overhead The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units. + -- @param #number Overhead The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units. -- The default overhead is 1, so equal balance. The @{#AI_AIR_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance AIR missiles payload, may still be less effective than a F-15C with short missiles... -- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes. @@ -2068,7 +2064,7 @@ do -- AI_AIR_DISPATCHER --- Gets the overhead of planes as part of the defense system, in comparison with the attackers. -- @param #AI_AIR_DISPATCHER self -- @param #string SquadronName The name of the squadron. - -- @return #number The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units. + -- @return #number The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units. -- The default overhead is 1, so equal balance. The @{#AI_AIR_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance AIR missiles payload, may still be less effective than a F-15C with short missiles... -- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes. @@ -2674,17 +2670,17 @@ do -- AI_AIR_DISPATCHER return self end - --- Set the default fuel treshold when defenders will RTB or Refuel in the air. - -- The fuel treshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed. + --- Set the default fuel threshold when defenders will RTB or Refuel in the air. + -- The fuel threshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed. -- @param #AI_AIR_DISPATCHER self - -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the %-tage of the treshold of fuel remaining in the tank when the plane will go RTB or Refuel. + -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the threshold of fuel remaining in the tank when the plane will go RTB or Refuel. -- @return #AI_AIR_DISPATCHER -- @usage -- -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) -- - -- -- Now Setup the default fuel treshold. + -- -- Now Setup the default fuel threshold. -- AIRDispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. -- function AI_AIR_DISPATCHER:SetDefaultFuelThreshold( FuelThreshold ) @@ -2695,18 +2691,18 @@ do -- AI_AIR_DISPATCHER end - --- Set the fuel treshold for the squadron when defenders will RTB or Refuel in the air. - -- The fuel treshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed. + --- Set the fuel threshold for the squadron when defenders will RTB or Refuel in the air. + -- The fuel threshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed. -- @param #AI_AIR_DISPATCHER self -- @param #string SquadronName The name of the squadron. - -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the %-tage of the treshold of fuel remaining in the tank when the plane will go RTB or Refuel. + -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the threshold of fuel remaining in the tank when the plane will go RTB or Refuel. -- @return #AI_AIR_DISPATCHER -- @usage -- -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) -- - -- -- Now Setup the default fuel treshold. + -- -- Now Setup the default fuel threshold. -- AIRDispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. -- function AI_AIR_DISPATCHER:SetSquadronFuelThreshold( SquadronName, FuelThreshold ) @@ -2726,7 +2722,7 @@ do -- AI_AIR_DISPATCHER -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) -- - -- -- Now Setup the default fuel treshold. + -- -- Now Setup the default fuel threshold. -- AIRDispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. -- -- -- Now Setup the default tanker. @@ -2749,7 +2745,7 @@ do -- AI_AIR_DISPATCHER -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) -- - -- -- Now Setup the squadron fuel treshold. + -- -- Now Setup the squadron fuel threshold. -- AIRDispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. -- -- -- Now Setup the squadron tanker. @@ -2847,7 +2843,7 @@ do -- AI_AIR_DISPATCHER -- @param #AI_AIR_DISPATCHER self function AI_AIR_DISPATCHER:CountDefendersEngaged( AttackerDetection, AttackerCount ) - -- First, count the active AIGroups Units, targetting the DetectedSet + -- First, count the active AIGroups Units, targeting the DetectedSet local DefendersEngaged = 0 local DefendersTotal = 0 diff --git a/Moose Development/Moose/AI/AI_Air_Engage.lua b/Moose Development/Moose/AI/AI_Air_Engage.lua index c9f6bc562..a80402a2f 100644 --- a/Moose Development/Moose/AI/AI_Air_Engage.lua +++ b/Moose Development/Moose/AI/AI_Air_Engage.lua @@ -42,8 +42,8 @@ -- -- ![Process](..\Presentations\AI_GCI\Dia10.JPG) -- --- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB. --- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. +-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB. +-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. -- -- ![Process](..\Presentations\AI_GCI\Dia13.JPG) -- diff --git a/Moose Development/Moose/AI/AI_Air_Patrol.lua b/Moose Development/Moose/AI/AI_Air_Patrol.lua index 32bd99cea..185e64572 100644 --- a/Moose Development/Moose/AI/AI_Air_Patrol.lua +++ b/Moose Development/Moose/AI/AI_Air_Patrol.lua @@ -39,8 +39,8 @@ -- -- ![Process](..\Presentations\AI_CAP\Dia10.JPG) -- --- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB. --- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. +-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB. +-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. -- -- ![Process](..\Presentations\AI_CAP\Dia13.JPG) -- @@ -70,7 +70,7 @@ -- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets. -- * **@{#AI_AIR_PATROL.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}. -- * **@{#AI_AIR_PATROL.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task. --- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB. +-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB. -- -- ## 3. Set the Range of Engagement -- diff --git a/Moose Development/Moose/AI/AI_BAI.lua b/Moose Development/Moose/AI/AI_BAI.lua index 42bccbf06..b71afd048 100644 --- a/Moose Development/Moose/AI/AI_BAI.lua +++ b/Moose Development/Moose/AI/AI_BAI.lua @@ -49,7 +49,7 @@ -- Upon started, The AI will **Route** itself towards the random 3D point within a patrol zone, -- using a random speed within the given altitude and speed limits. -- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits. --- This cycle will continue until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB. +-- This cycle will continue until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB. -- -- ![Route Event](..\Presentations\AI_BAI\Dia5.JPG) -- @@ -87,7 +87,7 @@ -- It will keep patrolling there, until it is notified to RTB or move to another BOMB Zone. -- It can be notified to go RTB through the **RTB** event. -- --- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. +-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. -- -- ![Engage Event](..\Presentations\AI_BAI\Dia12.JPG) -- @@ -117,7 +117,7 @@ -- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets. -- * **@{#AI_BAI_ZONE.Destroy}**: The AI has destroyed a target @{Wrapper.Unit}. -- * **@{#AI_BAI_ZONE.Destroyed}**: The AI has destroyed all target @{Wrapper.Unit}s assigned in the BOMB task. --- * **Status**: The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB. +-- * **Status**: The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB. -- -- ## 3. Modify the Engage Zone behaviour to pinpoint a **map object** or **scenery object** -- @@ -602,7 +602,7 @@ function AI_BAI_ZONE:onafterEngage( Controllable, From, Event, To, self:SetRefreshTimeInterval( 2 ) self:SetDetectionActivated() - self:__Target( -2 ) -- Start Targetting + self:__Target( -2 ) -- Start targeting end end diff --git a/Moose Development/Moose/AI/AI_CAP.lua b/Moose Development/Moose/AI/AI_CAP.lua index 5366db4b4..611a27b54 100644 --- a/Moose Development/Moose/AI/AI_CAP.lua +++ b/Moose Development/Moose/AI/AI_CAP.lua @@ -4,7 +4,7 @@ -- -- * Patrol AI airplanes within a given zone. -- * Trigger detected events when enemy airplanes are detected. --- * Manage a fuel treshold to RTB on time. +-- * Manage a fuel threshold to RTB on time. -- * Engage the enemy when detected. -- -- @@ -65,8 +65,8 @@ -- -- ![Process](..\Presentations\AI_CAP\Dia10.JPG) -- --- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB. --- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. +-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB. +-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. -- -- ![Process](..\Presentations\AI_CAP\Dia13.JPG) -- @@ -96,7 +96,7 @@ -- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets. -- * **@{#AI_CAP_ZONE.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}. -- * **@{#AI_CAP_ZONE.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task. --- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB. +-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB. -- -- ## 3. Set the Range of Engagement -- diff --git a/Moose Development/Moose/AI/AI_CAS.lua b/Moose Development/Moose/AI/AI_CAS.lua index 33e07849d..d8879f409 100644 --- a/Moose Development/Moose/AI/AI_CAS.lua +++ b/Moose Development/Moose/AI/AI_CAS.lua @@ -49,7 +49,7 @@ -- Upon started, The AI will **Route** itself towards the random 3D point within a patrol zone, -- using a random speed within the given altitude and speed limits. -- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits. --- This cycle will continue until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB. +-- This cycle will continue until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB. -- -- ![Route Event](..\Presentations\AI_CAS\Dia5.JPG) -- @@ -87,7 +87,7 @@ -- It will keep patrolling there, until it is notified to RTB or move to another CAS Zone. -- It can be notified to go RTB through the **RTB** event. -- --- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. +-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. -- -- ![Engage Event](..\Presentations\AI_CAS\Dia12.JPG) -- @@ -117,7 +117,7 @@ -- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets. -- * **@{#AI_CAS_ZONE.Destroy}**: The AI has destroyed a target @{Wrapper.Unit}. -- * **@{#AI_CAS_ZONE.Destroyed}**: The AI has destroyed all target @{Wrapper.Unit}s assigned in the CAS task. --- * **Status**: The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB. +-- * **Status**: The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB. -- -- === -- @@ -520,7 +520,7 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To, self:SetRefreshTimeInterval( 2 ) self:SetDetectionActivated() - self:__Target( -2 ) -- Start Targetting + self:__Target( -2 ) -- Start targeting end end diff --git a/Moose Development/Moose/AI/AI_Patrol.lua b/Moose Development/Moose/AI/AI_Patrol.lua index 767dc4400..a20f02ee4 100644 --- a/Moose Development/Moose/AI/AI_Patrol.lua +++ b/Moose Development/Moose/AI/AI_Patrol.lua @@ -4,7 +4,7 @@ -- -- * Patrol AI airplanes within a given zone. -- * Trigger detected events when enemy airplanes are detected. --- * Manage a fuel treshold to RTB on time. +-- * Manage a fuel threshold to RTB on time. -- -- === -- @@ -72,8 +72,8 @@ -- -- ![Process](..\Presentations\AI_PATROL\Dia10.JPG) -- --- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB. --- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. +-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB. +-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. -- -- ![Process](..\Presentations\AI_PATROL\Dia11.JPG) -- @@ -101,7 +101,7 @@ -- * **RTB** ( Group ): Route the AI to the home base. -- * **Detect** ( Group ): The AI is detecting targets. -- * **Detected** ( Group ): The AI has detected new targets. --- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB. +-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB. -- -- ## 3. Set or Get the AI controllable -- @@ -133,8 +133,8 @@ -- ## 6. Manage the "out of fuel" in the AI_PATROL_ZONE -- -- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base. --- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated. --- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit, +-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated. +-- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit, -- while a new AI is targetted to the AI_PATROL_ZONE. -- Once the time is finished, the old AI will return to the base. -- Use the method @{#AI_PATROL_ZONE.ManageFuel}() to have this proces in place. @@ -142,7 +142,7 @@ -- ## 7. Manage "damage" behaviour of the AI in the AI_PATROL_ZONE -- -- When the AI is damaged, it is required that a new AIControllable is started. However, damage cannon be foreseen early on. --- Therefore, when the damage treshold is reached, the AI will return immediately to the home base (RTB). +-- Therefore, when the damage threshold is reached, the AI will return immediately to the home base (RTB). -- Use the method @{#AI_PATROL_ZONE.ManageDamage}() to have this proces in place. -- -- === @@ -581,11 +581,11 @@ function AI_PATROL_ZONE:ClearDetectedUnits() end --- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base. --- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated. --- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_PATROL_ZONE. +-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated. +-- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_PATROL_ZONE. -- Once the time is finished, the old AI will return to the base. -- @param #AI_PATROL_ZONE self --- @param #number PatrolFuelThresholdPercentage The treshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel. +-- @param #number PatrolFuelThresholdPercentage The threshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel. -- @param #number PatrolOutOfFuelOrbitTime The amount of seconds the out of fuel AIControllable will orbit before returning to the base. -- @return #AI_PATROL_ZONE self function AI_PATROL_ZONE:ManageFuel( PatrolFuelThresholdPercentage, PatrolOutOfFuelOrbitTime ) @@ -596,14 +596,14 @@ function AI_PATROL_ZONE:ManageFuel( PatrolFuelThresholdPercentage, PatrolOutOfFu return self end ---- When the AI is damaged beyond a certain treshold, it is required that the AI returns to the home base. +--- When the AI is damaged beyond a certain threshold, it is required that the AI returns to the home base. -- However, damage cannot be foreseen early on. --- Therefore, when the damage treshold is reached, +-- Therefore, when the damage threshold is reached, -- the AI will return immediately to the home base (RTB). -- Note that for groups, the average damage of the complete group will be calculated. --- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage treshold will be 0.25. +-- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage threshold will be 0.25. -- @param #AI_PATROL_ZONE self --- @param #number PatrolDamageThreshold The treshold in percentage (between 0 and 1) when the AI is considered to be damaged. +-- @param #number PatrolDamageThreshold The threshold in percentage (between 0 and 1) when the AI is considered to be damaged. -- @return #AI_PATROL_ZONE self function AI_PATROL_ZONE:ManageDamage( PatrolDamageThreshold ) diff --git a/Moose Development/Moose/Cargo/Cargo.lua b/Moose Development/Moose/Cargo/Cargo.lua index df796aed1..c17c4affb 100644 --- a/Moose Development/Moose/Cargo/Cargo.lua +++ b/Moose Development/Moose/Cargo/Cargo.lua @@ -194,7 +194,7 @@ -- * is of type `Workmaterials` -- * will report when a carrier is within 500 meters -- * will board to carriers when the carrier is within 500 meters from the cargo object --- * will dissapear when the cargo is within 25 meters from the carrier during boarding +-- * will disappear when the cargo is within 25 meters from the carrier during boarding -- -- So the overall syntax of the #CARGO naming tag and arguments are: -- @@ -220,7 +220,7 @@ -- * is of type `Workmaterials` -- * will report when a carrier is within 500 meters -- * will board to carriers when the carrier is within 500 meters from the cargo object --- * will dissapear when the cargo is within 25 meters from the carrier during boarding +-- * will disappear when the cargo is within 25 meters from the carrier during boarding -- -- So the overall syntax of the #CARGO naming tag and arguments are: -- diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 02a2d8b57..c5f19f1ef 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -903,7 +903,7 @@ function DATABASE:_RegisterPlayers() return self end ---- Private method that registers all Groups and Units within in the mission. +--- Private method that registers all Groups and Units within the mission. -- @param #DATABASE self -- @return #DATABASE self function DATABASE:_RegisterGroupsAndUnits() @@ -944,7 +944,7 @@ function DATABASE:_RegisterGroupsAndUnits() return self end ---- Private method that registers all Units of skill Client or Player within in the mission. +--- Private method that registers all Units of skill Client or Player within the mission. -- @param #DATABASE self -- @return #DATABASE self function DATABASE:_RegisterClients() @@ -957,7 +957,8 @@ function DATABASE:_RegisterClients() return self end ---- @param #DATABASE self +--- Private method that registers all Statics within the mission. +-- @param #DATABASE self function DATABASE:_RegisterStatics() local CoalitionsData = { GroupsRed = coalition.getStaticObjects( coalition.side.RED ), GroupsBlue = coalition.getStaticObjects( coalition.side.BLUE ), GroupsNeutral = coalition.getStaticObjects( coalition.side.NEUTRAL ) } diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index ea8b7575a..500e1871a 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -32,11 +32,11 @@ -- -- ![Objects](..\Presentations\EVENT\Dia5.JPG) -- --- There are 5 levels of kind of objects that the _EVENTDISPATCHER services: +-- There are 5 types/levels of objects that the _EVENTDISPATCHER services: -- -- * _DATABASE object: The core of the MOOSE objects. Any object that is created, deleted or updated, is done in this database. --- * SET_ derived classes: Subsets of the _DATABASE object. These subsets are updated by the _EVENTDISPATCHER as the second priority. --- * UNIT objects: UNIT objects can subscribe to DCS events. Each DCS event will be directly published to teh subscribed UNIT object. +-- * SET_ derived classes: These are subsets of the _DATABASE object. These subsets are updated by the _EVENTDISPATCHER as the second priority. +-- * UNIT objects: UNIT objects can subscribe to DCS events. Each DCS event will be directly published to the subscribed UNIT object. -- * GROUP objects: GROUP objects can subscribe to DCS events. Each DCS event will be directly published to the subscribed GROUP object. -- * Any other object: Various other objects can subscribe to DCS events. Each DCS event triggered will be published to each subscribed object. -- @@ -131,6 +131,8 @@ -- * Weapon data: Certain events populate weapon information. -- * Place data: Certain events populate place information. -- +-- Example code snippet: +-- -- --- This function is an Event Handling function that will be called when Tank1 is Dead. -- -- EventData is an EVENTDATA structure. -- -- We use the EventData.IniUnit to smoke the tank Green. @@ -150,6 +152,7 @@ -- In case a STATIC object is involved, the documentation indicates which fields will and won't not be populated. -- The fields **IniObjectCategory** and **TgtObjectCategory** contain the indicator which **kind of object is involved** in the event. -- You can use the enumerator **Object.Category.UNIT** and **Object.Category.STATIC** to check on IniObjectCategory and TgtObjectCategory. +-- -- Example code snippet: -- -- if Event.IniObjectCategory == Object.Category.UNIT then diff --git a/Moose Development/Moose/Core/Fsm.lua b/Moose Development/Moose/Core/Fsm.lua index e34e39eac..cca8c509b 100644 --- a/Moose Development/Moose/Core/Fsm.lua +++ b/Moose Development/Moose/Core/Fsm.lua @@ -18,11 +18,11 @@ -- -- A Finite State Machine (FSM) models a process flow that transitions between various **States** through triggered **Events**. -- --- A FSM can only be in one of a finite number of states. +-- A FSM can only be in one of a finite number of states. -- The machine is in only one state at a time; the state it is in at any given time is called the **current state**. -- It can change from one state to another when initiated by an **__internal__ or __external__ triggering event**, which is called a **transition**. --- An **FSM implementation** is defined by **a list of its states**, **its initial state**, and **the triggering events** for **each possible transition**. --- An FSM implementation is composed out of **two parts**, a set of **state transition rules**, and an implementation set of **state transition handlers**, implementing those transitions. +-- A **FSM implementation** is defined by **a list of its states**, **its initial state**, and **the triggering events** for **each possible transition**. +-- A FSM implementation is composed out of **two parts**, a set of **state transition rules**, and an implementation set of **state transition handlers**, implementing those transitions. -- -- The FSM class supports a **hierarchical implementation of a Finite State Machine**, -- that is, it allows to **embed existing FSM implementations in a master FSM**. @@ -34,21 +34,21 @@ -- orders him to destroy x targets and account the results. -- Other examples of ready made FSM could be: -- --- * route a plane to a zone flown by a human --- * detect targets by an AI and report to humans --- * account for destroyed targets by human players --- * handle AI infantry to deploy from or embark to a helicopter or airplane or vehicle --- * let an AI patrol a zone +-- * Route a plane to a zone flown by a human. +-- * Detect targets by an AI and report to humans. +-- * Account for destroyed targets by human players. +-- * Handle AI infantry to deploy from or embark to a helicopter or airplane or vehicle. +-- * Let an AI patrol a zone. -- --- The **MOOSE framework** uses extensively the FSM class and derived FSM\_ classes, +-- The **MOOSE framework** extensively uses the FSM class and derived FSM\_ classes, -- because **the goal of MOOSE is to simplify mission design complexity for mission building**. -- By efficiently utilizing the FSM class and derived classes, MOOSE allows mission designers to quickly build processes. -- **Ready made FSM-based implementations classes** exist within the MOOSE framework that **can easily be re-used, -- and tailored** by mission designers through **the implementation of Transition Handlers**. -- Each of these FSM implementation classes start either with: -- --- * an acronym **AI\_**, which indicates an FSM implementation directing **AI controlled** @{GROUP} and/or @{UNIT}. These AI\_ classes derive the @{#FSM_CONTROLLABLE} class. --- * an acronym **TASK\_**, which indicates an FSM implementation executing a @{TASK} executed by Groups of players. These TASK\_ classes derive the @{#FSM_TASK} class. +-- * an acronym **AI\_**, which indicates a FSM implementation directing **AI controlled** @{GROUP} and/or @{UNIT}. These AI\_ classes derive the @{#FSM_CONTROLLABLE} class. +-- * an acronym **TASK\_**, which indicates a FSM implementation executing a @{TASK} executed by Groups of players. These TASK\_ classes derive the @{#FSM_TASK} class. -- * an acronym **ACT\_**, which indicates an Sub-FSM implementation, directing **Humans actions** that need to be done in a @{TASK}, seated in a @{CLIENT} (slot) or a @{UNIT} (CA join). These ACT\_ classes derive the @{#FSM_PROCESS} class. -- -- Detailed explanations and API specifics are further below clarified and FSM derived class specifics are described in those class documentation sections. @@ -59,7 +59,7 @@ -- I've reworked this development (taken the concept), and created a **hierarchical state machine** out of it, embedded within the DCS simulator. -- Additionally, I've added extendability and created an API that allows seamless FSM implementation. -- --- The following derived classes are available in the MOOSE framework, that implement a specialised form of a FSM: +-- The following derived classes are available in the MOOSE framework, that implement a specialized form of a FSM: -- -- * @{#FSM_TASK}: Models Finite State Machines for @{Task}s. -- * @{#FSM_PROCESS}: Models Finite State Machines for @{Task} actions, which control @{Client}s. diff --git a/Moose Development/Moose/Core/Menu.lua b/Moose Development/Moose/Core/Menu.lua index bd64eb773..b3d6ab618 100644 --- a/Moose Development/Moose/Core/Menu.lua +++ b/Moose Development/Moose/Core/Menu.lua @@ -778,7 +778,7 @@ end do -- This local variable is used to cache the menus registered under groups. - -- Menus don't dissapear when groups for players are destroyed and restarted. + -- Menus don't disappear when groups for players are destroyed and restarted. -- So every menu for a client created must be tracked so that program logic accidentally does not create. -- the same menus twice during initialization logic. -- These menu classes are handling this logic with this variable. diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index a067cc680..fc0749415 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -1373,7 +1373,7 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) SpawnTemplate.modulation = self.SpawnInitModu end - -- Set country, coaliton and categroy. + -- Set country, coalition and category. SpawnTemplate.CategoryID = self.SpawnInitCategory or SpawnTemplate.CategoryID SpawnTemplate.CountryID = self.SpawnInitCountry or SpawnTemplate.CountryID SpawnTemplate.CoalitionID = self.SpawnInitCoalition or SpawnTemplate.CoalitionID @@ -2360,7 +2360,7 @@ end -- The known AIRBASE objects are automatically imported at mission start by MOOSE. -- Therefore, there isn't any New() constructor defined for AIRBASE objects. -- --- Ships and Farps are added within the mission, and are therefore not known. +-- Ships and FARPs are added within the mission, and are therefore not known. -- For these AIRBASE objects, there isn't an @{Wrapper.Airbase#AIRBASE} enumeration defined. -- You need to provide the **exact name** of the airbase as the parameter to the @{Wrapper.Airbase#AIRBASE.FindByName}() method! -- diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 332a73dda..a8759d97b 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -9,7 +9,7 @@ -- * Create polygon zones. -- * Create moving zones around a unit. -- * Create moving zones around a group. --- * Provide the zone behaviour. Some zones are static, while others are moveable. +-- * Provide the zone behavior. Some zones are static, while others are moveable. -- * Enquiry if a coordinate is within a zone. -- * Smoke zones. -- * Set a zone probability to control zone selection. @@ -20,10 +20,10 @@ -- * Draw zones (circular and polygon) on the F10 map. -- -- --- There are essentially two core functions that zones accomodate: +-- There are essentially two core functions that zones accommodate: -- -- * Test if an object is within the zone boundaries. --- * Provide the zone behaviour. Some zones are static, while others are moveable. +-- * Provide the zone behavior. Some zones are static, while others are moveable. -- -- The object classes are using the zone classes to test the zone boundaries, which can take various forms: -- @@ -53,7 +53,6 @@ -- @module Core.Zone -- @image Core_Zones.JPG - --- @type ZONE_BASE -- @field #string ZoneName Name of the zone. -- @field #number ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability. @@ -61,7 +60,6 @@ -- @field #table Color Table with four entries, e.g. {1, 0, 0, 0.15}. First three are RGB color code. Fourth is the transparency Alpha value. -- @extends Core.Fsm#FSM - --- This class is an abstract BASE class for derived classes, and is not meant to be instantiated. -- -- ## Each zone has a name: @@ -107,11 +105,10 @@ ZONE_BASE = { ClassName = "ZONE_BASE", ZoneName = "", ZoneProbability = 1, - DrawID=nil, - Color={} + DrawID = nil, + Color = {}, } - --- The ZONE_BASE.BoundingSquare -- @type ZONE_BASE.BoundingSquare -- @field DCS#Distance x1 The lower x coordinate (left down) @@ -119,7 +116,6 @@ ZONE_BASE = { -- @field DCS#Distance x2 The higher x coordinate (right up) -- @field DCS#Distance y2 The higher y coordinate (right up) - --- ZONE_BASE constructor -- @param #ZONE_BASE self -- @param #string ZoneName Name of the zone. @@ -133,8 +129,6 @@ function ZONE_BASE:New( ZoneName ) return self end - - --- Returns the name of the zone. -- @param #ZONE_BASE self -- @return #string The name of the zone. @@ -144,7 +138,6 @@ function ZONE_BASE:GetName() return self.ZoneName end - --- Sets the name of the zone. -- @param #ZONE_BASE self -- @param #string ZoneName The name of the zone. @@ -201,7 +194,6 @@ function ZONE_BASE:IsPointVec3InZone( PointVec3 ) return InZone end - --- Returns the @{DCS#Vec2} coordinate of the zone. -- @param #ZONE_BASE self -- @return #nil. @@ -225,7 +217,6 @@ function ZONE_BASE:GetPointVec2() return PointVec2 end - --- Returns the @{DCS#Vec3} of the zone. -- @param #ZONE_BASE self -- @param DCS#Distance Height The height to add to the land height where the center of the zone is located. @@ -264,31 +255,30 @@ end -- @param #ZONE_BASE self -- @param DCS#Distance Height The height to add to the land height where the center of the zone is located. -- @return Core.Point#COORDINATE The Coordinate of the zone. -function ZONE_BASE:GetCoordinate( Height ) --R2.1 - self:F2(self.ZoneName) +function ZONE_BASE:GetCoordinate( Height ) -- R2.1 + self:F2( self.ZoneName ) local Vec3 = self:GetVec3( Height ) if self.Coordinate then -- Update coordinates. - self.Coordinate.x=Vec3.x - self.Coordinate.y=Vec3.y - self.Coordinate.z=Vec3.z + self.Coordinate.x = Vec3.x + self.Coordinate.y = Vec3.y + self.Coordinate.z = Vec3.z - --env.info("FF GetCoordinate NEW for ZONE_BASE "..tostring(self.ZoneName)) + -- env.info("FF GetCoordinate NEW for ZONE_BASE "..tostring(self.ZoneName)) else -- Create a new coordinate object. - self.Coordinate=COORDINATE:NewFromVec3(Vec3) + self.Coordinate = COORDINATE:NewFromVec3( Vec3 ) - --env.info("FF GetCoordinate NEW for ZONE_BASE "..tostring(self.ZoneName)) + -- env.info("FF GetCoordinate NEW for ZONE_BASE "..tostring(self.ZoneName)) end return self.Coordinate end - --- Define a random @{DCS#Vec2} within the zone. -- @param #ZONE_BASE self -- @return DCS#Vec2 The Vec2 coordinates. @@ -314,7 +304,7 @@ end -- @param #ZONE_BASE self -- @return #nil The bounding square. function ZONE_BASE:GetBoundingSquare() - --return { x1 = 0, y1 = 0, x2 = 0, y2 = 0 } + -- return { x1 = 0, y1 = 0, x2 = 0, y2 = 0 } return nil end @@ -325,22 +315,21 @@ function ZONE_BASE:BoundZone() end - --- Set color of zone. -- @param #ZONE_BASE self -- @param #table RGBcolor RGB color table. Default `{1, 0, 0}`. -- @param #number Alpha Transparacy between 0 and 1. Default 0.15. -- @return #ZONE_BASE self -function ZONE_BASE:SetColor(RGBcolor, Alpha) +function ZONE_BASE:SetColor( RGBcolor, Alpha ) - RGBcolor=RGBcolor or {1, 0, 0} - Alpha=Alpha or 0.15 + RGBcolor = RGBcolor or { 1, 0, 0 } + Alpha = Alpha or 0.15 - self.Color={} - self.Color[1]=RGBcolor[1] - self.Color[2]=RGBcolor[2] - self.Color[3]=RGBcolor[3] - self.Color[4]=Alpha + self.Color = {} + self.Color[1] = RGBcolor[1] + self.Color[2] = RGBcolor[2] + self.Color[3] = RGBcolor[3] + self.Color[4] = Alpha return self end @@ -356,10 +345,10 @@ end -- @param #ZONE_BASE self -- @return #table Table with three entries, e.g. {1, 0, 0}, which is the RGB color code. function ZONE_BASE:GetColorRGB() - local rgb={} - rgb[1]=self.Color[1] - rgb[2]=self.Color[2] - rgb[3]=self.Color[3] + local rgb = {} + rgb[1] = self.Color[1] + rgb[2] = self.Color[2] + rgb[3] = self.Color[3] return rgb end @@ -367,7 +356,7 @@ end -- @param #ZONE_BASE self -- @return #number Alpha value. function ZONE_BASE:GetColorAlpha() - local alpha=self.Color[4] + local alpha = self.Color[4] return alpha end @@ -375,12 +364,12 @@ end -- @param #ZONE_BASE self -- @param #number Delay (Optional) Delay before the drawing is removed. -- @return #ZONE_BASE self -function ZONE_BASE:UndrawZone(Delay) - if Delay and Delay>0 then - self:ScheduleOnce(Delay, ZONE_BASE.UndrawZone, self) +function ZONE_BASE:UndrawZone( Delay ) + if Delay and Delay > 0 then + self:ScheduleOnce( Delay, ZONE_BASE.UndrawZone, self ) else if self.DrawID then - UTILS.RemoveMark(self.DrawID) + UTILS.RemoveMark( self.DrawID ) end end return self @@ -394,7 +383,6 @@ function ZONE_BASE:GetDrawID() return self.DrawID end - --- Smokes the zone boundaries in a color. -- @param #ZONE_BASE self -- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. @@ -459,7 +447,6 @@ function ZONE_BASE:GetZoneMaybe() end end - --- The ZONE_RADIUS class, defined by a zone name, a location and a radius. -- @type ZONE_RADIUS -- @field DCS#Vec2 Vec2 The current location of the zone. @@ -498,8 +485,8 @@ end -- -- @field #ZONE_RADIUS ZONE_RADIUS = { - ClassName="ZONE_RADIUS", - } + ClassName = "ZONE_RADIUS", +} --- Constructor of @{#ZONE_RADIUS}, taking the zone name, the zone location and a radius. -- @param #ZONE_RADIUS self @@ -516,7 +503,7 @@ function ZONE_RADIUS:New( ZoneName, Vec2, Radius ) self.Radius = Radius self.Vec2 = Vec2 - --self.Coordinate=COORDINATE:NewFromVec2(Vec2) + -- self.Coordinate=COORDINATE:NewFromVec2(Vec2) return self end @@ -526,13 +513,13 @@ end -- @param DCS#Vec2 Vec2 The location of the zone. -- @param DCS#Distance Radius The radius of the zone. -- @return #ZONE_RADIUS self -function ZONE_RADIUS:UpdateFromVec2(Vec2, Radius) +function ZONE_RADIUS:UpdateFromVec2( Vec2, Radius ) -- New center of the zone. - self.Vec2=Vec2 + self.Vec2 = Vec2 if Radius then - self.Radius=Radius + self.Radius = Radius end return self @@ -543,14 +530,14 @@ end -- @param DCS#Vec3 Vec3 The location of the zone. -- @param DCS#Distance Radius The radius of the zone. -- @return #ZONE_RADIUS self -function ZONE_RADIUS:UpdateFromVec3(Vec3, Radius) +function ZONE_RADIUS:UpdateFromVec3( Vec3, Radius ) -- New center of the zone. - self.Vec2.x=Vec3.x - self.Vec2.y=Vec3.z + self.Vec2.x = Vec3.x + self.Vec2.y = Vec3.z if Radius then - self.Radius=Radius + self.Radius = Radius end return self @@ -560,7 +547,7 @@ end -- @param #ZONE_RADIUS self -- @param #number Points (Optional) The amount of points in the circle. Default 360. -- @return #ZONE_RADIUS self -function ZONE_RADIUS:MarkZone(Points) +function ZONE_RADIUS:MarkZone( Points ) local Point = {} local Vec2 = self:GetVec2() @@ -568,16 +555,16 @@ function ZONE_RADIUS:MarkZone(Points) Points = Points and Points or 360 local Angle - local RadialBase = math.pi*2 + local RadialBase = math.pi * 2 - for Angle = 0, 360, (360 / Points ) do + 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()) + COORDINATE:NewFromVec2( Point ):MarkToAll( self:GetName() ) end @@ -593,18 +580,18 @@ end -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. -- @return #ZONE_RADIUS self -function ZONE_RADIUS:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) +function ZONE_RADIUS:DrawZone( Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly ) - local coordinate=self:GetCoordinate() + local coordinate = self:GetCoordinate() - local Radius=self:GetRadius() + local Radius = self:GetRadius() - Color=Color or self:GetColorRGB() - Alpha=Alpha or 1 - FillColor=FillColor or Color - FillAlpha=FillAlpha or self:GetColorAlpha() + Color = Color or self:GetColorRGB() + Alpha = Alpha or 1 + FillColor = FillColor or Color + FillAlpha = FillAlpha or self:GetColorAlpha() - self.DrawID=coordinate:CircleToAll(Radius, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) + self.DrawID = coordinate:CircleToAll( Radius, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly ) return self end @@ -623,10 +610,10 @@ function ZONE_RADIUS:BoundZone( Points, CountryID, UnBound ) Points = Points and Points or 360 local Angle - local RadialBase = math.pi*2 + local RadialBase = math.pi * 2 -- - for Angle = 0, 360, (360 / Points ) do + 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() @@ -634,16 +621,16 @@ function ZONE_RADIUS:BoundZone( Points, CountryID, UnBound ) local CountryName = _DATABASE.COUNTRY_NAME[CountryID] local Tire = { - ["country"] = CountryName, - ["category"] = "Fortifications", - ["canCargo"] = false, - ["shape_name"] = "H-tyre_B_WF", - ["type"] = "Black_Tyre_WF", - --["unitId"] = Angle + 10000, - ["y"] = Point.y, - ["x"] = Point.x, - ["name"] = string.format( "%s-Tire #%0d", self:GetName(), Angle ), - ["heading"] = 0, + ["country"] = CountryName, + ["category"] = "Fortifications", + ["canCargo"] = false, + ["shape_name"] = "H-tyre_B_WF", + ["type"] = "Black_Tyre_WF", + -- ["unitId"] = Angle + 10000, + ["y"] = Point.y, + ["x"] = Point.x, + ["name"] = string.format( "%s-Tire #%0d", self:GetName(), Angle ), + ["heading"] = 0, } -- end of ["group"] local Group = coalition.addStaticObject( CountryID, Tire ) @@ -655,7 +642,6 @@ function ZONE_RADIUS:BoundZone( Points, CountryID, UnBound ) return self end - --- Smokes the zone boundaries in a color. -- @param #ZONE_RADIUS self -- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. @@ -675,10 +661,10 @@ function ZONE_RADIUS:SmokeZone( SmokeColor, Points, AddHeight, AngleOffset ) Points = Points and Points or 360 local Angle - local RadialBase = math.pi*2 + local RadialBase = math.pi * 2 for Angle = 0, 360, 360 / Points do - local Radial = ( Angle + AngleOffset ) * RadialBase / 360 + 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 ) @@ -687,7 +673,6 @@ function ZONE_RADIUS:SmokeZone( SmokeColor, Points, AddHeight, AngleOffset ) return self end - --- Flares the zone boundaries in a color. -- @param #ZONE_RADIUS self -- @param Utilities.Utils#FLARECOLOR FlareColor The flare color. @@ -706,7 +691,7 @@ function ZONE_RADIUS:FlareZone( FlareColor, Points, Azimuth, AddHeight ) Points = Points and Points or 360 local Angle - local RadialBase = math.pi*2 + local RadialBase = math.pi * 2 for Angle = 0, 360, 360 / Points do local Radial = Angle * RadialBase / 360 @@ -784,10 +769,6 @@ function ZONE_RADIUS:GetVec3( Height ) return Vec3 end - - - - --- Scan the zone for the presence of units of the given ObjectCategories. -- Note that after a zone has been scanned, the zone can be evaluated by: -- @@ -813,27 +794,27 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) local ZoneCoord = self:GetCoordinate() local ZoneRadius = self:GetRadius() - self:F({ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS()}) + self:F( { ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS() } ) local SphereSearch = { id = world.VolumeType.SPHERE, - params = { + params = { point = ZoneCoord:GetVec3(), radius = ZoneRadius, - } - } + }, + } local function EvaluateZone( ZoneObject ) - --if ZoneObject:isExist() then --FF: isExist always returns false for SCENERY objects since DCS 2.2 and still in DCS 2.5 + -- if ZoneObject:isExist() then --FF: isExist always returns false for SCENERY objects since DCS 2.2 and still in DCS 2.5 if ZoneObject then local ObjectCategory = ZoneObject:getCategory() - --local name=ZoneObject:getName() - --env.info(string.format("Zone object %s", tostring(name))) - --self:E(ZoneObject) + -- local name=ZoneObject:getName() + -- env.info(string.format("Zone object %s", tostring(name))) + -- self:E(ZoneObject) - if ( ObjectCategory == Object.Category.UNIT and ZoneObject:isExist() and ZoneObject:isActive() ) or (ObjectCategory == Object.Category.STATIC and ZoneObject:isExist()) then + if (ObjectCategory == Object.Category.UNIT and ZoneObject:isExist() and ZoneObject:isActive()) or (ObjectCategory == Object.Category.STATIC and ZoneObject:isExist()) then local CoalitionDCSUnit = ZoneObject:getCoalition() @@ -872,7 +853,7 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) 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] } ) + self:F2( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } ) end end @@ -893,7 +874,6 @@ function ZONE_RADIUS:GetScannedUnits() return self.ScanData.Units end - --- Get a set of scanned units. -- @param #ZONE_RADIUS self -- @return Core.Set#SET_UNIT Set of units and statics inside the zone. @@ -926,19 +906,19 @@ end -- @return Core.Set#SET_GROUP Set of groups. function ZONE_RADIUS:GetScannedSetGroup() - self.ScanSetGroup=self.ScanSetGroup or SET_GROUP:New() --Core.Set#SET_GROUP + self.ScanSetGroup = self.ScanSetGroup or SET_GROUP:New() -- Core.Set#SET_GROUP - self.ScanSetGroup.Set={} + self.ScanSetGroup.Set = {} if self.ScanData then for ObjectID, UnitObject in pairs( self.ScanData.Units ) do local UnitObject = UnitObject -- DCS#Unit if UnitObject:isExist() then - local FoundUnit=UNIT:FindByName(UnitObject:getName()) + local FoundUnit = UNIT:FindByName( UnitObject:getName() ) if FoundUnit then - local group=FoundUnit:GetGroup() - self.ScanSetGroup:AddGroup(group) + local group = FoundUnit:GetGroup() + self.ScanSetGroup:AddGroup( group ) end end end @@ -947,7 +927,6 @@ function ZONE_RADIUS:GetScannedSetGroup() return self.ScanSetGroup end - --- Count the number of different coalitions inside the zone. -- @param #ZONE_RADIUS self -- @return #number Counted coalitions. @@ -1000,7 +979,6 @@ function ZONE_RADIUS:GetScannedCoalition( Coalition ) end end - --- Get scanned scenery type -- @param #ZONE_RADIUS self -- @return #table Table of DCS scenery type objects. @@ -1008,7 +986,6 @@ function ZONE_RADIUS:GetScannedSceneryType( SceneryType ) return self.ScanData.Scenery[SceneryType] end - --- Get scanned scenery table -- @param #ZONE_RADIUS self -- @return #table Table of DCS scenery objects. @@ -1016,9 +993,8 @@ function ZONE_RADIUS:GetScannedScenery() return self.ScanData.Scenery end - --- Is All in Zone of Coalition? --- Check if only the specifed coalition is inside the zone and noone else. +-- Check if only the specified coalition is inside the zone and noone else. -- @param #ZONE_RADIUS self -- @param #number Coalition Coalition ID of the coalition which is checked to be the only one in the zone. -- @return #boolean True, if **only** that coalition is inside the zone and no one else. @@ -1027,11 +1003,10 @@ end -- local IsGuarded = self.Zone:IsAllInZoneOfCoalition( self.Coalition ) function ZONE_RADIUS:IsAllInZoneOfCoalition( Coalition ) - --self:E( { Coalitions = self.Coalitions, Count = self:CountScannedCoalitions() } ) + -- self:E( { Coalitions = self.Coalitions, Count = self:CountScannedCoalitions() } ) return self:CountScannedCoalitions() == 1 and self:GetScannedCoalition( Coalition ) == true end - --- Is All in Zone of Other Coalition? -- Check if only one coalition is inside the zone and the specified coalition is not the one. -- You first need to use the @{#ZONE_RADIUS.Scan} method to scan the zone before it can be evaluated! @@ -1044,27 +1019,27 @@ end -- local IsCaptured = self.Zone:IsAllInZoneOfOtherCoalition( self.Coalition ) function ZONE_RADIUS:IsAllInZoneOfOtherCoalition( Coalition ) - --self:E( { Coalitions = self.Coalitions, Count = self:CountScannedCoalitions() } ) + -- self:E( { Coalitions = self.Coalitions, Count = self:CountScannedCoalitions() } ) return self:CountScannedCoalitions() == 1 and self:GetScannedCoalition( Coalition ) == nil end - --- Is Some in Zone of Coalition? --- Check if more than one coaltion is inside the zone and the specifed coalition is one of them. +-- Check if more than one coalition is inside the zone and the specified coalition is one of them. -- You first need to use the @{#ZONE_RADIUS.Scan} method to scan the zone before it can be evaluated! -- Note that once a zone has been scanned, multiple evaluations can be done on the scan result set. -- @param #ZONE_RADIUS self --- @param #number Coalition ID of the coaliton which is checked to be inside the zone. +-- @param #number Coalition ID of the coalition which is checked to be inside the zone. -- @return #boolean True if more than one coalition is inside the zone and the specified coalition is one of them. -- @usage +-- -- self.Zone:Scan() -- local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition ) +-- function ZONE_RADIUS:IsSomeInZoneOfCoalition( Coalition ) return self:CountScannedCoalitions() > 1 and self:GetScannedCoalition( Coalition ) == true end - --- Is None in Zone of Coalition? -- You first need to use the @{#ZONE_RADIUS.Scan} method to scan the zone before it can be evaluated! -- Note that once a zone has been scanned, multiple evaluations can be done on the scan result set. @@ -1072,30 +1047,30 @@ end -- @param Coalition -- @return #boolean -- @usage +-- -- self.Zone:Scan() -- local IsOccupied = self.Zone:IsNoneInZoneOfCoalition( self.Coalition ) +-- function ZONE_RADIUS:IsNoneInZoneOfCoalition( Coalition ) return self:GetScannedCoalition( Coalition ) == nil end - --- Is None in Zone? -- You first need to use the @{#ZONE_RADIUS.Scan} method to scan the zone before it can be evaluated! -- Note that once a zone has been scanned, multiple evaluations can be done on the scan result set. -- @param #ZONE_RADIUS self -- @return #boolean -- @usage +-- -- self.Zone:Scan() -- local IsEmpty = self.Zone:IsNoneInZone() +-- function ZONE_RADIUS:IsNoneInZone() return self:CountScannedCoalitions() == 0 end - - - --- Searches the zone -- @param #ZONE_RADIUS self -- @param ObjectCategories A list of categories, which are members of Object.Category @@ -1107,19 +1082,18 @@ function ZONE_RADIUS:SearchZone( EvaluateFunction, ObjectCategories ) local ZoneCoord = self:GetCoordinate() local ZoneRadius = self:GetRadius() - self:F({ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS()}) + self:F( { ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS() } ) local SphereSearch = { id = world.VolumeType.SPHERE, params = { - point = ZoneCoord:GetVec3(), - radius = ZoneRadius / 2, - } + point = ZoneCoord:GetVec3(), + radius = ZoneRadius / 2, + }, } local function EvaluateZone( ZoneDCSUnit ) - local ZoneUnit = UNIT:Find( ZoneDCSUnit ) return EvaluateFunction( ZoneUnit ) @@ -1139,7 +1113,7 @@ function ZONE_RADIUS:IsVec2InZone( Vec2 ) local ZoneVec2 = self:GetVec2() if ZoneVec2 then - if (( Vec2.x - ZoneVec2.x )^2 + ( Vec2.y - ZoneVec2.y ) ^2 ) ^ 0.5 <= self:GetRadius() then + if ((Vec2.x - ZoneVec2.x) ^ 2 + (Vec2.y - ZoneVec2.y) ^ 2) ^ 0.5 <= self:GetRadius() then return true end end @@ -1173,8 +1147,8 @@ function ZONE_RADIUS:GetRandomVec2( inner, outer ) 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); + 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 } ) @@ -1211,7 +1185,6 @@ function ZONE_RADIUS:GetRandomVec3( inner, outer ) return { x = Vec2.x, y = self.y, z = Vec2.y } end - --- Returns a @{Core.Point#POINT_VEC3} object reflecting a random 3D location within the zone. -- @param #ZONE_RADIUS self -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. @@ -1227,7 +1200,6 @@ function ZONE_RADIUS:GetRandomPointVec3( inner, outer ) return PointVec3 end - --- Returns a @{Core.Point#COORDINATE} object reflecting a random 3D location within the zone. -- @param #ZONE_RADIUS self -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. @@ -1236,19 +1208,16 @@ end function ZONE_RADIUS:GetRandomCoordinate( inner, outer ) self:F( self.ZoneName, inner, outer ) - local Coordinate = COORDINATE:NewFromVec2( self:GetRandomVec2(inner, outer) ) + local Coordinate = COORDINATE:NewFromVec2( self:GetRandomVec2( inner, outer ) ) self:T3( { Coordinate = Coordinate } ) return Coordinate end - - --- @type ZONE -- @extends #ZONE_RADIUS - --- The ZONE class, defined by the zone name as defined within the Mission Editor. -- This class implements the inherited functions from @{#ZONE_RADIUS} taking into account the own zone format and properties. -- @@ -1278,9 +1247,8 @@ end -- -- @field #ZONE ZONE = { - ClassName="ZONE", - } - + ClassName = "ZONE", +} --- Constructor of ZONE taking the zone name. -- @param #ZONE self @@ -1289,10 +1257,10 @@ ZONE = { function ZONE:New( ZoneName ) -- First try to find the zone in the DB. - local zone=_DATABASE:FindZone(ZoneName) + local zone = _DATABASE:FindZone( ZoneName ) if zone then - --env.info("FF found zone in DB") + -- env.info("FF found zone in DB") return zone end @@ -1306,11 +1274,11 @@ function ZONE:New( ZoneName ) end -- Create a new ZONE_RADIUS. - local self=BASE:Inherit( self, ZONE_RADIUS:New(ZoneName, {x=Zone.point.x, y=Zone.point.z}, Zone.radius)) - self:F(ZoneName) + local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, { x = Zone.point.x, y = Zone.point.z }, Zone.radius ) ) + self:F( ZoneName ) -- Color of zone. - self.Color={1, 0, 0, 0.15} + self.Color = { 1, 0, 0, 0.15 } -- DCS zone. self.Zone = Zone @@ -1328,13 +1296,10 @@ function ZONE:FindByName( ZoneName ) return ZoneFound end - - --- @type ZONE_UNIT -- @field Wrapper.Unit#UNIT ZoneUNIT -- @extends Core.Zone#ZONE_RADIUS - --- # ZONE_UNIT class, extends @{Zone#ZONE_RADIUS} -- -- The ZONE_UNIT class defined by a zone attached to a @{Wrapper.Unit#UNIT} with a radius and optional offsets. @@ -1342,8 +1307,8 @@ end -- -- @field #ZONE_UNIT ZONE_UNIT = { - ClassName="ZONE_UNIT", - } + ClassName = "ZONE_UNIT", +} --- Constructor to create a ZONE_UNIT instance, taking the zone name, a zone unit and a radius and optional offsets in X and Y directions. -- @param #ZONE_UNIT self @@ -1358,12 +1323,12 @@ ZONE_UNIT = { -- relative_to_unit If true, theta is measured clockwise from unit's direction else clockwise from north. If using dx, dy setting this to true makes +x parallel to unit heading. -- dx, dy OR rho, theta may be used, not both. -- @return #ZONE_UNIT self -function ZONE_UNIT:New( ZoneName, ZoneUNIT, Radius, Offset) +function ZONE_UNIT:New( ZoneName, ZoneUNIT, Radius, Offset ) if Offset then -- check if the inputs was reasonable, either (dx, dy) or (rho, theta) can be given, else raise an exception. if (Offset.dx or Offset.dy) and (Offset.rho or Offset.theta) then - error("Cannot use (dx, dy) with (rho, theta)") + error( "Cannot use (dx, dy) with (rho, theta)" ) end self.dy = Offset.dy or 0.0 @@ -1386,7 +1351,6 @@ function ZONE_UNIT:New( ZoneName, ZoneUNIT, Radius, Offset) return self end - --- Returns the current location of the @{Wrapper.Unit#UNIT}. -- @param #ZONE_UNIT self -- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Unit#UNIT}location and the offset, if any. @@ -1398,9 +1362,9 @@ function ZONE_UNIT:GetVec2() local heading if self.relative_to_unit then - heading = ( self.ZoneUNIT:GetHeading() or 0.0 ) * math.pi / 180.0 - else - heading = 0.0 + heading = (self.ZoneUNIT:GetHeading() or 0.0) * math.pi / 180.0 + else + heading = 0.0 end -- update the zone position with the offsets. @@ -1414,8 +1378,8 @@ function ZONE_UNIT:GetVec2() -- if using the polar coordinates 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 ) + 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 @@ -1436,14 +1400,14 @@ function ZONE_UNIT:GetRandomVec2() self:F( self.ZoneName ) local RandomVec2 = {} - --local Vec2 = self.ZoneUNIT:GetVec2() -- FF: This does not take care of the new offset feature! + -- local Vec2 = self.ZoneUNIT:GetVec2() -- FF: This does not take care of the new offset feature! local Vec2 = self:GetVec2() if not Vec2 then Vec2 = self.LastVec2 end - local angle = math.random() * math.pi*2; + 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(); @@ -1473,14 +1437,13 @@ end --- @type ZONE_GROUP -- @extends #ZONE_RADIUS - --- The ZONE_GROUP class defines by a zone around a @{Wrapper.Group#GROUP} with a radius. The current leader of the group defines the center of the zone. -- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. -- -- @field #ZONE_GROUP ZONE_GROUP = { - ClassName="ZONE_GROUP", - } + ClassName = "ZONE_GROUP", +} --- Constructor to create a ZONE_GROUP instance, taking the zone name, a zone @{Wrapper.Group#GROUP} and a radius. -- @param #ZONE_GROUP self @@ -1501,7 +1464,6 @@ function ZONE_GROUP:New( ZoneName, ZoneGROUP, Radius ) return self end - --- Returns the current location of the @{Wrapper.Group}. -- @param #ZONE_GROUP self -- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Group} location. @@ -1531,7 +1493,7 @@ function ZONE_GROUP:GetRandomVec2() local Point = {} local Vec2 = self._.ZoneGROUP:GetVec2() - local angle = math.random() * math.pi*2; + 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(); @@ -1555,12 +1517,10 @@ function ZONE_GROUP:GetRandomPointVec2( inner, outer ) return PointVec2 end - --- @type ZONE_POLYGON_BASE -- --@field #ZONE_POLYGON_BASE.ListVec2 Polygon The polygon defined by an array of @{DCS#Vec2}. -- @extends #ZONE_BASE - --- The ZONE_POLYGON_BASE class defined by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon. -- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. -- This class is an abstract BASE class for derived classes, and is not meant to be instantiated. @@ -1571,7 +1531,7 @@ end -- -- * @{#ZONE_POLYGON_BASE.GetRandomVec2}(): Gets a random 2D point in the zone. -- * @{#ZONE_POLYGON_BASE.GetRandomPointVec2}(): Return a @{Core.Point#POINT_VEC2} object representing a random 2D point within the zone. --- * @{#ZONE_POLYGON_BASE.GetRandomPointVec3}(): Return a @{Core.Point#POINT_VEC3} object representing a random 3D point at landheight within the zone. +-- * @{#ZONE_POLYGON_BASE.GetRandomPointVec3}(): Return a @{Core.Point#POINT_VEC3} object representing a random 3D point at land height within the zone. -- -- ## Draw zone -- @@ -1581,8 +1541,8 @@ end -- -- @field #ZONE_POLYGON_BASE ZONE_POLYGON_BASE = { - ClassName="ZONE_POLYGON_BASE", - } + ClassName = "ZONE_POLYGON_BASE", +} --- A 2D points array. -- @type ZONE_POLYGON_BASE.ListVec2 @@ -1623,14 +1583,14 @@ end -- @param #ZONE_POLYGON_BASE self -- @param #ZONE_POLYGON_BASE.ListVec2 Vec2Array An array of @{DCS#Vec2}, forming a polygon. -- @return #ZONE_POLYGON_BASE self -function ZONE_POLYGON_BASE:UpdateFromVec2(Vec2Array) +function ZONE_POLYGON_BASE:UpdateFromVec2( Vec2Array ) self._.Polygon = {} - for i=1,#Vec2Array do + for i = 1, #Vec2Array do self._.Polygon[i] = {} - self._.Polygon[i].x=Vec2Array[i].x - self._.Polygon[i].y=Vec2Array[i].y + self._.Polygon[i].x = Vec2Array[i].x + self._.Polygon[i].y = Vec2Array[i].y end return self @@ -1640,14 +1600,14 @@ end -- @param #ZONE_POLYGON_BASE self -- @param #ZONE_POLYGON_BASE.ListVec3 Vec2Array An array of @{DCS#Vec3}, forming a polygon. -- @return #ZONE_POLYGON_BASE self -function ZONE_POLYGON_BASE:UpdateFromVec3(Vec3Array) +function ZONE_POLYGON_BASE:UpdateFromVec3( Vec3Array ) self._.Polygon = {} - for i=1,#Vec3Array do + for i = 1, #Vec3Array do self._.Polygon[i] = {} - self._.Polygon[i].x=Vec3Array[i].x - self._.Polygon[i].y=Vec3Array[i].z + self._.Polygon[i].x = Vec3Array[i].x + self._.Polygon[i].y = Vec3Array[i].z end return self @@ -1661,14 +1621,14 @@ function ZONE_POLYGON_BASE:GetVec2() local Bounds = self:GetBoundingSquare() - return { x = ( Bounds.x2 + Bounds.x1 ) / 2, y = ( Bounds.y2 + Bounds.y1 ) / 2 } + return { x = (Bounds.x2 + Bounds.x1) / 2, y = (Bounds.y2 + Bounds.y1) / 2 } end --- Get a vertex of the polygon. -- @param #ZONE_POLYGON_BASE self -- @param #number Index Index of the vertex. Default 1. -- @return DCS#Vec2 Vertex of the polygon. -function ZONE_POLYGON_BASE:GetVertexVec2(Index) +function ZONE_POLYGON_BASE:GetVertexVec2( Index ) return self._.Polygon[Index or 1] end @@ -1676,10 +1636,10 @@ end -- @param #ZONE_POLYGON_BASE self -- @param #number Index Index of the vertex. Default 1. -- @return DCS#Vec3 Vertex of the polygon. -function ZONE_POLYGON_BASE:GetVertexVec3(Index) - local vec2=self:GetVertexVec2(Index) +function ZONE_POLYGON_BASE:GetVertexVec3( Index ) + local vec2 = self:GetVertexVec2( Index ) if vec2 then - local vec3={x=vec2.x, y=land.getHeight(vec2), z=vec2.y} + local vec3 = { x = vec2.x, y = land.getHeight( vec2 ), z = vec2.y } return vec3 end return nil @@ -1689,16 +1649,15 @@ end -- @param #ZONE_POLYGON_BASE self -- @param #number Index Index of the vertex. Default 1. -- @return Core.Point#COORDINATE Vertex of the polygon. -function ZONE_POLYGON_BASE:GetVertexCoordinate(Index) - local vec2=self:GetVertexVec2(Index) +function ZONE_POLYGON_BASE:GetVertexCoordinate( Index ) + local vec2 = self:GetVertexVec2( Index ) if vec2 then - local coord=COORDINATE:NewFromVec2(vec2) + local coord = COORDINATE:NewFromVec2( vec2 ) return coord end return nil end - --- Get a list of verticies of the polygon. -- @param #ZONE_POLYGON_BASE self -- @return List of DCS#Vec2 verticies defining the edges of the polygon. @@ -1711,11 +1670,11 @@ end -- @return #table List of DCS#Vec3 verticies defining the edges of the polygon. function ZONE_POLYGON_BASE:GetVerticiesVec3() - local coords={} + local coords = {} - for i,vec2 in ipairs(self._.Polygon) do - local vec3={x=vec2.x, y=land.getHeight(vec2), z=vec2.y} - table.insert(coords, vec3) + for i, vec2 in ipairs( self._.Polygon ) do + local vec3 = { x = vec2.x, y = land.getHeight( vec2 ), z = vec2.y } + table.insert( coords, vec3 ) end return coords @@ -1726,11 +1685,11 @@ end -- @return #table List of COORDINATES verticies defining the edges of the polygon. function ZONE_POLYGON_BASE:GetVerticiesCoordinates() - local coords={} + local coords = {} - for i,vec2 in ipairs(self._.Polygon) do - local coord=COORDINATE:NewFromVec2(vec2) - table.insert(coords, coord) + for i, vec2 in ipairs( self._.Polygon ) do + local coord = COORDINATE:NewFromVec2( vec2 ) + table.insert( coords, coord ) end return coords @@ -1794,7 +1753,6 @@ function ZONE_POLYGON_BASE:BoundZone( UnBound ) return self end - --- Draw the zone on the F10 map. **NOTE** Currently, only polygons with **exactly four points** are supported! -- @param #ZONE_POLYGON_BASE self -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. @@ -1805,34 +1763,32 @@ end -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. -- @return #ZONE_POLYGON_BASE self -function ZONE_POLYGON_BASE:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) +function ZONE_POLYGON_BASE:DrawZone( Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly ) - local coordinate=COORDINATE:NewFromVec2(self._.Polygon[1]) + local coordinate = COORDINATE:NewFromVec2( self._.Polygon[1] ) - Color=Color or self:GetColorRGB() - Alpha=Alpha or 1 - FillColor=FillColor or Color - FillAlpha=FillAlpha or self:GetColorAlpha() + Color = Color or self:GetColorRGB() + Alpha = Alpha or 1 + FillColor = FillColor or Color + FillAlpha = FillAlpha or self:GetColorAlpha() + if #self._.Polygon == 4 then - if #self._.Polygon==4 then + local Coord2 = COORDINATE:NewFromVec2( self._.Polygon[2] ) + local Coord3 = COORDINATE:NewFromVec2( self._.Polygon[3] ) + local Coord4 = COORDINATE:NewFromVec2( self._.Polygon[4] ) - local Coord2=COORDINATE:NewFromVec2(self._.Polygon[2]) - local Coord3=COORDINATE:NewFromVec2(self._.Polygon[3]) - local Coord4=COORDINATE:NewFromVec2(self._.Polygon[4]) - - self.DrawID=coordinate:QuadToAll(Coord2, Coord3, Coord4, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) + self.DrawID = coordinate:QuadToAll( Coord2, Coord3, Coord4, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly ) else - local Coordinates=self:GetVerticiesCoordinates() - table.remove(Coordinates, 1) + local Coordinates = self:GetVerticiesCoordinates() + table.remove( Coordinates, 1 ) - self.DrawID=coordinate:MarkupToAllFreeForm(Coordinates, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) + self.DrawID = coordinate:MarkupToAllFreeForm( Coordinates, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly ) end - return self end @@ -1844,10 +1800,10 @@ end function ZONE_POLYGON_BASE:SmokeZone( SmokeColor, Segments ) self:F2( SmokeColor ) - Segments=Segments or 10 + Segments = Segments or 10 - local i=1 - local j=#self._.Polygon + local i = 1 + local j = #self._.Polygon while i <= #self._.Polygon do self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) @@ -1856,8 +1812,8 @@ function ZONE_POLYGON_BASE:SmokeZone( SmokeColor, Segments ) local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y for Segment = 0, Segments do -- We divide each line in 5 segments and smoke a point on the line. - local PointX = self._.Polygon[i].x + ( Segment * DeltaX / Segments ) - local PointY = self._.Polygon[i].y + ( Segment * DeltaY / Segments ) + 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 @@ -1867,7 +1823,6 @@ function ZONE_POLYGON_BASE:SmokeZone( SmokeColor, Segments ) return self end - --- Flare the zone boundaries in a color. -- @param #ZONE_POLYGON_BASE self -- @param Utilities.Utils#FLARECOLOR FlareColor The flare color. @@ -1876,14 +1831,14 @@ end -- @param #number AddHeight (optional) The height to be added for the smoke. -- @return #ZONE_POLYGON_BASE self function ZONE_POLYGON_BASE:FlareZone( FlareColor, Segments, Azimuth, AddHeight ) - self:F2(FlareColor) + self:F2( FlareColor ) - Segments=Segments or 10 + Segments = Segments or 10 AddHeight = AddHeight or 0 - local i=1 - local j=#self._.Polygon + local i = 1 + local j = #self._.Polygon while i <= #self._.Polygon do self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) @@ -1892,9 +1847,9 @@ function ZONE_POLYGON_BASE:FlareZone( FlareColor, Segments, Azimuth, AddHeight ) local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y for Segment = 0, Segments do -- We divide each line in 5 segments and smoke a point on the line. - 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) + 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 @@ -1903,9 +1858,6 @@ function ZONE_POLYGON_BASE:FlareZone( FlareColor, Segments, Azimuth, AddHeight ) return self end - - - --- Returns if a location is within the zone. -- Source learned and taken from: https://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html -- @param #ZONE_POLYGON_BASE self @@ -1923,10 +1875,9 @@ function ZONE_POLYGON_BASE:IsVec2InZone( Vec2 ) 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 + 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 @@ -1989,7 +1940,6 @@ function ZONE_POLYGON_BASE:GetRandomPointVec3() return PointVec3 end - --- Return a @{Core.Point#COORDINATE} object representing a random 3D point at landheight within the zone. -- @param #ZONE_POLYGON_BASE self -- @return Core.Point#COORDINATE @@ -2003,7 +1953,6 @@ function ZONE_POLYGON_BASE:GetRandomCoordinate() return Coordinate end - --- Get the bounding square the zone. -- @param #ZONE_POLYGON_BASE self -- @return #ZONE_POLYGON_BASE.BoundingSquare The bounding square. @@ -2016,10 +1965,10 @@ function ZONE_POLYGON_BASE:GetBoundingSquare() 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 = ( x2 < self._.Polygon[i].x ) and self._.Polygon[i].x or x2 - y1 = ( y1 > self._.Polygon[i].y ) and self._.Polygon[i].y or y1 - y2 = ( y2 < self._.Polygon[i].y ) and self._.Polygon[i].y or y2 + x1 = (x1 > self._.Polygon[i].x) and self._.Polygon[i].x or x1 + x2 = (x2 < self._.Polygon[i].x) and self._.Polygon[i].x or x2 + y1 = (y1 > self._.Polygon[i].y) and self._.Polygon[i].y or y1 + y2 = (y2 < self._.Polygon[i].y) and self._.Polygon[i].y or y2 end @@ -2035,41 +1984,40 @@ end -- @param #number Segments (Optional) Number of segments within boundary line. Default 10. -- @param #boolean Closed (Optional) Link the last point with the first one to obtain a closed boundary. Default false -- @return #ZONE_POLYGON_BASE self -function ZONE_POLYGON_BASE:Boundary(Coalition, Color, Radius, Alpha, Segments, Closed) - Coalition = Coalition or -1 - Color = Color or {1, 1, 1} - Radius = Radius or 1000 - Alpha = Alpha or 1 - Segments = Segments or 10 - Closed = Closed or false - local i = 1 - local j = #self._.Polygon - if (Closed) then - Limit = #self._.Polygon + 1 - else - Limit = #self._.Polygon +function ZONE_POLYGON_BASE:Boundary( Coalition, Color, Radius, Alpha, Segments, Closed ) + Coalition = Coalition or -1 + Color = Color or { 1, 1, 1 } + Radius = Radius or 1000 + Alpha = Alpha or 1 + Segments = Segments or 10 + Closed = Closed or false + local i = 1 + local j = #self._.Polygon + if (Closed) then + Limit = #self._.Polygon + 1 + else + Limit = #self._.Polygon + end + while i <= #self._.Polygon do + self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) + if j ~= Limit then + local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x + local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y + for Segment = 0, Segments do + local PointX = self._.Polygon[i].x + (Segment * DeltaX / Segments) + local PointY = self._.Polygon[i].y + (Segment * DeltaY / Segments) + ZONE_RADIUS:New( "Zone", { x = PointX, y = PointY }, Radius ):DrawZone( Coalition, Color, 1, Color, Alpha, nil, true ) + end end - while i <= #self._.Polygon do - self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) - if j ~= Limit then - local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x - local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y - for Segment = 0, Segments do - local PointX = self._.Polygon[i].x + ( Segment * DeltaX / Segments ) - local PointY = self._.Polygon[i].y + ( Segment * DeltaY / Segments ) - ZONE_RADIUS:New( "Zone", {x = PointX, y = PointY}, Radius ):DrawZone(Coalition, Color, 1, Color, Alpha, nil, true) - end - end - j = i - i = i + 1 - end - return self + j = i + i = i + 1 + end + return self end --- @type ZONE_POLYGON -- @extends #ZONE_POLYGON_BASE - --- The ZONE_POLYGON class defined by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon. -- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. -- @@ -2095,8 +2043,8 @@ end -- -- @field #ZONE_POLYGON ZONE_POLYGON = { - ClassName="ZONE_POLYGON", - } + ClassName = "ZONE_POLYGON", +} --- Constructor to create a ZONE_POLYGON instance, taking the zone name and the @{Wrapper.Group#GROUP} defined within the Mission Editor. -- The @{Wrapper.Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected by ZONE_POLYGON. @@ -2117,7 +2065,6 @@ function ZONE_POLYGON:New( ZoneName, ZoneGroup ) return self end - --- Constructor to create a ZONE_POLYGON instance, taking the zone name and the **name** of the @{Wrapper.Group#GROUP} defined within the Mission Editor. -- The @{Wrapper.Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected by ZONE_POLYGON. -- @param #ZONE_POLYGON self @@ -2138,7 +2085,6 @@ function ZONE_POLYGON:NewFromGroupName( GroupName ) return self end - --- Find a polygon zone in the _DATABASE using the name of the polygon zone. -- @param #ZONE_POLYGON self -- @param #string ZoneName The name of the polygon zone. @@ -2154,16 +2100,13 @@ do -- ZONE_AIRBASE --- @type ZONE_AIRBASE -- @extends #ZONE_RADIUS - --- The ZONE_AIRBASE class defines by a zone around a @{Wrapper.Airbase#AIRBASE} with a radius. -- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. -- -- @field #ZONE_AIRBASE ZONE_AIRBASE = { - ClassName="ZONE_AIRBASE", - } - - + ClassName = "ZONE_AIRBASE", + } --- Constructor to create a ZONE_AIRBASE instance, taking the zone name, a zone @{Wrapper.Airbase#AIRBASE} and a radius. -- @param #ZONE_AIRBASE self @@ -2172,7 +2115,7 @@ do -- ZONE_AIRBASE -- @return #ZONE_AIRBASE self function ZONE_AIRBASE:New( AirbaseName, Radius ) - Radius=Radius or 4000 + Radius = Radius or 4000 local Airbase = AIRBASE:FindByName( AirbaseName ) @@ -2223,7 +2166,7 @@ do -- ZONE_AIRBASE local Point = {} local Vec2 = self._.ZoneAirbase:GetVec2() - local angle = math.random() * math.pi*2; + 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(); @@ -2247,5 +2190,4 @@ do -- ZONE_AIRBASE return PointVec2 end - end diff --git a/Moose Development/Moose/Functional/Artillery.lua b/Moose Development/Moose/Functional/Artillery.lua index 8f7192641..f611ff58d 100644 --- a/Moose Development/Moose/Functional/Artillery.lua +++ b/Moose Development/Moose/Functional/Artillery.lua @@ -73,7 +73,7 @@ -- @field Core.Point#COORDINATE RearmingPlaceCoord Coordinates of the rearming place. If the place is more than 100 m away from the ARTY group, the group will go there. -- @field #boolean RearmingArtyOnRoad If true, ARTY group will move to rearming place using mainly roads. Default false. -- @field Core.Point#COORDINATE InitialCoord Initial coordinates of the ARTY group. --- @field #boolean report Arty group sends messages about their current state or target to its coaliton. +-- @field #boolean report Arty group sends messages about their current state or target to its coalition. -- @field #table ammoshells Table holding names of the shell types which are included when counting the ammo. Default is {"weapons.shells"} which include most shells. -- @field #table ammorockets Table holding names of the rocket types which are included when counting the ammo. Default is {"weapons.nurs"} which includes most unguided rockets. -- @field #table ammomissiles Table holding names of the missile types which are included when counting the ammo. Default is {"weapons.missiles"} which includes some guided missiles. diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index bcc2a4fff..39ff5d97c 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -69,7 +69,7 @@ -- @field #string category Category of aircarft: "plane" or "heli". -- @field #number groupsize Number of aircraft in group. -- @field #string friendly Possible departure/destination airport: all=blue+red+neutral, same=spawn+neutral, spawnonly=spawn, blue=blue+neutral, blueonly=blue, red=red+neutral, redonly=red. --- @field #table ctable Table with the valid coalitons from choice self.friendly. +-- @field #table ctable Table with the valid coalitions from choice self.friendly. -- @field #table aircraft Table which holds the basic aircraft properties (speed, range, ...). -- @field #number Vcruisemax Max cruise speed in m/s (250 m/s = 900 km/h = 486 kt) set by user. -- @field #number Vclimb Default climb rate in ft/min. @@ -348,7 +348,7 @@ RAT={ category = nil, -- Category of aircarft: "plane" or "heli". groupsize=nil, -- Number of aircraft in the group. friendly = "same", -- Possible departure/destination airport: same=spawn+neutral, spawnonly=spawn, blue=blue+neutral, blueonly=blue, red=red+neutral, redonly=red, neutral. - ctable = {}, -- Table with the valid coalitons from choice self.friendly. + ctable = {}, -- Table with the valid coalitions from choice self.friendly. aircraft = {}, -- Table which holds the basic aircraft properties (speed, range, ...). Vcruisemax=nil, -- Max cruise speed in set by user. Vclimb=1500, -- Default climb rate in ft/min. @@ -657,7 +657,7 @@ end -- @param #RAT self -- @param #number naircraft (Optional) Number of aircraft to spawn. Default is one aircraft. -- @return #boolean True if spawning was successful or nil if nothing was spawned. --- @usage yak:Spawn(5) will spawn five aircraft. By default aircraft will spawn at neutral and red airports if the template group is part of the red coaliton. +-- @usage yak:Spawn(5) will spawn five aircraft. By default aircraft will spawn at neutral and red airports if the template group is part of the red coalition. function RAT:Spawn(naircraft) -- Make sure that this function is only been called once per RAT object. diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index e96feb3b5..2bd6d1e8d 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -50,7 +50,7 @@ -- @field #boolean Report If true, send status messages to coalition. -- @field Wrapper.Static#STATIC warehouse The phyical warehouse structure. -- @field #string alias Alias of the warehouse. Name its called when sending messages. --- @field Core.Zone#ZONE zone Zone around the warehouse. If this zone is captured, the warehouse and all its assets goes to the capturing coaliton. +-- @field Core.Zone#ZONE zone Zone around the warehouse. If this zone is captured, the warehouse and all its assets goes to the capturing coalition. -- @field Wrapper.Airbase#AIRBASE airbase Airbase the warehouse belongs to. -- @field #string airbasename Name of the airbase associated to the warehouse. -- @field Core.Point#COORDINATE road Closest point to warehouse on road. @@ -764,7 +764,7 @@ -- warehouseBatumi:Load("D:\\My Warehouse Data\\") -- warehouseBatumi:Start() -- --- This sequence loads all assets from file. If a warehouse was captured in the last mission, it also respawns the static warehouse structure with the right coaliton. +-- This sequence loads all assets from file. If a warehouse was captured in the last mission, it also respawns the static warehouse structure with the right coalition. -- However, it due to DCS limitations it is not possible to set the airbase coalition. This has to be done manually in the mission editor. Or alternatively, one could -- spawn some ground units via a self request and let them capture the airbase. -- @@ -1811,7 +1811,7 @@ WAREHOUSE.version="1.0.2" -- DONE: Add shipping lanes between warehouses. -- DONE: Handle cases with immobile units <== should be handled by dispatcher classes. -- DONE: Handle cases for aircraft carriers and other ships. Place warehouse on carrier possible? On others probably not - exclude them? --- DONE: Add general message function for sending to coaliton or debug. +-- DONE: Add general message function for sending to coalition or debug. -- DONE: Fine tune event handlers. -- DONE: Improve generalized attributes. -- DONE: If warehouse is destroyed, all asssets are gone. @@ -3155,7 +3155,7 @@ end -- @param MinAssets (Optional) Minimum number of assets the warehouse should have. Default 0. -- @param #string Descriptor (Optional) Descriptor describing the selected assets which should be in stock. See @{#WAREHOUSE.Descriptor} for possible values. -- @param DescriptorValue (Optional) Descriptor value selecting the type of assets which should be in stock. --- @param DCS#Coalition.side Coalition (Optional) Coalition side of the warehouse. Default is the same coaliton as the present warehouse. Set to false for any coalition. +-- @param DCS#Coalition.side Coalition (Optional) Coalition side of the warehouse. Default is the same coalition as the present warehouse. Set to false for any coalition. -- @param Core.Point#COORDINATE RefCoordinate (Optional) Coordinate to which the closest warehouse is searched. Default is the warehouse calling this function. -- @return #WAREHOUSE The the nearest warehouse object. Or nil if no warehouse is found. -- @return #number The distance to the nearest warehouse in meters. Or nil if no warehouse is found. @@ -3267,7 +3267,7 @@ function WAREHOUSE:onafterStart(From, Event, To) -- Short info. local text=string.format("Starting warehouse %s alias %s:\n",self.warehouse:GetName(), self.alias) - text=text..string.format("Coaliton = %s\n", self:GetCoalitionName()) + text=text..string.format("Coalition = %s\n", self:GetCoalitionName()) text=text..string.format("Country = %s\n", self:GetCountryName()) text=text..string.format("Airbase = %s (category=%d)\n", self:GetAirbaseName(), self:GetAirbaseCategory()) env.info(text) @@ -8460,7 +8460,7 @@ function WAREHOUSE:_GetStockAssetsText(messagetoall) end --- Create or update mark text at warehouse, which is displayed in F10 map showing how many assets of each type are in stock. --- Only the coaliton of the warehouse owner is able to see it. +-- Only the coalition of the warehouse owner is able to see it. -- @param #WAREHOUSE self -- @return #string Text about warehouse stock function WAREHOUSE:_UpdateWarehouseMarkText() diff --git a/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua b/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua index d90dfcca7..9be2c35bf 100644 --- a/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua +++ b/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua @@ -804,7 +804,7 @@ do -- ZONE_CAPTURE_COALITION return IsEmpty end - --- Check if zone is "Guarded", i.e. only one (the defending) coaliton is present inside the zone. + --- Check if zone is "Guarded", i.e. only one (the defending) coalition is present inside the zone. -- @param #ZONE_CAPTURE_COALITION self -- @return #boolean self:IsAllInZoneOfCoalition( self.Coalition ) function ZONE_CAPTURE_COALITION:IsGuarded() @@ -826,7 +826,7 @@ do -- ZONE_CAPTURE_COALITION return IsCaptured end - --- Check if zone is "Attacked", i.e. another coaliton entered the zone. + --- Check if zone is "Attacked", i.e. another coalition entered the zone. -- @param #ZONE_CAPTURE_COALITION self -- @return #boolean self:IsSomeInZoneOfCoalition( self.Coalition ) function ZONE_CAPTURE_COALITION:IsAttacked() @@ -899,24 +899,23 @@ do -- ZONE_CAPTURE_COALITION end self:I(text) end - + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - --- Update Mark on F10 map. -- @param #ZONE_CAPTURE_COALITION self function ZONE_CAPTURE_COALITION:Mark() - + if self.MarkOn then - + local Coord = self:GetCoordinate() local ZoneName = self:GetZoneName() local State = self:GetState() - + -- Remove marks. if self.MarkRed then Coord:RemoveMark(self.MarkRed) @@ -924,21 +923,21 @@ do -- ZONE_CAPTURE_COALITION if self.MarkBlue then Coord:RemoveMark(self.MarkBlue) end - - -- Create new marks for each coaliton. + + -- Create new marks for each coalition. if self.Coalition == coalition.side.BLUE then - self.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Blue\nGuard Zone: " .. ZoneName .. "\nStatus: " .. State ) + 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.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.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 diff --git a/Moose Development/Moose/Functional/ZoneGoalCoalition.lua b/Moose Development/Moose/Functional/ZoneGoalCoalition.lua index 6296b4835..d2086f1ad 100644 --- a/Moose Development/Moose/Functional/ZoneGoalCoalition.lua +++ b/Moose Development/Moose/Functional/ZoneGoalCoalition.lua @@ -1,16 +1,16 @@ --- **Functional (WIP)** -- Base class that models processes to achieve goals involving a Zone for a Coalition. -- -- === --- --- ZONE_GOAL_COALITION models processes that have a Goal with a defined achievement involving a Zone for a Coalition. +-- +-- ZONE_GOAL_COALITION models processes that have a Goal with a defined achievement involving a Zone for a Coalition. -- Derived classes implement the ways how the achievements can be realized. --- +-- -- === --- +-- -- ### Author: **FlightControl** --- +-- -- === --- +-- -- @module Functional.ZoneGoalCoalition -- @image MOOSE.JPG @@ -24,34 +24,33 @@ do -- ZoneGoal -- @field #table ObjectCategories Table of object categories that are able to hold a zone. Default is UNITS and STATICS. -- @extends Functional.ZoneGoal#ZONE_GOAL - - --- ZONE_GOAL_COALITION models processes that have a Goal with a defined achievement involving a Zone for a Coalition. + --- ZONE_GOAL_COALITION models processes that have a Goal with a defined achievement involving a Zone for a Coalition. -- Derived classes implement the ways how the achievements can be realized. - -- + -- -- ## 1. ZONE_GOAL_COALITION constructor - -- + -- -- * @{#ZONE_GOAL_COALITION.New}(): Creates a new ZONE_GOAL_COALITION object. - -- + -- -- ## 2. ZONE_GOAL_COALITION is a finite state machine (FSM). - -- + -- -- ### 2.1 ZONE_GOAL_COALITION States - -- + -- -- ### 2.2 ZONE_GOAL_COALITION Events - -- + -- -- ### 2.3 ZONE_GOAL_COALITION State Machine - -- + -- -- @field #ZONE_GOAL_COALITION ZONE_GOAL_COALITION = { - ClassName = "ZONE_GOAL_COALITION", - Coalition = nil, - PreviousCoaliton = nil, - UnitCategories = nil, + ClassName = "ZONE_GOAL_COALITION", + Coalition = nil, + PreviousCoalition = nil, + UnitCategories = nil, ObjectCategories = nil, } - + --- @field #table ZONE_GOAL_COALITION.States ZONE_GOAL_COALITION.States = {} - + --- ZONE_GOAL_COALITION Constructor. -- @param #ZONE_GOAL_COALITION self -- @param Core.Zone#ZONE Zone A @{Zone} object with the goal to be achieved. @@ -59,33 +58,32 @@ do -- ZoneGoal -- @param #table UnitCategories Table of unit categories. See [DCS Class Unit](https://wiki.hoggitworld.com/view/DCS_Class_Unit). Default {Unit.Category.GROUND_UNIT}. -- @return #ZONE_GOAL_COALITION function ZONE_GOAL_COALITION:New( Zone, Coalition, UnitCategories ) - + if not Zone then - BASE:E("ERROR: No Zone specified in ZONE_GOAL_COALITON!") + BASE:E( "ERROR: No Zone specified in ZONE_GOAL_COALITION!" ) return nil end - + -- Inherit ZONE_GOAL. local self = BASE:Inherit( self, ZONE_GOAL:New( Zone ) ) -- #ZONE_GOAL_COALITION - self:F( { Zone = Zone, Coalition = Coalition } ) + self:F( { Zone = Zone, Coalition = Coalition } ) -- Set initial owner. - self:SetCoalition( Coalition or coalition.side.NEUTRAL) - + self:SetCoalition( Coalition or coalition.side.NEUTRAL ) + -- Set default unit and object categories for the zone scan. - self:SetUnitCategories(UnitCategories) + self:SetUnitCategories( UnitCategories ) self:SetObjectCategories() - + return self end - --- Set the owning coalition of the zone. -- @param #ZONE_GOAL_COALITION self -- @param DCSCoalition.DCSCoalition#coalition Coalition The coalition ID, e.g. *coalition.side.RED*. -- @return #ZONE_GOAL_COALITION function ZONE_GOAL_COALITION:SetCoalition( Coalition ) - self.PreviousCoalition=self.Coalition or Coalition + self.PreviousCoalition = self.Coalition or Coalition self.Coalition = Coalition return self end @@ -95,31 +93,31 @@ do -- ZoneGoal -- @param #table UnitCategories Table of unit categories. See [DCS Class Unit](https://wiki.hoggitworld.com/view/DCS_Class_Unit). Default {Unit.Category.GROUND_UNIT}. -- @return #ZONE_GOAL_COALITION function ZONE_GOAL_COALITION:SetUnitCategories( UnitCategories ) - - if UnitCategories and type(UnitCategories)~="table" then - UnitCategories={UnitCategories} + + if UnitCategories and type( UnitCategories ) ~= "table" then + UnitCategories = { UnitCategories } end - - self.UnitCategories=UnitCategories or {Unit.Category.GROUND_UNIT} - + + self.UnitCategories = UnitCategories or { Unit.Category.GROUND_UNIT } + return self end - + --- Set the owning coalition of the zone. -- @param #ZONE_GOAL_COALITION self -- @param #table ObjectCategories Table of unit categories. See [DCS Class Object](https://wiki.hoggitworld.com/view/DCS_Class_Object). Default {Object.Category.UNIT, Object.Category.STATIC}, i.e. all UNITS and STATICS. -- @return #ZONE_GOAL_COALITION function ZONE_GOAL_COALITION:SetObjectCategories( ObjectCategories ) - - if ObjectCategories and type(ObjectCategories)~="table" then - ObjectCategories={ObjectCategories} + + if ObjectCategories and type( ObjectCategories ) ~= "table" then + ObjectCategories = { ObjectCategories } end - - self.ObjectCategories=ObjectCategories or {Object.Category.UNIT, Object.Category.STATIC} - + + self.ObjectCategories = ObjectCategories or { Object.Category.UNIT, Object.Category.STATIC } + return self - end - + end + --- Get the owning coalition of the zone. -- @param #ZONE_GOAL_COALITION self -- @return DCSCoalition.DCSCoalition#coalition Coalition. @@ -127,39 +125,37 @@ do -- ZoneGoal return self.Coalition end - --- Get the previous coaliton, i.e. the one owning the zone before the current one. + --- Get the previous coalition, i.e. the one owning the zone before the current one. -- @param #ZONE_GOAL_COALITION self -- @return DCSCoalition.DCSCoalition#coalition Coalition. function ZONE_GOAL_COALITION:GetPreviousCoalition() return self.PreviousCoalition end - --- Get the owning coalition name of the zone. -- @param #ZONE_GOAL_COALITION self -- @return #string Coalition name. function ZONE_GOAL_COALITION:GetCoalitionName() - return UTILS.GetCoalitionName(self.Coalition) + return UTILS.GetCoalitionName( self.Coalition ) end - --- Check status Coalition ownership. -- @param #ZONE_GOAL_COALITION self -- @return #ZONE_GOAL_COALITION function ZONE_GOAL_COALITION:StatusZone() - + -- Get current state. local State = self:GetState() - + -- Debug text. - local text=string.format("Zone state=%s, Owner=%s, Scanning...", State, self:GetCoalitionName()) - self:F(text) - + local text = string.format( "Zone state=%s, Owner=%s, Scanning...", State, self:GetCoalitionName() ) + self:F( text ) + -- Scan zone. self:Scan( self.ObjectCategories, self.UnitCategories ) - + return self end - + end diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 853295f5a..d2d0f6657 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -6058,7 +6058,7 @@ function AIRBOSS:_RefuelAI( flight ) -- Guide AI to divert field -- ------------------------------ - -- Closest Airfield of the coaliton. + -- Closest Airfield of the coalition. local divertfield = self:GetCoordinate():GetClosestAirbase( Airbase.Category.AIRDROME, self:GetCoalition() ) -- Handle case where there is no divert field of the own coalition and try neutral instead. diff --git a/Moose Development/Moose/Tasking/Mission.lua b/Moose Development/Moose/Tasking/Mission.lua index f057c9648..e8da4f102 100644 --- a/Moose Development/Moose/Tasking/Mission.lua +++ b/Moose Development/Moose/Tasking/Mission.lua @@ -132,7 +132,7 @@ MISSION = { -- @param #string MissionName Name of the mission. This name will be used to reference the status of each mission by the players. -- @param #string MissionPriority String indicating the "priority" of the Mission. e.g. "Primary", "Secondary". It is free format and up to the Mission designer to choose. There are no rules behind this field. -- @param #string MissionBriefing String indicating the mission briefing to be shown when a player joins a @{CLIENT}. --- @param DCS#coaliton.side MissionCoalition Side of the coalition, i.e. and enumerator @{#DCS.coalition.side} corresponding to RED, BLUE or NEUTRAL. +-- @param DCS#coalition.side MissionCoalition Side of the coalition, i.e. and enumerator @{#DCS.coalition.side} corresponding to RED, BLUE or NEUTRAL. -- @return #MISSION self function MISSION:New( CommandCenter, MissionName, MissionPriority, MissionBriefing, MissionCoalition ) diff --git a/Moose Development/Moose/Tasking/Task.lua b/Moose Development/Moose/Tasking/Task.lua index 7cd79f453..f553cfc71 100644 --- a/Moose Development/Moose/Tasking/Task.lua +++ b/Moose Development/Moose/Tasking/Task.lua @@ -82,7 +82,7 @@ -- -- ![Mission](../Tasking/Report_Statistics_Progress.JPG) -- --- A statistic report on the progress of the mission. Each task achievement will increase the %-tage to 100% as a goal to complete the task. +-- A statistic report on the progress of the mission. Each task achievement will increase the % to 100% as a goal to complete the task. -- -- ## 1.3) Join a Task. -- diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 671fbb347..6ef1b9f68 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1353,7 +1353,7 @@ function UTILS.CheckMemory( output ) return mem end ---- Get the coalition name from its numerical ID, e.g. coaliton.side.RED. +--- Get the coalition name from its numerical ID, e.g. coalition.side.RED. -- @param #number Coalition The coalition ID. -- @return #string The coalition name, i.e. "Neutral", "Red" or "Blue" (or "Unknown"). function UTILS.GetCoalitionName( Coalition ) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 97a0e22d3..963558549 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -1,4 +1,4 @@ - --- **Wrapper** -- CONTROLLABLE is an intermediate class wrapping Group and Unit classes "controllers". +--- **Wrapper** -- CONTROLLABLE is an intermediate class wrapping Group and Unit classes "controllers". -- -- === -- @@ -11,14 +11,11 @@ -- @module Wrapper.Controllable -- @image Wrapper_Controllable.JPG - --- @type CONTROLLABLE -- @field DCS#Controllable DCSControllable The DCS controllable class. -- @field #string ControllableName The name of the controllable. -- @extends Wrapper.Positionable#POSITIONABLE - - --- Wrapper class to handle the "DCS Controllable objects", which are Groups and Units: -- -- * Support all DCS Controllable APIs. @@ -62,10 +59,10 @@ -- * @{#CONTROLLABLE.TaskHoldPosition}: (AIR) Hold position at the current position of the first unit of the controllable. -- * @{#CONTROLLABLE.TaskLand}: (AIR HELICOPTER) Landing at the ground. For helicopters only. -- * @{#CONTROLLABLE.TaskLandAtZone}: (AIR) Land the controllable at a @{Core.Zone#ZONE_RADIUS). --- * @{#CONTROLLABLE.TaskOrbitCircle}: (AIR) Orbit at the current position of the first unit of the controllable at a specified alititude. --- * @{#CONTROLLABLE.TaskOrbitCircleAtVec2}: (AIR) Orbit at a specified position at a specified alititude during a specified duration with a specified speed. +-- * @{#CONTROLLABLE.TaskOrbitCircle}: (AIR) Orbit at the current position of the first unit of the controllable at a specified altitude. +-- * @{#CONTROLLABLE.TaskOrbitCircleAtVec2}: (AIR) Orbit at a specified position at a specified altitude during a specified duration with a specified speed. -- * @{#CONTROLLABLE.TaskRefueling}: (AIR) Refueling from the nearest tanker. No parameters. --- * @{#CONTROLLABLE.TaskRoute}: (AIR + GROUND) Return a Misson task to follow a given route defined by Points. +-- * @{#CONTROLLABLE.TaskRoute}: (AIR + GROUND) Return a Mission task to follow a given route defined by Points. -- * @{#CONTROLLABLE.TaskRouteToVec2}: (AIR + GROUND) Make the Controllable move to a given point. -- * @{#CONTROLLABLE.TaskRouteToVec3}: (AIR + GROUND) Make the Controllable move to a given point. -- * @{#CONTROLLABLE.TaskRouteToZone}: (AIR + GROUND) Route the controllable to a given zone. @@ -86,7 +83,7 @@ -- -- ## 2.3) Task preparation -- --- There are certain task methods that allow to tailor the task behaviour: +-- There are certain task methods that allow to tailor the task behavior: -- -- * @{#CONTROLLABLE.TaskWrappedAction}: Return a WrappedAction Task taking a Command. -- * @{#CONTROLLABLE.TaskCombo}: Return a Combo Task taking an array of Tasks. @@ -133,7 +130,7 @@ -- -- # 5) Option methods -- --- Controllable **Option methods** change the behaviour of the Controllable while being alive. +-- Controllable **Option methods** change the behavior of the Controllable while being alive. -- -- ## 5.1) Rule of Engagement: -- @@ -190,7 +187,7 @@ CONTROLLABLE = { -- @return #CONTROLLABLE self function CONTROLLABLE:New( ControllableName ) local self = BASE:Inherit( self, POSITIONABLE:New( ControllableName ) ) -- #CONTROLLABLE - --self:F( ControllableName ) + -- self:F( ControllableName ) self.ControllableName = ControllableName self.TaskScheduler = SCHEDULER:New( self ) @@ -215,7 +212,6 @@ end -- Get methods - --- Returns the health. Dead controllables have health <= 1.0. -- @param #CONTROLLABLE self -- @return #number The controllable health value (unit or group average). @@ -274,7 +270,7 @@ function CONTROLLABLE:GetLife0() end --- Returns relative minimum amount of fuel (from 0.0 to 1.0) a unit or group has in its internal tanks. --- This method returns nil to ensure polymorphic behaviour! This method needs to be overridden by GROUP or UNIT. +-- This method returns nil to ensure polymorphic behavior! This method needs to be overridden by GROUP or UNIT. -- @param #CONTROLLABLE self -- @return #nil The CONTROLLABLE is not existing or alive. function CONTROLLABLE:GetFuelMin() @@ -284,7 +280,7 @@ function CONTROLLABLE:GetFuelMin() end --- Returns relative average amount of fuel (from 0.0 to 1.0) a unit or group has in its internal tanks. --- This method returns nil to ensure polymorphic behaviour! This method needs to be overridden by GROUP or UNIT. +-- This method returns nil to ensure polymorphic behavior! This method needs to be overridden by GROUP or UNIT. -- @param #CONTROLLABLE self -- @return #nil The CONTROLLABLE is not existing or alive. function CONTROLLABLE:GetFuelAve() @@ -294,7 +290,7 @@ function CONTROLLABLE:GetFuelAve() end --- Returns relative amount of fuel (from 0.0 to 1.0) the unit has in its internal tanks. --- This method returns nil to ensure polymorphic behaviour! This method needs to be overridden by GROUP or UNIT. +-- This method returns nil to ensure polymorphic behavior! This method needs to be overridden by GROUP or UNIT. -- @param #CONTROLLABLE self -- @return #nil The CONTROLLABLE is not existing or alive. function CONTROLLABLE:GetFuel() @@ -302,7 +298,6 @@ function CONTROLLABLE:GetFuel() return nil end - -- Tasks --- Clear all tasks from the controllable. @@ -321,7 +316,6 @@ function CONTROLLABLE:ClearTasks() return nil end - --- Popping current Task from the controllable. -- @param #CONTROLLABLE self -- @return Wrapper.Controllable#CONTROLLABLE self @@ -399,7 +393,7 @@ function CONTROLLABLE:SetTask( DCSTask, WaitTime ) local function SetTask( Controller, DCSTask ) if self and self:IsAlive() then local Controller = self:_GetController() - --self:I( "Before SetTask" ) + -- self:I( "Before SetTask" ) Controller:setTask( DCSTask ) -- AI_FORMATION class (used by RESCUEHELO) calls SetTask twice per second! hence spamming the DCS log file ==> setting this to trace. self:T( { ControllableName = self:GetName(), DCSTask = DCSTask } ) @@ -425,7 +419,7 @@ end --- Checking the Task Queue of the controllable. Returns false if no task is on the queue. true if there is a task. -- @param #CONTROLLABLE self -- @return Wrapper.Controllable#CONTROLLABLE self -function CONTROLLABLE:HasTask() --R2.2 +function CONTROLLABLE:HasTask() -- R2.2 local HasTaskResult = false @@ -440,7 +434,6 @@ function CONTROLLABLE:HasTask() --R2.2 return HasTaskResult end - --- Return a condition section for a controlled task. -- @param #CONTROLLABLE self -- @param DCS#Time time DCS mission time. @@ -452,7 +445,7 @@ end -- return DCS#Task function CONTROLLABLE:TaskCondition( time, userFlag, userFlagValue, condition, duration, lastWayPoint ) ---[[ + --[[ StopCondition = { time = Time, userFlag = string, @@ -485,8 +478,8 @@ function CONTROLLABLE:TaskControlled( DCSTask, DCSStopCondition ) id = 'ControlledTask', params = { task = DCSTask, - stopCondition = DCSStopCondition - } + stopCondition = DCSStopCondition, + }, } return DCSTaskControlled @@ -501,8 +494,8 @@ function CONTROLLABLE:TaskCombo( DCSTasks ) local DCSTaskCombo = { id = 'ComboTask', params = { - tasks = DCSTasks - } + tasks = DCSTasks, + }, } return DCSTaskCombo @@ -540,9 +533,6 @@ function CONTROLLABLE:SetTaskWaypoint( Waypoint, Task ) return Waypoint.task end - - - --- Executes a command action for the CONTROLLABLE. -- @param #CONTROLLABLE self -- @param DCS#Command DCSCommand The command to be executed. @@ -567,17 +557,18 @@ end -- @param #number ToWayPoint -- @return DCS#Task -- @usage --- --- This test demonstrates the use(s) of the SwitchWayPoint method of the GROUP class. --- HeliGroup = GROUP:FindByName( "Helicopter" ) -- --- --- Route the helicopter back to the FARP after 60 seconds. --- -- We use the SCHEDULER class to do this. --- SCHEDULER:New( nil, --- function( HeliGroup ) --- local CommandRTB = HeliGroup:CommandSwitchWayPoint( 2, 8 ) --- HeliGroup:SetCommand( CommandRTB ) --- end, { HeliGroup }, 90 --- ) +-- -- This test demonstrates the use(s) of the SwitchWayPoint method of the GROUP class. +-- HeliGroup = GROUP:FindByName( "Helicopter" ) +-- +-- -- Route the helicopter back to the FARP after 60 seconds. +-- -- We use the SCHEDULER class to do this. +-- SCHEDULER:New( nil, +-- function( HeliGroup ) +-- local CommandRTB = HeliGroup:CommandSwitchWayPoint( 2, 8 ) +-- HeliGroup:SetCommand( CommandRTB ) +-- end, { HeliGroup }, 90 +-- ) function CONTROLLABLE:CommandSwitchWayPoint( FromWayPoint, ToWayPoint ) self:F2( { FromWayPoint, ToWayPoint } ) @@ -619,16 +610,15 @@ function CONTROLLABLE:CommandStopRoute( StopRoute ) return CommandStopRoute end - --- Give an uncontrolled air controllable the start command. -- @param #CONTROLLABLE self -- @param #number delay (Optional) Delay before start command in seconds. -- @return #CONTROLLABLE self -function CONTROLLABLE:StartUncontrolled(delay) - if delay and delay>0 then - SCHEDULER:New(nil, CONTROLLABLE.StartUncontrolled, {self}, delay) +function CONTROLLABLE:StartUncontrolled( delay ) + if delay and delay > 0 then + SCHEDULER:New( nil, CONTROLLABLE.StartUncontrolled, { self }, delay ) else - self:SetCommand({id='Start', params={}}) + self:SetCommand( { id = 'Start', params = {} } ) end return self end @@ -640,7 +630,7 @@ end -- @param Core.Radio#BEACON.Type Type Beacon type (VOR, DME, TACAN, RSBN, ILS etc). -- @param Core.Radio#BEACON.System System Beacon system (VOR, DME, TACAN, RSBN, ILS etc). -- @param #number Frequency Frequency in Hz the beacon is running on. Use @{#UTILS.TACANToFrequency} to generate a frequency for TACAN beacons. --- @param #number UnitID The ID of the unit the beacon is attached to. Usefull if more units are in one group. +-- @param #number UnitID The ID of the unit the beacon is attached to. Useful if more units are in one group. -- @param #number Channel Channel the beacon is using. For, e.g. TACAN beacons. -- @param #string ModeChannel The TACAN mode of the beacon, i.e. "X" or "Y". -- @param #boolean AA If true, create and Air-Air beacon. IF nil, automatically set if CONTROLLABLE depending on whether unit is and aircraft or not. @@ -648,13 +638,13 @@ end -- @param #boolean Bearing If true, beacon provides bearing information - if supported by the unit the beacon is attached to. -- @param #number Delay (Optional) Delay in seconds before the beacon is activated. -- @return #CONTROLLABLE self -function CONTROLLABLE:CommandActivateBeacon(Type, System, Frequency, UnitID, Channel, ModeChannel, AA, Callsign, Bearing, Delay) +function CONTROLLABLE:CommandActivateBeacon( Type, System, Frequency, UnitID, Channel, ModeChannel, AA, Callsign, Bearing, Delay ) - AA=AA or self:IsAir() - UnitID=UnitID or self:GetID() + AA = AA or self:IsAir() + UnitID = UnitID or self:GetID() -- Command - local CommandActivateBeacon= { + local CommandActivateBeacon = { id = "ActivateBeacon", params = { ["type"] = Type, @@ -666,13 +656,13 @@ function CONTROLLABLE:CommandActivateBeacon(Type, System, Frequency, UnitID, Cha ["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) + 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) + self:SetCommand( CommandActivateBeacon ) end return self @@ -685,42 +675,41 @@ end -- @param #string Callsign Morse code identification callsign. -- @param #number Delay (Optional) Delay in seconds before the ICLS is deactivated. -- @return #CONTROLLABLE self -function CONTROLLABLE:CommandActivateICLS(Channel, UnitID, Callsign, Delay) +function CONTROLLABLE:CommandActivateICLS( Channel, UnitID, Callsign, Delay ) -- Command to activate ICLS system. - local CommandActivateICLS= { + local CommandActivateICLS = { id = "ActivateICLS", - params= { + params = { ["type"] = BEACON.Type.ICLS, ["channel"] = Channel, ["unitId"] = UnitID, ["callsign"] = Callsign, - } + }, } - if Delay and Delay>0 then - SCHEDULER:New(nil, self.CommandActivateICLS, {self}, Delay) + if Delay and Delay > 0 then + SCHEDULER:New( nil, self.CommandActivateICLS, { self }, Delay ) else - self:SetCommand(CommandActivateICLS) + self:SetCommand( CommandActivateICLS ) end return self end - --- Deactivate the active beacon of the CONTROLLABLE. -- @param #CONTROLLABLE self -- @param #number Delay (Optional) Delay in seconds before the beacon is deactivated. -- @return #CONTROLLABLE self -function CONTROLLABLE:CommandDeactivateBeacon(Delay) +function CONTROLLABLE:CommandDeactivateBeacon( Delay ) -- Command to deactivate - local CommandDeactivateBeacon={id='DeactivateBeacon', params={}} + local CommandDeactivateBeacon = { id = 'DeactivateBeacon', params = {} } - if Delay and Delay>0 then - SCHEDULER:New(nil, self.CommandActivateBeacon, {self}, Delay) + if Delay and Delay > 0 then + SCHEDULER:New( nil, self.CommandActivateBeacon, { self }, Delay ) else - self:SetCommand(CommandDeactivateBeacon) + self:SetCommand( CommandDeactivateBeacon ) end return self @@ -730,15 +719,15 @@ end -- @param #CONTROLLABLE self -- @param #number Delay (Optional) Delay in seconds before the ICLS is deactivated. -- @return #CONTROLLABLE self -function CONTROLLABLE:CommandDeactivateICLS(Delay) +function CONTROLLABLE:CommandDeactivateICLS( Delay ) -- Command to deactivate - local CommandDeactivateICLS={id='DeactivateICLS', params={}} + local CommandDeactivateICLS = { id = 'DeactivateICLS', params = {} } - if Delay and Delay>0 then - SCHEDULER:New(nil, self.CommandDeactivateICLS, {self}, Delay) + if Delay and Delay > 0 then + SCHEDULER:New( nil, self.CommandDeactivateICLS, { self }, Delay ) else - self:SetCommand(CommandDeactivateICLS) + self:SetCommand( CommandDeactivateICLS ) end return self @@ -750,15 +739,15 @@ end -- @param #number CallNumber The number value the group will be referred to as. Only valid numbers are 1-9. For example Uzi **5**-1. Default 1. -- @param #number Delay (Optional) Delay in seconds before the callsign is set. Default is immediately. -- @return #CONTROLLABLE self -function CONTROLLABLE:CommandSetCallsign(CallName, CallNumber, Delay) +function CONTROLLABLE:CommandSetCallsign( CallName, CallNumber, Delay ) -- Command to set the callsign. - local CommandSetCallsign={id='SetCallsign', params={callname=CallName, number=CallNumber or 1}} + 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) + if Delay and Delay > 0 then + SCHEDULER:New( nil, self.CommandSetCallsign, { self, CallName, CallNumber }, Delay ) else - self:SetCommand(CommandSetCallsign) + self:SetCommand( CommandSetCallsign ) end return self @@ -769,26 +758,26 @@ end -- @param #boolean SwitchOnOff If true (or nil) switch EPLRS on. If false switch off. -- @param #number Delay (Optional) Delay in seconds before the callsign is set. Default is immediately. -- @return #CONTROLLABLE self -function CONTROLLABLE:CommandEPLRS(SwitchOnOff, Delay) +function CONTROLLABLE:CommandEPLRS( SwitchOnOff, Delay ) - if SwitchOnOff==nil then - SwitchOnOff=true + if SwitchOnOff == nil then + SwitchOnOff = true end -- Command to set the callsign. - local CommandEPLRS={ - id='EPLRS', - params={ - value=SwitchOnOff, - groupId=self:GetID() - } + local CommandEPLRS = { + id = 'EPLRS', + params = { + value = SwitchOnOff, + groupId = self:GetID(), + }, } - if Delay and Delay>0 then - SCHEDULER:New(nil, self.CommandEPLRS, {self, SwitchOnOff}, Delay) + 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) + 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 @@ -798,52 +787,50 @@ end -- @param #CONTROLLABLE self -- @param #number Frequency Radio frequency in MHz. -- @param #number Modulation Radio modulation. Default `radio.modulation.AM`. --- @param #number Delay (Optional) Delay in seconds before the frequncy is set. Default is immediately. +-- @param #number Delay (Optional) Delay in seconds before the frequency is set. Default is immediately. -- @return #CONTROLLABLE self -function CONTROLLABLE:CommandSetFrequency(Frequency, Modulation, Delay) +function CONTROLLABLE:CommandSetFrequency( Frequency, Modulation, Delay ) local CommandSetFrequency = { id = 'SetFrequency', params = { - frequency = Frequency*1000000, + frequency = Frequency * 1000000, modulation = Modulation or radio.modulation.AM, - } + }, } - if Delay and Delay>0 then - SCHEDULER:New(nil, self.CommandSetFrequency, {self, Frequency, Modulation}, Delay) + if Delay and Delay > 0 then + SCHEDULER:New( nil, self.CommandSetFrequency, { self, Frequency, Modulation }, Delay ) else - self:SetCommand(CommandSetFrequency) + self:SetCommand( CommandSetFrequency ) end return self end - --- Set EPLRS data link on/off. -- @param #CONTROLLABLE self -- @param #boolean SwitchOnOff If true (or nil) switch EPLRS on. If false switch off. -- @param #number idx Task index. Default 1. -- @return #table Task wrapped action. -function CONTROLLABLE:TaskEPLRS(SwitchOnOff, idx) +function CONTROLLABLE:TaskEPLRS( SwitchOnOff, idx ) - if SwitchOnOff==nil then - SwitchOnOff=true + if SwitchOnOff == nil then + SwitchOnOff = true end -- Command to set the callsign. - local CommandEPLRS={ - id='EPLRS', - params={ - value=SwitchOnOff, - groupId=self:GetID() - } + local CommandEPLRS = { + id = 'EPLRS', + params = { + value = SwitchOnOff, + groupId = self:GetID(), + }, } - return self:TaskWrappedAction(CommandEPLRS, idx or 1) + return self:TaskWrappedAction( CommandEPLRS, idx or 1 ) end - -- TASKS FOR AIR CONTROLLABLES --- (AIR) Attack a Controllable. @@ -851,14 +838,14 @@ end -- @param Wrapper.Group#GROUP AttackGroup The Group to be attacked. -- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. -- @param DCS#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. +-- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aircraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aircraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. -- @param DCS#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. -- @param DCS#Distance Altitude (optional) Desired attack start altitude. Controllable/aircraft will make its attacks from the altitude. If the altitude is too low or too high to use weapon aircraft/controllable will choose closest altitude to the desired attack start altitude. If the desired altitude is defined controllable/aircraft will not attack from safe altitude. -- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackGroup" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. -- @param #boolean GroupAttack (Optional) If true, attack as group. -- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskAttackGroup( AttackGroup, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit, GroupAttack ) - --self:F2( { self.ControllableName, AttackGroup, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit } ) + -- self:F2( { self.ControllableName, AttackGroup, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit } ) -- AttackGroup = { -- id = 'AttackGroup', @@ -899,12 +886,12 @@ end -- @param Wrapper.Unit#UNIT AttackUnit The UNIT to be attacked -- @param #boolean GroupAttack (Optional) If true, all units in the group will attack the Unit when found. Default false. -- @param DCS#AI.Task.WeaponExpend WeaponExpend (Optional) Determines how many weapons will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (Optional) Limits maximal quantity of attack. The aicraft/controllable will not make more attacks than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. +-- @param #number AttackQty (Optional) Limits maximal quantity of attack. The aircraft/controllable will not make more attacks than allowed even if the target controllable not destroyed and the aircraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. -- @param DCS#Azimuth Direction (Optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. -- @param #number Altitude (Optional) The (minimum) altitude in meters from where to attack. Default is altitude of unit to attack but at least 1000 m. -- @param #number WeaponType (optional) The WeaponType. See [DCS Enumerator Weapon Type](https://wiki.hoggitworld.com/view/DCS_enum_weapon_flag) on Hoggit. -- @return DCS#Task The DCS task structure. -function CONTROLLABLE:TaskAttackUnit(AttackUnit, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, WeaponType) +function CONTROLLABLE:TaskAttackUnit( AttackUnit, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, WeaponType ) local DCSTask = { id = 'AttackUnit', @@ -919,19 +906,18 @@ function CONTROLLABLE:TaskAttackUnit(AttackUnit, GroupAttack, WeaponExpend, Atta attackQtyLimit = AttackQty and true or false, attackQty = AttackQty, weaponType = WeaponType or 1073741822, - } + }, } return DCSTask end - --- (AIR) Delivering weapon at the point on the ground. -- @param #CONTROLLABLE self -- @param DCS#Vec2 Vec2 2D-coordinates of the point to deliver weapon at. -- @param #boolean GroupAttack (optional) If true, all units in the group will attack the Unit when found. -- @param DCS#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. +-- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aircraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aircraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. -- @param DCS#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. -- @param #number Altitude (optional) The altitude from where to attack. -- @param #number WeaponType (optional) The WeaponType. @@ -966,7 +952,7 @@ end -- @param DCS#Vec2 Vec2 2D-coordinates of the point to deliver weapon at. -- @param #boolean GroupAttack (Optional) If true, all units in the group will attack the Unit when found. -- @param DCS#AI.Task.WeaponExpend WeaponExpend (Optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit will choose expend on its own discretion. --- @param #number AttackQty (Optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. +-- @param #number AttackQty (Optional) This parameter limits maximal quantity of attack. The aircraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aircraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. -- @param DCS#Azimuth Direction (Optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. -- @param #number Altitude (Optional) The altitude [meters] from where to attack. Default 30 m. -- @param #number WeaponType (Optional) The WeaponType. Default Auto=1073741822. @@ -994,19 +980,18 @@ function CONTROLLABLE:TaskAttackMapObject( Vec2, GroupAttack, WeaponExpend, Atta return DCSTask end - --- (AIR) Delivering weapon via CarpetBombing (all bombers in formation release at same time) at the point on the ground. -- @param #CONTROLLABLE self -- @param DCS#Vec2 Vec2 2D-coordinates of the point to deliver weapon at. -- @param #boolean GroupAttack (optional) If true, all units in the group will attack the Unit when found. -- @param DCS#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. +-- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aircraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aircraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. -- @param DCS#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. -- @param #number Altitude (optional) The altitude from where to attack. -- @param #number WeaponType (optional) The WeaponType. -- @param #number CarpetLength (optional) default to 500 m. -- @return DCS#Task The DCS task structure. -function CONTROLLABLE:TaskCarpetBombing(Vec2, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, WeaponType, CarpetLength) +function CONTROLLABLE:TaskCarpetBombing( Vec2, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, WeaponType, CarpetLength ) -- Build Task Structure local DCSTask = { @@ -1025,14 +1010,12 @@ function CONTROLLABLE:TaskCarpetBombing(Vec2, GroupAttack, WeaponExpend, AttackQ direction = Direction and math.rad(Direction) or 0, altitudeEnabled = Altitude and true or false, altitude = Altitude, - } + }, } return DCSTask end - - --- (AIR) Following another airborne controllable. -- The unit / controllable will follow lead unit of another controllable, wingmens of both controllables will continue following their leaders. -- Used to support CarpetBombing Task @@ -1041,7 +1024,7 @@ end -- @param DCS#Vec3 Vec3 Position of the unit / lead unit of the controllable relative lead unit of another controllable in frame reference oriented by course of lead unit of another controllable. If another controllable is on land the unit / controllable will orbit around. -- @param #number LastWaypointIndex Detach waypoint of another controllable. Once reached the unit / controllable Follow task is finished. -- @return DCS#Task The DCS task structure. -function CONTROLLABLE:TaskFollowBigFormation(FollowControllable, Vec3, LastWaypointIndex ) +function CONTROLLABLE:TaskFollowBigFormation( FollowControllable, Vec3, LastWaypointIndex ) local DCSTask = { id = 'FollowBigFormation', @@ -1049,14 +1032,13 @@ function CONTROLLABLE:TaskFollowBigFormation(FollowControllable, Vec3, LastWaypo groupId = FollowControllable:GetID(), pos = Vec3, lastWptIndexFlag = LastWaypointIndex and true or false, - lastWptIndex = LastWaypointIndex - } + lastWptIndex = LastWaypointIndex, + }, } return DCSTask end - --- (AIR HELICOPTER) Move the controllable to a Vec2 Point, wait for a defined duration and embark infantry groups. -- @param #CONTROLLABLE self -- @param Core.Point#COORDINATE Coordinate The point where to pickup the troops. @@ -1064,29 +1046,29 @@ end -- @param #number Duration (Optional) The maximum duration in seconds to wait until all groups have embarked. -- @param #table Distribution (Optional) Distribution used to put the infantry groups into specific carrier units. -- @return DCS#Task The DCS task structure. -function CONTROLLABLE:TaskEmbarking(Coordinate, GroupSetForEmbarking, Duration, Distribution) +function CONTROLLABLE:TaskEmbarking( Coordinate, GroupSetForEmbarking, Duration, Distribution ) -- Table of group IDs for embarking. - local g4e={} + local g4e = {} if GroupSetForEmbarking then - for _,_group in pairs(GroupSetForEmbarking:GetSet()) do - local group=_group --Wrapper.Group#GROUP - table.insert(g4e, group:GetID()) + for _, _group in pairs( GroupSetForEmbarking:GetSet() ) do + local group = _group -- Wrapper.Group#GROUP + table.insert( g4e, group:GetID() ) end else - self:E("ERROR: No groups for embarking specified!") + self:E( "ERROR: No groups for embarking specified!" ) return nil end -- Table of group IDs for embarking. - --local Distribution={} + -- local Distribution={} -- Distribution - --local distribution={} - --distribution[id]=gids + -- local distribution={} + -- distribution[id]=gids - local groupID=self and self:GetID() + local groupID = self and self:GetID() local DCSTask = { id = 'Embarking', @@ -1099,13 +1081,12 @@ function CONTROLLABLE:TaskEmbarking(Coordinate, GroupSetForEmbarking, Duration, duration = Duration, distributionFlag = Distribution and true or false, distribution = Distribution, - } + }, } return DCSTask end - --- Used in conjunction with the embarking task for a transport helicopter group. The Ground units will move to the specified location and wait to be picked up by a helicopter. -- The helicopter will then fly them to their dropoff point defined by another task for the ground forces; DisembarkFromTransport task. -- The controllable has to be an infantry group! @@ -1114,7 +1095,7 @@ end -- @param #number Radius Radius in meters. Default 200 m. -- @param #string UnitType The unit type name of the carrier, e.g. "UH-1H". Must not be specified. -- @return DCS#Task Embark to transport task. -function CONTROLLABLE:TaskEmbarkToTransport(Coordinate, Radius, UnitType) +function CONTROLLABLE:TaskEmbarkToTransport( Coordinate, Radius, UnitType ) local EmbarkToTransport = { id = "EmbarkToTransport", @@ -1123,46 +1104,44 @@ function CONTROLLABLE:TaskEmbarkToTransport(Coordinate, Radius, UnitType) y = Coordinate.z, zoneRadius = Radius or 200, selectedType = UnitType, - } + }, } return EmbarkToTransport end - --- Specifies the location infantry groups that is being transported by helicopters will be unloaded at. Used in conjunction with the EmbarkToTransport task. -- @param #CONTROLLABLE self -- @param Core.Point#COORDINATE Coordinate Coordinates where AI is expecting to be picked up. -- @return DCS#Task Embark to transport task. -function CONTROLLABLE:TaskDisembarking(Coordinate, GroupSetToDisembark) +function CONTROLLABLE:TaskDisembarking( Coordinate, GroupSetToDisembark ) -- Table of group IDs for disembarking. - local g4e={} + local g4e = {} if GroupSetToDisembark then - for _,_group in pairs(GroupSetToDisembark:GetSet()) do - local group=_group --Wrapper.Group#GROUP - table.insert(g4e, group:GetID()) + for _, _group in pairs( GroupSetToDisembark:GetSet() ) do + local group = _group -- Wrapper.Group#GROUP + table.insert( g4e, group:GetID() ) end else - self:E("ERROR: No groups for disembarking specified!") + self:E( "ERROR: No groups for disembarking specified!" ) return nil end - local Disembarking={ - id = "Disembarking", - params = { - x = Coordinate.x, - y = Coordinate.z, - groupsForEmbarking = g4e, -- This is no bug, the entry is really "groupsForEmbarking" even if we disembark the troops. - } - } + local Disembarking = { + id = "Disembarking", + params = { + x = Coordinate.x, + y = Coordinate.z, + groupsForEmbarking = g4e, -- This is no bug, the entry is really "groupsForEmbarking" even if we disembark the troops. + }, + } return Disembarking end - ---- (AIR) Orbit at a specified position at a specified alititude during a specified duration with a specified speed. +--- (AIR) Orbit at a specified position at a specified altitude during a specified duration with a specified speed. -- @param #CONTROLLABLE self -- @param DCS#Vec2 Point The point to hold the position. -- @param #number Altitude The altitude AGL in meters to hold the position. @@ -1177,8 +1156,8 @@ function CONTROLLABLE:TaskOrbitCircleAtVec2( Point, Altitude, Speed ) pattern = AI.Task.OrbitPattern.CIRCLE, point = Point, speed = Speed, - altitude = Altitude + land.getHeight( Point ) - } + altitude = Altitude + land.getHeight( Point ), + }, } return DCSTask @@ -1191,15 +1170,15 @@ end -- @param #number Speed Speed [m/s] flying the orbit pattern. Default 128 m/s = 250 knots. -- @param Core.Point#COORDINATE CoordRaceTrack (Optional) If this coordinate is specified, the CONTROLLABLE will fly a race-track pattern using this and the initial coordinate. -- @return #CONTROLLABLE self -function CONTROLLABLE:TaskOrbit(Coord, Altitude, Speed, CoordRaceTrack) +function CONTROLLABLE:TaskOrbit( Coord, Altitude, Speed, CoordRaceTrack ) - local Pattern=AI.Task.OrbitPattern.CIRCLE + local Pattern = AI.Task.OrbitPattern.CIRCLE - local P1=Coord:GetVec2() - local P2=nil + local P1 = Coord:GetVec2() + local P2 = nil if CoordRaceTrack then - Pattern=AI.Task.OrbitPattern.RACE_TRACK - P2=CoordRaceTrack:GetVec2() + Pattern = AI.Task.OrbitPattern.RACE_TRACK + P2 = CoordRaceTrack:GetVec2() end local Task = { @@ -1210,13 +1189,13 @@ function CONTROLLABLE:TaskOrbit(Coord, Altitude, Speed, CoordRaceTrack) point2 = P2, speed = Speed or UTILS.KnotsToMps(250), altitude = Altitude or Coord.y, - } + }, } return Task end ---- (AIR) Orbit at the current position of the first unit of the controllable at a specified alititude. +--- (AIR) Orbit at the current position of the first unit of the controllable at a specified altitude. -- @param #CONTROLLABLE self -- @param #number Altitude The altitude [m] to hold the position. -- @param #number Speed The speed [m/s] flying when holding the position. @@ -1235,8 +1214,6 @@ function CONTROLLABLE:TaskOrbitCircle( Altitude, Speed, Coordinate ) return nil end - - --- (AIR) Hold position at the current position of the first unit of the controllable. -- @param #CONTROLLABLE self -- @param #number Duration The maximum duration in seconds to hold the position. @@ -1247,7 +1224,6 @@ function CONTROLLABLE:TaskHoldPosition() return self:TaskOrbitCircle( 30, 10 ) end - --- (AIR) Delivering weapon on the runway. See [hoggit](https://wiki.hoggitworld.com/view/DCS_task_bombingRunway) -- -- Make sure the aircraft has the following role: @@ -1267,7 +1243,7 @@ end -- @param DCS#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. -- @param #boolean GroupAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a group and not to a single aircraft. -- @return DCS#Task The DCS task structure. -function CONTROLLABLE:TaskBombingRunway(Airbase, WeaponType, WeaponExpend, AttackQty, Direction, GroupAttack) +function CONTROLLABLE:TaskBombingRunway( Airbase, WeaponType, WeaponExpend, AttackQty, Direction, GroupAttack ) local DCSTask = { id = 'BombingRunway', @@ -1284,27 +1260,25 @@ function CONTROLLABLE:TaskBombingRunway(Airbase, WeaponType, WeaponExpend, Attac return DCSTask end - --- (AIR) Refueling from the nearest tanker. No parameters. -- @param #CONTROLLABLE self -- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskRefueling() - local DCSTask={ - id='Refueling', - params={} + local DCSTask = { + id = 'Refueling', + params = {}, } return DCSTask end - --- (AIR HELICOPTER) Landing at the ground. For helicopters only. -- @param #CONTROLLABLE self -- @param DCS#Vec2 Vec2 The point where to land. -- @param #number Duration The duration in seconds to stay on the ground. -- @return #CONTROLLABLE self -function CONTROLLABLE:TaskLandAtVec2(Vec2, Duration) +function CONTROLLABLE:TaskLandAtVec2( Vec2, Duration ) local DCSTask = { id = 'Land', @@ -1326,15 +1300,13 @@ end function CONTROLLABLE:TaskLandAtZone( Zone, Duration, RandomPoint ) -- Get landing point - local Point=RandomPoint and Zone:GetRandomVec2() or Zone:GetVec2() + local Point = RandomPoint and Zone:GetRandomVec2() or Zone:GetVec2() local DCSTask = CONTROLLABLE.TaskLandAtVec2( self, Point, Duration ) return DCSTask end - - --- (AIR) Following another airborne controllable. -- The unit / controllable will follow lead unit of another controllable, wingmens of both controllables will continue following their leaders. -- If another controllable is on land the unit / controllable will orbit around. @@ -1346,15 +1318,15 @@ end function CONTROLLABLE:TaskFollow( FollowControllable, Vec3, LastWaypointIndex ) self:F2( { self.ControllableName, FollowControllable, Vec3, LastWaypointIndex } ) --- Follow = { --- id = 'Follow', --- params = { --- groupId = Group.ID, --- pos = Vec3, --- lastWptIndexFlag = boolean, --- lastWptIndex = number --- } --- } + -- Follow = { + -- id = 'Follow', + -- params = { + -- groupId = Group.ID, + -- pos = Vec3, + -- lastWptIndexFlag = boolean, + -- lastWptIndex = number + -- } + -- } local LastWaypointIndexFlag = false local lastWptIndexFlagChangedManually = false @@ -1371,14 +1343,13 @@ function CONTROLLABLE:TaskFollow( FollowControllable, Vec3, LastWaypointIndex ) lastWptIndexFlag = LastWaypointIndexFlag, lastWptIndex = LastWaypointIndex, lastWptIndexFlagChangedManually = lastWptIndexFlagChangedManually, - } + }, } self:T3( { DCSTask } ) return DCSTask end - --- (AIR) Escort another airborne controllable. -- The unit / controllable will follow lead unit of another controllable, wingmens of both controllables will continue following their leaders. -- The unit / controllable will also protect that controllable from threats of specified types. @@ -1391,17 +1362,17 @@ end -- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskEscort( FollowControllable, Vec3, LastWaypointIndex, EngagementDistance, TargetTypes ) --- Escort = { --- id = 'Escort', --- params = { --- groupId = Group.ID, --- pos = Vec3, --- lastWptIndexFlag = boolean, --- lastWptIndex = number, --- engagementDistMax = Distance, --- targetTypes = array of AttributeName, --- } --- } + -- Escort = { + -- id = 'Escort', + -- params = { + -- groupId = Group.ID, + -- pos = Vec3, + -- lastWptIndexFlag = boolean, + -- lastWptIndex = number, + -- engagementDistMax = Distance, + -- targetTypes = array of AttributeName, + -- } + -- } local DCSTask DCSTask = { @@ -1419,7 +1390,6 @@ function CONTROLLABLE:TaskEscort( FollowControllable, Vec3, LastWaypointIndex, E return DCSTask end - -- GROUND TASKS --- (GROUND) Fire at a VEC2 point until ammunition is finished. @@ -1443,8 +1413,8 @@ function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType, Alti radius = Radius, expendQty = 100, -- dummy value expendQtyEnabled = false, - alt_type = ASL and 0 or 1 - } + alt_type = ASL and 0 or 1, + }, } if AmmoCount then @@ -1453,14 +1423,14 @@ function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType, Alti end if Altitude then - DCSTask.params.altitude=Altitude + DCSTask.params.altitude = Altitude end if WeaponType then - DCSTask.params.weaponType=WeaponType + DCSTask.params.weaponType = WeaponType end - --self:I(DCSTask) + -- self:I(DCSTask) return DCSTask end @@ -1469,11 +1439,10 @@ end -- @param #CONTROLLABLE self -- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskHold() - local DCSTask = {id = 'Hold', params = {}} + local DCSTask = { id = 'Hold', params = {} } return DCSTask end - -- TASKS FOR AIRBORNE AND GROUND UNITS/CONTROLLABLES --- (AIR + GROUND) The task makes the controllable/unit a FAC and orders the FAC to control the target (enemy ground controllable) destruction. @@ -1503,7 +1472,7 @@ function CONTROLLABLE:TaskFAC_AttackGroup( AttackGroup, WeaponType, Designation, modulation = Modulation or radio.modulation.AM, callname = CallsignName, number = CallsignNumber, - } + }, } return DCSTask @@ -1526,14 +1495,12 @@ function CONTROLLABLE:EnRouteTaskEngageTargets( Distance, TargetTypes, Priority maxDist = Distance, targetTypes = TargetTypes or {"Air"}, priority = Priority or 0, - } + }, } return DCSTask end - - --- (AIR) Engaging a targets of defined types at circle-shaped zone. -- @param #CONTROLLABLE self -- @param DCS#Vec2 Vec2 2D-coordinates of the zone. @@ -1550,20 +1517,19 @@ function CONTROLLABLE:EnRouteTaskEngageTargetsInZone( Vec2, Radius, TargetTypes, zoneRadius = Radius, targetTypes = TargetTypes or {"Air"}, priority = Priority or 0 - } + }, } return DCSTask end - --- (AIR) Engaging a controllable. The task does not assign the target controllable to the unit/controllable to attack now; it just allows the unit/controllable to engage the target controllable as well as other assigned targets. -- @param #CONTROLLABLE self -- @param Wrapper.Controllable#CONTROLLABLE AttackGroup The Controllable to be attacked. -- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. -- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. -- @param DCS#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. +-- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aircraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aircraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. -- @param DCS#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. -- @param DCS#Distance Altitude (optional) Desired attack start altitude. Controllable/aircraft will make its attacks from the altitude. If the altitude is too low or too high to use weapon aircraft/controllable will choose closest altitude to the desired attack start altitude. If the desired altitude is defined controllable/aircraft will not attack from safe altitude. -- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackGroup" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. @@ -1605,14 +1571,13 @@ function CONTROLLABLE:EnRouteTaskEngageGroup( AttackGroup, Priority, WeaponType, return DCSTask end - --- (AIR) Search and attack the Unit. -- @param #CONTROLLABLE self -- @param Wrapper.Unit#UNIT EngageUnit The UNIT. -- @param #number Priority (optional) All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. -- @param #boolean GroupAttack (optional) If true, all units in the group will attack the Unit when found. -- @param DCS#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. +-- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aircraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aircraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. -- @param DCS#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. -- @param DCS#Distance Altitude (optional) Desired altitude to perform the unit engagement. -- @param #boolean Visible (optional) Unit must be visible. @@ -1641,12 +1606,10 @@ function CONTROLLABLE:EnRouteTaskEngageUnit( EngageUnit, Priority, GroupAttack, return DCSTask end - - --- (AIR) Aircraft will act as an AWACS for friendly units (will provide them with information about contacts). No parameters. -- @param #CONTROLLABLE self -- @return DCS#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskAWACS( ) +function CONTROLLABLE:EnRouteTaskAWACS() local DCSTask = { id = 'AWACS', @@ -1656,11 +1619,10 @@ function CONTROLLABLE:EnRouteTaskAWACS( ) return DCSTask end - --- (AIR) Aircraft will act as a tanker for friendly units. No parameters. -- @param #CONTROLLABLE self -- @return DCS#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskTanker( ) +function CONTROLLABLE:EnRouteTaskTanker() local DCSTask = { id = 'Tanker', @@ -1670,13 +1632,12 @@ function CONTROLLABLE:EnRouteTaskTanker( ) return DCSTask end - -- En-route tasks for ground units/controllables --- (GROUND) Ground unit (EW-radar) will act as an EWR for friendly units (will provide them with information about contacts). No parameters. -- @param #CONTROLLABLE self -- @return DCS#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskEWR( ) +function CONTROLLABLE:EnRouteTaskEWR() local DCSTask = { id = 'EWR', @@ -1686,7 +1647,6 @@ function CONTROLLABLE:EnRouteTaskEWR( ) return DCSTask end - -- En-route tasks for airborne and ground units/controllables --- (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose the target (enemy ground controllable) as well as other assigned targets. @@ -1709,13 +1669,12 @@ function CONTROLLABLE:EnRouteTaskFAC_EngageGroup( AttackGroup, Priority, WeaponT designation = Designation, datalink = Datalink and Datalink or false, priority = Priority or 0, - } + }, } return DCSTask end - --- (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose a targets (enemy ground controllable) around as well as other assigned targets. -- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. -- If the task is assigned to the controllable lead unit will be a FAC. @@ -1725,13 +1684,13 @@ end -- @return DCS#Task The DCS task structure. function CONTROLLABLE:EnRouteTaskFAC( Radius, Priority ) --- FAC = { --- id = 'FAC', --- params = { --- radius = Distance, --- priority = number --- } --- } + -- FAC = { + -- id = 'FAC', + -- params = { + -- radius = Distance, + -- priority = number + -- } + -- } local DCSTask = { id = 'FAC', @@ -1744,7 +1703,6 @@ function CONTROLLABLE:EnRouteTaskFAC( Radius, Priority ) return DCSTask end - --- This creates a Task element, with an action to call a function as part of a Wrapped Task. -- This Task can then be embedded at a Waypoint by calling the method @{#CONTROLLABLE.SetTaskWaypoint}. -- @param #CONTROLLABLE self @@ -1797,24 +1755,22 @@ function CONTROLLABLE:TaskFunction( FunctionString, ... ) -- Script local DCSScript = {} - DCSScript[#DCSScript+1] = "local MissionControllable = GROUP:Find( ... ) " + DCSScript[#DCSScript + 1] = "local MissionControllable = GROUP:Find( ... ) " if arg and arg.n > 0 then - local ArgumentKey = '_' .. tostring( arg ):match("table: (.*)") + 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 ) )" + DCSScript[#DCSScript + 1] = "local Arguments = MissionControllable:GetState( MissionControllable, '" .. ArgumentKey .. "' ) " + DCSScript[#DCSScript + 1] = FunctionString .. "( MissionControllable, unpack( Arguments ) )" else - DCSScript[#DCSScript+1] = FunctionString .. "( MissionControllable )" + DCSScript[#DCSScript + 1] = FunctionString .. "( MissionControllable )" end -- DCS task. - local DCSTask = self:TaskWrappedAction(self:CommandDoScript(table.concat( DCSScript ))) + local DCSTask = self:TaskWrappedAction( self:CommandDoScript( table.concat( DCSScript ) ) ) return DCSTask end - - --- (AIR + GROUND) Return a mission task from a mission template. -- @param #CONTROLLABLE self -- @param #table TaskMission A table containing the mission task. @@ -1823,13 +1779,14 @@ function CONTROLLABLE:TaskMission( TaskMission ) local DCSTask = { id = 'Mission', - params = { TaskMission, }, + params = { + TaskMission, + }, } return DCSTask end - do -- Patrol methods --- (GROUND) Patrol iteratively using the waypoints the for the (parent) group. @@ -1851,32 +1808,31 @@ do -- Patrol methods -- Calculate the new Route. local FromCoord = PatrolGroup:GetCoordinate() - + -- test for submarine local depth = 0 local IsSub = false if PatrolGroup:IsShip() then local navalvec3 = FromCoord:GetVec3() - if navalvec3.y < 0 then + if navalvec3.y < 0 then depth = navalvec3.y IsSub = true end end - - + local Waypoint = Waypoints[1] local Speed = Waypoint.speed or (20 / 3.6) - local From = FromCoord:WaypointGround( Speed ) - - if IsSub then - From = FromCoord:WaypointNaval( Speed, Waypoint.alt ) + local From = FromCoord:WaypointGround( Speed ) + + if IsSub then + From = FromCoord:WaypointNaval( Speed, Waypoint.alt ) end - + table.insert( Waypoints, 1, From ) local TaskRoute = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolRoute" ) - self:F({Waypoints = Waypoints}) + self:F( { Waypoints = Waypoints } ) local Waypoint = Waypoints[#Waypoints] PatrolGroup:SetTaskWaypoint( Waypoint, TaskRoute ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone. @@ -1916,31 +1872,31 @@ do -- Patrol methods local IsSub = false if PatrolGroup:IsShip() then local navalvec3 = FromCoord:GetVec3() - if navalvec3.y < 0 then + if navalvec3.y < 0 then depth = navalvec3.y IsSub = true end end -- Loop until a waypoint has been found that is not the same as the current waypoint. - -- Otherwise the object zon't move or drive in circles and the algorithm would not do exactly + -- Otherwise the object won't move, or drive in circles, and the algorithm would not do exactly -- what it is supposed to do, which is making groups drive around. local ToWaypoint repeat -- Select a random waypoint and check if it is not the same waypoint as where the object is about. ToWaypoint = math.random( 1, #Waypoints ) - until( ToWaypoint ~= FromWaypoint ) + until (ToWaypoint ~= FromWaypoint) self:F( { FromWaypoint = FromWaypoint, ToWaypoint = ToWaypoint } ) - local Waypoint = Waypoints[ToWaypoint] -- Select random waypoint. + local Waypoint = Waypoints[ToWaypoint] -- Select random waypoint. local ToCoord = COORDINATE:NewFromVec2( { x = Waypoint.x, y = Waypoint.y } ) -- Create a "ground route point", which is a "point" structure that can be given as a parameter to a Task local Route = {} if IsSub then - Route[#Route+1] = FromCoord:WaypointNaval( Speed, depth ) - Route[#Route+1] = ToCoord:WaypointNaval( Speed, Waypoint.alt ) + Route[#Route + 1] = FromCoord:WaypointNaval( Speed, depth ) + Route[#Route + 1] = ToCoord:WaypointNaval( Speed, Waypoint.alt ) else - Route[#Route+1] = FromCoord:WaypointGround( Speed, Formation ) - Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation ) + Route[#Route + 1] = FromCoord:WaypointGround( Speed, Formation ) + Route[#Route + 1] = ToCoord:WaypointGround( Speed, Formation ) end local TaskRouteToZone = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolRouteRandom", Speed, Formation, ToWaypoint ) @@ -1972,43 +1928,43 @@ do -- Patrol methods PatrolGroup = self:GetGroup() -- Wrapper.Group#GROUP end - DelayMin=DelayMin or 1 - if not DelayMax or DelayMax LengthDirect*10) or (LengthRoad/LengthOnRoad*100<5)) + LongRoad = LengthOnRoad and ((LengthOnRoad > LengthDirect * 10) or (LengthRoad / LengthOnRoad * 100 < 5)) -- Debug info. - 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)) + 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 -- Route, ground waypoints along road. - local route={} - local canroad=false + local route = {} + local canroad = false -- Check if a valid path on road could be found. if GotPath and LengthRoad and LengthDirect > 2000 then -- if the length of the movement is less than 1 km, drive directly. @@ -2357,43 +2303,43 @@ do -- Route methods -- Road is long ==> we take the short cut. - table.insert(route, FromCoordinate:WaypointGround(Speed, OffRoadFormation)) - table.insert(route, ToCoordinate:WaypointGround(Speed, OffRoadFormation)) + table.insert( route, FromCoordinate:WaypointGround( Speed, OffRoadFormation ) ) + table.insert( route, ToCoordinate:WaypointGround( Speed, OffRoadFormation ) ) else -- Create waypoints. - 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")) + 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" ) ) -- Add the final coordinate because the final might not be on the 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)) + 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 + canroad = true else -- No path on road could be found (can happen!) ==> Route group directly from A to B. - table.insert(route, FromCoordinate:WaypointGround(Speed, OffRoadFormation)) - table.insert(route, ToCoordinate:WaypointGround(Speed, OffRoadFormation)) + table.insert( route, FromCoordinate:WaypointGround( Speed, OffRoadFormation ) ) + table.insert( route, ToCoordinate:WaypointGround( Speed, OffRoadFormation ) ) end -- Add passing waypoint function. if WaypointFunction then - local N=#route - for n,waypoint in pairs(route) do + 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 {}))} + waypoint.task.params.tasks = { self:TaskFunction( "CONTROLLABLE.___PassingWaypoint", n, N, WaypointFunction, unpack( WaypointFunctionArguments or {} ) ) } end end @@ -2407,40 +2353,40 @@ do -- Route methods -- @param #function WaypointFunction (Optional) Function called when passing a waypoint. First parameters of the function are the @{CONTROLLABLE} object, the number of the waypoint and the total number of waypoints. -- @param #table WaypointFunctionArguments (Optional) List of parameters passed to the *WaypointFunction*. -- @return Task - function CONTROLLABLE:TaskGroundOnRailRoads(ToCoordinate, Speed, WaypointFunction, WaypointFunctionArguments ) - self:F2({ToCoordinate=ToCoordinate, Speed=Speed}) + function CONTROLLABLE:TaskGroundOnRailRoads( ToCoordinate, Speed, WaypointFunction, WaypointFunctionArguments ) + self:F2( { ToCoordinate = ToCoordinate, Speed = Speed } ) -- Defaults. - Speed=Speed or 20 + Speed = Speed or 20 -- Current coordinate. local FromCoordinate = self:GetCoordinate() -- Get path and path length on railroad. - local PathOnRail, LengthOnRail=FromCoordinate:GetPathOnRoad(ToCoordinate, false, true) + local PathOnRail, LengthOnRail = FromCoordinate:GetPathOnRoad( ToCoordinate, false, true ) -- Debug info. - self:T(string.format("Length on railroad = %.3f km", LengthOnRail/1000)) + self:T( string.format( "Length on railroad = %.3f km", LengthOnRail / 1000 ) ) -- Route, ground waypoints along road. - local route={} + local route = {} -- Check if a valid path on railroad could be found. if PathOnRail then - table.insert(route, PathOnRail[1]:WaypointGround(Speed, "On Railroad")) - table.insert(route, PathOnRail[2]:WaypointGround(Speed, "On Railroad")) + table.insert( route, PathOnRail[1]:WaypointGround( Speed, "On Railroad" ) ) + table.insert( route, PathOnRail[2]:WaypointGround( Speed, "On Railroad" ) ) end -- Add passing waypoint function. if WaypointFunction then - local N=#route - for n,waypoint in pairs(route) do + 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 {}))} + waypoint.task.params.tasks = { self:TaskFunction( "CONTROLLABLE.___PassingWaypoint", n, N, WaypointFunction, unpack( WaypointFunctionArguments or {} ) ) } end end @@ -2452,11 +2398,10 @@ do -- Route methods -- @param #number n Current waypoint number passed. -- @param #number N Total number of waypoints. -- @param #function waypointfunction Function called when a waypoint is passed. - function CONTROLLABLE.___PassingWaypoint(controllable, n, N, waypointfunction, ...) - waypointfunction(controllable, n, N, ...) + function CONTROLLABLE.___PassingWaypoint( controllable, n, N, waypointfunction, ... ) + waypointfunction( controllable, n, N, ... ) end - --- Make the AIR Controllable fly towards a specific point. -- @param #CONTROLLABLE self -- @param Core.Point#COORDINATE ToCoordinate A Coordinate to drive to. @@ -2478,7 +2423,6 @@ do -- Route methods return self end - --- (AIR + GROUND) Route the controllable to a given zone. -- The controllable final destination point can be randomized. -- A speed can be given in km/h. @@ -2504,7 +2448,6 @@ do -- Route methods PointFrom.action = Formation or "Cone" PointFrom.speed = 20 / 3.6 - local PointTo = {} local ZonePoint @@ -2564,7 +2507,6 @@ do -- Route methods PointFrom.action = Formation or "Cone" PointFrom.speed = 20 / 3.6 - local PointTo = {} PointTo.x = Vec2.x @@ -2616,7 +2558,6 @@ function CONTROLLABLE:CommandDoScript( DoScript ) return DCSDoScript end - --- Return the mission template of the controllable. -- @param #CONTROLLABLE self -- @return #table The MissionTemplate @@ -2636,8 +2577,6 @@ function CONTROLLABLE:GetTaskRoute() return routines.utils.deepCopy( _DATABASE.Templates.Controllables[self.ControllableName].Template.route.points ) end - - --- Return the route of a controllable by using the @{Core.Database#DATABASE} class. -- @param #CONTROLLABLE self -- @param #number Begin The route point from where the copy will start. The base route point is 0. @@ -2671,7 +2610,7 @@ function CONTROLLABLE:CopyRoute( Begin, End, Randomize, Radius ) 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] ) + Points[#Points + 1] = routines.utils.deepCopy( Template.route.points[TPointID] ) if Randomize then if not Radius then Radius = 500 @@ -2689,7 +2628,6 @@ function CONTROLLABLE:CopyRoute( Begin, End, Randomize, Radius ) return nil end - --- Return the detected targets of the controllable. -- The optional parametes specify the detection methods that can be applied. -- If no detection method is given, the detection will use all the available methods by default. @@ -2708,35 +2646,33 @@ function CONTROLLABLE:GetDetectedTargets( DetectVisual, DetectOptical, DetectRad 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 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 + Params[#Params + 1] = DetectionVisual end if DetectionOptical then - Params[#Params+1] = DetectionOptical + Params[#Params + 1] = DetectionOptical end if DetectionRadar then - Params[#Params+1] = DetectionRadar + Params[#Params + 1] = DetectionRadar end if DetectionIRST then - Params[#Params+1] = DetectionIRST + Params[#Params + 1] = DetectionIRST end if DetectionRWR then - Params[#Params+1] = DetectionRWR + Params[#Params + 1] = DetectionRWR end if DetectionDLINK then - Params[#Params+1] = DetectionDLINK + 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] ) @@ -2772,12 +2708,12 @@ function CONTROLLABLE:IsTargetDetected( DCSObject, DetectVisual, DetectOptical, 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 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() @@ -2813,7 +2749,7 @@ function CONTROLLABLE:IsUnitDetected( Unit, DetectVisual, DetectOptical, DetectR self:F2( self.ControllableName ) if Unit and Unit:IsAlive() then - return self:IsTargetDetected(Unit:GetDCSObject(), DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK) + return self:IsTargetDetected( Unit:GetDCSObject(), DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK ) end return nil @@ -2836,11 +2772,11 @@ function CONTROLLABLE:IsGroupDetected( Group, DetectVisual, DetectOptical, Detec self:F2( self.ControllableName ) if Group and Group:IsAlive() then - for _,_unit in pairs(Group:GetUnits()) do - local unit=_unit --Wrapper.Unit#UNIT + for _, _unit in pairs( Group:GetUnits() ) do + local unit = _unit -- Wrapper.Unit#UNIT if unit and unit:IsAlive() then - local isdetected=self:IsUnitDetected(unit, DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK) + local isdetected = self:IsUnitDetected( unit, DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK ) if isdetected then return true @@ -2853,7 +2789,6 @@ function CONTROLLABLE:IsGroupDetected( Group, DetectVisual, DetectOptical, Detec return nil end - --- Return the detected targets of the controllable. -- The optional parametes specify the detection methods that can be applied. -- If **no** detection method is given, the detection will use **all** the available methods by default. @@ -2866,23 +2801,23 @@ end -- @param #boolean DetectRWR (Optional) If *false*, do not include targets detected by RWR. -- @param #boolean DetectDLINK (Optional) If *false*, do not include targets detected by data link. -- @return Core.Set#SET_UNIT Set of detected units. -function CONTROLLABLE:GetDetectedUnitSet(DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK) +function CONTROLLABLE:GetDetectedUnitSet( DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK ) -- Get detected DCS units. - local detectedtargets=self:GetDetectedTargets(DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK) + local detectedtargets = self:GetDetectedTargets( DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK ) - local unitset=SET_UNIT:New() + local unitset = SET_UNIT:New() - for DetectionObjectID, Detection in pairs(detectedtargets or {}) do - local DetectedObject=Detection.object -- DCS#Object + for DetectionObjectID, Detection in pairs( detectedtargets or {} ) do + local DetectedObject = Detection.object -- DCS#Object - if DetectedObject and DetectedObject:isExist() and DetectedObject.id_<50000000 then - local unit=UNIT:Find(DetectedObject) + 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) + if not unitset:FindUnit( unit:GetName() ) then + unitset:AddUnit( unit ) end end @@ -2903,24 +2838,24 @@ end -- @param #boolean DetectRWR (Optional) If *false*, do not include targets detected by RWR. -- @param #boolean DetectDLINK (Optional) If *false*, do not include targets detected by data link. -- @return Core.Set#SET_GROUP Set of detected groups. -function CONTROLLABLE:GetDetectedGroupSet(DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK) +function CONTROLLABLE:GetDetectedGroupSet( DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK ) -- Get detected DCS units. - local detectedtargets=self:GetDetectedTargets(DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK) + local detectedtargets = self:GetDetectedTargets( DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK ) - local groupset=SET_GROUP:New() + local groupset = SET_GROUP:New() - for DetectionObjectID, Detection in pairs(detectedtargets or {}) do - local DetectedObject=Detection.object -- DCS#Object + for DetectionObjectID, Detection in pairs( detectedtargets or {} ) do + local DetectedObject = Detection.object -- DCS#Object - if DetectedObject and DetectedObject:isExist() and DetectedObject.id_<50000000 then - local unit=UNIT:Find(DetectedObject) + 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() + local group = unit:GetGroup() - if group and not groupset:FindGroup(group:GetName()) then - groupset:AddGroup(group) + if group and not groupset:FindGroup( group:GetName() ) then + groupset:AddGroup( group ) end end @@ -2930,7 +2865,6 @@ function CONTROLLABLE:GetDetectedGroupSet(DetectVisual, DetectOptical, DetectRad return groupset end - -- Options --- Set option. @@ -2938,7 +2872,7 @@ end -- @param #number OptionID ID/Type of the option. -- @param #number OptionValue Value of the option -- @return #CONTROLLABLE self -function CONTROLLABLE:SetOption(OptionID, OptionValue) +function CONTROLLABLE:SetOption( OptionID, OptionValue ) local DCSControllable = self:GetDCSObject() if DCSControllable then @@ -2956,7 +2890,7 @@ end -- @param Wrapper.Controllable#CONTROLLABLE self -- @param #number ROEvalue ROE value. See ENUMS.ROE. -- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROE(ROEvalue) +function CONTROLLABLE:OptionROE( ROEvalue ) local DCSControllable = self:GetDCSObject() @@ -2965,11 +2899,11 @@ function CONTROLLABLE:OptionROE(ROEvalue) local Controller = self:_GetController() if self:IsAir() then - Controller:setOption(AI.Option.Air.id.ROE, ROEvalue ) + Controller:setOption( AI.Option.Air.id.ROE, ROEvalue ) elseif self:IsGround() then - Controller:setOption(AI.Option.Ground.id.ROE, ROEvalue ) + Controller:setOption( AI.Option.Ground.id.ROE, ROEvalue ) elseif self:IsShip() then - Controller:setOption(AI.Option.Naval.id.ROE, ROEvalue ) + Controller:setOption( AI.Option.Naval.id.ROE, ROEvalue ) end return self @@ -3199,7 +3133,6 @@ function CONTROLLABLE:OptionROTNoReactionPossible() return nil end - --- No evasion on enemy threats. -- @param #CONTROLLABLE self -- @return #CONTROLLABLE self @@ -3224,7 +3157,7 @@ end -- @param #CONTROLLABLE self -- @param #number ROTvalue ROT value. See ENUMS.ROT. -- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROT(ROTvalue) +function CONTROLLABLE:OptionROT( ROTvalue ) self:F2( { self.ControllableName } ) local DCSControllable = self:GetDCSObject() @@ -3297,7 +3230,6 @@ function CONTROLLABLE:OptionROTEvadeFirePossible() return nil end - --- Evade on fire. -- @param #CONTROLLABLE self -- @return #CONTROLLABLE self @@ -3336,7 +3268,6 @@ function CONTROLLABLE:OptionROTVerticalPossible() return nil end - --- Evade on fire using vertical manoeuvres. -- @param #CONTROLLABLE self -- @return #CONTROLLABLE self @@ -3368,10 +3299,10 @@ function CONTROLLABLE:OptionAlarmStateAuto() local Controller = self:_GetController() if self:IsGround() then - Controller:setOption(AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.AUTO) + Controller:setOption( AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.AUTO ) elseif self:IsShip() then - --Controller:setOption(AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.AUTO) - Controller:setOption(9, 0) + -- Controller:setOption(AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.AUTO) + Controller:setOption( 9, 0 ) end return self @@ -3394,8 +3325,8 @@ function CONTROLLABLE:OptionAlarmStateGreen() Controller:setOption( AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.GREEN ) elseif self:IsShip() then -- AI.Option.Naval.id.ALARM_STATE does not seem to exist! - --Controller:setOption( AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.GREEN ) - Controller:setOption(9, 1) + -- Controller:setOption( AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.GREEN ) + Controller:setOption( 9, 1 ) end return self @@ -3415,10 +3346,10 @@ function CONTROLLABLE:OptionAlarmStateRed() local Controller = self:_GetController() if self:IsGround() then - Controller:setOption(AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.RED) + Controller:setOption( AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.RED ) elseif self:IsShip() then - --Controller:setOption(AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.RED) - Controller:setOption(9, 2) + -- Controller:setOption(AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.RED) + Controller:setOption( 9, 2 ) end return self @@ -3427,18 +3358,17 @@ function CONTROLLABLE:OptionAlarmStateRed() return nil end - --- Set RTB on bingo fuel. -- @param #CONTROLLABLE self -- @param #boolean RTB true if RTB on bingo fuel (default), false if no RTB on bingo fuel. -- Warning! When you switch this option off, the airborne group will continue to fly until all fuel has been consumed, and will crash. -- @return #CONTROLLABLE self -function CONTROLLABLE:OptionRTBBingoFuel( RTB ) --R2.2 +function CONTROLLABLE:OptionRTBBingoFuel( RTB ) -- R2.2 self:F2( { self.ControllableName } ) - --RTB = RTB or true - if RTB==nil then - RTB=true + -- RTB = RTB or true + if RTB == nil then + RTB = true end local DCSControllable = self:GetDCSObject() @@ -3455,7 +3385,6 @@ function CONTROLLABLE:OptionRTBBingoFuel( RTB ) --R2.2 return nil end - --- Set RTB on ammo. -- @param #CONTROLLABLE self -- @param #boolean WeaponsFlag Weapons.flag enumerator. @@ -3477,7 +3406,6 @@ function CONTROLLABLE:OptionRTBAmmo( WeaponsFlag ) return nil end - --- Allow to Jettison of weapons upon threat. -- @param #CONTROLLABLE self -- @return #CONTROLLABLE self @@ -3498,7 +3426,6 @@ function CONTROLLABLE:OptionAllowJettisonWeaponsOnThreat() return nil end - --- Keep weapons upon threat. -- @param #CONTROLLABLE self -- @return #CONTROLLABLE self @@ -3523,15 +3450,15 @@ end -- @param #CONTROLLABLE self -- @param #boolean Prohibit If true or nil, prohibit. If false, do not prohibit. -- @return #CONTROLLABLE self -function CONTROLLABLE:OptionProhibitAfterburner(Prohibit) +function CONTROLLABLE:OptionProhibitAfterburner( Prohibit ) self:F2( { self.ControllableName } ) - if Prohibit==nil then - Prohibit=true + if Prohibit == nil then + Prohibit = true end if self:IsAir() then - self:SetOption(AI.Option.Air.id.PROHIBIT_AB, Prohibit) + self:SetOption( AI.Option.Air.id.PROHIBIT_AB, Prohibit ) end return self @@ -3544,7 +3471,7 @@ function CONTROLLABLE:OptionECM_Never() self:F2( { self.ControllableName } ) if self:IsAir() then - self:SetOption(AI.Option.Air.id.ECM_USING, 0) + self:SetOption( AI.Option.Air.id.ECM_USING, 0 ) end return self @@ -3557,13 +3484,12 @@ function CONTROLLABLE:OptionECM_OnlyLockByRadar() self:F2( { self.ControllableName } ) if self:IsAir() then - self:SetOption(AI.Option.Air.id.ECM_USING, 1) + self:SetOption( AI.Option.Air.id.ECM_USING, 1 ) end return self end - --- Defines the usage of Electronic Counter Measures by airborne forces. If the AI is being detected by a radar they will enable their ECM. -- @param #CONTROLLABLE self -- @return #CONTROLLABLE self @@ -3571,7 +3497,7 @@ function CONTROLLABLE:OptionECM_DetectedLockByRadar() self:F2( { self.ControllableName } ) if self:IsAir() then - self:SetOption(AI.Option.Air.id.ECM_USING, 2) + self:SetOption( AI.Option.Air.id.ECM_USING, 2 ) end return self @@ -3584,7 +3510,7 @@ function CONTROLLABLE:OptionECM_AlwaysOn() self:F2( { self.ControllableName } ) if self:IsAir() then - self:SetOption(AI.Option.Air.id.ECM_USING, 3) + self:SetOption( AI.Option.Air.id.ECM_USING, 3 ) end return self @@ -3613,7 +3539,7 @@ end -- @param #CONTROLLABLE self -- @return #table WayPoints If WayPoints is given, then return the WayPoints structure. function CONTROLLABLE:GetWayPoints() - self:F( ) + self:F() if self.WayPoints then return self.WayPoints @@ -3636,7 +3562,6 @@ function CONTROLLABLE:WayPointFunction( WayPoint, WayPointIndex, WayPointFunctio return self end - --- Executes the WayPoint plan. -- The function gets a WayPoint parameter, that you can use to restart the mission at a specific WayPoint. -- Note that when the WayPoint parameter is used, the new start mission waypoint of the controllable will be 1! @@ -3698,8 +3623,8 @@ end --- Sets Controllable Option for Restriction of Afterburner. -- @param #CONTROLLABLE self -- @param #boolean RestrictBurner If true, restrict burner. If false or nil, allow (unrestrict) burner. -function CONTROLLABLE:OptionRestrictBurner(RestrictBurner) - self:F2({self.ControllableName}) +function CONTROLLABLE:OptionRestrictBurner( RestrictBurner ) + self:F2( { self.ControllableName } ) local DCSControllable = self:GetDCSObject() @@ -3711,11 +3636,11 @@ function CONTROLLABLE:OptionRestrictBurner(RestrictBurner) -- Issue https://github.com/FlightControl-Master/MOOSE/issues/1216 if RestrictBurner == true then if self:IsAir() then - Controller:setOption(16, true) + Controller:setOption( 16, true ) end else if self:IsAir() then - Controller:setOption(16, false) + Controller:setOption( 16, false ) end end @@ -3729,20 +3654,20 @@ end -- @param #number range Defines the range -- @return #CONTROLLABLE self -- @usage Range can be one of MAX_RANGE = 0, NEZ_RANGE = 1, HALF_WAY_RMAX_NEZ = 2, TARGET_THREAT_EST = 3, RANDOM_RANGE = 4. Defaults to 3. See: https://wiki.hoggitworld.com/view/DCS_option_missileAttack -function CONTROLLABLE:OptionAAAttackRange(range) +function CONTROLLABLE:OptionAAAttackRange( range ) self:F2( { self.ControllableName } ) -- defaults to 3 local range = range or 3 - if range < 0 or range > 4 then + 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 + if self:IsAir() then + self:SetOption( AI.Option.Air.val.MISSILE_ATTACK, range ) + end end return self end @@ -3753,22 +3678,22 @@ end -- @param #CONTROLLABLE self -- @param #number EngageRange Engage range limit in percent (a number between 0 and 100). Default 100. -- @return #CONTROLLABLE self -function CONTROLLABLE:OptionEngageRange(EngageRange) +function CONTROLLABLE:OptionEngageRange( EngageRange ) self:F2( { self.ControllableName } ) -- Set default if not specified. - EngageRange=EngageRange or 100 - if EngageRange < 0 or EngageRange > 100 then + 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 + if self:IsGround() then + self:SetOption( AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION, EngageRange ) + end end - return self + return self end return nil end @@ -3781,26 +3706,26 @@ end -- @param #boolean shortcut If true and onroad is set, take a shorter route - if available - off road, default false -- @param #string formation Formation string as in the mission editor, e.g. "Vee", "Diamond", "Line abreast", etc. Defaults to "Off Road" -- @return #CONTROLLABLE self -function CONTROLLABLE:RelocateGroundRandomInRadius(speed, radius, onroad, shortcut, formation) +function CONTROLLABLE:RelocateGroundRandomInRadius( speed, radius, onroad, shortcut, formation ) 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 - local _formation = formation or "Off Road" + 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 + local _formation = formation or "Off Road" - -- create a DCS Task an push it on the group - if onroad then - _grptsk, _candoroad = self:TaskGroundOnRoad(_tocoord,_speed,_formation,_shortcut) - self:Route(_grptsk,5) - else - self:TaskRouteToVec2(_tocoord:GetVec2(),_speed,_formation) - end + -- create a DCS Task an push it on the group + if onroad then + _grptsk, _candoroad = self:TaskGroundOnRoad( _tocoord, _speed, _formation, _shortcut ) + self:Route( _grptsk, 5 ) + else + self:TaskRouteToVec2( _tocoord:GetVec2(), _speed, _formation ) + end return self end @@ -3809,7 +3734,7 @@ end -- @param #CONTROLLABLE self -- @param #number Seconds Any positive number: AI will disperse, but only for the specified time before continuing their route. 0: AI will not disperse. -- @return #CONTROLLABLE self -function CONTROLLABLE:OptionDisperseOnAttack(Seconds) +function CONTROLLABLE:OptionDisperseOnAttack( Seconds ) self:F2( { self.ControllableName } ) -- Set default if not specified. local seconds = Seconds or 0 @@ -3817,11 +3742,11 @@ function CONTROLLABLE:OptionDisperseOnAttack(Seconds) 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 + if self:IsGround() then + self:SetOption( AI.Option.Ground.id.DISPERSE_ON_ATTACK, seconds ) + end end - return self + return self end return nil end @@ -3836,11 +3761,11 @@ function POSITIONABLE:IsSubmarine() if DCSUnit then local UnitDescriptor = DCSUnit:getDesc() - if UnitDescriptor.attributes["Submarines"] == true then - return true - else - return false - end + if UnitDescriptor.attributes["Submarines"] == true then + return true + else + return false + end end return nil diff --git a/Moose Development/Moose/Wrapper/Identifiable.lua b/Moose Development/Moose/Wrapper/Identifiable.lua index 5b340aec8..a450c4ef3 100644 --- a/Moose Development/Moose/Wrapper/Identifiable.lua +++ b/Moose Development/Moose/Wrapper/Identifiable.lua @@ -176,7 +176,7 @@ function IDENTIFIABLE:GetCoalitionName() if DCSIdentifiable then - -- Get coaliton ID. + -- Get coalition ID. local IdentifiableCoalition = DCSIdentifiable:getCoalition() self:T3( IdentifiableCoalition ) diff --git a/Moose Development/Moose/Wrapper/Marker.lua b/Moose Development/Moose/Wrapper/Marker.lua index 88a868501..b5dcfc5df 100644 --- a/Moose Development/Moose/Wrapper/Marker.lua +++ b/Moose Development/Moose/Wrapper/Marker.lua @@ -15,7 +15,6 @@ -- @module Wrapper.Marker -- @image Wrapper_Marker.png - --- Marker class. -- @type MARKER -- @field #string ClassName Name of the class. @@ -24,7 +23,7 @@ -- @field #number mid Marker ID. -- @field Core.Point#COORDINATE coordinate Coordinate of the mark. -- @field #string text Text displayed in the mark panel. --- @field #string message Message dispayed when the mark is added. +-- @field #string message Message displayed when the mark is added. -- @field #boolean readonly Marker is read-only. -- @field #number coalition Coalition to which the marker is displayed. -- @extends Core.Fsm#FSM @@ -42,29 +41,29 @@ -- # Create a Marker -- -- -- Create a MARKER object at Batumi with a trivial text. --- local Coordinate=AIRBASE:FindByName("Batumi"):GetCoordinate() --- mymarker=MARKER:New(Coordinate, "I am Batumi Airfield") +-- local Coordinate = AIRBASE:FindByName( "Batumi" ):GetCoordinate() +-- mymarker = MARKER:New( Coordinate, "I am Batumi Airfield" ) -- --- Now this does **not** show the marker yet. We still need to specifiy to whom it is shown. There are several options, i.e. --- show the marker to everyone, to a speficic coaliton only, or only to a specific group. +-- Now this does **not** show the marker yet. We still need to specify to whom it is shown. There are several options, i.e. +-- show the marker to everyone, to a specific coalition only, or only to a specific group. -- -- ## For Everyone -- -- If the marker should be visible to everyone, you can use the :ToAll() function. -- --- mymarker=MARKER:New(Coordinate, "I am Batumi Airfield"):ToAll() +-- mymarker = MARKER:New( Coordinate, "I am Batumi Airfield" ):ToAll() -- --- ## For a Coaliton +-- ## For a Coalition -- -- If the maker should be visible to a specific coalition, you can use the :ToCoalition() function. -- --- mymarker=MARKER:New(Coordinate, "I am Batumi Airfield"):ToCoaliton(coaliton.side.BLUE) +-- mymarker = MARKER:New( Coordinate , "I am Batumi Airfield" ):ToCoalition( coalition.side.BLUE ) -- --- ### To Blue Coaliton +-- ### To Blue Coalition -- -- ### To Red Coalition -- --- This would show the marker only to the Blue coaliton. +-- This would show the marker only to the Blue coalition. -- -- ## For a Group -- @@ -76,28 +75,28 @@ -- -- The marker text and coordinate can be updated easily as shown below. -- --- However, note that **updateing involves to remove and recreate the marker if either text or its coordinate is changed**. +-- However, note that **updating involves to remove and recreate the marker if either text or its coordinate is changed**. -- *This is a DCS scripting engine limitation.* -- -- ## Update Text -- --- If you created a marker "mymarker" as shown above, you can update the dispayed test by +-- If you created a marker "mymarker" as shown above, you can update the displayed test by -- --- mymarker:UpdateText("I am the new text at Batumi") +-- mymarker:UpdateText( "I am the new text at Batumi" ) -- -- The update can also be delayed by, e.g. 90 seconds, using -- --- mymarker:UpdateText("I am the new text at Batumi", 90) +-- mymarker:UpdateText( "I am the new text at Batumi", 90 ) -- -- ## Update Coordinate -- -- If you created a marker "mymarker" as shown above, you can update its coordinate on the F10 map by -- --- mymarker:UpdateCoordinate(NewCoordinate) +-- mymarker:UpdateCoordinate( NewCoordinate ) -- -- The update can also be delayed by, e.g. 60 seconds, using -- --- mymarker:UpdateCoordinate(NewCoordinate, 60) +-- mymarker:UpdateCoordinate( NewCoordinate , 60 ) -- -- # Retrieve Data -- @@ -105,18 +104,18 @@ -- -- ## Text -- --- local text=mymarker:GetText() --- env.info("Marker Text = " .. text) +-- local text =mymarker:GetText() +-- env.info( "Marker Text = " .. text ) -- -- ## Coordinate -- --- local Coordinate=mymarker:GetCoordinate() --- env.info("Marker Coordinate LL DSM = " .. Coordinate:ToStringLLDMS()) +-- local Coordinate = mymarker:GetCoordinate() +-- env.info( "Marker Coordinate LL DSM = " .. Coordinate:ToStringLLDMS() ) -- -- -- # FSM Events -- --- Moose creates addditonal events, so called FSM event, when markers are added, changed, removed, and text or the coordianteis updated. +-- Moose creates additional events, so called FSM event, when markers are added, changed, removed, and text or the coordinate is updated. -- -- These events can be captured and used for processing via OnAfter functions as shown below. -- @@ -133,26 +132,25 @@ -- -- # Examples -- --- -- @field #MARKER MARKER = { - ClassName = "MARKER", - Debug = false, - lid = nil, - mid = nil, - coordinate = nil, - text = nil, - message = nil, - readonly = nil, - coalition = nil, + ClassName = "MARKER", + Debug = false, + lid = nil, + mid = nil, + coordinate = nil, + text = nil, + message = nil, + readonly = nil, + coalition = nil, } --- Marker ID. Running number. -_MARKERID=0 +_MARKERID = 0 --- Marker class version. -- @field #string version -MARKER.version="0.1.0" +MARKER.version = "0.1.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -172,38 +170,38 @@ MARKER.version="0.1.0" -- @param Core.Point#COORDINATE Coordinate Coordinate where to place the marker. -- @param #string Text Text displayed on the mark panel. -- @return #MARKER self -function MARKER:New(Coordinate, Text) +function MARKER:New( Coordinate, Text ) -- Inherit everything from FSM class. - local self=BASE:Inherit(self, FSM:New()) -- #MARKER + local self = BASE:Inherit( self, FSM:New() ) -- #MARKER - self.coordinate=Coordinate + self.coordinate = Coordinate - self.text=Text + self.text = Text -- Defaults - self.readonly=false - self.message="" + self.readonly = false + self.message = "" -- New marker ID. This is not the one of the actual marker. - _MARKERID=_MARKERID+1 + _MARKERID = _MARKERID + 1 - self.myid=_MARKERID + self.myid = _MARKERID -- Log ID. - self.lid=string.format("Marker #%d | ", self.myid) + self.lid = string.format( "Marker #%d | ", self.myid ) -- Start State. - self:SetStartState("Invisible") + self:SetStartState( "Invisible" ) -- Add FSM transitions. -- From State --> Event --> To State - self:AddTransition("Invisible", "Added", "Visible") -- Marker was added. - self:AddTransition("Visible", "Removed", "Invisible") -- Marker was removed. - self:AddTransition("*", "Changed", "*") -- Marker was changed. + self:AddTransition( "Invisible", "Added", "Visible" ) -- Marker was added. + self:AddTransition( "Visible", "Removed", "Invisible" ) -- Marker was removed. + self:AddTransition( "*", "Changed", "*" ) -- Marker was changed. - self:AddTransition("*", "TextUpdate", "*") -- Text updated. - self:AddTransition("*", "CoordUpdate", "*") -- Coordinates updated. + self:AddTransition( "*", "TextUpdate", "*" ) -- Text updated. + self:AddTransition( "*", "CoordUpdate", "*" ) -- Coordinates updated. --- Triggers the FSM event "Added". -- @function [parent=#MARKER] Added @@ -223,7 +221,6 @@ function MARKER:New(Coordinate, Text) -- @param #string To To state. -- @param Core.Event#EVENTDATA EventData Event data table. - --- Triggers the FSM event "Removed". -- @function [parent=#MARKER] Removed -- @param #MARKER self @@ -242,7 +239,6 @@ function MARKER:New(Coordinate, Text) -- @param #string To To state. -- @param Core.Event#EVENTDATA EventData Event data table. - --- Triggers the FSM event "Changed". -- @function [parent=#MARKER] Changed -- @param #MARKER self @@ -261,7 +257,6 @@ function MARKER:New(Coordinate, Text) -- @param #string To To state. -- @param Core.Event#EVENTDATA EventData Event data table. - --- Triggers the FSM event "TextUpdate". -- @function [parent=#MARKER] TextUpdate -- @param #MARKER self @@ -280,7 +275,6 @@ function MARKER:New(Coordinate, Text) -- @param #string To To state. -- @param #string Text The new text. - --- Triggers the FSM event "CoordUpdate". -- @function [parent=#MARKER] CoordUpdate -- @param #MARKER self @@ -299,11 +293,10 @@ function MARKER:New(Coordinate, Text) -- @param #string To To state. -- @param Core.Point#COORDINATE Coordinate The updated Coordinate. - -- Handle events. - self:HandleEvent(EVENTS.MarkAdded) - self:HandleEvent(EVENTS.MarkRemoved) - self:HandleEvent(EVENTS.MarkChange) + self:HandleEvent( EVENTS.MarkAdded ) + self:HandleEvent( EVENTS.MarkRemoved ) + self:HandleEvent( EVENTS.MarkChange ) return self end @@ -317,7 +310,7 @@ end -- @return #MARKER self function MARKER:ReadOnly() - self.readonly=true + self.readonly = true return self end @@ -326,9 +319,9 @@ end -- @param #MARKER self -- @param #string Text Message displayed when the marker is added. -- @return #MARKER self -function MARKER:Message(Text) +function MARKER:Message( Text ) - self.message=Text or "" + self.message = Text or "" return self end @@ -337,28 +330,28 @@ end -- @param #MARKER self -- @param #number Delay (Optional) Delay in seconds, before the marker is created. -- @return #MARKER self -function MARKER:ToAll(Delay) +function MARKER:ToAll( Delay ) - if Delay and Delay>0 then - self:ScheduleOnce(Delay, MARKER.ToAll, self) + 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 + self.toall = true + self.tocoalition = nil + self.coalition = nil + self.togroup = nil + self.groupname = nil + self.groupid = nil -- First remove an existing mark. if self.shown then self:Remove() end - self.mid=UTILS.GetMarkID() + self.mid = UTILS.GetMarkID() -- Call DCS function. - trigger.action.markToAll(self.mid, self.text, self.coordinate:GetVec3(), self.readonly, self.message) + trigger.action.markToAll( self.mid, self.text, self.coordinate:GetVec3(), self.readonly, self.message ) end @@ -367,32 +360,32 @@ end --- Place marker visible for a specific coalition only. -- @param #MARKER self --- @param #number Coalition Coalition 1=Red, 2=Blue, 0=Neutral. See `coaliton.side.RED`. +-- @param #number Coalition Coalition 1=Red, 2=Blue, 0=Neutral. See `coalition.side.RED`. -- @param #number Delay (Optional) Delay in seconds, before the marker is created. -- @return #MARKER self -function MARKER:ToCoalition(Coalition, Delay) +function MARKER:ToCoalition( Coalition, Delay ) - if Delay and Delay>0 then - self:ScheduleOnce(Delay, MARKER.ToCoalition, self, Coalition) + if Delay and Delay > 0 then + self:ScheduleOnce( Delay, MARKER.ToCoalition, self, Coalition ) else - self.coalition=Coalition + self.coalition = Coalition - self.tocoaliton=true - self.toall=false - self.togroup=false - self.groupname=nil - self.groupid=nil + self.tocoalition = true + self.toall = false + self.togroup = false + self.groupname = nil + self.groupid = nil -- First remove an existing mark. if self.shown then self:Remove() end - self.mid=UTILS.GetMarkID() + self.mid = UTILS.GetMarkID() -- Call DCS function. - trigger.action.markToCoalition(self.mid, self.text, self.coordinate:GetVec3(), self.coalition, self.readonly, self.message) + trigger.action.markToCoalition( self.mid, self.text, self.coordinate:GetVec3(), self.coalition, self.readonly, self.message ) end @@ -403,8 +396,8 @@ end -- @param #MARKER self -- @param #number Delay (Optional) Delay in seconds, before the marker is created. -- @return #MARKER self -function MARKER:ToBlue(Delay) - self:ToCoalition(coalition.side.BLUE, Delay) +function MARKER:ToBlue( Delay ) + self:ToCoalition( coalition.side.BLUE, Delay ) return self end @@ -412,8 +405,8 @@ end -- @param #MARKER self -- @param #number Delay (Optional) Delay in seconds, before the marker is created. -- @return #MARKER self -function MARKER:ToRed(Delay) - self:ToCoalition(coalition.side.RED, Delay) +function MARKER:ToRed( Delay ) + self:ToCoalition( coalition.side.RED, Delay ) return self end @@ -421,51 +414,50 @@ end -- @param #MARKER self -- @param #number Delay (Optional) Delay in seconds, before the marker is created. -- @return #MARKER self -function MARKER:ToNeutral(Delay) - self:ToCoalition(coalition.side.NEUTRAL, Delay) +function MARKER:ToNeutral( Delay ) + self:ToCoalition( coalition.side.NEUTRAL, Delay ) return self end - --- Place marker visible for a specific group only. -- @param #MARKER self -- @param Wrapper.Group#GROUP Group The group to which the marker is displayed. -- @param #number Delay (Optional) Delay in seconds, before the marker is created. -- @return #MARKER self -function MARKER:ToGroup(Group, Delay) +function MARKER:ToGroup( Group, Delay ) - if Delay and Delay>0 then - self:ScheduleOnce(Delay, MARKER.ToGroup, self, Group) + if Delay and Delay > 0 then + self:ScheduleOnce( Delay, MARKER.ToGroup, self, Group ) else -- Check if group exists. - if Group and Group:IsAlive()~=nil then + if Group and Group:IsAlive() ~= nil then - self.groupid=Group:GetID() + self.groupid = Group:GetID() if self.groupid then - self.groupname=Group:GetName() + self.groupname = Group:GetName() - self.togroup=true - self.tocoaliton=nil - self.coalition=nil - self.toall=nil + self.togroup = true + self.tocoalition = nil + self.coalition = nil + self.toall = nil -- First remove an existing mark. if self.shown then self:Remove() end - self.mid=UTILS.GetMarkID() + self.mid = UTILS.GetMarkID() -- Call DCS function. - trigger.action.markToGroup(self.mid, self.text, self.coordinate:GetVec3(), self.groupid, self.readonly, self.message) + trigger.action.markToGroup( self.mid, self.text, self.coordinate:GetVec3(), self.groupid, self.readonly, self.message ) end else - --TODO: Warning! + -- TODO: Warning! end end @@ -478,17 +470,17 @@ end -- @param #string Text Updated text. -- @param #number Delay (Optional) Delay in seconds, before the marker is created. -- @return #MARKER self -function MARKER:UpdateText(Text, Delay) +function MARKER:UpdateText( Text, Delay ) - if Delay and Delay>0 then - self:ScheduleOnce(Delay, MARKER.UpdateText, self, Text) + if Delay and Delay > 0 then + self:ScheduleOnce( Delay, MARKER.UpdateText, self, Text ) else - self.text=tostring(Text) + self.text = tostring( Text ) self:Refresh() - self:TextUpdate(tostring(Text)) + self:TextUpdate( tostring( Text ) ) end @@ -500,17 +492,17 @@ end -- @param Core.Point#COORDINATE Coordinate The new coordinate. -- @param #number Delay (Optional) Delay in seconds, before the marker is created. -- @return #MARKER self -function MARKER:UpdateCoordinate(Coordinate, Delay) +function MARKER:UpdateCoordinate( Coordinate, Delay ) - if Delay and Delay>0 then - self:ScheduleOnce(Delay, MARKER.UpdateCoordinate, self, Coordinate) + if Delay and Delay > 0 then + self:ScheduleOnce( Delay, MARKER.UpdateCoordinate, self, Coordinate ) else - self.coordinate=Coordinate + self.coordinate = Coordinate self:Refresh() - self:CoordUpdate(Coordinate) + self:CoordUpdate( Coordinate ) end @@ -521,28 +513,28 @@ end -- @param #MARKER self -- @param #number Delay (Optional) Delay in seconds, before the marker is created. -- @return #MARKER self -function MARKER:Refresh(Delay) +function MARKER:Refresh( Delay ) - if Delay and Delay>0 then - self:ScheduleOnce(Delay, MARKER.Refresh, self) + if Delay and Delay > 0 then + self:ScheduleOnce( Delay, MARKER.Refresh, self ) else if self.toall then self:ToAll() - elseif self.tocoaliton then + elseif self.tocoalition then - self:ToCoalition(self.coalition) + self:ToCoalition( self.coalition ) elseif self.togroup then - local group=GROUP:FindByName(self.groupname) + local group = GROUP:FindByName( self.groupname ) - self:ToGroup(group) + self:ToGroup( group ) else - self:E(self.lid.."ERROR: unknown To in :Refresh()!") + self:E( self.lid .. "ERROR: unknown To in :Refresh()!" ) end end @@ -554,16 +546,16 @@ end -- @param #MARKER self -- @param #number Delay (Optional) Delay in seconds, before the marker is removed. -- @return #MARKER self -function MARKER:Remove(Delay) +function MARKER:Remove( Delay ) - if Delay and Delay>0 then - self:ScheduleOnce(Delay, MARKER.Remove, self) + if Delay and Delay > 0 then + self:ScheduleOnce( Delay, MARKER.Remove, self ) else if self.shown then -- Call DCS function. - trigger.action.removeMark(self.mid) + trigger.action.removeMark( self.mid ) end @@ -590,24 +582,23 @@ end -- @param #MARKER self -- @param #string Text Marker text. Default is an empty sting "". -- @return #MARKER self -function MARKER:SetText(Text) - self.text=Text and tostring(Text) or "" +function MARKER:SetText( Text ) + self.text = Text and tostring( Text ) or "" return self end - --- Check if marker is currently visible on the F10 map. -- @param #MARKER self -- @return #boolean True if the marker is currently visible. function MARKER:IsVisible() - return self:Is("Visible") + return self:Is( "Visible" ) end --- Check if marker is currently invisible on the F10 map. -- @param #MARKER self -- @return function MARKER:IsInvisible() - return self:Is("Invisible") + return self:Is( "Invisible" ) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -617,19 +608,19 @@ end --- Event function when a MARKER is added. -- @param #MARKER self -- @param Core.Event#EVENTDATA EventData -function MARKER:OnEventMarkAdded(EventData) +function MARKER:OnEventMarkAdded( EventData ) if EventData and EventData.MarkID then - local MarkID=EventData.MarkID + local MarkID = EventData.MarkID - self:T3(self.lid..string.format("Captured event MarkAdded for Mark ID=%s", tostring(MarkID))) + self:T3( self.lid .. string.format( "Captured event MarkAdded for Mark ID=%s", tostring( MarkID ) ) ) - if MarkID==self.mid then + if MarkID == self.mid then - self.shown=true + self.shown = true - self:Added(EventData) + self:Added( EventData ) end @@ -640,19 +631,19 @@ end --- Event function when a MARKER is removed. -- @param #MARKER self -- @param Core.Event#EVENTDATA EventData -function MARKER:OnEventMarkRemoved(EventData) +function MARKER:OnEventMarkRemoved( EventData ) if EventData and EventData.MarkID then - local MarkID=EventData.MarkID + local MarkID = EventData.MarkID - self:T3(self.lid..string.format("Captured event MarkAdded for Mark ID=%s", tostring(MarkID))) + self:T3( self.lid .. string.format( "Captured event MarkAdded for Mark ID=%s", tostring( MarkID ) ) ) - if MarkID==self.mid then + if MarkID == self.mid then - self.shown=false + self.shown = false - self:Removed(EventData) + self:Removed( EventData ) end @@ -663,19 +654,19 @@ end --- Event function when a MARKER changed. -- @param #MARKER self -- @param Core.Event#EVENTDATA EventData -function MARKER:OnEventMarkChange(EventData) +function MARKER:OnEventMarkChange( EventData ) if EventData and EventData.MarkID then - local MarkID=EventData.MarkID + local MarkID = EventData.MarkID - self:T3(self.lid..string.format("Captured event MarkChange for Mark ID=%s", tostring(MarkID))) + self:T3( self.lid .. string.format( "Captured event MarkChange for Mark ID=%s", tostring( MarkID ) ) ) - if MarkID==self.mid then + if MarkID == self.mid then - self:Changed(EventData) + self:Changed( EventData ) - self:TextChanged(tostring(EventData.MarkText)) + self:TextChanged( tostring( EventData.MarkText ) ) end @@ -693,17 +684,17 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param Core.Event#EVENTDATA EventData Event data table. -function MARKER:onafterAdded(From, Event, To, EventData) +function MARKER:onafterAdded( From, Event, To, EventData ) -- Debug info. - 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) + 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 @@ -713,17 +704,17 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param Core.Event#EVENTDATA EventData Event data table. -function MARKER:onafterRemoved(From, Event, To, EventData) +function MARKER:onafterRemoved( From, Event, To, EventData ) -- Debug info. - 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) + 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 @@ -733,17 +724,17 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param Core.Event#EVENTDATA EventData Event data table. -function MARKER:onafterChanged(From, Event, To, EventData) +function MARKER:onafterChanged( From, Event, To, EventData ) -- Debug info. - 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) + 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 @@ -753,9 +744,9 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param #string Text The updated text, displayed in the mark panel. -function MARKER:onafterTextUpdate(From, Event, To, Text) +function MARKER:onafterTextUpdate( From, Event, To, Text ) - self:T(self.lid..string.format("New Marker Text:\n%s", Text)) + self:T( self.lid .. string.format( "New Marker Text:\n%s", Text ) ) end @@ -765,12 +756,8 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param Core.Point#COORDINATE Coordinate The updated coordinates. -function MARKER:onafterCoordUpdate(From, Event, To, Coordinate) +function MARKER:onafterCoordUpdate( From, Event, To, Coordinate ) - self:T(self.lid..string.format("New Marker Coordinate in LL DMS: %s", Coordinate:ToStringLLDMS())) + self:T( self.lid .. string.format( "New Marker Coordinate in LL DMS: %s", Coordinate:ToStringLLDMS() ) ) end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From d6cfaa505065314f8693d8350a0a53d5fed867a1 Mon Sep 17 00:00:00 2001 From: TommyC81 Date: Tue, 28 Dec 2021 14:01:44 +0400 Subject: [PATCH 046/200] Update Scoring.lua (#1674) Code formatting and minor typo/documentation fixes. --- .../Moose/Functional/Scoring.lua | 843 ++++++++---------- 1 file changed, 392 insertions(+), 451 deletions(-) diff --git a/Moose Development/Moose/Functional/Scoring.lua b/Moose Development/Moose/Functional/Scoring.lua index b914d445c..75bc18704 100644 --- a/Moose Development/Moose/Functional/Scoring.lua +++ b/Moose Development/Moose/Functional/Scoring.lua @@ -1,9 +1,9 @@ --- **Functional** - Administer the scoring of player achievements, and create a CSV file logging the scoring events for use at team or squadron websites. --- +-- -- === --- +-- -- ## Features: --- +-- -- * Set the scoring scales based on threat level. -- * Positive scores and negative scores. -- * A contribution model to score achievements. @@ -14,169 +14,167 @@ -- * Score the hits and destroys of scenery. -- * Log scores into a CSV file. -- * Connect to a remote server using JSON and IP. --- +-- -- === --- +-- -- ## Missions: --- +-- -- [SCO - Scoring](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SCO%20-%20Scoring) --- +-- -- === --- --- Administers the scoring of player achievements, +-- +-- Administers the scoring of player achievements, -- and creates a CSV file logging the scoring events and results for use at team or squadron websites. --- --- SCORING automatically calculates the threat level of the objects hit and destroyed by players, +-- +-- SCORING automatically calculates the threat level of the objects hit and destroyed by players, -- which can be @{Wrapper.Unit}, @{Static) and @{Scenery} objects. --- --- Positive score points are granted when enemy or neutral targets are destroyed. --- Negative score points or penalties are given when a friendly target is hit or destroyed. +-- +-- Positive score points are granted when enemy or neutral targets are destroyed. +-- Negative score points or penalties are given when a friendly target is hit or destroyed. -- This brings a lot of dynamism in the scoring, where players need to take care to inflict damage on the right target. -- By default, penalties weight heavier in the scoring, to ensure that players don't commit fratricide. -- The total score of the player is calculated by **adding the scores minus the penalties**. --- +-- -- ![Banner Image](..\Presentations\SCORING\Dia4.JPG) --- +-- -- The score value is calculated based on the **threat level of the player** and the **threat level of the target**. --- A calculated score takes the threat level of the target divided by a balanced threat level of the player unit. --- As such, if the threat level of the target is high, and the player threat level is low, a higher score will be given than +-- A calculated score takes the threat level of the target divided by a balanced threat level of the player unit. +-- As such, if the threat level of the target is high, and the player threat level is low, a higher score will be given than -- if the threat level of the player would be high too. --- +-- -- ![Banner Image](..\Presentations\SCORING\Dia5.JPG) --- +-- -- When multiple players hit the same target, and finally succeed in destroying the target, then each player who contributed to the target -- destruction, will receive a score. This is important for targets that require significant damage before it can be destroyed, like -- ships or heavy planes. --- +-- -- ![Banner Image](..\Presentations\SCORING\Dia13.JPG) --- +-- -- Optionally, the score values can be **scaled** by a **scale**. Specific scales can be set for positive cores or negative penalties. -- The default range of the scores granted is a value between 0 and 10. The default range of penalties given is a value between 0 and 30. --- +-- -- ![Banner Image](..\Presentations\SCORING\Dia7.JPG) --- +-- -- **Additional scores** can be granted to **specific objects**, when the player(s) destroy these objects. --- +-- -- ![Banner Image](..\Presentations\SCORING\Dia9.JPG) --- +-- -- Various @{Zone}s can be defined for which scores are also granted when objects in that @{Zone} are destroyed. -- This is **specifically useful** to designate **scenery targets on the map** that will generate points when destroyed. --- --- With a small change in MissionScripting.lua, the scoring results can also be logged in a **CSV file**. +-- +-- With a small change in MissionScripting.lua, the scoring results can also be logged in a **CSV file**. -- These CSV files can be used to: --- +-- -- * Upload scoring to a database or a BI tool to publish the scoring results to the player community. -- * Upload scoring in an (online) Excel like tool, using pivot tables and pivot charts to show mission results. --- * Share scoring amoung players after the mission to discuss mission results. --- +-- * Share scoring among players after the mission to discuss mission results. +-- -- Scores can be **reported**. **Menu options** are automatically added to **each player group** when a player joins a client slot or a CA unit. --- Use the radio menu F10 to consult the scores while running the mission. +-- Use the radio menu F10 to consult the scores while running the mission. -- Scores can be reported for your user, or an overall score can be reported of all players currently active in the mission. --- +-- -- === --- +-- -- ### Authors: **FlightControl** --- +-- -- ### Contributions: --- +-- -- * **Wingthor (TAW)**: Testing & Advice. -- * **Dutch-Baron (TAW)**: Testing & Advice. -- * **[Whisper](http://forums.eagle.ru/member.php?u=3829): Testing and Advice. --- --- === --- +-- +-- === +-- -- @module Functional.Scoring -- @image Scoring.JPG - --- @type SCORING -- @field Players A collection of the current players that have joined the game. -- @extends Core.Base#BASE --- SCORING class --- +-- -- # Constructor: --- +-- -- local Scoring = SCORING:New( "Scoring File" ) --- --- +-- -- # Set the destroy score or penalty scale: --- +-- -- Score scales can be set for scores granted when enemies or friendlies are destroyed. --- Use the method @{#SCORING.SetScaleDestroyScore}() to set the scale of enemy destroys (positive destroys). +-- Use the method @{#SCORING.SetScaleDestroyScore}() to set the scale of enemy destroys (positive destroys). -- Use the method @{#SCORING.SetScaleDestroyPenalty}() to set the scale of friendly destroys (negative destroys). --- +-- -- local Scoring = SCORING:New( "Scoring File" ) -- Scoring:SetScaleDestroyScore( 10 ) -- Scoring:SetScaleDestroyPenalty( 40 ) --- +-- -- The above sets the scale for valid scores to 10. So scores will be given in a scale from 0 to 10. -- The penalties will be given in a scale from 0 to 40. --- +-- -- # Define special targets that will give extra scores: --- +-- -- Special targets can be set that will give extra scores to the players when these are destroyed. --- Use the methods @{#SCORING.AddUnitScore}() and @{#SCORING.RemoveUnitScore}() to specify a special additional score for a specific @{Wrapper.Unit}s. --- Use the methods @{#SCORING.AddStaticScore}() and @{#SCORING.RemoveStaticScore}() to specify a special additional score for a specific @{Static}s. --- Use the method @{#SCORING.SetGroupGroup}() to specify a special additional score for a specific @{Wrapper.Group}s. --- +-- Use the methods @{#SCORING.AddUnitScore}() and @{#SCORING.RemoveUnitScore}() to specify a special additional score for a specific @{Wrapper.Unit}s. +-- Use the methods @{#SCORING.AddStaticScore}() and @{#SCORING.RemoveStaticScore}() to specify a special additional score for a specific @{Static}s. +-- Use the method @{#SCORING.SetGroupGroup}() to specify a special additional score for a specific @{Wrapper.Group}s. +-- -- local Scoring = SCORING:New( "Scoring File" ) -- Scoring:AddUnitScore( UNIT:FindByName( "Unit #001" ), 200 ) -- Scoring:AddStaticScore( STATIC:FindByName( "Static #1" ), 100 ) --- +-- -- The above grants an additional score of 200 points for Unit #001 and an additional 100 points of Static #1 if these are destroyed. -- Note that later in the mission, one can remove these scores set, for example, when the a goal achievement time limit is over. -- For example, this can be done as follows: --- +-- -- Scoring:RemoveUnitScore( UNIT:FindByName( "Unit #001" ) ) --- +-- -- # Define destruction zones that will give extra scores: --- +-- -- Define zones of destruction. Any object destroyed within the zone of the given category will give extra points. --- Use the method @{#SCORING.AddZoneScore}() to add a @{Zone} for additional scoring. --- Use the method @{#SCORING.RemoveZoneScore}() to remove a @{Zone} for additional scoring. --- There are interesting variations that can be achieved with this functionality. For example, if the @{Zone} is a @{Core.Zone#ZONE_UNIT}, +-- Use the method @{#SCORING.AddZoneScore}() to add a @{Zone} for additional scoring. +-- Use the method @{#SCORING.RemoveZoneScore}() to remove a @{Zone} for additional scoring. +-- There are interesting variations that can be achieved with this functionality. For example, if the @{Zone} is a @{Core.Zone#ZONE_UNIT}, -- then the zone is a moving zone, and anything destroyed within that @{Zone} will generate points. --- The other implementation could be to designate a scenery target (a building) in the mission editor surrounded by a @{Zone}, +-- The other implementation could be to designate a scenery target (a building) in the mission editor surrounded by a @{Zone}, -- just large enough around that building. --- +-- -- # Add extra Goal scores upon an event or a condition: --- +-- -- A mission has goals and achievements. The scoring system provides an API to set additional scores when a goal or achievement event happens. -- Use the method @{#SCORING.AddGoalScore}() to add a score for a Player at any time in your mission. --- +-- -- # (Decommissioned) Configure fratricide level. --- --- **This functionality is decomissioned until the DCS bug concerning Unit:destroy() not being functional in multi player for player units has been fixed by ED**. --- +-- +-- **This functionality is decommissioned until the DCS bug concerning Unit:destroy() not being functional in multi player for player units has been fixed by ED**. +-- -- When a player commits too much damage to friendlies, his penalty score will reach a certain level. --- Use the method @{#SCORING.SetFratricide}() to define the level when a player gets kicked. --- By default, the fratricide level is the default penalty mutiplier * 2 for the penalty score. --- +-- Use the method @{#SCORING.SetFratricide}() to define the level when a player gets kicked. +-- By default, the fratricide level is the default penalty multiplier * 2 for the penalty score. +-- -- # Penalty score when a player changes the coalition. --- +-- -- When a player changes the coalition, he can receive a penalty score. -- Use the method @{#SCORING.SetCoalitionChangePenalty}() to define the penalty when a player changes coalition. --- By default, the penalty for changing coalition is the default penalty scale. --- +-- By default, the penalty for changing coalition is the default penalty scale. +-- -- # Define output CSV files. --- +-- -- The CSV file is given the name of the string given in the @{#SCORING.New}{} constructor, followed by the .csv extension. -- The file is incrementally saved in the **\\Saved Games\\DCS\\Logs** folder, and has a time stamp indicating each mission run. -- See the following example: --- +-- -- local ScoringFirstMission = SCORING:New( "FirstMission" ) -- local ScoringSecondMission = SCORING:New( "SecondMission" ) --- +-- -- The above documents that 2 Scoring objects are created, ScoringFirstMission and ScoringSecondMission. --- --- ### **IMPORTANT!!!* +-- +-- ### **IMPORTANT!!!* -- In order to allow DCS world to write CSV files, you need to adapt a configuration file in your DCS world installation **on the server**. -- For this, browse to the **missionscripting.lua** file in your DCS world installation folder. -- For me, this installation folder is in _D:\\Program Files\\Eagle Dynamics\\DCS World\Scripts_. --- +-- -- Edit a few code lines in the MissionScripting.lua file. Comment out the lines **os**, **io** and **lfs**: --- +-- -- do -- --sanitizeModule('os') -- --sanitizeModule('io') @@ -184,44 +182,44 @@ -- require = nil -- loadlib = nil -- end --- --- When these lines are not sanitized, functions become available to check the time, and to write files to your system at the above specified location. +-- +-- When these lines are not sanitized, functions become available to check the time, and to write files to your system at the above specified location. -- Note that the MissionScripting.lua file provides a warning. So please beware of this warning as outlined by Eagle Dynamics! --- +-- -- --Sanitize Mission Scripting environment --- --This makes unavailable some unsecure functions. --- --Mission downloaded from server to client may contain potentialy harmful lua code that may use these functions. --- --You can remove the code below and make availble these functions at your own risk. --- +-- --This makes unavailable some unsecure functions. +-- --Mission downloaded from server to client may contain potentially harmful lua code that may use these functions. +-- --You can remove the code below and make available these functions at your own risk. +-- -- The MOOSE designer cannot take any responsibility of any damage inflicted as a result of the de-sanitization. -- That being said, I hope that the SCORING class provides you with a great add-on to score your squad mates achievements. --- +-- -- # Configure messages. --- +-- -- When players hit or destroy targets, messages are sent. -- Various methods exist to configure: --- +-- -- * Which messages are sent upon the event. -- * Which audience receives the message. --- +-- -- ## Configure the messages sent upon the event. --- +-- -- Use the following methods to configure when to send messages. By default, all messages are sent. --- +-- -- * @{#SCORING.SetMessagesHit}(): Configure to send messages after a target has been hit. -- * @{#SCORING.SetMessagesDestroy}(): Configure to send messages after a target has been destroyed. -- * @{#SCORING.SetMessagesAddon}(): Configure to send messages for additional score, after a target has been destroyed. -- * @{#SCORING.SetMessagesZone}(): Configure to send messages for additional score, after a target has been destroyed within a given zone. --- +-- -- ## Configure the audience of the messages. --- +-- -- Use the following methods to configure the audience of the messages. By default, the messages are sent to all players in the mission. --- +-- -- * @{#SCORING.SetMessagesToAll}(): Configure to send messages to all players. -- * @{#SCORING.SetMessagesToCoalition}(): Configure to send messages to only those players within the same coalition as the player. -- -- === --- +-- -- @field #SCORING SCORING = { ClassName = "SCORING", @@ -229,45 +227,42 @@ SCORING = { Players = {}, } -local _SCORINGCoalition = - { - [1] = "Red", - [2] = "Blue", - } +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", - } +local _SCORINGCategory = { + [Unit.Category.AIRPLANE] = "Plane", + [Unit.Category.HELICOPTER] = "Helicopter", + [Unit.Category.GROUND_UNIT] = "Vehicle", + [Unit.Category.SHIP] = "Ship", + [Unit.Category.STRUCTURE] = "Structure", +} --- Creates a new SCORING object to administer the scoring achieved by players. -- @param #SCORING self -- @param #string GameName The name of the game. This name is also logged in the CSV score file. -- @return #SCORING self -- @usage --- --- -- Define a new scoring object for the mission Gori Valley. --- ScoringObject = SCORING:New( "Gori Valley" ) --- +-- +-- -- Define a new scoring object for the mission Gori Valley. +-- ScoringObject = SCORING:New( "Gori Valley" ) +-- function SCORING:New( GameName ) -- Inherits from BASE local self = BASE:Inherit( self, BASE:New() ) -- #SCORING - - if GameName then + + if GameName then self.GameName = GameName else error( "A game name must be given to register the scoring results" ) end - - + -- Additional Object scores self.ScoringObjects = {} - + -- Additional Zone scores. self.ScoringZones = {} @@ -277,7 +272,7 @@ function SCORING:New( GameName ) self:SetMessagesDestroy( true ) self:SetMessagesScore( true ) self:SetMessagesZone( true ) - + -- Scales self:SetScaleDestroyScore( 10 ) self:SetScaleDestroyPenalty( 30 ) @@ -285,39 +280,36 @@ function SCORING:New( GameName ) -- Default fratricide penalty level (maximum penalty that can be assigned to a player before he gets kicked). self:SetFratricide( self.ScaleDestroyPenalty * 3 ) self.penaltyonfratricide = true - + -- Default penalty when a player changes coalition. self:SetCoalitionChangePenalty( self.ScaleDestroyPenalty ) self.penaltyoncoalitionchange = true - + self:SetDisplayMessagePrefix() - + -- Event handlers 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.PlayerEnterUnit ) + -- self:HandleEvent( EVENTS.PlayerEnterUnit ) self:HandleEvent( EVENTS.PlayerLeaveUnit ) - - -- During mission startup, especially for single player, + + -- During mission startup, especially for single player, -- iterate the database for the player that has joined, and add him to the scoring, and set the menu. -- But this can only be started one second after the mission has started, so i need to schedule this ... - self.ScoringPlayerScan = BASE:ScheduleOnce( 1, - function() - for PlayerName, PlayerUnit in pairs( _DATABASE:GetPlayerUnits() ) do - self:_AddPlayerFromUnit( PlayerUnit ) - self:SetScoringMenu( PlayerUnit:GetGroup() ) - end + self.ScoringPlayerScan = BASE:ScheduleOnce( 1, function() + for PlayerName, PlayerUnit in pairs( _DATABASE:GetPlayerUnits() ) do + self:_AddPlayerFromUnit( PlayerUnit ) + self:SetScoringMenu( PlayerUnit:GetGroup() ) end - ) - + end ) -- Create the CSV file. self:OpenCSV( GameName ) return self - + end --- Set a prefix string that will be displayed at each scoring message sent. @@ -329,7 +321,6 @@ function SCORING:SetDisplayMessagePrefix( DisplayMessagePrefix ) return self end - --- Set the scale for scoring valid destroys (enemy destroys). -- A default calculated score is a value between 1 and 10. -- The scale magnifies the scores given to the players. @@ -349,12 +340,12 @@ end function SCORING:SetScaleDestroyPenalty( Scale ) self.ScaleDestroyPenalty = Scale - + return self end --- Add a @{Wrapper.Unit} for additional scoring when the @{Wrapper.Unit} is destroyed. --- Note that if there was already a @{Wrapper.Unit} declared within the scoring with the same name, +-- Note that if there was already a @{Wrapper.Unit} declared within the scoring with the same name, -- then the old @{Wrapper.Unit} will be replaced with the new @{Wrapper.Unit}. -- @param #SCORING self -- @param Wrapper.Unit#UNIT ScoreUnit The @{Wrapper.Unit} for which the Score needs to be given. @@ -365,7 +356,7 @@ function SCORING:AddUnitScore( ScoreUnit, Score ) local UnitName = ScoreUnit:GetName() self.ScoringObjects[UnitName] = Score - + return self end @@ -378,12 +369,12 @@ function SCORING:RemoveUnitScore( ScoreUnit ) local UnitName = ScoreUnit:GetName() self.ScoringObjects[UnitName] = nil - + return self end --- Add a @{Static} for additional scoring when the @{Static} is destroyed. --- Note that if there was already a @{Static} declared within the scoring with the same name, +-- Note that if there was already a @{Static} declared within the scoring with the same name, -- then the old @{Static} will be replaced with the new @{Static}. -- @param #SCORING self -- @param Wrapper.Static#UNIT ScoreStatic The @{Static} for which the Score needs to be given. @@ -394,7 +385,7 @@ function SCORING:AddStaticScore( ScoreStatic, Score ) local StaticName = ScoreStatic:GetName() self.ScoringObjects[StaticName] = Score - + return self end @@ -407,11 +398,10 @@ function SCORING:RemoveStaticScore( ScoreStatic ) local StaticName = ScoreStatic:GetName() self.ScoringObjects[StaticName] = nil - + return self end - --- Specify a special additional score for a @{Wrapper.Group}. -- @param #SCORING self -- @param Wrapper.Group#GROUP ScoreGroup The @{Wrapper.Group} for which each @{Wrapper.Unit} a Score is given. @@ -425,7 +415,7 @@ function SCORING:AddScoreGroup( ScoreGroup, Score ) local UnitName = ScoreUnit:GetName() self.ScoringObjects[UnitName] = Score end - + return self end @@ -433,7 +423,7 @@ end -- Note that if a @{Zone} with the same name is already within the scoring added, the @{Zone} (type) and Score will be replaced! -- This allows for a dynamic destruction zone evolution within your mission. -- @param #SCORING self --- @param Core.Zone#ZONE_BASE ScoreZone The @{Zone} which defines the destruction score perimeters. +-- @param Core.Zone#ZONE_BASE ScoreZone The @{Zone} which defines the destruction score perimeters. -- Note that a zone can be a polygon or a moving zone. -- @param #number Score The Score value. -- @return #SCORING @@ -444,7 +434,7 @@ function SCORING:AddZoneScore( ScoreZone, Score ) self.ScoringZones[ZoneName] = {} self.ScoringZones[ZoneName].ScoreZone = ScoreZone self.ScoringZones[ZoneName].Score = Score - + return self end @@ -452,7 +442,7 @@ end -- The scoring will search if any @{Zone} is added with the given name, and will remove that zone from the scoring. -- This allows for a dynamic destruction zone evolution within your mission. -- @param #SCORING self --- @param Core.Zone#ZONE_BASE ScoreZone The @{Zone} which defines the destruction score perimeters. +-- @param Core.Zone#ZONE_BASE ScoreZone The @{Zone} which defines the destruction score perimeters. -- Note that a zone can be a polygon or a moving zone. -- @return #SCORING function SCORING:RemoveZoneScore( ScoreZone ) @@ -460,14 +450,13 @@ function SCORING:RemoveZoneScore( ScoreZone ) local ZoneName = ScoreZone:GetName() self.ScoringZones[ZoneName] = nil - + return self end - --- Configure to send messages after a target has been hit. -- @param #SCORING self --- @param #boolean OnOff If true is given, the messages are sent. +-- @param #boolean OnOff If true is given, the messages are sent. -- @return #SCORING function SCORING:SetMessagesHit( OnOff ) @@ -485,7 +474,7 @@ end --- Configure to send messages after a target has been destroyed. -- @param #SCORING self --- @param #boolean OnOff If true is given, the messages are sent. +-- @param #boolean OnOff If true is given, the messages are sent. -- @return #SCORING function SCORING:SetMessagesDestroy( OnOff ) @@ -503,7 +492,7 @@ end --- Configure to send messages after a target has been destroyed and receives additional scores. -- @param #SCORING self --- @param #boolean OnOff If true is given, the messages are sent. +-- @param #boolean OnOff If true is given, the messages are sent. -- @return #SCORING function SCORING:SetMessagesScore( OnOff ) @@ -521,7 +510,7 @@ end --- Configure to send messages after a target has been hit in a zone, and additional score is received. -- @param #SCORING self --- @param #boolean OnOff If true is given, the messages are sent. +-- @param #boolean OnOff If true is given, the messages are sent. -- @return #SCORING function SCORING:SetMessagesZone( OnOff ) @@ -571,10 +560,9 @@ function SCORING:IfMessagesToCoalition() return self.MessagesAudience == 2 end - --- When a player commits too much damage to friendlies, his penalty score will reach a certain level. --- Use this method to define the level when a player gets kicked. --- By default, the fratricide level is the default penalty mutiplier * 2 for the penalty score. +-- Use this method to define the level when a player gets kicked. +-- By default, the fratricide level is the default penalty multiplier * 2 for the penalty score. -- @param #SCORING self -- @param #number Fratricide The amount of maximum penalty that may be inflicted by a friendly player before he gets kicked. -- @return #SCORING @@ -604,9 +592,9 @@ end --- When a player changes the coalition, he can receive a penalty score. -- Use the method @{#SCORING.SetCoalitionChangePenalty}() to define the penalty when a player changes coalition. --- By default, the penalty for changing coalition is the default penalty scale. +-- By default, the penalty for changing coalition is the default penalty scale. -- @param #SCORING self --- @param #number CoalitionChangePenalty The amount of penalty that is given. +-- @param #number CoalitionChangePenalty The amount of penalty that is given. -- @return #SCORING function SCORING:SetCoalitionChangePenalty( CoalitionChangePenalty ) @@ -614,20 +602,18 @@ function SCORING:SetCoalitionChangePenalty( CoalitionChangePenalty ) return self end - --- Sets the scoring menu. -- @param #SCORING self -- @return #SCORING 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 ) + 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 - --- Add a new player entering a Unit. -- @param #SCORING self -- @param Wrapper.Unit#UNIT UnitData @@ -670,43 +656,41 @@ function SCORING:_AddPlayerFromUnit( UnitData ) if self.Players[PlayerName].UnitCoalition ~= UnitCoalition and self.penaltyoncoalitionchange then self.Players[PlayerName].Penalty = self.Players[PlayerName].Penalty + self.CoalitionChangePenalty or 50 self.Players[PlayerName].PenaltyCoalition = self.Players[PlayerName].PenaltyCoalition + 1 - MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' changed coalition from " .. _SCORINGCoalition[self.Players[PlayerName].UnitCoalition] .. " to " .. _SCORINGCoalition[UnitCoalition] .. - "(changed " .. self.Players[PlayerName].PenaltyCoalition .. " times the coalition). ".. self.CoalitionChangePenalty .."Penalty points added.", - MESSAGE.Type.Information - ):ToAll() - self:ScoreCSV( PlayerName, "", "COALITION_PENALTY", 1, -1*self.CoalitionChangePenalty, self.Players[PlayerName].UnitName, _SCORINGCoalition[self.Players[PlayerName].UnitCoalition], _SCORINGCategory[self.Players[PlayerName].UnitCategory], self.Players[PlayerName].UnitType, - UnitName, _SCORINGCoalition[UnitCoalition], _SCORINGCategory[UnitCategory], UnitData:GetTypeName() ) + MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' changed coalition from " .. _SCORINGCoalition[self.Players[PlayerName].UnitCoalition] .. " to " .. _SCORINGCoalition[UnitCoalition] .. "(changed " .. self.Players[PlayerName].PenaltyCoalition .. " times the coalition). " .. + self.CoalitionChangePenalty .. "Penalty points added.", + MESSAGE.Type.Information ) + :ToAll() + self:ScoreCSV( PlayerName, "", "COALITION_PENALTY", 1, -1 * self.CoalitionChangePenalty, self.Players[PlayerName].UnitName, _SCORINGCoalition[self.Players[PlayerName].UnitCoalition], _SCORINGCategory[self.Players[PlayerName].UnitCategory], self.Players[PlayerName].UnitType, UnitName, _SCORINGCoalition[UnitCoalition], _SCORINGCategory[UnitCategory], UnitData:GetTypeName() ) end end - + self.Players[PlayerName].UnitName = UnitName self.Players[PlayerName].UnitCoalition = UnitCoalition self.Players[PlayerName].UnitCategory = UnitCategory self.Players[PlayerName].UnitType = UnitTypeName - self.Players[PlayerName].UNIT = UnitData + self.Players[PlayerName].UNIT = UnitData self.Players[PlayerName].ThreatLevel = UnitThreatLevel self.Players[PlayerName].ThreatType = UnitThreatType - + -- TODO: make fratricide switchable if self.Players[PlayerName].Penalty > self.Fratricide * 0.50 and self.penaltyonfratricide then if self.Players[PlayerName].PenaltyWarning < 1 then MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "': WARNING! If you continue to commit FRATRICIDE and have a PENALTY score higher than " .. self.Fratricide .. ", you will be COURT MARTIALED and DISMISSED from this mission! \nYour total penalty is: " .. self.Players[PlayerName].Penalty, - MESSAGE.Type.Information - ):ToAll() + MESSAGE.Type.Information ) + :ToAll() self.Players[PlayerName].PenaltyWarning = self.Players[PlayerName].PenaltyWarning + 1 end end if self.Players[PlayerName].Penalty > self.Fratricide and self.penaltyonfratricide then MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' committed FRATRICIDE, he will be COURT MARTIALED and is DISMISSED from this mission!", - MESSAGE.Type.Information - ):ToAll() + MESSAGE.Type.Information ) + :ToAll() UnitData:GetGroup():Destroy() end end end - --- Add a goal score for a player. -- The method takes the Player name for which the Goal score needs to be set. -- The GoalTag is a string or identifier that is taken into the CSV file scoring log to identify the goal. @@ -722,28 +706,28 @@ function SCORING:AddGoalScorePlayer( PlayerName, GoalTag, Text, Score ) self:F( { PlayerName, PlayerName, GoalTag, Text, Score } ) -- PlayerName can be nil, if the Unit with the player crashed or due to another reason. - if PlayerName then + 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.Goals[GoalTag].Score = PlayerData.Goals[GoalTag].Score + Score PlayerData.Score = PlayerData.Score + Score - - MESSAGE:NewType( self.DisplayMessagePrefix .. Text, MESSAGE.Type.Information ):ToAll() - + + MESSAGE:NewType( self.DisplayMessagePrefix .. Text, + MESSAGE.Type.Information ) + :ToAll() + self:ScoreCSV( PlayerName, "", "GOAL_" .. string.upper( GoalTag ), 1, Score, nil ) end end - - --- Add a goal score for a player. -- The method takes the PlayerUnit for which the Goal score needs to be set. -- The GoalTag is a string or identifier that is taken into the CSV file scoring log to identify the goal. -- A free text can be given that is shown to the players. -- The Score can be both positive and negative. -- @param #SCORING self --- @param Wrapper.Unit#UNIT PlayerUnit The @{Wrapper.Unit} of the Player. Other Properties for the scoring are taken from this PlayerUnit, like coalition, type etc. +-- @param Wrapper.Unit#UNIT PlayerUnit The @{Wrapper.Unit} of the Player. Other Properties for the scoring are taken from this PlayerUnit, like coalition, type etc. -- @param #string GoalTag The string or identifier that is used in the CSV file to identify the goal (sort or group later in Excel). -- @param #string Text A free text that is shown to the players. -- @param #number Score The score can be both positive or negative ( Penalty ). @@ -754,20 +738,21 @@ function SCORING:AddGoalScore( PlayerUnit, GoalTag, Text, Score ) self:F( { PlayerUnit.UnitName, PlayerName, GoalTag, Text, Score } ) -- PlayerName can be nil, if the Unit with the player crashed or due to another reason. - if PlayerName then + 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.Goals[GoalTag].Score = PlayerData.Goals[GoalTag].Score + Score PlayerData.Score = PlayerData.Score + Score - - MESSAGE:NewType( self.DisplayMessagePrefix .. Text, MESSAGE.Type.Information ):ToAll() - + + MESSAGE:NewType( self.DisplayMessagePrefix .. Text, + MESSAGE.Type.Information ) + :ToAll() + self:ScoreCSV( PlayerName, "", "GOAL_" .. string.upper( GoalTag ), 1, Score, PlayerUnit:GetName() ) end end - --- Registers Scores the players completing a Mission Task. -- @param #SCORING self -- @param Tasking.Mission#MISSION Mission @@ -782,23 +767,25 @@ function SCORING:_AddMissionTaskScore( Mission, PlayerUnit, Text, Score ) self:F( { Mission:GetName(), PlayerUnit.UnitName, PlayerName, Text, Score } ) -- PlayerName can be nil, if the Unit with the player crashed or due to another reason. - if PlayerName then + 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() - + + 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 @@ -816,21 +803,21 @@ function SCORING:_AddMissionGoalScore( Mission, PlayerName, Text, Score ) self:F( { Mission:GetName(), PlayerName, Text, Score } ) -- PlayerName can be nil, if the Unit with the player crashed or due to another reason. - if PlayerName then + 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 ) @@ -844,7 +831,7 @@ end -- @param #string Text -- @param #number Score function SCORING:_AddMissionScore( Mission, Text, Score ) - + local MissionName = Mission:GetName() self:F( { Mission, Text, Score } ) @@ -858,32 +845,30 @@ function SCORING:_AddMissionScore( Mission, Text, Score ) 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() + 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 - - --- Handles the OnPlayerEnterUnit event for the scoring. -- @param #SCORING self -- @param Core.Event#EVENTDATA Event ---function SCORING:OnEventPlayerEnterUnit( Event ) +-- function SCORING:OnEventPlayerEnterUnit( Event ) -- if Event.IniUnit then -- self:_AddPlayerFromUnit( Event.IniUnit ) -- self:SetScoringMenu( Event.IniGroup ) -- end ---end +-- end --- Handles the OnBirth event for the scoring. -- @param #SCORING self -- @param Core.Event#EVENTDATA Event function SCORING:OnEventBirth( Event ) - + if Event.IniUnit then if Event.IniObjectCategory == 1 then local PlayerName = Event.IniUnit:GetPlayerName() @@ -903,12 +888,11 @@ function SCORING:OnEventPlayerLeaveUnit( Event ) local Menu = self:GetState( Event.IniUnit:GetGroup(), "ScoringMenu" ) -- Core.Menu#MENU_GROUP if Menu then -- TODO: Check if this fixes #281. - --Menu:Remove() + -- Menu:Remove() end end end - --- Handles the OnHit event for the scoring. -- @param #SCORING self -- @param Core.Event#EVENTDATA Event @@ -953,9 +937,9 @@ function SCORING:_EventOnHit( Event ) InitPlayerName = Event.IniPlayerName InitCoalition = Event.IniCoalition - --TODO: Workaround Client DCS Bug - --InitCategory = InitUnit:getCategory() - --InitCategory = InitUnit:getDesc().category + -- TODO: Workaround Client DCS Bug + -- InitCategory = InitUnit:getCategory() + -- InitCategory = InitUnit:getDesc().category InitCategory = Event.IniCategory InitType = Event.IniTypeName @@ -963,10 +947,9 @@ function SCORING:_EventOnHit( Event ) InitUnitCategory = _SCORINGCategory[InitCategory] InitUnitType = InitType - self:T( { InitUnitName, InitGroupName, InitPlayerName, InitCoalition, InitCategory, InitType , InitUnitCoalition, InitUnitCategory, InitUnitType } ) + self:T( { InitUnitName, InitGroupName, InitPlayerName, InitCoalition, InitCategory, InitType, InitUnitCoalition, InitUnitCategory, InitUnitType } ) end - if Event.TgtDCSUnit then TargetUnit = Event.TgtDCSUnit @@ -977,9 +960,9 @@ function SCORING:_EventOnHit( Event ) TargetPlayerName = Event.TgtPlayerName TargetCoalition = Event.TgtCoalition - --TODO: Workaround Client DCS Bug - --TargetCategory = TargetUnit:getCategory() - --TargetCategory = TargetUnit:getDesc().category + -- TODO: Workaround Client DCS Bug + -- TargetCategory = TargetUnit:getCategory() + -- TargetCategory = TargetUnit:getDesc().category TargetCategory = Event.TgtCategory TargetType = Event.TgtTypeName @@ -998,21 +981,21 @@ function SCORING:_EventOnHit( Event ) end self:T( "Hitting Something" ) - + -- What is he hitting? if TargetCategory then - + -- A target got hit, score it. -- Player contains the score data from self.Players[InitPlayerName] local Player = self.Players[InitPlayerName] - + -- Ensure there is a hit table per TargetCategory and TargetUnitName. Player.Hit[TargetCategory] = Player.Hit[TargetCategory] or {} Player.Hit[TargetCategory][TargetUnitName] = Player.Hit[TargetCategory][TargetUnitName] or {} - + -- PlayerHit contains the score counters and data per unit that was hit. local PlayerHit = Player.Hit[TargetCategory][TargetUnitName] - + PlayerHit.Score = PlayerHit.Score or 0 PlayerHit.Penalty = PlayerHit.Penalty or 0 PlayerHit.ScoreHit = PlayerHit.ScoreHit or 0 @@ -1024,39 +1007,33 @@ function SCORING:_EventOnHit( Event ) -- Only grant hit scores if there was more than one second between the last hit. if timer.getTime() - PlayerHit.TimeStamp > 1 then PlayerHit.TimeStamp = timer.getTime() - + if TargetPlayerName ~= nil then -- It is a player hitting another player ... - + -- Ensure there is a Player to Player hit reference table. Player.HitPlayers[TargetPlayerName] = true end - + local Score = 0 - + if InitCoalition then -- A coalition object was hit. if InitCoalition == TargetCoalition then Player.Penalty = Player.Penalty + 10 PlayerHit.Penalty = PlayerHit.Penalty + 10 PlayerHit.PenaltyHit = PlayerHit.PenaltyHit + 1 - + if TargetPlayerName ~= nil then -- It is a player hitting another player ... - 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() ) + 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() ) + 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 @@ -1064,33 +1041,26 @@ function SCORING:_EventOnHit( Event ) PlayerHit.Score = PlayerHit.Score + 1 PlayerHit.ScoreHit = PlayerHit.ScoreHit + 1 if TargetPlayerName ~= nil then -- It is a player hitting another player ... - 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() ) + 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() ) + 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 -- A scenery object was hit. - 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() ) + 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 @@ -1099,10 +1069,10 @@ function SCORING:_EventOnHit( Event ) elseif InitPlayerName == nil then -- It is an AI hitting a player??? end - + -- It is a weapon initiated by a player, that is hitting something -- This seems to occur only with scenery and static objects. - if Event.WeaponPlayerName ~= nil then + if Event.WeaponPlayerName ~= nil then self:_AddPlayerFromUnit( Event.WeaponUNIT ) if self.Players[Event.WeaponPlayerName] then -- This should normally not happen, but i'll test it anyway. if TargetPlayerName ~= nil then -- It is a player hitting another player ... @@ -1110,21 +1080,21 @@ function SCORING:_EventOnHit( Event ) end self:T( "Hitting Scenery" ) - + -- What is he hitting? if TargetCategory then - + -- A scenery or static got hit, score it. -- Player contains the score data from self.Players[WeaponPlayerName] local Player = self.Players[Event.WeaponPlayerName] - + -- Ensure there is a hit table per TargetCategory and TargetUnitName. Player.Hit[TargetCategory] = Player.Hit[TargetCategory] or {} Player.Hit[TargetCategory][TargetUnitName] = Player.Hit[TargetCategory][TargetUnitName] or {} - + -- PlayerHit contains the score counters and data per unit that was hit. local PlayerHit = Player.Hit[TargetCategory][TargetUnitName] - + PlayerHit.Score = PlayerHit.Score or 0 PlayerHit.Penalty = PlayerHit.Penalty or 0 PlayerHit.ScoreHit = PlayerHit.ScoreHit or 0 @@ -1136,46 +1106,41 @@ function SCORING:_EventOnHit( Event ) -- Only grant hit scores if there was more than one second between the last hit. if timer.getTime() - PlayerHit.TimeStamp > 1 then PlayerHit.TimeStamp = timer.getTime() - + local Score = 0 - + if InitCoalition then -- A coalition object was hit, probably a static. if InitCoalition == TargetCoalition then -- TODO: Penalty according scale - Player.Penalty = Player.Penalty + 10 --* self.ScaleDestroyPenalty - PlayerHit.Penalty = PlayerHit.Penalty + 10 --* self.ScaleDestroyPenalty + Player.Penalty = Player.Penalty + 10 -- * self.ScaleDestroyPenalty + PlayerHit.Penalty = PlayerHit.Penalty + 10 -- * self.ScaleDestroyPenalty PlayerHit.PenaltyHit = PlayerHit.PenaltyHit + 1 * self.ScaleDestroyPenalty - - 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() ) + + 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() ) + 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 -- A scenery object was hit. - 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() ) + 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 @@ -1211,8 +1176,8 @@ function SCORING:_EventOnDeadOrCrash( Event ) TargetPlayerName = Event.IniPlayerName TargetCoalition = Event.IniCoalition - --TargetCategory = TargetUnit:getCategory() - --TargetCategory = TargetUnit:getDesc().category -- Workaround + -- TargetCategory = TargetUnit:getCategory() + -- TargetCategory = TargetUnit:getDesc().category -- Workaround TargetCategory = Event.IniCategory TargetType = Event.IniTypeName @@ -1242,10 +1207,10 @@ function SCORING:_EventOnDeadOrCrash( Event ) -- What is the player destroying? if Player and Player.Hit and Player.Hit[TargetCategory] and Player.Hit[TargetCategory][TargetUnitName] and Player.Hit[TargetCategory][TargetUnitName].TimeStamp ~= 0 then -- Was there a hit for this unit for this player before registered??? - + 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 {} @@ -1253,7 +1218,7 @@ function SCORING:_EventOnDeadOrCrash( Event ) 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.Penalty = TargetDestroy.Penalty or 0 TargetDestroy.PenaltyDestroy = TargetDestroy.PenaltyDestroy or 0 if TargetCoalition then @@ -1261,85 +1226,73 @@ function SCORING:_EventOnDeadOrCrash( Event ) 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 } ) - + 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 -- A player destroyed another player - 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() ) + 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() ) + 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 ) + Destroyed = true else local ThreatLevelTarget = TargetThreatLevel local ThreatTypeTarget = TargetThreatType local ThreatLevelPlayer = Player.ThreatLevel / 10 + 1 - local ThreatScore = math.ceil( ( ThreatLevelTarget / ThreatLevelPlayer ) * self.ScaleDestroyScore / 10 ) - - self:F( { ThreatLevel = ThreatScore, ThreatLevelTarget = ThreatLevelTarget, ThreatTypeTarget = ThreatTypeTarget, ThreatLevelPlayer = ThreatLevelPlayer } ) - + 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 -- A player destroyed another player - 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() ) + 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() ) + 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 ) - + Destroyed = true + local UnitName = TargetUnit:GetName() local Score = self.ScoringObjects[UnitName] if Score then Player.Score = Player.Score + Score TargetDestroy.Score = TargetDestroy.Score + Score - MESSAGE - :NewType( self.DisplayMessagePrefix .. "Special target '" .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. " destroyed! " .. - "Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! Total: " .. Player.Score - Player.Penalty, - MESSAGE.Type.Information - ) - :ToAllIf( self:IfMessagesScore() and self:IfMessagesToAll() ) - :ToCoalitionIf( InitCoalition, self:IfMessagesScore() and self:IfMessagesToCoalition() ) + 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 - + -- Check if there are Zones where the destruction happened. for ZoneName, ScoreZoneData in pairs( self.ScoringZones ) do self:F( { ScoringZone = ScoreZoneData } ) @@ -1348,42 +1301,39 @@ function SCORING:_EventOnDeadOrCrash( Event ) 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() ) + 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 -- Check if there are Zones where the destruction happened. for ZoneName, ScoreZoneData in pairs( self.ScoringZones ) do - self:F( { ScoringZone = ScoreZoneData } ) + self:F( { ScoringZone = ScoreZoneData } ) local ScoreZone = ScoreZoneData.ScoreZone -- Core.Zone#ZONE_BASE 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 + MESSAGE:NewType( self.DisplayMessagePrefix .. "Scenery destroyed in zone '" .. ScoreZone:GetName() .. "'." .. + "Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " .. "Total: " .. Player.Score - Player.Penalty, + MESSAGE.Type.Information ) + :ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() ) + :ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() ) + self:ScoreCSV( PlayerName, "", "DESTROY_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, "", "Scenery", TargetUnitType ) + Destroyed = true end end end - + -- Delete now the hit cache if the target was destroyed. -- Otherwise points will be granted every time a target gets killed by the players that hit that target. -- This is only relevant for player to player destroys. @@ -1395,7 +1345,6 @@ function SCORING:_EventOnDeadOrCrash( Event ) end end - --- Produce detailed report of player hit scores. -- @param #SCORING self -- @param #string PlayerName The name of the player. @@ -1437,18 +1386,17 @@ function SCORING:ReportDetailedPlayerHits( PlayerName ) PlayerScore = PlayerScore + Score PlayerPenalty = PlayerPenalty + Penalty else - --ScoreMessageHits = ScoreMessageHits .. string.format( "%s:%d ", string.format(CategoryName, 1, 1), 0 ) + -- ScoreMessageHits = ScoreMessageHits .. string.format( "%s:%d ", string.format(CategoryName, 1, 1), 0 ) end end if ScoreMessageHits ~= "" then ScoreMessage = "Hits: " .. ScoreMessageHits end end - + return ScoreMessage, PlayerScore, PlayerPenalty end - --- Produce detailed report of player destroy scores. -- @param #SCORING self -- @param #string PlayerName The name of the player. @@ -1495,7 +1443,7 @@ function SCORING:ReportDetailedPlayerDestroys( PlayerName ) PlayerScore = PlayerScore + Score PlayerPenalty = PlayerPenalty + Penalty else - --ScoreMessageDestroys = ScoreMessageDestroys .. string.format( "%s:%d ", string.format(CategoryName, 1, 1), 0 ) + -- ScoreMessageDestroys = ScoreMessageDestroys .. string.format( "%s:%d ", string.format(CategoryName, 1, 1), 0 ) end end if ScoreMessageDestroys ~= "" then @@ -1535,7 +1483,7 @@ function SCORING:ReportDetailedPlayerCoalitionChanges( PlayerName ) ScoreMessage = ScoreMessage .. "Coalition Penalties: " .. ScoreMessageCoalitionChangePenalties end end - + return ScoreMessage, PlayerScore, PlayerPenalty end @@ -1572,7 +1520,7 @@ function SCORING:ReportDetailedPlayerGoals( PlayerName ) ScoreMessage = "Goals: " .. ScoreMessageGoal end end - + return ScoreMessage, PlayerScore, PlayerPenalty end @@ -1610,11 +1558,10 @@ function SCORING:ReportDetailedPlayerMissions( PlayerName ) ScoreMessage = "Tasks: " .. ScoreTask .. " Mission: " .. ScoreMission .. " ( " .. ScoreMessageMission .. ")" end end - + return ScoreMessage, PlayerScore, PlayerPenalty end - --- Report Group Score Summary -- @param #SCORING self -- @param Wrapper.Group#GROUP PlayerGroup The player group. @@ -1628,11 +1575,11 @@ function SCORING:ReportScoreGroupSummary( PlayerGroup ) for UnitID, PlayerUnit in pairs( PlayerUnits ) do local PlayerUnit = PlayerUnit -- Wrapper.Unit#UNIT local PlayerName = PlayerUnit:GetPlayerName() - + if PlayerName then - + local ReportHits, ScoreHits, PenaltyHits = self:ReportDetailedPlayerHits( PlayerName ) - ReportHits = ReportHits ~= "" and "\n- " .. ReportHits or ReportHits + ReportHits = ReportHits ~= "" and "\n- " .. ReportHits or ReportHits self:F( { ReportHits, ScoreHits, PenaltyHits } ) local ReportDestroys, ScoreDestroys, PenaltyDestroys = self:ReportDetailedPlayerDestroys( PlayerName ) @@ -1650,17 +1597,16 @@ function SCORING:ReportScoreGroupSummary( PlayerGroup ) 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 - ) + + 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 @@ -1680,11 +1626,11 @@ function SCORING:ReportScoreGroupDetailed( PlayerGroup ) for UnitID, PlayerUnit in pairs( PlayerUnits ) do local PlayerUnit = PlayerUnit -- Wrapper.Unit#UNIT local PlayerName = PlayerUnit:GetPlayerName() - + if PlayerName then - + local ReportHits, ScoreHits, PenaltyHits = self:ReportDetailedPlayerHits( PlayerName ) - ReportHits = ReportHits ~= "" and "\n- " .. ReportHits or ReportHits + ReportHits = ReportHits ~= "" and "\n- " .. ReportHits or ReportHits self:F( { ReportHits, ScoreHits, PenaltyHits } ) local ReportDestroys, ScoreDestroys, PenaltyDestroys = self:ReportDetailedPlayerDestroys( PlayerName ) @@ -1694,7 +1640,7 @@ function SCORING:ReportScoreGroupDetailed( PlayerGroup ) 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 } ) @@ -1702,22 +1648,21 @@ function SCORING:ReportScoreGroupDetailed( PlayerGroup ) 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 - ) + + 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 @@ -1734,13 +1679,13 @@ function SCORING:ReportScoreAllSummary( PlayerGroup ) 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 + ReportHits = ReportHits ~= "" and "\n- " .. ReportHits or ReportHits self:F( { ReportHits, ScoreHits, PenaltyHits } ) local ReportDestroys, ScoreDestroys, PenaltyDestroys = self:ReportDetailedPlayerDestroys( PlayerName ) @@ -1758,34 +1703,32 @@ function SCORING:ReportScoreAllSummary( PlayerGroup ) 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 - ) + + 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) +function SCORING:SecondsToClock( sSeconds ) local nSeconds = sSeconds if nSeconds == 0 then - --return nil; + -- return nil; 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 + 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 @@ -1799,7 +1742,7 @@ end -- ScoringObject:OpenCSV( "Player Scores" ) function SCORING:OpenCSV( ScoringCSV ) self:F( ScoringCSV ) - + if lfs and io and os then if ScoringCSV then self.ScoringCSV = ScoringCSV @@ -1810,9 +1753,9 @@ function SCORING:OpenCSV( ScoringCSV ) 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") + self.CSVFile:write( '"GameName","RunTime","Time","PlayerName","TargetPlayerName","ScoreType","PlayerUnitCoalition","PlayerUnitCategory","PlayerUnitType","PlayerUnitName","TargetUnitCoalition","TargetUnitCategory","TargetUnitType","TargetUnitName","Times","Score"\n' ) + + self.RunTime = os.date( "%y-%m-%d_%H-%M-%S" ) else error( "A string containing the CSV file name must be given." ) end @@ -1822,7 +1765,6 @@ function SCORING:OpenCSV( ScoringCSV ) return self end - --- Registers a score for a player. -- @param #SCORING self -- @param #string PlayerName The name of the player. @@ -1840,10 +1782,10 @@ end -- @param #string TargetUnitType The type of the target unit. -- @return #SCORING self function SCORING:ScoreCSV( PlayerName, TargetPlayerName, ScoreType, ScoreTimes, ScoreAmount, PlayerUnitName, PlayerUnitCoalition, PlayerUnitCategory, PlayerUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType ) - --write statistic information to file + -- write statistic information to file local ScoreTime = self:SecondsToClock( timer.getTime() ) PlayerName = PlayerName:gsub( '"', '_' ) - + TargetPlayerName = TargetPlayerName or "" TargetPlayerName = TargetPlayerName:gsub( '"', '_' ) @@ -1852,7 +1794,7 @@ function SCORING:ScoreCSV( PlayerName, TargetPlayerName, ScoreType, ScoreTimes, if PlayerUnit then if not PlayerUnitCategory then - --PlayerUnitCategory = SCORINGCategory[PlayerUnit:getCategory()] + -- PlayerUnitCategory = SCORINGCategory[PlayerUnit:getCategory()] PlayerUnitCategory = _SCORINGCategory[PlayerUnit:getDesc().category] end @@ -1905,7 +1847,6 @@ function SCORING:ScoreCSV( PlayerName, TargetPlayerName, ScoreType, ScoreTimes, end end - function SCORING:CloseCSV() if lfs and io and os then self.CSVFile:close() From a4b600b97dfaf882a265807a70f688fc0705819a Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Fri, 31 Dec 2021 17:44:01 +0100 Subject: [PATCH 047/200] Update Airbase.lua (#1680) Change Nevada AFB names with latest stable patch --- Moose Development/Moose/Wrapper/Airbase.lua | 30 ++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 95c3e75af..fbac09d45 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -137,31 +137,31 @@ AIRBASE.Caucasus = { -- * AIRBASE.Nevada.Laughlin_Airport -- * AIRBASE.Nevada.Lincoln_County -- * AIRBASE.Nevada.Mesquite --- * AIRBASE.Nevada.Mina_Airport_3Q0 +-- * AIRBASE.Nevada.Mina_Airport -- * AIRBASE.Nevada.North_Las_Vegas -- * AIRBASE.Nevada.Pahute_Mesa_Airstrip -- * AIRBASE.Nevada.Tonopah_Airport -- * AIRBASE.Nevada.Tonopah_Test_Range_Airfield --- +-- -- @field Nevada 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", + ["Creech_AFB"] = "Creech", + ["Groom_Lake_AFB"] = "Groom Lake", + ["McCarran_International_Airport"] = "McCarran International", + ["Nellis_AFB"] = "Nellis", + ["Beatty_Airport"] = "Beatty", + ["Boulder_City_Airport"] = "Boulder City", ["Echo_Bay"] = "Echo Bay", - ["Henderson_Executive_Airport"] = "Henderson Executive Airport", - ["Jean_Airport"] = "Jean Airport", - ["Laughlin_Airport"] = "Laughlin Airport", + ["Henderson_Executive_Airport"] = "Henderson Executive", + ["Jean_Airport"] = "Jean", + ["Laughlin_Airport"] = "Laughlin", ["Lincoln_County"] = "Lincoln County", ["Mesquite"] = "Mesquite", - ["Mina_Airport_3Q0"] = "Mina Airport 3Q0", + ["Mina_Airport"] = "Mina", ["North_Las_Vegas"] = "North Las Vegas", - ["Pahute_Mesa_Airstrip"] = "Pahute Mesa Airstrip", - ["Tonopah_Airport"] = "Tonopah Airport", - ["Tonopah_Test_Range_Airfield"] = "Tonopah Test Range Airfield", + ["Pahute_Mesa_Airstrip"] = "Pahute Mesa", + ["Tonopah_Airport"] = "Tonopah", + ["Tonopah_Test_Range_Airfield"] = "Tonopah Test Range", } --- Airbases of the Normandy map: From d54d991bdd8993a727b574c5432c49b3025e055c Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 31 Dec 2021 17:52:06 +0100 Subject: [PATCH 048/200] Fix ATC new NTTR AFB names --- Moose Development/Moose/Functional/ATC_Ground.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Functional/ATC_Ground.lua b/Moose Development/Moose/Functional/ATC_Ground.lua index 455f3eaf5..6b29c20d0 100644 --- a/Moose Development/Moose/Functional/ATC_Ground.lua +++ b/Moose Development/Moose/Functional/ATC_Ground.lua @@ -1288,7 +1288,7 @@ ATC_GROUND_NEVADA = { }, }, }, - [AIRBASE.Nevada.Mina_Airport_3Q0] = { + [AIRBASE.Nevada.Mina_Airport] = { PointsRunways = { [1] = { [1] = {["y"]=-290054.57371429,["x"]=-160930.02228572,}, From 854bee051985e5979f4574dd03ca4720b5fa4abf Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 31 Dec 2021 17:52:20 +0100 Subject: [PATCH 049/200] Mark visible as deprecated --- Moose Development/Moose/AI/AI_A2A_Dispatcher.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua index 6c5ac333c..d54f54a71 100644 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua @@ -1685,7 +1685,7 @@ do -- AI_A2A_DISPATCHER return DefenderSquadron end - --- Set the Squadron visible before startup of the dispatcher. + --- [DEPRECATED - Might create problems launching planes] Set the Squadron visible before startup of the dispatcher. -- All planes will be spawned as uncontrolled on the parking spot. -- They will lock the parking spot. -- @param #AI_A2A_DISPATCHER self From 70d922fad65de1da8794d61c95e9c649bf167bf6 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 4 Jan 2022 15:10:25 +0100 Subject: [PATCH 050/200] SHort name mina AP --- Moose Development/Moose/Functional/ATC_Ground.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Functional/ATC_Ground.lua b/Moose Development/Moose/Functional/ATC_Ground.lua index 6b29c20d0..822c1ace3 100644 --- a/Moose Development/Moose/Functional/ATC_Ground.lua +++ b/Moose Development/Moose/Functional/ATC_Ground.lua @@ -1053,7 +1053,7 @@ end -- * `AIRBASE.Nevada.Lincoln_County` -- * `AIRBASE.Nevada.McCarran_International_Airport` -- * `AIRBASE.Nevada.Mesquite` --- * `AIRBASE.Nevada.Mina_Airport_3Q0` +-- * `AIRBASE.Nevada.Mina_Airport` -- * `AIRBASE.Nevada.Nellis_AFB` -- * `AIRBASE.Nevada.North_Las_Vegas` -- * `AIRBASE.Nevada.Pahute_Mesa_Airstrip` From c2ecd86bb4dd3e725ca9ad790a14494e265dd828 Mon Sep 17 00:00:00 2001 From: TommyC81 Date: Mon, 10 Jan 2022 18:10:30 +0400 Subject: [PATCH 051/200] Minor fixes (#1684) * Update AI_A2A_Dispatcher.lua Minor code formatting. * Update Airbase.lua Code formatting. --- .../Moose/AI/AI_A2A_Dispatcher.lua | 6 +- Moose Development/Moose/Wrapper/Airbase.lua | 935 +++++++++--------- 2 files changed, 465 insertions(+), 476 deletions(-) diff --git a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua index d54f54a71..fb9e31513 100644 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua @@ -1510,7 +1510,7 @@ do -- AI_A2A_DISPATCHER local Message = "Clearing (" .. DefenderTask.Type .. ") " Message = Message .. Defender:GetName() if Target then - Message = Message .. ( Target and ( " from " .. Target.Index .. " [" .. Target.Set:Count() .. "]" ) ) or "" + Message = Message .. ((Target and (" from " .. Target.Index .. " [" .. Target.Set:Count() .. "]")) or "") end self:F( { Target = Message } ) end @@ -1559,7 +1559,7 @@ do -- AI_A2A_DISPATCHER local Message = "(" .. self.DefenderTasks[Defender].Type .. ") " Message = Message .. Defender:GetName() - Message = Message .. ( AttackerDetection and ( " target " .. AttackerDetection.Index .. " [" .. AttackerDetection.Set:Count() .. "]" ) ) or "" + Message = Message .. ((AttackerDetection and (" target " .. AttackerDetection.Index .. " [" .. AttackerDetection.Set:Count() .. "]")) or "") self:F( { AttackerDetection = Message } ) if AttackerDetection then self.DefenderTasks[Defender].Target = AttackerDetection @@ -2653,7 +2653,7 @@ do -- AI_A2A_DISPATCHER -- -- Let flights by default land and despawn at engine shutdown. -- A2ADispatcher:SetDefaultLandingAtEngineShutdown() -- - function AI_A2A_DISPATCHER:SetDefaultLandingAtEngineShutdown() + function AI_A2A_DISPATCHER:SetDefaultLandingAtEngineShutdown() self:SetDefaultLanding( AI_A2A_DISPATCHER.Landing.AtEngineShutdown ) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index fbac09d45..492d5af24 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -11,7 +11,6 @@ -- @module Wrapper.Airbase -- @image Wrapper_Airbase.JPG - --- @type AIRBASE -- @field #string ClassName Name of the class, i.e. "AIRBASE". -- @field #table CategoryName Names of airbase categories. @@ -52,7 +51,7 @@ -- * @{#AIRBASE.Find}(): Find a AIRBASE instance from the _DATABASE object using a DCS Airbase object. -- * @{#AIRBASE.FindByName}(): Find a AIRBASE instance from the _DATABASE object using a DCS Airbase name. -- --- IMPORTANT: ONE SHOULD NEVER SANATIZE these AIRBASE OBJECT REFERENCES! (make the AIRBASE object references nil). +-- IMPORTANT: ONE SHOULD NEVER SANITIZE these AIRBASE OBJECT REFERENCES! (make the AIRBASE object references nil). -- -- ## DCS Airbase APIs -- @@ -63,14 +62,14 @@ -- -- @field #AIRBASE AIRBASE AIRBASE = { - ClassName="AIRBASE", + ClassName = "AIRBASE", CategoryName = { - [Airbase.Category.AIRDROME] = "Airdrome", - [Airbase.Category.HELIPAD] = "Helipad", - [Airbase.Category.SHIP] = "Ship", - }, - activerwyno=nil, - } + [Airbase.Category.AIRDROME] = "Airdrome", + [Airbase.Category.HELIPAD] = "Helipad", + [Airbase.Category.SHIP] = "Ship", + }, + activerwyno = nil, +} --- Enumeration to identify the airbases in the Caucasus region. -- @@ -121,7 +120,7 @@ AIRBASE.Caucasus = { ["Nalchik"] = "Nalchik", ["Mozdok"] = "Mozdok", ["Beslan"] = "Beslan", - } +} --- Airbases of the Nevada map: -- @@ -162,7 +161,7 @@ AIRBASE.Nevada = { ["Pahute_Mesa_Airstrip"] = "Pahute Mesa", ["Tonopah_Airport"] = "Tonopah", ["Tonopah_Test_Range_Airfield"] = "Tonopah Test Range", - } +} --- Airbases of the Normandy map: -- @@ -386,64 +385,62 @@ AIRBASE.TheChannel = { -- * AIRBASE.Syria.An_Nasiriyah -- * AIRBASE.Syria.Abu_al_Duhur -- ---@field Syria -AIRBASE.Syria={ - ["Kuweires"]="Kuweires", - ["Marj_Ruhayyil"]="Marj Ruhayyil", - ["Kiryat_Shmona"]="Kiryat Shmona", - ["Marj_as_Sultan_North"]="Marj as Sultan North", - ["Eyn_Shemer"]="Eyn Shemer", - ["Incirlik"]="Incirlik", - ["Damascus"]="Damascus", - ["Bassel_Al_Assad"]="Bassel Al-Assad", - ["Rosh_Pina"]="Rosh Pina", - ["Aleppo"]="Aleppo", - ["Al_Qusayr"]="Al Qusayr", - ["Wujah_Al_Hajar"]="Wujah Al Hajar", - ["Al_Dumayr"]="Al-Dumayr", - ["Gazipasa"]="Gazipasa", - ["Ru_Convoy_4"]="Ru Convoy-4", - ["Hatay"]="Hatay", - ["Nicosia"]="Nicosia", - ["Pinarbashi"]="Pinarbashi", - ["Paphos"]="Paphos", - ["Kingsfield"]="Kingsfield", - ["Thalah"]="Tha'lah", - ["Haifa"]="Haifa", - ["Khalkhalah"]="Khalkhalah", - ["Megiddo"]="Megiddo", - ["Lakatamia"]="Lakatamia", - ["Rayak"]="Rayak", - ["Larnaca"]="Larnaca", - ["Mezzeh"]="Mezzeh", - ["Gecitkale"]="Gecitkale", - ["Akrotiri"]="Akrotiri", - ["Naqoura"]="Naqoura", - ["Gaziantep"]="Gaziantep", - ["Sayqal"]="Sayqal", - ["Tiyas"]="Tiyas", - ["Shayrat"]="Shayrat", - ["Taftanaz"]="Taftanaz", - ["H4"]="H4", - ["King_Hussein_Air_College"]="King Hussein Air College", - ["Rene_Mouawad"]="Rene Mouawad", - ["Jirah"]="Jirah", - ["Ramat_David"]="Ramat David", - ["Qabr_as_Sitt"]="Qabr as Sitt", - ["Minakh"]="Minakh", - ["Adana_Sakirpasa"]="Adana Sakirpasa", - ["Palmyra"]="Palmyra", - ["Hama"]="Hama", - ["Ercan"]="Ercan", - ["Marj_as_Sultan_South"]="Marj as Sultan South", - ["Tabqa"]="Tabqa", - ["Beirut_Rafic_Hariri"]="Beirut-Rafic Hariri", - ["An_Nasiriyah"]="An Nasiriyah", - ["Abu_al_Duhur"]="Abu al-Duhur", +-- @field Syria +AIRBASE.Syria = { + ["Kuweires"] = "Kuweires", + ["Marj_Ruhayyil"] = "Marj Ruhayyil", + ["Kiryat_Shmona"] = "Kiryat Shmona", + ["Marj_as_Sultan_North"] = "Marj as Sultan North", + ["Eyn_Shemer"] = "Eyn Shemer", + ["Incirlik"] = "Incirlik", + ["Damascus"] = "Damascus", + ["Bassel_Al_Assad"] = "Bassel Al-Assad", + ["Rosh_Pina"] = "Rosh Pina", + ["Aleppo"] = "Aleppo", + ["Al_Qusayr"] = "Al Qusayr", + ["Wujah_Al_Hajar"] = "Wujah Al Hajar", + ["Al_Dumayr"] = "Al-Dumayr", + ["Gazipasa"] = "Gazipasa", + ["Ru_Convoy_4"] = "Ru Convoy-4", + ["Hatay"] = "Hatay", + ["Nicosia"] = "Nicosia", + ["Pinarbashi"] = "Pinarbashi", + ["Paphos"] = "Paphos", + ["Kingsfield"] = "Kingsfield", + ["Thalah"] = "Tha'lah", + ["Haifa"] = "Haifa", + ["Khalkhalah"] = "Khalkhalah", + ["Megiddo"] = "Megiddo", + ["Lakatamia"] = "Lakatamia", + ["Rayak"] = "Rayak", + ["Larnaca"] = "Larnaca", + ["Mezzeh"] = "Mezzeh", + ["Gecitkale"] = "Gecitkale", + ["Akrotiri"] = "Akrotiri", + ["Naqoura"] = "Naqoura", + ["Gaziantep"] = "Gaziantep", + ["Sayqal"] = "Sayqal", + ["Tiyas"] = "Tiyas", + ["Shayrat"] = "Shayrat", + ["Taftanaz"] = "Taftanaz", + ["H4"] = "H4", + ["King_Hussein_Air_College"] = "King Hussein Air College", + ["Rene_Mouawad"] = "Rene Mouawad", + ["Jirah"] = "Jirah", + ["Ramat_David"] = "Ramat David", + ["Qabr_as_Sitt"] = "Qabr as Sitt", + ["Minakh"] = "Minakh", + ["Adana_Sakirpasa"] = "Adana Sakirpasa", + ["Palmyra"] = "Palmyra", + ["Hama"] = "Hama", + ["Ercan"] = "Ercan", + ["Marj_as_Sultan_South"] = "Marj as Sultan South", + ["Tabqa"] = "Tabqa", + ["Beirut_Rafic_Hariri"] = "Beirut-Rafic Hariri", + ["An_Nasiriyah"] = "An Nasiriyah", + ["Abu_al_Duhur"] = "Abu al-Duhur", } - - --- Airbases of the Mariana Islands map: -- -- * AIRBASE.MarianaIslands.Rota_Intl @@ -453,17 +450,16 @@ AIRBASE.Syria={ -- * AIRBASE.MarianaIslands.Tinian_Intl -- * AIRBASE.MarianaIslands.Olf_Orote -- ---@field MarianaIslands -AIRBASE.MarianaIslands={ - ["Rota_Intl"]="Rota Intl", - ["Andersen_AFB"]="Andersen AFB", - ["Antonio_B_Won_Pat_Intl"]="Antonio B. Won Pat Intl", - ["Saipan_Intl"]="Saipan Intl", - ["Tinian_Intl"]="Tinian Intl", - ["Olf_Orote"]="Olf Orote", +-- @field MarianaIslands +AIRBASE.MarianaIslands = { + ["Rota_Intl"] = "Rota Intl", + ["Andersen_AFB"] = "Andersen AFB", + ["Antonio_B_Won_Pat_Intl"] = "Antonio B. Won Pat Intl", + ["Saipan_Intl"] = "Saipan Intl", + ["Tinian_Intl"] = "Tinian Intl", + ["Olf_Orote"] = "Olf Orote", } - --- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy". -- @type AIRBASE.ParkingSpot -- @field Core.Point#COORDINATE Coordinate Coordinate of the parking spot. @@ -497,14 +493,14 @@ AIRBASE.MarianaIslands={ -- @field #number HelicopterUsable 216: Combines HelicopterOnly, OpenMed and OpenBig. -- @field #number FighterAircraft 244: Combines Shelter. OpenMed and OpenBig spots. So effectively all spots usable by fixed wing aircraft. AIRBASE.TerminalType = { - Runway=16, - HelicopterOnly=40, - Shelter=68, - OpenMed=72, - OpenBig=104, - OpenMedOrBig=176, - HelicopterUsable=216, - FighterAircraft=244, + Runway = 16, + HelicopterOnly = 40, + Shelter = 68, + OpenMed = 72, + OpenBig = 104, + OpenMedOrBig = 176, + HelicopterUsable = 216, + FighterAircraft = 244, } --- Runway data. @@ -523,59 +519,59 @@ AIRBASE.TerminalType = { -- @param #AIRBASE self -- @param #string AirbaseName The name of the airbase. -- @return #AIRBASE self -function AIRBASE:Register(AirbaseName) +function AIRBASE:Register( AirbaseName ) -- Inherit everything from positionable. - local self=BASE:Inherit(self, POSITIONABLE:New(AirbaseName)) --#AIRBASE + local self = BASE:Inherit( self, POSITIONABLE:New( AirbaseName ) ) -- #AIRBASE -- Set airbase name. - self.AirbaseName=AirbaseName + self.AirbaseName = AirbaseName -- Set airbase ID. - self.AirbaseID=self:GetID(true) + self.AirbaseID = self:GetID( true ) -- Get descriptors. - self.descriptors=self:GetDesc() + self.descriptors = self:GetDesc() -- Category. - self.category=self.descriptors and self.descriptors.category or Airbase.Category.AIRDROME + self.category = self.descriptors and self.descriptors.category or Airbase.Category.AIRDROME -- Set category. - if self.category==Airbase.Category.AIRDROME then - self.isAirdrome=true - elseif self.category==Airbase.Category.HELIPAD then - self.isHelipad=true - elseif self.category==Airbase.Category.SHIP then - self.isShip=true + if self.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 -- DCS bug: Oil rigs and gas platforms have category=2 (ship). Also they cannot be retrieved by coalition.getStaticObjects() - if self.descriptors.typeName=="Oil rig" or self.descriptors.typeName=="Ga" then - self.isHelipad=true - self.isShip=false - self.category=Airbase.Category.HELIPAD - _DATABASE:AddStatic(AirbaseName) - end + if self.descriptors.typeName == "Oil rig" or self.descriptors.typeName == "Ga" then + self.isHelipad = true + self.isShip = false + self.category = Airbase.Category.HELIPAD + _DATABASE:AddStatic( AirbaseName ) + end else - self:E("ERROR: Unknown airbase category!") + self:E( "ERROR: Unknown airbase category!" ) end self:_InitParkingSpots() - local vec2=self:GetVec2() + local vec2 = self:GetVec2() -- Init coordinate. self:GetCoordinate() if vec2 then if self.isShip then - local unit=UNIT:FindByName(AirbaseName) + local unit = UNIT:FindByName( AirbaseName ) if unit then - self.AirbaseZone=ZONE_UNIT:New(AirbaseName, unit, 2500) + self.AirbaseZone = ZONE_UNIT:New( AirbaseName, unit, 2500 ) end else - self.AirbaseZone=ZONE_RADIUS:New(AirbaseName, vec2, 2500) + self.AirbaseZone = ZONE_RADIUS:New( AirbaseName, vec2, 2500 ) end else - self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s", AirbaseName)) + self:E( string.format( "ERROR: Cound not get position Vec2 of airbase %s", AirbaseName ) ) end return self @@ -610,14 +606,14 @@ end -- @param #AIRBASE self -- @param #number id Airbase ID. -- @return #AIRBASE self -function AIRBASE:FindByID(id) +function AIRBASE:FindByID( id ) - for name,_airbase in pairs(_DATABASE.AIRBASES) do - local airbase=_airbase --#AIRBASE + for name, _airbase in pairs( _DATABASE.AIRBASES ) do + local airbase = _airbase -- #AIRBASE - local aid=tonumber(airbase:GetID(true)) + local aid = tonumber( airbase:GetID( true ) ) - if aid==id then + if aid == id then return airbase end @@ -632,7 +628,7 @@ end function AIRBASE:GetDCSObject() -- Get the DCS object. - local DCSAirbase = Airbase.getByName(self.AirbaseName) + local DCSAirbase = Airbase.getByName( self.AirbaseName ) if DCSAirbase then return DCSAirbase @@ -652,14 +648,14 @@ end -- @param DCS#Coalition coalition (Optional) Return only airbases belonging to the specified coalition. By default, all airbases of the map are returned. -- @param #number category (Optional) Return only airbases of a certain category, e.g. Airbase.Category.FARP -- @return #table Table containing all airbase objects of the current map. -function AIRBASE.GetAllAirbases(coalition, category) +function AIRBASE.GetAllAirbases( coalition, category ) - local airbases={} - for _,_airbase in pairs(_DATABASE.AIRBASES) do - local airbase=_airbase --#AIRBASE - if coalition==nil or airbase:GetCoalition()==coalition then - if category==nil or category==airbase:GetAirbaseCategory() then - table.insert(airbases, airbase) + local airbases = {} + for _, _airbase in pairs( _DATABASE.AIRBASES ) do + local airbase = _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 @@ -671,14 +667,14 @@ end -- @param DCS#Coalition coalition (Optional) Return only airbases belonging to the specified coalition. By default, all airbases of the map are returned. -- @param #number category (Optional) Return only airbases of a certain category, e.g. `Airbase.Category.HELIPAD`. -- @return #table Table containing all airbase names of the current map. -function AIRBASE.GetAllAirbaseNames(coalition, category) +function AIRBASE.GetAllAirbaseNames( coalition, category ) - local airbases={} - for airbasename,_airbase in pairs(_DATABASE.AIRBASES) do - local airbase=_airbase --#AIRBASE - if coalition==nil or airbase:GetCoalition()==coalition then - if category==nil or category==airbase:GetAirbaseCategory() then - table.insert(airbases, airbasename) + local airbases = {} + for airbasename, _airbase in pairs( _DATABASE.AIRBASES ) do + local airbase = _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 @@ -690,26 +686,26 @@ end -- @param #AIRBASE self -- @param #boolean unique (Optional) If true, ships will get a negative sign as the unit ID might be the same as an airbase ID. Default off! -- @return #number The airbase ID. -function AIRBASE:GetID(unique) +function AIRBASE:GetID( unique ) if self.AirbaseID then - return unique and self.AirbaseID or math.abs(self.AirbaseID) + return unique and self.AirbaseID or math.abs( self.AirbaseID ) else - for DCSAirbaseId, DCSAirbase in ipairs(world.getAirbases()) do + for DCSAirbaseId, DCSAirbase in ipairs( world.getAirbases() ) do -- Get the airbase name. local AirbaseName = DCSAirbase:getName() -- This gives the incorrect value to be inserted into the airdromeID for DCS 2.5.6! - local airbaseID=tonumber(DCSAirbase:getID()) + local airbaseID = tonumber( DCSAirbase:getID() ) - local airbaseCategory=self:GetAirbaseCategory() + local airbaseCategory = self:GetAirbaseCategory() - if AirbaseName==self.AirbaseName then - if airbaseCategory==Airbase.Category.SHIP or airbaseCategory==Airbase.Category.HELIPAD then + if AirbaseName == self.AirbaseName then + if airbaseCategory == Airbase.Category.SHIP or airbaseCategory == Airbase.Category.HELIPAD then -- Ships get a negative sign as their unit number might be the same as the ID of another airbase. return unique and -airbaseID or airbaseID else @@ -731,19 +727,19 @@ end -- @param #table TerminalIdBlacklist Table of white listed terminal IDs. -- @return #AIRBASE self -- @usage AIRBASE:FindByName("Batumi"):SetParkingSpotWhitelist({2, 3, 4}) --Only allow terminal IDs 2, 3, 4 -function AIRBASE:SetParkingSpotWhitelist(TerminalIdWhitelist) +function AIRBASE:SetParkingSpotWhitelist( TerminalIdWhitelist ) - if TerminalIdWhitelist==nil then - self.parkingWhitelist={} + if TerminalIdWhitelist == nil then + self.parkingWhitelist = {} return self end -- Ensure we got a table. - if type(TerminalIdWhitelist)~="table" then - TerminalIdWhitelist={TerminalIdWhitelist} + if type( TerminalIdWhitelist ) ~= "table" then + TerminalIdWhitelist = { TerminalIdWhitelist } end - self.parkingWhitelist=TerminalIdWhitelist + self.parkingWhitelist = TerminalIdWhitelist return self end @@ -755,24 +751,23 @@ end -- @param #table TerminalIdBlacklist Table of black listed terminal IDs. -- @return #AIRBASE self -- @usage AIRBASE:FindByName("Batumi"):SetParkingSpotBlacklist({2, 3, 4}) --Forbit terminal IDs 2, 3, 4 -function AIRBASE:SetParkingSpotBlacklist(TerminalIdBlacklist) +function AIRBASE:SetParkingSpotBlacklist( TerminalIdBlacklist ) - if TerminalIdBlacklist==nil then - self.parkingBlacklist={} + if TerminalIdBlacklist == nil then + self.parkingBlacklist = {} return self end -- Ensure we got a table. - if type(TerminalIdBlacklist)~="table" then - TerminalIdBlacklist={TerminalIdBlacklist} + if type( TerminalIdBlacklist ) ~= "table" then + TerminalIdBlacklist = { TerminalIdBlacklist } end - self.parkingBlacklist=TerminalIdBlacklist + self.parkingBlacklist = TerminalIdBlacklist return self end - --- Get category of airbase. -- @param #AIRBASE self -- @return #number Category of airbase from GetDesc().category. @@ -825,19 +820,19 @@ end -- @param #AIRBASE self -- @param #boolean available If true, only available parking spots will be returned. -- @return #table Table with parking data. See https://wiki.hoggitworld.com/view/DCS_func_getParking -function AIRBASE:GetParkingData(available) - self:F2(available) +function AIRBASE:GetParkingData( available ) + self:F2( available ) -- Get DCS airbase object. - local DCSAirbase=self:GetDCSObject() + local DCSAirbase = self:GetDCSObject() -- Get parking data. - local parkingdata=nil + local parkingdata = nil if DCSAirbase then - parkingdata=DCSAirbase:getParking(available) + parkingdata = DCSAirbase:getParking( available ) end - self:T2({parkingdata=parkingdata}) + self:T2( { parkingdata = parkingdata } ) return parkingdata end @@ -845,15 +840,15 @@ end -- @param #AIRBASE self -- @param #AIRBASE.TerminalType termtype Terminal type of which the number of spots is counted. Default all spots but spawn points on runway. -- @return #number Number of parking spots at this airbase. -function AIRBASE:GetParkingSpotsNumber(termtype) +function AIRBASE:GetParkingSpotsNumber( termtype ) -- Get free parking spots data. - local parkingdata=self:GetParkingData(false) + 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 + local nspots = 0 + for _, parkingspot in pairs( parkingdata ) do + if AIRBASE._CheckTerminalType( parkingspot.Term_Type, termtype ) then + nspots = nspots + 1 end end @@ -865,17 +860,17 @@ end -- @param #AIRBASE.TerminalType termtype Terminal type. -- @param #boolean allowTOAC If true, spots are considered free even though TO_AC is true. Default is off which is saver to avoid spawning aircraft on top of each other. Option might be enabled for FARPS and ships. -- @return #number Number of free parking spots at this airbase. -function AIRBASE:GetFreeParkingSpotsNumber(termtype, allowTOAC) +function AIRBASE:GetFreeParkingSpotsNumber( termtype, allowTOAC ) -- Get free parking spots data. - local parkingdata=self:GetParkingData(true) + local parkingdata = self:GetParkingData( true ) - local nfree=0 - for _,parkingspot in pairs(parkingdata) do + local nfree = 0 + for _, parkingspot in pairs( parkingdata ) do -- Spots on runway are not counted unless explicitly requested. - if AIRBASE._CheckTerminalType(parkingspot.Term_Type, termtype) then - if (allowTOAC and allowTOAC==true) or parkingspot.TO_AC==false then - nfree=nfree+1 + 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 @@ -888,18 +883,18 @@ end -- @param #AIRBASE.TerminalType termtype Terminal type. -- @param #boolean allowTOAC If true, spots are considered free even though TO_AC is true. Default is off which is saver to avoid spawning aircraft on top of each other. Option might be enabled for FARPS and ships. -- @return #table Table of coordinates of the free parking spots. -function AIRBASE:GetFreeParkingSpotsCoordinates(termtype, allowTOAC) +function AIRBASE:GetFreeParkingSpotsCoordinates( termtype, allowTOAC ) -- Get free parking spots data. - local parkingdata=self:GetParkingData(true) + local parkingdata = self:GetParkingData( true ) -- Put coordinates of free spots into table. - local spots={} - for _,parkingspot in pairs(parkingdata) do + local spots = {} + for _, parkingspot in pairs( parkingdata ) do -- Coordinates on runway are not returned unless explicitly requested. - 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)) + 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 @@ -911,23 +906,23 @@ end -- @param #AIRBASE self -- @param #AIRBASE.TerminalType termtype (Optional) Terminal type. Default all. -- @return #table Table of coordinates of parking spots. -function AIRBASE:GetParkingSpotsCoordinates(termtype) +function AIRBASE:GetParkingSpotsCoordinates( termtype ) -- Get all parking spots data. - local parkingdata=self:GetParkingData(false) + local parkingdata = self:GetParkingData( false ) -- Put coordinates of free spots into table. - local spots={} - for _,parkingspot in ipairs(parkingdata) do + local spots = {} + for _, parkingspot in ipairs( parkingdata ) do -- Coordinates on runway are not returned unless explicitly requested. - if AIRBASE._CheckTerminalType(parkingspot.Term_Type, termtype) then + if AIRBASE._CheckTerminalType( parkingspot.Term_Type, termtype ) then -- Get coordinate from Vec3 terminal position. - local _coord=COORDINATE:NewFromVec3(parkingspot.vTerminalPos) + local _coord = COORDINATE:NewFromVec3( parkingspot.vTerminalPos ) -- Add to table. - table.insert(spots, _coord) + table.insert( spots, _coord ) end end @@ -941,42 +936,42 @@ end function AIRBASE:_InitParkingSpots() -- Get parking data of all spots (free or occupied) - local parkingdata=self:GetParkingData(false) + local parkingdata = self:GetParkingData( false ) -- Init table. - self.parking={} - self.parkingByID={} + self.parking = {} + self.parkingByID = {} - self.NparkingTotal=0 - self.NparkingTerminal={} - for _,terminalType in pairs(AIRBASE.TerminalType) do - self.NparkingTerminal[terminalType]=0 + self.NparkingTotal = 0 + self.NparkingTerminal = {} + for _, terminalType in pairs( AIRBASE.TerminalType ) do + self.NparkingTerminal[terminalType] = 0 end -- Put coordinates of parking spots into table. - for _,spot in pairs(parkingdata) do + for _, spot in pairs( parkingdata ) do -- New parking spot. - local park={} --#AIRBASE.ParkingSpot - 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 + local park = {} -- #AIRBASE.ParkingSpot + 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 + 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 + 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) + self.parkingByID[park.TerminalID] = park + table.insert( self.parking, park ) end return self @@ -986,7 +981,7 @@ end -- @param #AIRBASE self -- @param #number TerminalID Terminal ID. -- @return #AIRBASE.ParkingSpot Parking spot. -function AIRBASE:_GetParkingSpotByID(TerminalID) +function AIRBASE:_GetParkingSpotByID( TerminalID ) return self.parkingByID[TerminalID] end @@ -994,18 +989,18 @@ end -- @param #AIRBASE self -- @param #AIRBASE.TerminalType termtype Terminal type. -- @return #table Table free parking spots. Table has the elements ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy". -function AIRBASE:GetParkingSpotsTable(termtype) +function AIRBASE:GetParkingSpotsTable( termtype ) -- Get parking data of all spots (free or occupied) - local parkingdata=self:GetParkingData(false) + local parkingdata = self:GetParkingData( false ) -- Get parking data of all free spots. - local parkingfree=self:GetParkingData(true) + local parkingfree = self:GetParkingData( true ) -- Function to ckeck if any parking spot is free. - local function _isfree(_tocheck) - for _,_spot in pairs(parkingfree) do - if _spot.Term_Index==_tocheck.Term_Index then + local function _isfree( _tocheck ) + for _, _spot in pairs( parkingfree ) do + if _spot.Term_Index == _tocheck.Term_Index then return true end end @@ -1013,23 +1008,23 @@ function AIRBASE:GetParkingSpotsTable(termtype) end -- Put coordinates of parking spots into table. - local spots={} - for _,_spot in pairs(parkingdata) do + local spots = {} + for _, _spot in pairs( parkingdata ) do - if AIRBASE._CheckTerminalType(_spot.Term_Type, termtype) then + if AIRBASE._CheckTerminalType( _spot.Term_Type, termtype ) then - local spot=self:_GetParkingSpotByID(_spot.Term_Index) + local spot = self:_GetParkingSpotByID( _spot.Term_Index ) if spot then - spot.Free=_isfree(_spot) -- updated - spot.TOAC=_spot.TO_AC -- updated + spot.Free = _isfree( _spot ) -- updated + spot.TOAC = _spot.TO_AC -- updated - table.insert(spots, spot) + table.insert( spots, spot ) else - self:E(string.format("ERROR: Parking spot %s is nil!", tostring(_spot.Term_Index))) + self:E( string.format( "ERROR: Parking spot %s is nil!", tostring( _spot.Term_Index ) ) ) end @@ -1045,23 +1040,23 @@ end -- @param #AIRBASE.TerminalType termtype Terminal type. -- @param #boolean allowTOAC If true, spots are considered free even though TO_AC is true. Default is off which is saver to avoid spawning aircraft on top of each other. Option might be enabled for FARPS and ships. -- @return #table Table free parking spots. Table has the elements ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy". -function AIRBASE:GetFreeParkingSpotsTable(termtype, allowTOAC) +function AIRBASE:GetFreeParkingSpotsTable( termtype, allowTOAC ) -- Get parking data of all free spots. - local parkingfree=self:GetParkingData(true) + local parkingfree = self:GetParkingData( true ) -- Put coordinates of free spots into table. - 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 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) + local spot = self:_GetParkingSpotByID( _spot.Term_Index ) - spot.Free=true -- updated - spot.TOAC=_spot.TO_AC -- updated + spot.Free = true -- updated + spot.TOAC = _spot.TO_AC -- updated - table.insert(freespots, spot) + table.insert( freespots, spot ) end end @@ -1074,20 +1069,20 @@ end -- @param #AIRBASE self -- @param #number TerminalID The terminal ID of the parking spot. -- @return #AIRBASE.ParkingSpot Table free parking spots. Table has the elements ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy". -function AIRBASE:GetParkingSpotData(TerminalID) +function AIRBASE:GetParkingSpotData( TerminalID ) -- Get parking data. - local parkingdata=self:GetParkingSpotsTable() + local parkingdata = self:GetParkingSpotsTable() - for _,_spot in pairs(parkingdata) do - local spot=_spot --#AIRBASE.ParkingSpot - self:T({TerminalID=spot.TerminalID,TerminalType=spot.TerminalType}) - if TerminalID==spot.TerminalID then + for _, _spot in pairs( parkingdata ) do + local spot = _spot -- #AIRBASE.ParkingSpot + 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)) + self:E( "ERROR: Could not find spot with Terminal ID=" .. tostring( TerminalID ) ) return nil end @@ -1095,35 +1090,33 @@ end -- @param #AIRBASE self -- @param #AIRBASE.TerminalType termtype Terminal type for which marks should be placed. -- @param #boolean mark If false, do not place markers but only give output to DCS.log file. Default true. -function AIRBASE:MarkParkingSpots(termtype, mark) +function AIRBASE:MarkParkingSpots( termtype, mark ) -- Default is true. - if mark==nil then - mark=true + if mark == nil then + mark = true end -- Get parking data from getParking() wrapper function. - local parkingdata=self:GetParkingSpotsTable(termtype) + local parkingdata = self:GetParkingSpotsTable( termtype ) -- Get airbase name. - local airbasename=self:GetName() - self:E(string.format("Parking spots at %s for terminal type %s:", airbasename, tostring(termtype))) + local airbasename = self:GetName() + self:E( string.format( "Parking spots at %s for terminal type %s:", airbasename, tostring( termtype ) ) ) - for _,_spot in pairs(parkingdata) do + for _, _spot in pairs( parkingdata ) do -- Mark text. - 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) + 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 ) -- Create mark on the F10 map. if mark then - _spot.Coordinate:MarkToAll(_text) + _spot.Coordinate:MarkToAll( _text ) end -- Info to DCS.log file. - 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) + 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 @@ -1140,33 +1133,33 @@ end -- @param #number nspots (Optional) Number of freeparking spots requested. Default is the number of aircraft in the group. -- @param #table parkingdata (Optional) Parking spots data table. If not given it is automatically derived from the GetParkingSpotsTable() function. -- @return #table Table of coordinates and terminal IDs of free parking spots. Each table entry has the elements .Coordinate and .TerminalID. -function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nspots, parkingdata) - +function AIRBASE:FindFreeParkingSpotForAircraft( group, terminaltype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nspots, parkingdata ) + -- Init default - scanradius=scanradius or 50 - if scanunits==nil then - scanunits=true + scanradius = scanradius or 50 + if scanunits == nil then + scanunits = true end - if scanstatics==nil then - scanstatics=true + if scanstatics == nil then + scanstatics = true end - if scanscenery==nil then - scanscenery=false + if scanscenery == nil then + scanscenery = false end - if verysafe==nil then - verysafe=false + if verysafe == nil then + verysafe = false end -- Function calculating the overlap of two (square) objects. - local function _overlap(object1, object2, dist) - local pos1=object1 --Wrapper.Positionable#POSITIONABLE - local pos2=object2 --Wrapper.Positionable#POSITIONABLE - local r1=pos1:GetBoundingRadius() - local r2=pos2:GetBoundingRadius() + local function _overlap( object1, object2, dist ) + local pos1 = object1 -- Wrapper.Positionable#POSITIONABLE + local pos2 = object2 -- Wrapper.Positionable#POSITIONABLE + local r1 = pos1:GetBoundingRadius() + local r2 = pos2:GetBoundingRadius() if r1 and r2 then - local safedist=(r1+r2)*1.1 + 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))) + 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 @@ -1174,21 +1167,21 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, end -- Get airport name. - local airport=self:GetName() + local airport = self:GetName() -- Get parking spot data table. This contains free and "non-free" spots. -- Note that there are three major issues with the DCS getParking() function: -- 1. A spot is considered as NOT free until an aircraft that is present has finally taken off. This might be a bit long especiall at smaller airports. -- 2. A "free" spot does not take the aircraft size into accound. So if two big aircraft are spawned on spots next to each other, they might overlap and get destroyed. -- 3. The routine return a free spot, if there a static objects placed on the spot. - parkingdata=parkingdata or self:GetParkingSpotsTable(terminaltype) + parkingdata = parkingdata or self:GetParkingSpotsTable( terminaltype ) -- Get the aircraft size, i.e. it's longest side of x,z. local aircraft = nil -- fix local problem below - local _aircraftsize, ax,ay,az + local _aircraftsize, ax, ay, az if group and group.ClassName == "GROUP" then - aircraft=group:GetUnit(1) - _aircraftsize, ax,ay,az=aircraft:GetObjectSize() + aircraft = group:GetUnit( 1 ) + _aircraftsize, ax, ay, az = aircraft:GetObjectSize() else -- SU27 dimensions _aircraftsize = 23 @@ -1197,127 +1190,126 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, az = 17 -- width end - -- Number of spots we are looking for. Note that, e.g. grouping can require a number different from the group size! - local _nspots=nspots or group:GetSize() + local _nspots = nspots or group:GetSize() -- Debug info. - self:E(string.format("%s: Looking for %d parking spot(s) for aircraft of size %.1f m (x=%.1f,y=%.1f,z=%.1f) at terminal type %s.", airport, _nspots, _aircraftsize, ax, ay, az, tostring(terminaltype))) + self:E( string.format( "%s: Looking for %d parking spot(s) for aircraft of size %.1f m (x=%.1f,y=%.1f,z=%.1f) at terminal type %s.", airport, _nspots, _aircraftsize, ax, ay, az, tostring( terminaltype ) ) ) -- Table of valid spots. - local validspots={} - local nvalid=0 + local validspots = {} + local nvalid = 0 -- Test other stuff if no parking spot is available. - local _test=false + local _test = false if _test then return validspots end -- Mark all found obstacles on F10 map for debugging. - local markobstacles=false + local markobstacles = false -- Loop over all known parking spots - for _,parkingspot in pairs(parkingdata) do + for _, parkingspot in pairs( parkingdata ) do -- Coordinate of the parking spot. - local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE - local _termid=parkingspot.TerminalID + local _spot = parkingspot.Coordinate -- Core.Point#COORDINATE + local _termid = parkingspot.TerminalID -- Check terminal type and black/white listed parking spots. - if AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) and self:_CheckParkingLists(_termid) then + if AIRBASE._CheckTerminalType( parkingspot.TerminalType, terminaltype ) and self:_CheckParkingLists( _termid ) then -- Very safe uses the DCS getParking() info to check if a spot is free. Unfortunately, the function returns free=false until the aircraft has actually taken-off. - if verysafe and (parkingspot.Free==false or parkingspot.TOAC==true) then + if verysafe and (parkingspot.Free == false or parkingspot.TOAC == true) then -- DCS getParking() routine returned that spot is not free. - 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))) + 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 -- Scan a radius of 50 meters around the spot. - local _,_,_,_units,_statics,_sceneries=_spot:ScanObjects(scanradius, scanunits, scanstatics, scanscenery) + local _, _, _, _units, _statics, _sceneries = _spot:ScanObjects( scanradius, scanunits, scanstatics, scanscenery ) -- Loop over objects within scan radius. - local occupied=false + local occupied = false -- Check all units. - for _,unit in pairs(_units) do - local _coord=unit:GetCoordinate() - local _dist=_coord:Get2DDistance(_spot) - local _safe=_overlap(aircraft, unit, _dist) + 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))) + 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 + occupied = true end end -- Check all statics. - 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) + 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))) + 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 + occupied = true end end -- Check all scenery. - 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) + 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))) + 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 + occupied = true end end -- Now check the already given spots so that we do not put a large aircraft next to one we already assigned a nearby spot. - for _,_takenspot in pairs(validspots) do - local _dist=_takenspot.Coordinate:Get2DDistance(_spot) - local _safe=_overlap(aircraft, aircraft, _dist) + for _, _takenspot in pairs( validspots ) do + local _dist = _takenspot.Coordinate:Get2DDistance( _spot ) + local _safe = _overlap( aircraft, aircraft, _dist ) if not _safe then - occupied=true + occupied = true end end - --_spot:MarkToAll(string.format("Parking spot %d free=%s", parkingspot.TerminalID, tostring(not occupied))) + -- _spot:MarkToAll(string.format("Parking spot %d free=%s", parkingspot.TerminalID, tostring(not occupied))) if occupied then - self:I(string.format("%s: Parking spot id %d occupied.", airport, _termid)) + 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}) + 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)) + nvalid = nvalid + 1 + self:I( string.format( "%s: Parking spot id %d free. Nfree=%d/%d.", airport, _termid, nvalid, _nspots ) ) end end -- loop over units -- We found enough spots. - if nvalid>=_nspots then + if nvalid >= _nspots then return validspots end end -- check terminal type @@ -1325,30 +1317,29 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, -- Retrun spots we found, even if there were not enough. return validspots - + end --- Check black and white lists. -- @param #AIRBASE self -- @param #number TerminalID Terminal ID to check. -- @return #boolean `true` if this is a valid spot. -function AIRBASE:_CheckParkingLists(TerminalID) +function AIRBASE:_CheckParkingLists( TerminalID ) -- First check the black list. If we find a match, this spot is forbidden! - if self.parkingBlacklist and #self.parkingBlacklist>0 then - for _,terminalID in pairs(self.parkingBlacklist or {}) do - if terminalID==TerminalID then + if self.parkingBlacklist and #self.parkingBlacklist > 0 then + for _, terminalID in pairs( self.parkingBlacklist or {} ) do + if terminalID == TerminalID then -- This is a invalid spot. return false end end end - -- Check if a whitelist was defined. - if self.parkingWhitelist and #self.parkingWhitelist>0 then - for _,terminalID in pairs(self.parkingWhitelist or {}) do - if terminalID==TerminalID then + if self.parkingWhitelist and #self.parkingWhitelist > 0 then + for _, terminalID in pairs( self.parkingWhitelist or {} ) do + if terminalID == TerminalID then -- This is a valid spot. return true end @@ -1365,16 +1356,16 @@ end -- @param #number Term_Type Termial type from getParking routine. -- @param #AIRBASE.TerminalType termtype Terminal type from AIRBASE.TerminalType enumerator. -- @return #boolean True if terminal types match. -function AIRBASE._CheckTerminalType(Term_Type, termtype) +function AIRBASE._CheckTerminalType( Term_Type, termtype ) -- Nill check for Term_Type. - if Term_Type==nil then + if Term_Type == nil then return false end -- If no terminal type is requested, we return true. BUT runways are excluded unless explicitly requested. - if termtype==nil then - if Term_Type==AIRBASE.TerminalType.Runway then + if termtype == nil then + if Term_Type == AIRBASE.TerminalType.Runway then return false else return true @@ -1382,25 +1373,25 @@ function AIRBASE._CheckTerminalType(Term_Type, termtype) end -- Init no match. - local match=false + local match = false -- Standar case. - if Term_Type==termtype then - match=true + if Term_Type == termtype then + match = true end -- Artificial cases. Combination of terminal types. - if termtype==AIRBASE.TerminalType.OpenMedOrBig then - if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig then - match=true + 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 + 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 @@ -1416,162 +1407,160 @@ end -- @param #number magvar (Optional) Magnetic variation in degrees. -- @param #boolean mark (Optional) Place markers with runway data on F10 map. -- @return #table Runway data. -function AIRBASE:GetRunwayData(magvar, mark) +function AIRBASE:GetRunwayData( magvar, mark ) -- Runway table. - local runways={} + local runways = {} - if self:GetAirbaseCategory()~=Airbase.Category.AIRDROME then + if self:GetAirbaseCategory() ~= Airbase.Category.AIRDROME then return {} end -- Get spawn points on runway. These can be used to determine the runway heading. - local runwaycoords=self:GetParkingSpotsCoordinates(AIRBASE.TerminalType.Runway) + local runwaycoords = self:GetParkingSpotsCoordinates( AIRBASE.TerminalType.Runway ) -- Debug: For finding the numbers of the spawn points belonging to each runway. if false then - for i,_coord in pairs(runwaycoords) do - local coord=_coord --Core.Point#COORDINATE - coord:Translate(100, 0):MarkToAll("Runway i="..i) + for i, _coord in pairs( runwaycoords ) do + local coord = _coord -- Core.Point#COORDINATE + coord:Translate( 100, 0 ):MarkToAll( "Runway i=" .. i ) end end -- Magnetic declination. - magvar=magvar or UTILS.GetMagneticDeclination() + magvar = magvar or UTILS.GetMagneticDeclination() -- Number of runways. - local N=#runwaycoords - local N2=N/2 - local exception=false + local N = #runwaycoords + local N2 = N / 2 + local exception = false -- Airbase name. - local name=self:GetName() - + local name = self:GetName() -- Exceptions - if name==AIRBASE.Nevada.Jean_Airport or - name==AIRBASE.Nevada.Creech_AFB or - name==AIRBASE.PersianGulf.Abu_Dhabi_International_Airport or - name==AIRBASE.PersianGulf.Dubai_Intl or - name==AIRBASE.PersianGulf.Shiraz_International_Airport or - name==AIRBASE.PersianGulf.Kish_International_Airport or - name==AIRBASE.MarianaIslands.Andersen_AFB then + if name == AIRBASE.Nevada.Jean_Airport or + name == AIRBASE.Nevada.Creech_AFB or + name == AIRBASE.PersianGulf.Abu_Dhabi_International_Airport or + name == AIRBASE.PersianGulf.Dubai_Intl or + name == AIRBASE.PersianGulf.Shiraz_International_Airport or + name == AIRBASE.PersianGulf.Kish_International_Airport or + name == AIRBASE.MarianaIslands.Andersen_AFB then -- 1-->4, 2-->3, 3-->2, 4-->1 - exception=1 + 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 + 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 -- 1-->3, 2-->4, 3-->1, 4-->2 - exception=2 + exception = 2 end --- Function returning the index of the runway coordinate belonding to the given index i. - local function f(i) + local function f( i ) local j - if exception==1 then + if exception == 1 then - j=N-(i-1) -- 1-->4, 2-->3 + j = N - (i - 1) -- 1-->4, 2-->3 - elseif exception==2 then + elseif exception == 2 then - if i<=N2 then - j=i+N2 -- 1-->3, 2-->4 + if i <= N2 then + j = i + N2 -- 1-->3, 2-->4 else - j=i-N2 -- 3-->1, 4-->3 + j = i - N2 -- 3-->1, 4-->3 end else - if i%2==0 then - j=i-1 -- even 2-->1, 4-->3 + if i % 2 == 0 then + j = i - 1 -- even 2-->1, 4-->3 else - j=i+1 -- odd 1-->2, 3-->4 + j = i + 1 -- odd 1-->2, 3-->4 end end -- Special case where there is no obvious order. - 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 + 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 + 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 + for i = 1, N do -- Get the other spawn point coordinate. - local j=f(i) + local j = f( i ) -- Debug info. - --env.info(string.format("Runway i=%s j=%s (N=%d #runwaycoord=%d)", tostring(i), tostring(j), N, #runwaycoords)) + -- env.info(string.format("Runway i=%s j=%s (N=%d #runwaycoord=%d)", tostring(i), tostring(j), N, #runwaycoords)) -- Coordinates of the two runway points. - local c1=runwaycoords[i] --Core.Point#COORDINATE - local c2=runwaycoords[j] --Core.Point#COORDINATE + local c1 = runwaycoords[i] -- Core.Point#COORDINATE + local c2 = runwaycoords[j] -- Core.Point#COORDINATE -- Heading of runway. - local hdg=c1:HeadingTo(c2) + local hdg = c1:HeadingTo( c2 ) -- Runway ID: heading=070° ==> idx="07" - local idx=string.format("%02d", UTILS.Round((hdg-magvar)/10, 0)) + local idx = string.format( "%02d", UTILS.Round( (hdg - magvar) / 10, 0 ) ) -- Runway table. - local runway={} --#AIRBASE.Runway - runway.heading=hdg - runway.idx=idx - runway.length=c1:Get2DDistance(c2) - runway.position=c1 - runway.endpoint=c2 + local runway = {} -- #AIRBASE.Runway + runway.heading = hdg + runway.idx = idx + runway.length = c1:Get2DDistance( c2 ) + runway.position = c1 + runway.endpoint = c2 -- Debug info. - --self:I(string.format("Airbase %s: Adding runway id=%s, heading=%03d, length=%d m i=%d j=%d", self:GetName(), runway.idx, runway.heading, runway.length, i, j)) + -- self:I(string.format("Airbase %s: Adding runway id=%s, heading=%03d, length=%d m i=%d j=%d", self:GetName(), runway.idx, runway.heading, runway.length, i, j)) -- Debug mark 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)) + 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 -- Add runway. - table.insert(runways, runway) + table.insert( runways, runway ) end @@ -1581,18 +1570,18 @@ end --- Set the active runway in case it cannot be determined by the wind direction. -- @param #AIRBASE self -- @param #number iactive Number of the active runway in the runway data table. -function AIRBASE:SetActiveRunway(iactive) - self.activerwyno=iactive +function AIRBASE:SetActiveRunway( iactive ) + self.activerwyno = iactive end --- Get the active runway based on current wind direction. -- @param #AIRBASE self -- @param #number magvar (Optional) Magnetic variation in degrees. -- @return #AIRBASE.Runway Active runway data table. -function AIRBASE:GetActiveRunway(magvar) +function AIRBASE:GetActiveRunway( magvar ) -- Get runways data (initialize if necessary). - local runways=self:GetRunwayData(magvar) + local runways = self:GetRunwayData( magvar ) -- Return user forced active runway if it was set. if self.activerwyno then @@ -1600,46 +1589,46 @@ function AIRBASE:GetActiveRunway(magvar) end -- Get wind vector. - local Vwind=self:GetCoordinate():GetWindWithTurbulenceVec3() - local norm=UTILS.VecNorm(Vwind) + local Vwind = self:GetCoordinate():GetWindWithTurbulenceVec3() + local norm = UTILS.VecNorm( Vwind ) -- Active runway number. - local iact=1 + local iact = 1 -- Check if wind is blowing (norm>0). - if norm>0 then + if norm > 0 then -- Normalize wind (not necessary). - Vwind.x=Vwind.x/norm - Vwind.y=0 - Vwind.z=Vwind.z/norm + Vwind.x = Vwind.x / norm + Vwind.y = 0 + Vwind.z = Vwind.z / norm -- Loop over runways. - local dotmin=nil - for i,_runway in pairs(runways) do - local runway=_runway --#AIRBASE.Runway + local dotmin = nil + for i, _runway in pairs( runways ) do + local runway = _runway -- #AIRBASE.Runway -- Angle in rad. - local alpha=math.rad(runway.heading) + local alpha = math.rad( runway.heading ) -- Runway vector. - local Vrunway={x=math.cos(alpha), y=0, z=math.sin(alpha)} + local Vrunway = { x = math.cos( alpha ), y = 0, z = math.sin( alpha ) } -- Dot product: parallel component of the two vectors. - local dot=UTILS.VecDot(Vwind, Vrunway) + local dot = UTILS.VecDot( Vwind, Vrunway ) -- Debug. - --env.info(string.format("runway=%03d° dot=%.3f", runway.heading, dot)) + -- env.info(string.format("runway=%03d° dot=%.3f", runway.heading, dot)) -- New min? - if dotmin==nil or dot radius %.1f m. Despawn = %s.", self:GetName(), unit:GetName(), group:GetName(),_i, dist, radius, tostring(despawn))) - --unit:FlareGreen() + self:T( string.format( "%s, unit %s of group %s was NOT spawned on runway #%d. Distance %.1f > radius %.1f m. Despawn = %s.", self:GetName(), unit:GetName(), group:GetName(), _i, dist, radius, tostring( despawn ) ) ) + -- unit:FlareGreen() 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())) + 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())) + self:T( string.format( "%s, checking if group %s is on runway. Group is NOT alive.", self:GetName(), group:GetName() ) ) end return false From e847b92cce9d472737c7d28ef7f740997c20c736 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 11 Jan 2022 15:14:40 +0100 Subject: [PATCH 052/200] RAT - Docu corrections --- Moose Development/Moose/Functional/RAT.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index 39ff5d97c..139189959 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -1289,7 +1289,7 @@ end --- Include all airports which lie in a zone as possible destinations. -- @param #RAT self --- @param Core.Zone#ZONE zone Zone in which the departure airports lie. Has to be a MOOSE zone. +-- @param Core.Zone#ZONE zone Zone in which the destination airports lie. Has to be a MOOSE zone. -- @return #RAT RAT self object. function RAT:SetDestinationsFromZone(zone) self:F2(zone) @@ -1305,7 +1305,7 @@ end --- Include all airports which lie in a zone as possible destinations. -- @param #RAT self --- @param Core.Zone#ZONE zone Zone in which the destination airports lie. Has to be a MOOSE zone. +-- @param Core.Zone#ZONE zone Zone in which the departure airports lie. Has to be a MOOSE zone. -- @return #RAT RAT self object. function RAT:SetDeparturesFromZone(zone) self:F2(zone) From 964831becf0054c6a3a34f10cc5eab4b01f68676 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 15 Jan 2022 11:34:23 +0100 Subject: [PATCH 053/200] CTLD - make container shape configureable --- Moose Development/Moose/Ops/CTLD.lua | 6441 +++++++++++++------------- 1 file changed, 3197 insertions(+), 3244 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 6822d3f55..04fb227d2 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -22,37 +22,37 @@ -- @module Ops.CTLD -- @image OPS_CTLD.jpg --- Date: Dec 2021 +-- Date: Jan 2022 do - ------------------------------------------------------ - --- **CTLD_ENGINEERING** class, extends Core.Base#BASE - -- @type CTLD_ENGINEERING - -- @field #string ClassName - -- @field #string lid - -- @field #string Name - -- @field Wrapper.Group#GROUP Group - -- @field Wrapper.Unit#UNIT Unit - -- @field Wrapper.Group#GROUP HeliGroup - -- @field Wrapper.Unit#UNIT HeliUnit - -- @field #string State - -- @extends Core.Base#BASE - CTLD_ENGINEERING = { - ClassName = "CTLD_ENGINEERING", - lid = "", - Name = "none", - Group = nil, - Unit = nil, - -- C_Ops = nil, - HeliGroup = nil, - HeliUnit = nil, - State = "", +------------------------------------------------------ +--- **CTLD_ENGINEERING** class, extends Core.Base#BASE +-- @type CTLD_ENGINEERING +-- @field #string ClassName +-- @field #string lid +-- @field #string Name +-- @field Wrapper.Group#GROUP Group +-- @field Wrapper.Unit#UNIT Unit +-- @field Wrapper.Group#GROUP HeliGroup +-- @field Wrapper.Unit#UNIT HeliUnit +-- @field #string State +-- @extends Core.Base#BASE +CTLD_ENGINEERING = { + ClassName = "CTLD_ENGINEERING", + lid = "", + Name = "none", + Group = nil, + Unit = nil, + --C_Ops = nil, + HeliGroup = nil, + HeliUnit = nil, + State = "", } - + --- CTLD_ENGINEERING class version. -- @field #string version CTLD_ENGINEERING.Version = "0.0.3" - + --- Create a new instance. -- @param #CTLD_ENGINEERING self -- @param #string Name @@ -60,26 +60,26 @@ do -- @param Wrapper.Group#GROUP HeliGroup HeliGroup -- @param Wrapper.Unit#UNIT HeliUnit HeliUnit -- @return #CTLD_ENGINEERING self - function CTLD_ENGINEERING:New( Name, GroupName, HeliGroup, HeliUnit ) - - -- Inherit everything from BASE class. - local self = BASE:Inherit( self, BASE:New() ) -- #CTLD_ENGINEERING - - -- BASE:I({Name, GroupName}) - + function CTLD_ENGINEERING:New(Name, GroupName, HeliGroup, HeliUnit) + + -- Inherit everything from BASE class. + local self=BASE:Inherit(self, BASE:New()) -- #CTLD_ENGINEERING + + --BASE:I({Name, GroupName}) + self.Name = Name or "Engineer Squad" -- #string - self.Group = GROUP:FindByName( GroupName ) -- Wrapper.Group#GROUP - self.Unit = self.Group:GetUnit( 1 ) -- Wrapper.Unit#UNIT - -- self.C_Ops = C_Ops -- Ops.CTLD#CTLD + self.Group = GROUP:FindByName(GroupName) -- Wrapper.Group#GROUP + self.Unit = self.Group:GetUnit(1) -- Wrapper.Unit#UNIT + --self.C_Ops = C_Ops -- Ops.CTLD#CTLD self.HeliGroup = HeliGroup -- Wrapper.Group#GROUP self.HeliUnit = HeliUnit -- Wrapper.Unit#UNIT - -- self.distance = Distance or UTILS.NMToMeters(1) + --self.distance = Distance or UTILS.NMToMeters(1) self.currwpt = nil -- Core.Point#COORDINATE - self.lid = string.format( "%s (%s) | ", self.Name, self.Version ) - -- Start State. + self.lid = string.format("%s (%s) | ",self.Name, self.Version) + -- Start State. self.State = "Stopped" self.marktimer = 300 -- wait this many secs before trying a crate again - + --[[ Add FSM transitions. -- From State --> Event --> To State self:AddTransition("Stopped", "Start", "Running") -- Start FSM. @@ -89,145 +89,145 @@ do self:AddTransition("*", "Arrive", "Arrived") self:AddTransition("*", "Build", "Building") self:AddTransition("*", "Done", "Running") - self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. - + self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. + self:__Start(5) --]] self:Start() - local parent = self:GetParent( self ) + local parent = self:GetParent(self) return self end - + --- (Internal) Set the status -- @param #CTLD_ENGINEERING self -- @param #string State -- @return #CTLD_ENGINEERING self - function CTLD_ENGINEERING:SetStatus( State ) + function CTLD_ENGINEERING:SetStatus(State) self.State = State return self end - + --- (Internal) Get the status -- @param #CTLD_ENGINEERING self -- @return #string State function CTLD_ENGINEERING:GetStatus() return self.State end - + --- (Internal) Check the status -- @param #CTLD_ENGINEERING self -- @param #string State -- @return #boolean Outcome - function CTLD_ENGINEERING:IsStatus( State ) + function CTLD_ENGINEERING:IsStatus(State) return self.State == State end - + --- (Internal) Check the negative status -- @param #CTLD_ENGINEERING self -- @param #string State -- @return #boolean Outcome - function CTLD_ENGINEERING:IsNotStatus( State ) + function CTLD_ENGINEERING:IsNotStatus(State) return self.State ~= State end - + --- (Internal) Set start status. -- @param #CTLD_ENGINEERING self -- @return #CTLD_ENGINEERING self function CTLD_ENGINEERING:Start() - self:T( self.lid .. "Start" ) - self:SetStatus( "Running" ) + self:T(self.lid.."Start") + self:SetStatus("Running") return self end - + --- (Internal) Set stop status. -- @param #CTLD_ENGINEERING self -- @return #CTLD_ENGINEERING self function CTLD_ENGINEERING:Stop() - self:T( self.lid .. "Stop" ) - self:SetStatus( "Stopped" ) + self:T(self.lid.."Stop") + self:SetStatus("Stopped") return self end - + --- (Internal) Set build status. -- @param #CTLD_ENGINEERING self -- @return #CTLD_ENGINEERING self function CTLD_ENGINEERING:Build() - self:T( self.lid .. "Build" ) - self:SetStatus( "Building" ) + self:T(self.lid.."Build") + self:SetStatus("Building") return self end - + --- (Internal) Set done status. -- @param #CTLD_ENGINEERING self -- @return #CTLD_ENGINEERING self function CTLD_ENGINEERING:Done() - self:T( self.lid .. "Done" ) + self:T(self.lid.."Done") local grp = self.Group -- Wrapper.Group#GROUP - grp:RelocateGroundRandomInRadius( 7, 100, false, false, "Diamond" ) - self:SetStatus( "Running" ) + grp:RelocateGroundRandomInRadius(7,100,false,false,"Diamond") + self:SetStatus("Running") return self end - + --- (Internal) Search for crates in reach. -- @param #CTLD_ENGINEERING self -- @param #table crates Table of found crate Ops.CTLD#CTLD_CARGO objects. -- @param #number number Number of crates found. -- @return #CTLD_ENGINEERING self - function CTLD_ENGINEERING:Search( crates, number ) - self:T( self.lid .. "Search" ) - self:SetStatus( "Searching" ) + function CTLD_ENGINEERING:Search(crates,number) + self:T(self.lid.."Search") + self:SetStatus("Searching") -- find crates close by - -- local COps = self.C_Ops -- Ops.CTLD#CTLD + --local COps = self.C_Ops -- Ops.CTLD#CTLD local dist = self.distance -- #number local group = self.Group -- Wrapper.Group#GROUP - -- local crates,number = COps:_FindCratesNearby(group,nil, dist) -- #table + --local crates,number = COps:_FindCratesNearby(group,nil, dist) -- #table local ctable = {} local ind = 0 if number > 0 then -- get set of dropped only - for _, _cargo in pairs( crates ) do - local cgotype = _cargo:GetType() - if _cargo:WasDropped() and cgotype ~= CTLD_CARGO.Enum.STATIC then - local ok = false - local chalk = _cargo:GetMark() - if chalk == nil then + for _,_cargo in pairs (crates) do + local cgotype = _cargo:GetType() + if _cargo:WasDropped() and cgotype ~= CTLD_CARGO.Enum.STATIC then + local ok = false + local chalk = _cargo:GetMark() + if chalk == nil then + ok = true + else + -- have we tried this cargo recently? + local tag = chalk.tag or "none" + local timestamp = chalk.timestamp or 0 + --self:I({chalk}) + -- enough time gone? + local gone = timer.getAbsTime() - timestamp + --self:I({time=gone}) + if gone >= self.marktimer then ok = true - else - -- have we tried this cargo recently? - local tag = chalk.tag or "none" - local timestamp = chalk.timestamp or 0 - -- self:I({chalk}) - -- enough time gone? - local gone = timer.getAbsTime() - timestamp - -- self:I({time=gone}) - if gone >= self.marktimer then - ok = true - _cargo:WipeMark() - end -- end time check - end -- end chalk - if ok then - local chalk = {} - chalk.tag = "Engineers" - chalk.timestamp = timer.getAbsTime() - _cargo:AddMark( chalk ) - ind = ind + 1 - table.insert( ctable, ind, _cargo ) - end - end -- end dropped + _cargo:WipeMark() + end -- end time check + end -- end chalk + if ok then + local chalk = {} + chalk.tag = "Engineers" + chalk.timestamp = timer.getAbsTime() + _cargo:AddMark(chalk) + ind = ind + 1 + table.insert(ctable,ind,_cargo) + end + end -- end dropped end -- end for end -- end number - + if ind > 0 then local crate = ctable[1] -- Ops.CTLD#CTLD_CARGO local static = crate:GetPositionable() -- Wrapper.Static#STATIC local crate_pos = static:GetCoordinate() -- Core.Point#COORDINATE local gpos = group:GetCoordinate() -- Core.Point#COORDINATE -- see how far we are from the crate - local distance = self:_GetDistance( gpos, crate_pos ) - self:T( string.format( "%s Distance to crate: %d", self.lid, distance ) ) + local distance = self:_GetDistance(gpos,crate_pos) + self:T(string.format("%s Distance to crate: %d", self.lid, distance)) -- move there - if distance > 30 and distance ~= -1 and self:IsStatus( "Searching" ) then - group:RouteGroundTo( crate_pos, 15, "Line abreast", 1 ) + if distance > 30 and distance ~= -1 and self:IsStatus("Searching") then + group:RouteGroundTo(crate_pos,15,"Line abreast",1) self.currwpt = crate_pos -- Core.Point#COORDINATE self:Move() elseif distance <= 30 and distance ~= -1 then @@ -235,102 +235,101 @@ do self:Arrive() end else - self:T( self.lid .. "No crates in reach!" ) + self:T(self.lid.."No crates in reach!") end return self end - + --- (Internal) Move towards crates in reach. -- @param #CTLD_ENGINEERING self -- @return #CTLD_ENGINEERING self function CTLD_ENGINEERING:Move() - self:T( self.lid .. "Move" ) - self:SetStatus( "Moving" ) + self:T(self.lid.."Move") + self:SetStatus("Moving") -- check if we arrived on target - -- local COps = self.C_Ops -- Ops.CTLD#CTLD + --local COps = self.C_Ops -- Ops.CTLD#CTLD local group = self.Group -- Wrapper.Group#GROUP local tgtpos = self.currwpt -- Core.Point#COORDINATE local gpos = group:GetCoordinate() -- Core.Point#COORDINATE -- see how far we are from the crate - local distance = self:_GetDistance( gpos, tgtpos ) - self:T( string.format( "%s Distance remaining: %d", self.lid, distance ) ) + local distance = self:_GetDistance(gpos,tgtpos) + self:T(string.format("%s Distance remaining: %d", self.lid, distance)) if distance <= 30 and distance ~= -1 then - -- arrived - self:Arrive() + -- arrived + self:Arrive() end return self end - + --- (Internal) Arrived at crates in reach. Stop group. -- @param #CTLD_ENGINEERING self -- @return #CTLD_ENGINEERING self function CTLD_ENGINEERING:Arrive() - self:T( self.lid .. "Arrive" ) - self:SetStatus( "Arrived" ) + self:T(self.lid.."Arrive") + self:SetStatus("Arrived") self.currwpt = nil local Grp = self.Group -- Wrapper.Group#GROUP Grp:RouteStop() return self end - + --- (Internal) Return distance in meters between two coordinates. -- @param #CTLD_ENGINEERING self -- @param Core.Point#COORDINATE _point1 Coordinate one -- @param Core.Point#COORDINATE _point2 Coordinate two -- @return #number Distance in meters or -1 - function CTLD_ENGINEERING:_GetDistance( _point1, _point2 ) - self:T( self.lid .. " _GetDistance" ) + function CTLD_ENGINEERING:_GetDistance(_point1, _point2) + self:T(self.lid .. " _GetDistance") if _point1 and _point2 then - local distance1 = _point1:Get2DDistance( _point2 ) - local distance2 = _point1:DistanceFromPointVec2( _point2 ) - -- self:I({dist1=distance1, dist2=distance2}) - if distance1 and type( distance1 ) == "number" then + local distance1 = _point1:Get2DDistance(_point2) + local distance2 = _point1:DistanceFromPointVec2(_point2) + --self:I({dist1=distance1, dist2=distance2}) + if distance1 and type(distance1) == "number" then return distance1 - elseif distance2 and type( distance2 ) == "number" then + elseif distance2 and type(distance2) == "number" then return distance2 else - self:E( "*****Cannot calculate distance!" ) - self:E( { _point1, _point2 } ) + self:E("*****Cannot calculate distance!") + self:E({_point1,_point2}) return -1 end else - self:E( "******Cannot calculate distance!" ) - self:E( { _point1, _point2 } ) + self:E("******Cannot calculate distance!") + self:E({_point1,_point2}) return -1 end end - ------------------------------------------------------ - --- **CTLD_CARGO** class, extends Core.Base#BASE - -- @type CTLD_CARGO - -- @field #number ID ID of this cargo. - -- @field #string Name Name for menu. - -- @field #table Templates Table of #POSITIONABLE objects. - -- @field #CTLD_CARGO.Enum CargoType Enumerator of Type. - -- @field #boolean HasBeenMoved Flag for moving. - -- @field #boolean LoadDirectly Flag for direct loading. - -- @field #number CratesNeeded Crates needed to build. - -- @field Wrapper.Positionable#POSITIONABLE Positionable Representation of cargo in the mission. - -- @field #boolean HasBeenDropped True if dropped from heli. - -- @field #number PerCrateMass Mass in kg - -- @field #number Stock Number of builds available, -1 for unlimited - -- @extends Core.Base#BASE - CTLD_CARGO = { - ClassName = "CTLD_CARGO", - ID = 0, - Name = "none", - Templates = { - }, - CargoType = "none", - HasBeenMoved = false, - LoadDirectly = false, - CratesNeeded = 0, - Positionable = nil, - HasBeenDropped = false, - PerCrateMass = 0, - Stock = nil, - Mark = nil, +------------------------------------------------------ +--- **CTLD_CARGO** class, extends Core.Base#BASE +-- @type CTLD_CARGO +-- @field #number ID ID of this cargo. +-- @field #string Name Name for menu. +-- @field #table Templates Table of #POSITIONABLE objects. +-- @field #CTLD_CARGO.Enum CargoType Enumerator of Type. +-- @field #boolean HasBeenMoved Flag for moving. +-- @field #boolean LoadDirectly Flag for direct loading. +-- @field #number CratesNeeded Crates needed to build. +-- @field Wrapper.Positionable#POSITIONABLE Positionable Representation of cargo in the mission. +-- @field #boolean HasBeenDropped True if dropped from heli. +-- @field #number PerCrateMass Mass in kg +-- @field #number Stock Number of builds available, -1 for unlimited +-- @extends Core.Base#BASE +CTLD_CARGO = { + ClassName = "CTLD_CARGO", + ID = 0, + Name = "none", + Templates = {}, + CargoType = "none", + HasBeenMoved = false, + LoadDirectly = false, + CratesNeeded = 0, + Positionable = nil, + HasBeenDropped = false, + PerCrateMass = 0, + Stock = nil, + Mark = nil, } - + --- Define cargo types. -- @type CTLD_CARGO.Enum -- @field #string Type Type of Cargo. @@ -343,13 +342,13 @@ do ["ENGINEERS"] = "Engineers", -- #string engineers ["STATIC"] = "Static", -- #string engineers } - + --- Function to create new CTLD_CARGO object. -- @param #CTLD_CARGO self -- @param #number ID ID of this #CTLD_CARGO -- @param #string Name Name for menu. -- @param #table Templates Table of #POSITIONABLE objects. - -- @param #CTLD_CARGO.Enum SortEnum Enumerator of Type. + -- @param #CTLD_CARGO.Enum Sorte Enumerator of Type. -- @param #boolean HasBeenMoved Flag for moving. -- @param #boolean LoadDirectly Flag for direct loading. -- @param #number CratesNeeded Crates needed to build. @@ -358,119 +357,119 @@ do -- @param #number PerCrateMass Mass in kg -- @param #number Stock Number of builds available, nil for unlimited -- @return #CTLD_CARGO self - function CTLD_CARGO:New( ID, Name, Templates, SortEnum, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass, Stock ) + function CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass, Stock) -- Inherit everything from BASE class. - local self = BASE:Inherit( self, BASE:New() ) -- #CTLD - self:T( { ID, Name, Templates, SortEnum, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped } ) - self.ID = ID or math.random( 100000, 1000000 ) + local self=BASE:Inherit(self, BASE:New()) -- #CTLD + self:T({ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped}) + self.ID = ID or math.random(100000,1000000) self.Name = Name or "none" -- #string self.Templates = Templates or {} -- #table - self.CargoType = SortEnum or "type" -- #CTLD_CARGO.Enum + self.CargoType = Sorte or "type" -- #CTLD_CARGO.Enum self.HasBeenMoved = HasBeenMoved or false -- #boolean self.LoadDirectly = LoadDirectly or false -- #boolean self.CratesNeeded = CratesNeeded or 0 -- #number self.Positionable = Positionable or nil -- Wrapper.Positionable#POSITIONABLE - self.HasBeenDropped = Dropped or false -- #boolean + self.HasBeenDropped = Dropped or false --#boolean self.PerCrateMass = PerCrateMass or 0 -- #number - self.Stock = Stock or nil -- #number + self.Stock = Stock or nil --#number self.Mark = nil return self end - + --- Query ID. -- @param #CTLD_CARGO self -- @return #number ID function CTLD_CARGO:GetID() return self.ID end - + --- Query Mass. -- @param #CTLD_CARGO self -- @return #number Mass in kg function CTLD_CARGO:GetMass() return self.PerCrateMass - end + end --- Query Name. -- @param #CTLD_CARGO self -- @return #string Name function CTLD_CARGO:GetName() return self.Name end - + --- Query Templates. -- @param #CTLD_CARGO self -- @return #table Templates function CTLD_CARGO:GetTemplates() return self.Templates end - + --- Query has moved. -- @param #CTLD_CARGO self -- @return #boolean Has moved function CTLD_CARGO:HasMoved() return self.HasBeenMoved end - + --- Query was dropped. -- @param #CTLD_CARGO self -- @return #boolean Has been dropped. function CTLD_CARGO:WasDropped() return self.HasBeenDropped end - + --- Query directly loadable. -- @param #CTLD_CARGO self -- @return #boolean loadable function CTLD_CARGO:CanLoadDirectly() return self.LoadDirectly end - + --- Query number of crates or troopsize. -- @param #CTLD_CARGO self -- @return #number Crates or size of troops. function CTLD_CARGO:GetCratesNeeded() return self.CratesNeeded end - + --- Query type. -- @param #CTLD_CARGO self -- @return #CTLD_CARGO.Enum Type function CTLD_CARGO:GetType() return self.CargoType end - + --- Query type. -- @param #CTLD_CARGO self -- @return Wrapper.Positionable#POSITIONABLE Positionable function CTLD_CARGO:GetPositionable() return self.Positionable end - + --- Set HasMoved. -- @param #CTLD_CARGO self -- @param #boolean moved - function CTLD_CARGO:SetHasMoved( moved ) + function CTLD_CARGO:SetHasMoved(moved) self.HasBeenMoved = moved or false end - - --- Query if cargo has been loaded. + + --- Query if cargo has been loaded. -- @param #CTLD_CARGO self -- @param #boolean loaded function CTLD_CARGO:Isloaded() if self.HasBeenMoved and not self.WasDropped() then return true else - return false - end + return false + end end - + --- Set WasDropped. -- @param #CTLD_CARGO self -- @param #boolean dropped - function CTLD_CARGO:SetWasDropped( dropped ) + function CTLD_CARGO:SetWasDropped(dropped) self.HasBeenDropped = dropped or false end - + --- Get Stock. -- @param #CTLD_CARGO self -- @return #number Stock @@ -481,1048 +480,1045 @@ do return -1 end end - + --- Add Stock. -- @param #CTLD_CARGO self -- @param #number Number to add, one if nil. -- @return #CTLD_CARGO self - function CTLD_CARGO:AddStock( Number ) + function CTLD_CARGO:AddStock(Number) if self.Stock then -- Stock nil? local number = Number or 1 self.Stock = self.Stock + number end return self end - + --- Remove Stock. -- @param #CTLD_CARGO self -- @param #number Number to reduce, one if nil. -- @return #CTLD_CARGO self - function CTLD_CARGO:RemoveStock( Number ) + function CTLD_CARGO:RemoveStock(Number) if self.Stock then -- Stock nil? local number = Number or 1 self.Stock = self.Stock - number - if self.Stock < 0 then - self.Stock = 0 - end + if self.Stock < 0 then self.Stock = 0 end end return self end - + --- Query crate type for REPAIR -- @param #CTLD_CARGO self -- @param #boolean function CTLD_CARGO:IsRepair() - if self.CargoType == "Repair" then - return true - else - return false - end + if self.CargoType == "Repair" then + return true + else + return false + end end - + --- Query crate type for STATIC -- @param #CTLD_CARGO self -- @param #boolean function CTLD_CARGO:IsStatic() - if self.CargoType == "Static" then - return true - else - return false - end + if self.CargoType == "Static" then + return true + else + return false + end end - - function CTLD_CARGO:AddMark( Mark ) + + function CTLD_CARGO:AddMark(Mark) self.Mark = Mark return self end - - function CTLD_CARGO:GetMark( Mark ) + + function CTLD_CARGO:GetMark(Mark) return self.Mark end - + function CTLD_CARGO:WipeMark() self.Mark = nil return self end - + end do - ------------------------------------------------------------------------- - --- **CTLD** class, extends Core.Base#BASE, Core.Fsm#FSM - -- @type CTLD - -- @field #string ClassName Name of the class. - -- @field #number verbose Verbosity level. - -- @field #string lid Class id string for output to DCS log file. - -- @field #number coalition Coalition side number, e.g. `coalition.side.RED`. - -- @extends Core.Fsm#FSM +------------------------------------------------------------------------- +--- **CTLD** class, extends Core.Base#BASE, Core.Fsm#FSM +-- @type CTLD +-- @field #string ClassName Name of the class. +-- @field #number verbose Verbosity level. +-- @field #string lid Class id string for output to DCS log file. +-- @field #number coalition Coalition side number, e.g. `coalition.side.RED`. +-- @extends Core.Fsm#FSM - --- *Combat Troop & Logistics Deployment (CTLD): Everyone wants to be a POG, until there's POG stuff to be done.* (Mil Saying) - -- - -- === - -- - -- ![Banner Image](OPS_CTLD.jpg) - -- - -- # CTLD Concept - -- - -- * MOOSE-based CTLD for Players. - -- * Object oriented refactoring of Ciribob's fantastic CTLD script. - -- * No need for extra MIST loading. - -- * Additional events to tailor your mission. - -- * ANY late activated group can serve as cargo, either as troops, crates, which have to be build on-location, or static like ammo chests. - -- * Option to persist (save&load) your dropped troops, crates and vehicles. - -- - -- ## 0. Prerequisites - -- - -- You need to load an .ogg sound file for the pilot's beacons into the mission, e.g. "beacon.ogg", use a once trigger, "sound to country" for that. - -- Create the late-activated troops, vehicles (no statics at this point!) that will make up your deployable forces. - -- - -- ## 1. Basic Setup - -- - -- ## 1.1 Create and start a CTLD instance - -- - -- A basic setup example is the following: - -- - -- -- Instantiate and start a CTLD for the blue side, using helicopter groups named "Helicargo" and alias "Lufttransportbrigade I" - -- local my_ctld = CTLD:New(coalition.side.BLUE,{"Helicargo"},"Lufttransportbrigade I") - -- my_ctld:__Start(5) - -- - -- ## 1.2 Add cargo types available - -- - -- Add *generic* cargo types that you need for your missions, here infantry units, vehicles and a FOB. These need to be late-activated Wrapper.Group#GROUP objects: - -- - -- -- add infantry unit called "Anti-Tank Small" using template "ATS", of type TROOP with size 3 - -- -- infantry units will be loaded directly from LOAD zones into the heli (matching number of free seats needed) - -- my_ctld:AddTroopsCargo("Anti-Tank Small",{"ATS"},CTLD_CARGO.Enum.TROOPS,3) - -- -- if you want to add weight to your Heli, troops can have a weight in kg **per person**. Currently no max weight checked. Fly carefully. - -- my_ctld:AddTroopsCargo("Anti-Tank Small",{"ATS"},CTLD_CARGO.Enum.TROOPS,3,80) - -- - -- -- add infantry unit called "Anti-Tank" using templates "AA" and "AA"", of type TROOP with size 4. No weight. We only have 2 in stock: - -- my_ctld:AddTroopsCargo("Anti-Air",{"AA","AA2"},CTLD_CARGO.Enum.TROOPS,4,nil,2) - -- - -- -- add an engineers unit called "Wrenches" using template "Engineers", of type ENGINEERS with size 2. Engineers can be loaded, dropped, - -- -- and extracted like troops. However, they will seek to build and/or repair crates found in a given radius. Handy if you can't stay - -- -- to build or repair or under fire. - -- my_ctld:AddTroopsCargo("Wrenches",{"Engineers"},CTLD_CARGO.Enum.ENGINEERS,4) - -- myctld.EngineerSearch = 2000 -- teams will search for crates in this radius. - -- - -- -- add vehicle called "Humvee" using template "Humvee", of type VEHICLE, size 2, i.e. needs two crates to be build - -- -- vehicles and FOB will be spawned as crates in a LOAD zone first. Once transported to DROP zones, they can be build into the objects - -- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2) - -- -- if you want to add weight to your Heli, crates can have a weight in kg **per crate**. Currently no max weight checked. Fly carefully. - -- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2,2775) - -- -- if you want to limit your stock, add a number (here: 10) as parameter after weight. No parameter / nil means unlimited stock. - -- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2,2775,10) - -- - -- -- add infantry unit called "Forward Ops Base" using template "FOB", of type FOB, size 4, i.e. needs four crates to be build: - -- my_ctld:AddCratesCargo("Forward Ops Base",{"FOB"},CTLD_CARGO.Enum.FOB,4) - -- - -- -- add crates to repair FOB or VEHICLE type units - the 2nd parameter needs to match the template you want to repair - -- my_ctld:AddCratesRepair("Humvee Repair","Humvee",CTLD_CARGO.Enum.REPAIR,1) - -- my_ctld.repairtime = 300 -- takes 300 seconds to repair something - -- - -- -- add static cargo objects, e.g ammo chests - the name needs to refer to a STATIC object in the mission editor, - -- -- here: it's the UNIT name (not the GROUP name!), the second parameter is the weight in kg. - -- my_ctld:AddStaticsCargo("Ammunition",500) - -- - -- ## 1.3 Add logistics zones - -- - -- Add zones for loading troops and crates and dropping, building crates - -- - -- -- Add a zone of type LOAD to our setup. Players can load troops and crates. - -- -- "Loadzone" is the name of the zone from the ME. Players can load, if they are inside the zone. - -- -- Smoke and Flare color for this zone is blue, it is active (can be used) and has a radio beacon. - -- my_ctld:AddCTLDZone("Loadzone",CTLD.CargoZoneType.LOAD,SMOKECOLOR.Blue,true,true) - -- - -- -- Add a zone of type DROP. Players can drop crates here. - -- -- Smoke and Flare color for this zone is blue, it is active (can be used) and has a radio beacon. - -- -- NOTE: Troops can be unloaded anywhere, also when hovering in parameters. - -- my_ctld:AddCTLDZone("Dropzone",CTLD.CargoZoneType.DROP,SMOKECOLOR.Red,true,true) - -- - -- -- Add two zones of type MOVE. Dropped troops and vehicles will move to the nearest one. See options. - -- -- Smoke and Flare color for this zone is blue, it is active (can be used) and has a radio beacon. - -- my_ctld:AddCTLDZone("Movezone",CTLD.CargoZoneType.MOVE,SMOKECOLOR.Orange,false,false) - -- - -- my_ctld:AddCTLDZone("Movezone2",CTLD.CargoZoneType.MOVE,SMOKECOLOR.White,true,true) - -- - -- -- Add a zone of type SHIP to our setup. Players can load troops and crates from this ship - -- -- "Tarawa" is the unitname (callsign) of the ship from the ME. Players can load, if they are inside the zone. - -- -- The ship is 240 meters long and 20 meters wide. - -- -- Note that you need to adjust the max hover height to deck height plus 5 meters or so for loading to work. - -- -- When the ship is moving, forcing hoverload might not be a good idea. - -- my_ctld:AddCTLDZone("Tarawa",CTLD.CargoZoneType.SHIP,SMOKECOLOR.Blue,true,true,240,20) - -- - -- ## 2. Options - -- - -- The following options are available (with their defaults). Only set the ones you want changed: - -- - -- my_ctld.useprefix = true -- (DO NOT SWITCH THIS OFF UNLESS YOU KNOW WHAT YOU ARE DOING!) Adjust **before** starting CTLD. If set to false, *all* choppers of the coalition side will be enabled for CTLD. - -- my_ctld.CrateDistance = 35 -- List and Load crates in this radius only. - -- my_ctld.dropcratesanywhere = false -- Option to allow crates to be dropped anywhere. - -- my_ctld.maximumHoverHeight = 15 -- Hover max this high to load. - -- my_ctld.minimumHoverHeight = 4 -- Hover min this low to load. - -- my_ctld.forcehoverload = true -- Crates (not: troops) can **only** be loaded while hovering. - -- my_ctld.hoverautoloading = true -- Crates in CrateDistance in a LOAD zone will be loaded automatically if space allows. - -- my_ctld.smokedistance = 2000 -- Smoke or flares can be request for zones this far away (in meters). - -- my_ctld.movetroopstowpzone = true -- Troops and vehicles will move to the nearest MOVE zone... - -- my_ctld.movetroopsdistance = 5000 -- .. but only if this far away (in meters) - -- my_ctld.smokedistance = 2000 -- Only smoke or flare zones if requesting player unit is this far away (in meters) - -- my_ctld.suppressmessages = false -- Set to true if you want to script your own messages. - -- my_ctld.repairtime = 300 -- Number of seconds it takes to repair a unit. - -- my_ctld.cratecountry = country.id.GERMANY -- ID of crates. Will default to country.id.RUSSIA for RED coalition setups. - -- my_ctld.allowcratepickupagain = true -- allow re-pickup crates that were dropped. - -- my_ctld.enableslingload = false -- allow cargos to be slingloaded - might not work for all cargo types - -- my_ctld.pilotmustopendoors = false -- force opening of doors - -- my_ctld.SmokeColor = SMOKECOLOR.Red -- color to use when dropping smoke from heli - -- my_ctld.FlareColor = FLARECOLOR.Red -- color to use when flaring from heli - -- - -- ## 2.1 User functions - -- - -- ### 2.1.1 Adjust or add chopper unit-type capabilities - -- - -- Use this function to adjust what a heli type can or cannot do: - -- - -- -- E.g. update unit capabilities for testing. Please stay realistic in your mission design. - -- -- Make a Gazelle into a heavy truck, this type can load both crates and troops and eight of each type: - -- my_ctld:UnitCapabilities("SA342L", true, true, 8, 8, 12) - -- - -- -- Default unit type capabilities are: - -- - -- ["SA342Mistral"] = {type="SA342Mistral", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12}, - -- ["SA342L"] = {type="SA342L", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12}, - -- ["SA342M"] = {type="SA342M", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12}, - -- ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12}, - -- ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8, length = 15}, - -- ["Mi-8MT"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15}, - -- ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 15}, - -- ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18}, - -- ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18}, - -- ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25}, - -- - -- ### 2.1.2 Activate and deactivate zones - -- - -- Activate a zone: - -- - -- -- Activate zone called Name of type #CTLD.CargoZoneType ZoneType: - -- my_ctld:ActivateZone(Name,CTLD.CargoZoneType.MOVE) - -- - -- Deactivate a zone: - -- - -- -- Deactivate zone called Name of type #CTLD.CargoZoneType ZoneType: - -- my_ctld:DeactivateZone(Name,CTLD.CargoZoneType.DROP) - -- - -- ## 2.1.3 Limit and manage available resources - -- - -- When adding generic cargo types, you can effectively limit how many units can be dropped/build by the players, e.g. - -- - -- -- if you want to limit your stock, add a number (here: 10) as parameter after weight. No parameter / nil means unlimited stock. - -- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2,2775,10) - -- - -- You can manually add or remove the available stock like so: - -- - -- -- Crates - -- my_ctld:AddStockCrates("Humvee", 2) - -- my_ctld:RemoveStockCrates("Humvee", 2) - -- - -- -- Troops - -- my_ctld:AddStockTroops("Anti-Air", 2) - -- my_ctld:RemoveStockTroops("Anti-Air", 2) - -- - -- Notes: - -- Troops dropped back into a LOAD zone will effectively be added to the stock. Crates lost in e.g. a heli crash are just that - lost. - -- - -- ## 3. Events - -- - -- The class comes with a number of FSM-based events that missions designers can use to shape their mission. - -- These are: - -- - -- ## 3.1 OnAfterTroopsPickedUp - -- - -- This function is called when a player has loaded Troops: - -- - -- function my_ctld:OnAfterTroopsPickedUp(From, Event, To, Group, Unit, Cargo) - -- ... your code here ... - -- end - -- - -- ## 3.2 OnAfterCratesPickedUp - -- - -- This function is called when a player has picked up crates: - -- - -- function my_ctld:OnAfterCratesPickedUp(From, Event, To, Group, Unit, Cargo) - -- ... your code here ... - -- end - -- - -- ## 3.3 OnAfterTroopsDeployed - -- - -- This function is called when a player has deployed troops into the field: - -- - -- function my_ctld:OnAfterTroopsDeployed(From, Event, To, Group, Unit, Troops) - -- ... your code here ... - -- end - -- - -- ## 3.4 OnAfterTroopsExtracted - -- - -- This function is called when a player has re-boarded already deployed troops from the field: - -- - -- function my_ctld:OnAfterTroopsExtracted(From, Event, To, Group, Unit, Troops) - -- ... your code here ... - -- end - -- - -- ## 3.5 OnAfterCratesDropped - -- - -- This function is called when a player has deployed crates to a DROP zone: - -- - -- function my_ctld:OnAfterCratesDropped(From, Event, To, Group, Unit, Cargotable) - -- ... your code here ... - -- end - -- - -- ## 3.6 OnAfterCratesBuild, OnAfterCratesRepaired - -- - -- This function is called when a player has build a vehicle or FOB: - -- - -- function my_ctld:OnAfterCratesBuild(From, Event, To, Group, Unit, Vehicle) - -- ... your code here ... - -- end - -- - -- function my_ctld:OnAfterCratesRepaired(From, Event, To, Group, Unit, Vehicle) - -- ... your code here ... - -- end - -- - -- ## 3.7 A simple SCORING example: - -- - -- To award player with points, using the SCORING Class (SCORING: my_Scoring, CTLD: CTLD_Cargotransport) - -- - -- function CTLD_Cargotransport:OnAfterCratesDropped(From, Event, To, Group, Unit, Cargotable) - -- local points = 10 - -- if Unit then - -- local PlayerName = Unit:GetPlayerName() - -- my_scoring:_AddPlayerFromUnit( Unit ) - -- my_scoring:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for transporting cargo crates!", PlayerName, points), points) - -- end - -- end - -- - -- function CTLD_Cargotransport:OnAfterCratesBuild(From, Event, To, Group, Unit, Vehicle) - -- local points = 5 - -- if Unit then +--- *Combat Troop & Logistics Deployment (CTLD): Everyone wants to be a POG, until there\'s POG stuff to be done.* (Mil Saying) +-- +-- === +-- +-- ![Banner Image](OPS_CTLD.jpg) +-- +-- # CTLD Concept +-- +-- * MOOSE-based CTLD for Players. +-- * Object oriented refactoring of Ciribob\'s fantastic CTLD script. +-- * No need for extra MIST loading. +-- * Additional events to tailor your mission. +-- * ANY late activated group can serve as cargo, either as troops, crates, which have to be build on-location, or static like ammo chests. +-- * Option to persist (save&load) your dropped troops, crates and vehicles. +-- +-- ## 0. Prerequisites +-- +-- You need to load an .ogg soundfile for the pilot\'s beacons into the mission, e.g. "beacon.ogg", use a once trigger, "sound to country" for that. +-- Create the late-activated troops, vehicles (no statics at this point!) that will make up your deployable forces. +-- +-- ## 1. Basic Setup +-- +-- ## 1.1 Create and start a CTLD instance +-- +-- A basic setup example is the following: +-- +-- -- Instantiate and start a CTLD for the blue side, using helicopter groups named "Helicargo" and alias "Lufttransportbrigade I" +-- local my_ctld = CTLD:New(coalition.side.BLUE,{"Helicargo"},"Lufttransportbrigade I") +-- my_ctld:__Start(5) +-- +-- ## 1.2 Add cargo types available +-- +-- Add *generic* cargo types that you need for your missions, here infantry units, vehicles and a FOB. These need to be late-activated Wrapper.Group#GROUP objects: +-- +-- -- add infantry unit called "Anti-Tank Small" using template "ATS", of type TROOP with size 3 +-- -- infantry units will be loaded directly from LOAD zones into the heli (matching number of free seats needed) +-- my_ctld:AddTroopsCargo("Anti-Tank Small",{"ATS"},CTLD_CARGO.Enum.TROOPS,3) +-- -- if you want to add weight to your Heli, troops can have a weight in kg **per person**. Currently no max weight checked. Fly carefully. +-- my_ctld:AddTroopsCargo("Anti-Tank Small",{"ATS"},CTLD_CARGO.Enum.TROOPS,3,80) +-- +-- -- add infantry unit called "Anti-Tank" using templates "AA" and "AA"", of type TROOP with size 4. No weight. We only have 2 in stock: +-- my_ctld:AddTroopsCargo("Anti-Air",{"AA","AA2"},CTLD_CARGO.Enum.TROOPS,4,nil,2) +-- +-- -- add an engineers unit called "Wrenches" using template "Engineers", of type ENGINEERS with size 2. Engineers can be loaded, dropped, +-- -- and extracted like troops. However, they will seek to build and/or repair crates found in a given radius. Handy if you can\'t stay +-- -- to build or repair or under fire. +-- my_ctld:AddTroopsCargo("Wrenches",{"Engineers"},CTLD_CARGO.Enum.ENGINEERS,4) +-- myctld.EngineerSearch = 2000 -- teams will search for crates in this radius. +-- +-- -- add vehicle called "Humvee" using template "Humvee", of type VEHICLE, size 2, i.e. needs two crates to be build +-- -- vehicles and FOB will be spawned as crates in a LOAD zone first. Once transported to DROP zones, they can be build into the objects +-- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2) +-- -- if you want to add weight to your Heli, crates can have a weight in kg **per crate**. Currently no max weight checked. Fly carefully. +-- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2,2775) +-- -- if you want to limit your stock, add a number (here: 10) as parameter after weight. No parameter / nil means unlimited stock. +-- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2,2775,10) +-- +-- -- add infantry unit called "Forward Ops Base" using template "FOB", of type FOB, size 4, i.e. needs four crates to be build: +-- my_ctld:AddCratesCargo("Forward Ops Base",{"FOB"},CTLD_CARGO.Enum.FOB,4) +-- +-- -- add crates to repair FOB or VEHICLE type units - the 2nd parameter needs to match the template you want to repair +-- my_ctld:AddCratesRepair("Humvee Repair","Humvee",CTLD_CARGO.Enum.REPAIR,1) +-- my_ctld.repairtime = 300 -- takes 300 seconds to repair something +-- +-- -- add static cargo objects, e.g ammo chests - the name needs to refer to a STATIC object in the mission editor, +-- -- here: it\'s the UNIT name (not the GROUP name!), the second parameter is the weight in kg. +-- my_ctld:AddStaticsCargo("Ammunition",500) +-- +-- ## 1.3 Add logistics zones +-- +-- Add zones for loading troops and crates and dropping, building crates +-- +-- -- Add a zone of type LOAD to our setup. Players can load troops and crates. +-- -- "Loadzone" is the name of the zone from the ME. Players can load, if they are inside the zone. +-- -- Smoke and Flare color for this zone is blue, it is active (can be used) and has a radio beacon. +-- my_ctld:AddCTLDZone("Loadzone",CTLD.CargoZoneType.LOAD,SMOKECOLOR.Blue,true,true) +-- +-- -- Add a zone of type DROP. Players can drop crates here. +-- -- Smoke and Flare color for this zone is blue, it is active (can be used) and has a radio beacon. +-- -- NOTE: Troops can be unloaded anywhere, also when hovering in parameters. +-- my_ctld:AddCTLDZone("Dropzone",CTLD.CargoZoneType.DROP,SMOKECOLOR.Red,true,true) +-- +-- -- Add two zones of type MOVE. Dropped troops and vehicles will move to the nearest one. See options. +-- -- Smoke and Flare color for this zone is blue, it is active (can be used) and has a radio beacon. +-- my_ctld:AddCTLDZone("Movezone",CTLD.CargoZoneType.MOVE,SMOKECOLOR.Orange,false,false) +-- +-- my_ctld:AddCTLDZone("Movezone2",CTLD.CargoZoneType.MOVE,SMOKECOLOR.White,true,true) +-- +-- -- Add a zone of type SHIP to our setup. Players can load troops and crates from this ship +-- -- "Tarawa" is the unitname (callsign) of the ship from the ME. Players can load, if they are inside the zone. +-- -- The ship is 240 meters long and 20 meters wide. +-- -- Note that you need to adjust the max hover height to deck height plus 5 meters or so for loading to work. +-- -- When the ship is moving, forcing hoverload might not be a good idea. +-- my_ctld:AddCTLDZone("Tarawa",CTLD.CargoZoneType.SHIP,SMOKECOLOR.Blue,true,true,240,20) +-- +-- ## 2. Options +-- +-- The following options are available (with their defaults). Only set the ones you want changed: +-- +-- my_ctld.useprefix = true -- (DO NOT SWITCH THIS OFF UNLESS YOU KNOW WHAT YOU ARE DOING!) Adjust **before** starting CTLD. If set to false, *all* choppers of the coalition side will be enabled for CTLD. +-- my_ctld.CrateDistance = 35 -- List and Load crates in this radius only. +-- my_ctld.dropcratesanywhere = false -- Option to allow crates to be dropped anywhere. +-- my_ctld.maximumHoverHeight = 15 -- Hover max this high to load. +-- my_ctld.minimumHoverHeight = 4 -- Hover min this low to load. +-- my_ctld.forcehoverload = true -- Crates (not: troops) can **only** be loaded while hovering. +-- my_ctld.hoverautoloading = true -- Crates in CrateDistance in a LOAD zone will be loaded automatically if space allows. +-- my_ctld.smokedistance = 2000 -- Smoke or flares can be request for zones this far away (in meters). +-- my_ctld.movetroopstowpzone = true -- Troops and vehicles will move to the nearest MOVE zone... +-- my_ctld.movetroopsdistance = 5000 -- .. but only if this far away (in meters) +-- my_ctld.smokedistance = 2000 -- Only smoke or flare zones if requesting player unit is this far away (in meters) +-- my_ctld.suppressmessages = false -- Set to true if you want to script your own messages. +-- my_ctld.repairtime = 300 -- Number of seconds it takes to repair a unit. +-- my_ctld.cratecountry = country.id.GERMANY -- ID of crates. Will default to country.id.RUSSIA for RED coalition setups. +-- my_ctld.allowcratepickupagain = true -- allow re-pickup crates that were dropped. +-- my_ctld.enableslingload = false -- allow cargos to be slingloaded - might not work for all cargo types +-- my_ctld.pilotmustopendoors = false -- force opening of doors +-- my_ctld.SmokeColor = SMOKECOLOR.Red -- color to use when dropping smoke from heli +-- my_ctld.FlareColor = FLARECOLOR.Red -- color to use when flaring from heli +-- my_ctld.basetype = "container_cargo" -- default shape of the cargo container +-- +-- ## 2.1 User functions +-- +-- ### 2.1.1 Adjust or add chopper unit-type capabilities +-- +-- Use this function to adjust what a heli type can or cannot do: +-- +-- -- E.g. update unit capabilities for testing. Please stay realistic in your mission design. +-- -- Make a Gazelle into a heavy truck, this type can load both crates and troops and eight of each type: +-- my_ctld:UnitCapabilities("SA342L", true, true, 8, 8, 12) +-- +-- -- Default unit type capabilities are: +-- +-- ["SA342Mistral"] = {type="SA342Mistral", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12}, +-- ["SA342L"] = {type="SA342L", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12}, +-- ["SA342M"] = {type="SA342M", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12}, +-- ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12}, +-- ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8, length = 15}, +-- ["Mi-8MT"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15}, +-- ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 15}, +-- ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18}, +-- ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18}, +-- ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25}, +-- +-- +-- ### 2.1.2 Activate and deactivate zones +-- +-- Activate a zone: +-- +-- -- Activate zone called Name of type #CTLD.CargoZoneType ZoneType: +-- my_ctld:ActivateZone(Name,CTLD.CargoZoneType.MOVE) +-- +-- Deactivate a zone: +-- +-- -- Deactivate zone called Name of type #CTLD.CargoZoneType ZoneType: +-- my_ctld:DeactivateZone(Name,CTLD.CargoZoneType.DROP) +-- +-- ## 2.1.3 Limit and manage available resources +-- +-- When adding generic cargo types, you can effectively limit how many units can be dropped/build by the players, e.g. +-- +-- -- if you want to limit your stock, add a number (here: 10) as parameter after weight. No parameter / nil means unlimited stock. +-- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2,2775,10) +-- +-- You can manually add or remove the available stock like so: +-- +-- -- Crates +-- my_ctld:AddStockCrates("Humvee", 2) +-- my_ctld:RemoveStockCrates("Humvee", 2) +-- +-- -- Troops +-- my_ctld:AddStockTroops("Anti-Air", 2) +-- my_ctld:RemoveStockTroops("Anti-Air", 2) +-- +-- Notes: +-- Troops dropped back into a LOAD zone will effectively be added to the stock. Crates lost in e.g. a heli crash are just that - lost. +-- +-- ## 3. Events +-- +-- The class comes with a number of FSM-based events that missions designers can use to shape their mission. +-- These are: +-- +-- ## 3.1 OnAfterTroopsPickedUp +-- +-- This function is called when a player has loaded Troops: +-- +-- function my_ctld:OnAfterTroopsPickedUp(From, Event, To, Group, Unit, Cargo) +-- ... your code here ... +-- end +-- +-- ## 3.2 OnAfterCratesPickedUp +-- +-- This function is called when a player has picked up crates: +-- +-- function my_ctld:OnAfterCratesPickedUp(From, Event, To, Group, Unit, Cargo) +-- ... your code here ... +-- end +-- +-- ## 3.3 OnAfterTroopsDeployed +-- +-- This function is called when a player has deployed troops into the field: +-- +-- function my_ctld:OnAfterTroopsDeployed(From, Event, To, Group, Unit, Troops) +-- ... your code here ... +-- end +-- +-- ## 3.4 OnAfterTroopsExtracted +-- +-- This function is called when a player has re-boarded already deployed troops from the field: +-- +-- function my_ctld:OnAfterTroopsExtracted(From, Event, To, Group, Unit, Troops) +-- ... your code here ... +-- end +-- +-- ## 3.5 OnAfterCratesDropped +-- +-- This function is called when a player has deployed crates to a DROP zone: +-- +-- function my_ctld:OnAfterCratesDropped(From, Event, To, Group, Unit, Cargotable) +-- ... your code here ... +-- end +-- +-- ## 3.6 OnAfterCratesBuild, OnAfterCratesRepaired +-- +-- This function is called when a player has build a vehicle or FOB: +-- +-- function my_ctld:OnAfterCratesBuild(From, Event, To, Group, Unit, Vehicle) +-- ... your code here ... +-- end +-- +-- function my_ctld:OnAfterCratesRepaired(From, Event, To, Group, Unit, Vehicle) +-- ... your code here ... +-- end + -- +-- ## 3.7 A simple SCORING example: +-- +-- To award player with points, using the SCORING Class (SCORING: my_Scoring, CTLD: CTLD_Cargotransport) +-- +-- function CTLD_Cargotransport:OnAfterCratesDropped(From, Event, To, Group, Unit, Cargotable) +-- local points = 10 +-- if Unit then +-- local PlayerName = Unit:GetPlayerName() +-- my_scoring:_AddPlayerFromUnit( Unit ) +-- my_scoring:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for transporting cargo crates!", PlayerName, points), points) +-- end +-- end +-- +-- function CTLD_Cargotransport:OnAfterCratesBuild(From, Event, To, Group, Unit, Vehicle) +-- local points = 5 +-- if Unit then -- local PlayerName = Unit:GetPlayerName() -- my_scoring:_AddPlayerFromUnit( Unit ) -- my_scoring:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for the construction of Units!", PlayerName, points), points) - -- end - -- end - -- - -- ## 4. F10 Menu structure - -- - -- CTLD management menu is under the F10 top menu and called "CTLD" - -- - -- ## 4.1 Manage Crates - -- - -- Use this entry to get, load, list nearby, drop, build and repair crates. Also see options. - -- - -- ## 4.2 Manage Troops - -- - -- Use this entry to load, drop and extract troops. NOTE - with extract you can only load troops from the field that were deployed prior. - -- Currently limited CTLD_CARGO troops, which are build from **one** template. Also, this will heal/complete your units as they are respawned. - -- - -- ## 4.3 List boarded cargo - -- - -- Lists what you have loaded. Shows load capabilities for number of crates and number of seats for troops. - -- - -- ## 4.4 Smoke & Flare zones nearby or drop smoke or flare from Heli - -- - -- Does what it says. - -- - -- ## 4.5 List active zone beacons - -- - -- Lists active radio beacons for all zones, where zones are both active and have a beacon. @see `CTLD:AddCTLDZone()` - -- - -- ## 4.6 Show hover parameters - -- - -- Lists hover parameters and indicates if these are curently fulfilled. Also @see options on hover heights. - -- - -- ## 4.7 List Inventory - -- - -- Lists invetory of available units to drop or build. - -- - -- ## 5. Support for Hercules mod by Anubis - -- - -- Basic support for the Hercules mod By Anubis has been build into CTLD. Currently this does **not** cover objects and troops which can - -- be loaded from the Rearm/Refuel menu, i.e. you can drop them into the field, but you cannot use them in functions scripted with this class. - -- - -- local my_ctld = CTLD:New(coalition.side.BLUE,{"Helicargo", "Hercules"},"Lufttransportbrigade I") - -- - -- Enable these options for Hercules support: - -- - -- my_ctld.enableHercules = true - -- my_ctld.HercMinAngels = 155 -- for troop/cargo drop via chute in meters, ca 470 ft - -- my_ctld.HercMaxAngels = 2000 -- for troop/cargo drop via chute in meters, ca 6000 ft - -- my_ctld.HercMaxSpeed = 77 -- 77mps or 270kph or 150kn - -- - -- Also, the following options need to be set to `true`: - -- - -- my_ctld.useprefix = true -- this is true by default and MUST BE ON. - -- - -- Standard transport capabilities as per the real Hercules are: - -- - -- ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64}, -- 19t cargo, 64 paratroopers - -- - -- ## 6. Save and load back units - persistance - -- - -- You can save and later load back units dropped or build to make your mission persistent. - -- For this to work, you need to de-sanitize **io** and **lfs** in your MissionScripting.lua, which is located in your DCS installtion folder under Scripts. - -- There is a risk involved in doing that; if you do not know what that means, this is possibly not for you. - -- - -- Use the following options to manage your saves: - -- - -- my_ctld.enableLoadSave = true -- allow auto-saving and loading of files - -- my_ctld.saveinterval = 600 -- save every 10 minutes - -- my_ctld.filename = "missionsave.csv" -- example filename - -- my_ctld.filepath = "C:\\Users\\myname\\Saved Games\\DCS\Missions\\MyMission" -- example path - -- my_ctld.eventoninject = true -- fire OnAfterCratesBuild and OnAfterTroopsDeployed events when loading (uses Inject functions) - -- - -- Then use an initial load at the beginning of your mission: - -- - -- my_ctld:__Load(10) - -- - -- **Caveat:** - -- If you use units build by multiple templates, they will effectively double on loading. Dropped crates are not saved. Current stock is not saved. - -- - -- @field #CTLD - CTLD = { - ClassName = "CTLD", - verbose = 0, - lid = "", - coalition = 1, - coalitiontxt = "blue", - PilotGroups = {}, -- #GROUP_SET of heli pilots - CtldUnits = {}, -- Table of helicopter #GROUPs - FreeVHFFrequencies = {}, -- Table of VHF - FreeUHFFrequencies = {}, -- Table of UHF - FreeFMFrequencies = {}, -- Table of FM - CargoCounter = 0, - wpZones = {}, - Cargo_Troops = {}, -- generic troops objects - Cargo_Crates = {}, -- generic crate objects - Loaded_Cargo = {}, -- cargo aboard units - Spawned_Crates = {}, -- Holds objects for crates spawned generally - Spawned_Cargo = {}, -- Binds together spawned_crates and their CTLD_CARGO objects - CrateDistance = 35, -- list crates in this radius - debug = false, - wpZones = {}, - dropOffZones = {}, - pickupZones = {}, - } +-- end +-- end +-- +-- ## 4. F10 Menu structure +-- +-- CTLD management menu is under the F10 top menu and called "CTLD" +-- +-- ## 4.1 Manage Crates +-- +-- Use this entry to get, load, list nearby, drop, build and repair crates. Also see options. +-- +-- ## 4.2 Manage Troops +-- +-- Use this entry to load, drop and extract troops. NOTE - with extract you can only load troops from the field that were deployed prior. +-- Currently limited CTLD_CARGO troops, which are build from **one** template. Also, this will heal/complete your units as they are respawned. +-- +-- ## 4.3 List boarded cargo +-- +-- Lists what you have loaded. Shows load capabilities for number of crates and number of seats for troops. +-- +-- ## 4.4 Smoke & Flare zones nearby or drop smoke or flare from Heli +-- +-- Does what it says. +-- +-- ## 4.5 List active zone beacons +-- +-- Lists active radio beacons for all zones, where zones are both active and have a beacon. @see `CTLD:AddCTLDZone()` +-- +-- ## 4.6 Show hover parameters +-- +-- Lists hover parameters and indicates if these are curently fulfilled. Also @see options on hover heights. +-- +-- ## 4.7 List Inventory +-- +-- Lists invetory of available units to drop or build. +-- +-- ## 5. Support for Hercules mod by Anubis +-- +-- Basic support for the Hercules mod By Anubis has been build into CTLD. Currently this does **not** cover objects and troops which can +-- be loaded from the Rearm/Refuel menu, i.e. you can drop them into the field, but you cannot use them in functions scripted with this class. +-- +-- local my_ctld = CTLD:New(coalition.side.BLUE,{"Helicargo", "Hercules"},"Lufttransportbrigade I") +-- +-- Enable these options for Hercules support: +-- +-- my_ctld.enableHercules = true +-- my_ctld.HercMinAngels = 155 -- for troop/cargo drop via chute in meters, ca 470 ft +-- my_ctld.HercMaxAngels = 2000 -- for troop/cargo drop via chute in meters, ca 6000 ft +-- my_ctld.HercMaxSpeed = 77 -- 77mps or 270kph or 150kn +-- +-- Also, the following options need to be set to `true`: +-- +-- my_ctld.useprefix = true -- this is true by default and MUST BE ON. +-- +-- Standard transport capabilities as per the real Hercules are: +-- +-- ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64}, -- 19t cargo, 64 paratroopers +-- +-- ## 6. Save and load back units - persistance +-- +-- You can save and later load back units dropped or build to make your mission persistent. +-- For this to work, you need to de-sanitize **io** and **lfs** in your MissionScripting.lua, which is located in your DCS installtion folder under Scripts. +-- There is a risk involved in doing that; if you do not know what that means, this is possibly not for you. +-- +-- Use the following options to manage your saves: +-- +-- my_ctld.enableLoadSave = true -- allow auto-saving and loading of files +-- my_ctld.saveinterval = 600 -- save every 10 minutes +-- my_ctld.filename = "missionsave.csv" -- example filename +-- my_ctld.filepath = "C:\\Users\\myname\\Saved Games\\DCS\Missions\\MyMission" -- example path +-- my_ctld.eventoninject = true -- fire OnAfterCratesBuild and OnAfterTroopsDeployed events when loading (uses Inject functions) +-- +-- Then use an initial load at the beginning of your mission: +-- +-- my_ctld:__Load(10) +-- +-- **Caveat:** +-- If you use units build by multiple templates, they will effectively double on loading. Dropped crates are not saved. Current stock is not saved. +-- +-- @field #CTLD +CTLD = { + ClassName = "CTLD", + verbose = 0, + lid = "", + coalition = 1, + coalitiontxt = "blue", + PilotGroups = {}, -- #GROUP_SET of heli pilots + CtldUnits = {}, -- Table of helicopter #GROUPs + FreeVHFFrequencies = {}, -- Table of VHF + FreeUHFFrequencies = {}, -- Table of UHF + FreeFMFrequencies = {}, -- Table of FM + CargoCounter = 0, + wpZones = {}, + Cargo_Troops = {}, -- generic troops objects + Cargo_Crates = {}, -- generic crate objects + Loaded_Cargo = {}, -- cargo aboard units + Spawned_Crates = {}, -- Holds objects for crates spawned generally + Spawned_Cargo = {}, -- Binds together spawned_crates and their CTLD_CARGO objects + CrateDistance = 35, -- list crates in this radius + debug = false, + wpZones = {}, + dropOffZones = {}, + pickupZones = {}, +} - ------------------------------ - -- DONE: Zone Checks - -- DONE: TEST Hover load and unload - -- DONE: Crate unload - -- DONE: Hover (auto-)load - -- DONE: (More) Housekeeping - -- DONE: Troops running to WP Zone - -- DONE: Zone Radio Beacons - -- DONE: Stats Running - -- DONE: Added support for Hercules - -- TODO: Possibly - either/or loading crates and troops - -- DONE: Make inject respect existing cargo types - -- DONE: Drop beacons or flares/smoke - -- DONE: Add statics as cargo - -- DONE: List cargo in stock - -- DONE: Limit of troops, crates buildable? - -- DONE: Allow saving of Troops & Vehicles - ------------------------------ +------------------------------ +-- DONE: Zone Checks +-- DONE: TEST Hover load and unload +-- DONE: Crate unload +-- DONE: Hover (auto-)load +-- DONE: (More) Housekeeping +-- DONE: Troops running to WP Zone +-- DONE: Zone Radio Beacons +-- DONE: Stats Running +-- DONE: Added support for Hercules +-- TODO: Possibly - either/or loading crates and troops +-- DONE: Make inject respect existing cargo types +-- DONE: Drop beacons or flares/smoke +-- DONE: Add statics as cargo +-- DONE: List cargo in stock +-- DONE: Limit of troops, crates buildable? +-- DONE: Allow saving of Troops & Vehicles +------------------------------ - --- Radio Beacons - -- @type CTLD.ZoneBeacon - -- @field #string name -- Name of zone for the coordinate - -- @field #number frequency -- in mHz - -- @field #number modulation -- i.e.radio.modulation.FM or radio.modulation.AM +--- Radio Beacons +-- @type CTLD.ZoneBeacon +-- @field #string name -- Name of zone for the coordinate +-- @field #number frequency -- in mHz +-- @field #number modulation -- i.e.radio.modulation.FM or radio.modulation.AM - --- Zone Info. - -- @type CTLD.CargoZone - -- @field #string name Name of Zone. - -- @field #string color Smoke color for zone, e.g. SMOKECOLOR.Red. - -- @field #boolean active Active or not. - -- @field #string type Type of zone, i.e. load,drop,move,ship - -- @field #boolean hasbeacon Create and run radio beacons if active. - -- @field #table fmbeacon Beacon info as #CTLD.ZoneBeacon - -- @field #table uhfbeacon Beacon info as #CTLD.ZoneBeacon - -- @field #table vhfbeacon Beacon info as #CTLD.ZoneBeacon - -- @field #number shiplength For ships - length of ship - -- @field #number shipwidth For ships - width of ship +--- Zone Info. +-- @type CTLD.CargoZone +-- @field #string name Name of Zone. +-- @field #string color Smoke color for zone, e.g. SMOKECOLOR.Red. +-- @field #boolean active Active or not. +-- @field #string type Type of zone, i.e. load,drop,move,ship +-- @field #boolean hasbeacon Create and run radio beacons if active. +-- @field #table fmbeacon Beacon info as #CTLD.ZoneBeacon +-- @field #table uhfbeacon Beacon info as #CTLD.ZoneBeacon +-- @field #table vhfbeacon Beacon info as #CTLD.ZoneBeacon +-- @field #number shiplength For ships - length of ship +-- @field #number shipwidth For ships - width of ship - --- Zone Type Info. - -- @type CTLD.CargoZoneType - CTLD.CargoZoneType = { - LOAD = "load", - DROP = "drop", - MOVE = "move", - SHIP = "ship", - } +--- Zone Type Info. +-- @type CTLD.CargoZoneType +CTLD.CargoZoneType = { + LOAD = "load", + DROP = "drop", + MOVE = "move", + SHIP = "ship", +} - --- Buildable table info. - -- @type CTLD.Buildable - -- @field #string Name Name of the object. - -- @field #number Required Required crates. - -- @field #number Found Found crates. - -- @field #table Template Template names for this build. - -- @field #boolean CanBuild Is buildable or not. - -- @field #CTLD_CARGO.Enum Type Type enumerator (for moves). +--- Buildable table info. +-- @type CTLD.Buildable +-- @field #string Name Name of the object. +-- @field #number Required Required crates. +-- @field #number Found Found crates. +-- @field #table Template Template names for this build. +-- @field #boolean CanBuild Is buildable or not. +-- @field #CTLD_CARGO.Enum Type Type enumerator (for moves). - --- Unit capabilities. - -- @type CTLD.UnitCapabilities - -- @field #string type Unit type. - -- @field #boolean crates Can transport crate. - -- @field #boolean troops Can transport troops. - -- @field #number cratelimit Number of crates transportable. - -- @field #number trooplimit Number of troop units transportable. - CTLD.UnitTypes = { - ["SA342Mistral"] = { type = "SA342Mistral", crates = false, troops = true, cratelimit = 0, trooplimit = 4, length = 12 }, - ["SA342L"] = { type = "SA342L", crates = false, troops = true, cratelimit = 0, trooplimit = 2, length = 12 }, - ["SA342M"] = { type = "SA342M", crates = false, troops = true, cratelimit = 0, trooplimit = 4, length = 12 }, - ["SA342Minigun"] = { type = "SA342Minigun", crates = false, troops = true, cratelimit = 0, trooplimit = 2, length = 12 }, - ["UH-1H"] = { type = "UH-1H", crates = true, troops = true, cratelimit = 1, trooplimit = 8, length = 15 }, - ["Mi-8MTV2"] = { type = "Mi-8MTV2", crates = true, troops = true, cratelimit = 2, trooplimit = 12, length = 15 }, - ["Mi-8MT"] = { type = "Mi-8MTV2", crates = true, troops = true, cratelimit = 2, trooplimit = 12, length = 15 }, - ["Ka-50"] = { type = "Ka-50", crates = false, troops = false, cratelimit = 0, trooplimit = 0, length = 15 }, - ["Mi-24P"] = { type = "Mi-24P", crates = true, troops = true, cratelimit = 2, trooplimit = 8, length = 18 }, - ["Mi-24V"] = { type = "Mi-24V", crates = true, troops = true, cratelimit = 2, trooplimit = 8, length = 18 }, - ["Hercules"] = { type = "Hercules", crates = true, troops = true, cratelimit = 7, trooplimit = 64, length = 25 }, -- 19t cargo, 64 paratroopers. Actually it's longer, but the center coord is off-center of the model. - } +--- Unit capabilities. +-- @type CTLD.UnitCapabilities +-- @field #string type Unit type. +-- @field #boolean crates Can transport crate. +-- @field #boolean troops Can transport troops. +-- @field #number cratelimit Number of crates transportable. +-- @field #number trooplimit Number of troop units transportable. +CTLD.UnitTypes = { + ["SA342Mistral"] = {type="SA342Mistral", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12}, + ["SA342L"] = {type="SA342L", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12}, + ["SA342M"] = {type="SA342M", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12}, + ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12}, + ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8, length = 15}, + ["Mi-8MTV2"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15}, + ["Mi-8MT"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15}, + ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 15}, + ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18}, + ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18}, + ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25}, -- 19t cargo, 64 paratroopers. + --Actually it's longer, but the center coord is off-center of the model. +} - --- CTLD class version. - -- @field #string version - CTLD.version = "1.0.1" +--- CTLD class version. +-- @field #string version +CTLD.version="1.0.2" - --- Instantiate a new CTLD. - -- @param #CTLD self - -- @param #string Coalition Coalition of this CTLD. I.e. coalition.side.BLUE or coalition.side.RED or coalition.side.NEUTRAL - -- @param #table Prefixes Table of pilot prefixes. - -- @param #string Alias Alias of this CTLD for logging. - -- @return #CTLD self - function CTLD:New( Coalition, Prefixes, Alias ) - -- Inherit everything from FSM class. - local self = BASE:Inherit( self, FSM:New() ) -- #CTLD - - BASE:T( { Coalition, Prefixes, Alias } ) - - -- set Coalition - if Coalition and type( Coalition ) == "string" then - if Coalition == "blue" then - self.coalition = coalition.side.BLUE - self.coalitiontxt = Coalition - elseif Coalition == "red" then - self.coalition = coalition.side.RED - self.coalitiontxt = Coalition - elseif Coalition == "neutral" then - self.coalition = coalition.side.NEUTRAL - self.coalitiontxt = Coalition - else - self:E( "ERROR: Unknown coalition in CTLD!" ) - end +--- Instantiate a new CTLD. +-- @param #CTLD self +-- @param #string Coalition Coalition of this CTLD. I.e. coalition.side.BLUE or coalition.side.RED or coalition.side.NEUTRAL +-- @param #table Prefixes Table of pilot prefixes. +-- @param #string Alias Alias of this CTLD for logging. +-- @return #CTLD self +function CTLD:New(Coalition, Prefixes, Alias) + -- Inherit everything from FSM class. + local self=BASE:Inherit(self, FSM:New()) -- #CTLD + + BASE:T({Coalition, Prefixes, Alias}) + + --set Coalition + if Coalition and type(Coalition)=="string" then + if Coalition=="blue" then + self.coalition=coalition.side.BLUE + self.coalitiontxt = Coalition + elseif Coalition=="red" then + self.coalition=coalition.side.RED + self.coalitiontxt = Coalition + elseif Coalition=="neutral" then + self.coalition=coalition.side.NEUTRAL + self.coalitiontxt = Coalition else - self.coalition = Coalition - self.coalitiontxt = string.lower( UTILS.GetCoalitionName( self.coalition ) ) + self:E("ERROR: Unknown coalition in CTLD!") end - - -- Set alias. - if Alias then - self.alias = tostring( Alias ) - else - self.alias = "UNHCR" - if self.coalition then - if self.coalition == coalition.side.RED then - self.alias = "Red CTLD" - elseif self.coalition == coalition.side.BLUE then - self.alias = "Blue CTLD" - end + else + self.coalition = Coalition + self.coalitiontxt = string.lower(UTILS.GetCoalitionName(self.coalition)) + end + + -- Set alias. + if Alias then + self.alias=tostring(Alias) + else + self.alias="UNHCR" + if self.coalition then + if self.coalition==coalition.side.RED then + self.alias="Red CTLD" + elseif self.coalition==coalition.side.BLUE then + self.alias="Blue CTLD" end end + end + + -- Set some string id for output to DCS.log file. + self.lid=string.format("%s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown") + + -- Start State. + self:SetStartState("Stopped") - -- Set some string id for output to DCS.log file. - self.lid = string.format( "%s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName( self.coalition ) or "unknown" ) - - -- Start State. - self:SetStartState( "Stopped" ) - - -- Add FSM transitions. - -- From State --> Event --> To State + -- Add FSM transitions. + -- From State --> Event --> To State self:AddTransition("Stopped", "Start", "Running") -- Start FSM. self:AddTransition("*", "Status", "*") -- CTLD status update. - self:AddTransition("*", "TroopsPickedUp", "*") -- CTLD pickup event. - self:AddTransition("*", "TroopsExtracted", "*") -- CTLD extract event. - self:AddTransition("*", "CratesPickedUp", "*") -- CTLD pickup event. - self:AddTransition("*", "TroopsDeployed", "*") -- CTLD deploy event. - self:AddTransition("*", "TroopsRTB", "*") -- CTLD deploy event. - self:AddTransition("*", "CratesDropped", "*") -- CTLD deploy event. - self:AddTransition("*", "CratesBuild", "*") -- CTLD build event. - self:AddTransition("*", "CratesRepaired", "*") -- CTLD repair event. - self:AddTransition("*", "Load", "*") -- CTLD load event. - self:AddTransition("*", "Save", "*") -- CTLD save event. + self:AddTransition("*", "TroopsPickedUp", "*") -- CTLD pickup event. + self:AddTransition("*", "TroopsExtracted", "*") -- CTLD extract event. + self:AddTransition("*", "CratesPickedUp", "*") -- CTLD pickup event. + self:AddTransition("*", "TroopsDeployed", "*") -- CTLD deploy event. + self:AddTransition("*", "TroopsRTB", "*") -- CTLD deploy event. + self:AddTransition("*", "CratesDropped", "*") -- CTLD deploy event. + self:AddTransition("*", "CratesBuild", "*") -- CTLD build event. + self:AddTransition("*", "CratesRepaired", "*") -- CTLD repair event. + self:AddTransition("*", "Load", "*") -- CTLD load event. + self:AddTransition("*", "Save", "*") -- CTLD save event. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. - - -- tables - self.PilotGroups = {} - self.CtldUnits = {} - - -- Beacons - self.FreeVHFFrequencies = {} - self.FreeUHFFrequencies = {} - self.FreeFMFrequencies = {} - self.UsedVHFFrequencies = {} - self.UsedUHFFrequencies = {} - self.UsedFMFrequencies = {} - - -- radio beacons - self.RadioSound = "beacon.ogg" - - -- zones stuff - self.pickupZones = {} - self.dropOffZones = {} - self.wpZones = {} - self.shipZones = {} - - -- Cargo - self.Cargo_Crates = {} - self.Cargo_Troops = {} - self.Cargo_Statics = {} - self.Loaded_Cargo = {} - self.Spawned_Crates = {} - self.Spawned_Cargo = {} - self.MenusDone = {} - self.DroppedTroops = {} - self.DroppedCrates = {} - self.CargoCounter = 0 - self.CrateCounter = 0 - self.TroopCounter = 0 - - -- added engineering - self.Engineers = 0 -- #number use as counter - self.EngineersInField = {} -- #table holds #CTLD_ENGINEERING objects - self.EngineerSearch = 2000 -- #number search distance for crates to build or repair - - -- setup - self.CrateDistance = 35 -- list/load crates in this radius - self.ExtractFactor = 3.33 -- factor for troops extraction, i.e. CrateDistance * Extractfactor - self.prefixes = Prefixes or { "Cargoheli" } - -- self.I({prefixes = self.prefixes}) - self.useprefix = true - - self.maximumHoverHeight = 15 - self.minimumHoverHeight = 4 - self.forcehoverload = true - self.hoverautoloading = true - self.dropcratesanywhere = false -- #1570 - - self.smokedistance = 2000 - self.movetroopstowpzone = true - self.movetroopsdistance = 5000 - - -- added support Hercules Mod - self.enableHercules = false - self.HercMinAngels = 165 -- for troop/cargo drop via chute - self.HercMaxAngels = 2000 -- for troop/cargo drop via chute - self.HercMaxSpeed = 77 -- 280 kph or 150kn eq 77 mps - - -- message suppression - self.suppressmessages = false - - -- time to repair a unit/group - self.repairtime = 300 - - -- place spawned crates in front of aircraft - self.placeCratesAhead = false - - -- country of crates spawned - self.cratecountry = country.id.GERMANY - - -- for opening doors - self.pilotmustopendoors = false - - if self.coalition == coalition.side.RED then - self.cratecountry = country.id.RUSSIA - end - - -- load and save dropped TROOPS - self.enableLoadSave = false - self.filepath = nil - self.saveinterval = 600 - self.eventoninject = true - - local AliaS = string.gsub( self.alias, " ", "_" ) - self.filename = string.format( "CTLD_%s_Persist.csv", AliaS ) - - -- allow re-pickup crates - self.allowcratepickupagain = true - - -- slingload - self.enableslingload = false - - -- Smokes and Flares - self.SmokeColor = SMOKECOLOR.Red - self.FlareColor = FLARECOLOR.Red - - for i = 1, 100 do - math.random() - end - - self:_GenerateVHFrequencies() - self:_GenerateUHFrequencies() - self:_GenerateFMFrequencies() - - ------------------------ - --- Pseudo Functions --- - ------------------------ - + + -- tables + self.PilotGroups ={} + self.CtldUnits = {} + + -- Beacons + self.FreeVHFFrequencies = {} + self.FreeUHFFrequencies = {} + self.FreeFMFrequencies = {} + self.UsedVHFFrequencies = {} + self.UsedUHFFrequencies = {} + self.UsedFMFrequencies = {} + + -- radio beacons + self.RadioSound = "beacon.ogg" + + -- zones stuff + self.pickupZones = {} + self.dropOffZones = {} + self.wpZones = {} + self.shipZones = {} + + -- Cargo + self.Cargo_Crates = {} + self.Cargo_Troops = {} + self.Cargo_Statics = {} + self.Loaded_Cargo = {} + self.Spawned_Crates = {} + self.Spawned_Cargo = {} + self.MenusDone = {} + self.DroppedTroops = {} + self.DroppedCrates = {} + self.CargoCounter = 0 + self.CrateCounter = 0 + self.TroopCounter = 0 + + -- added engineering + self.Engineers = 0 -- #number use as counter + self.EngineersInField = {} -- #table holds #CTLD_ENGINEERING objects + self.EngineerSearch = 2000 -- #number search distance for crates to build or repair + + -- setup + self.CrateDistance = 35 -- list/load crates in this radius + self.ExtractFactor = 3.33 -- factor for troops extraction, i.e. CrateDistance * Extractfactor + self.prefixes = Prefixes or {"Cargoheli"} + --self.I({prefixes = self.prefixes}) + self.useprefix = true + + self.maximumHoverHeight = 15 + self.minimumHoverHeight = 4 + self.forcehoverload = true + self.hoverautoloading = true + self.dropcratesanywhere = false -- #1570 + + self.smokedistance = 2000 + self.movetroopstowpzone = true + self.movetroopsdistance = 5000 + + -- added support Hercules Mod + self.enableHercules = false + self.HercMinAngels = 165 -- for troop/cargo drop via chute + self.HercMaxAngels = 2000 -- for troop/cargo drop via chute + self.HercMaxSpeed = 77 -- 280 kph or 150kn eq 77 mps + + -- message suppression + self.suppressmessages = false + + -- time to repair a unit/group + self.repairtime = 300 + + -- place spawned crates in front of aircraft + self.placeCratesAhead = false + + -- country of crates spawned + self.cratecountry = country.id.GERMANY + + -- for opening doors + self.pilotmustopendoors = false + + if self.coalition == coalition.side.RED then + self.cratecountry = country.id.RUSSIA + end + + -- load and save dropped TROOPS + self.enableLoadSave = false + self.filepath = nil + self.saveinterval = 600 + self.eventoninject = true + + local AliaS = string.gsub(self.alias," ","_") + self.filename = string.format("CTLD_%s_Persist.csv",AliaS) + + -- allow re-pickup crates + self.allowcratepickupagain = true + + -- slingload + self.enableslingload = false + self.basetype = "container_cargo" -- shape of the container + + -- Smokes and Flares + self.SmokeColor = SMOKECOLOR.Red + self.FlareColor = FLARECOLOR.Red + + for i=1,100 do + math.random() + end + + self:_GenerateVHFrequencies() + self:_GenerateUHFrequencies() + self:_GenerateFMFrequencies() + + ------------------------ + --- Pseudo Functions --- + ------------------------ + --- Triggers the FSM event "Start". Starts the CTLD. Initializes parameters and starts event handlers. - -- @function [parent=#CTLD] Start - -- @param #CTLD self - - --- Triggers the FSM event "Start" after a delay. Starts the CTLD. Initializes parameters and starts event handlers. - -- @function [parent=#CTLD] __Start - -- @param #CTLD self - -- @param #number delay Delay in seconds. - - --- Triggers the FSM event "Stop". Stops the CTLD and all its event handlers. - -- @param #CTLD self - - --- Triggers the FSM event "Stop" after a delay. Stops the CTLD and all its event handlers. - -- @function [parent=#CTLD] __Stop - -- @param #CTLD self - -- @param #number delay Delay in seconds. - - --- Triggers the FSM event "Status". - -- @function [parent=#CTLD] Status - -- @param #CTLD self - - --- Triggers the FSM event "Status" after a delay. - -- @function [parent=#CTLD] __Status - -- @param #CTLD self - -- @param #number delay Delay in seconds. - - --- Triggers the FSM event "Load". - -- @function [parent=#CTLD] Load - -- @param #CTLD self - - --- Triggers the FSM event "Load" after a delay. - -- @function [parent=#CTLD] __Load - -- @param #CTLD self - -- @param #number delay Delay in seconds. - - --- Triggers the FSM event "Save". - -- @function [parent=#CTLD] Load - -- @param #CTLD self - - --- Triggers the FSM event "Save" after a delay. - -- @function [parent=#CTLD] __Save - -- @param #CTLD self - -- @param #number delay Delay in seconds. - - --- FSM Function OnAfterTroopsPickedUp. - -- @function [parent=#CTLD] OnAfterTroopsPickedUp - -- @param #CTLD self - -- @param #string From State. - -- @param #string Event Trigger. - -- @param #string To State. - -- @param Wrapper.Group#GROUP Group Group Object. - -- @param Wrapper.Unit#UNIT Unit Unit Object. - -- @param #CTLD_CARGO Cargo Cargo troops. - -- @return #CTLD self - - --- FSM Function OnAfterTroopsExtracted. - -- @function [parent=#CTLD] OnAfterTroopsExtracted - -- @param #CTLD self - -- @param #string From State. - -- @param #string Event Trigger. - -- @param #string To State. - -- @param Wrapper.Group#GROUP Group Group Object. - -- @param Wrapper.Unit#UNIT Unit Unit Object. - -- @param #CTLD_CARGO Cargo Cargo troops. - -- @return #CTLD self - - --- FSM Function OnAfterCratesPickedUp. - -- @function [parent=#CTLD] OnAfterCratesPickedUp - -- @param #CTLD self - -- @param #string From State . - -- @param #string Event Trigger. - -- @param #string To State. - -- @param Wrapper.Group#GROUP Group Group Object. - -- @param Wrapper.Unit#UNIT Unit Unit Object. - -- @param #CTLD_CARGO Cargo Cargo crate. - -- @return #CTLD self - - --- FSM Function OnAfterTroopsDeployed. - -- @function [parent=#CTLD] OnAfterTroopsDeployed - -- @param #CTLD self - -- @param #string From State. - -- @param #string Event Trigger. - -- @param #string To State. - -- @param Wrapper.Group#GROUP Group Group Object. - -- @param Wrapper.Unit#UNIT Unit Unit Object. - -- @param Wrapper.Group#GROUP Troops Troops #GROUP Object. - -- @return #CTLD self - - --- FSM Function OnAfterCratesDropped. - -- @function [parent=#CTLD] OnAfterCratesDropped - -- @param #CTLD self - -- @param #string From State. - -- @param #string Event Trigger. - -- @param #string To State. - -- @param Wrapper.Group#GROUP Group Group Object. - -- @param Wrapper.Unit#UNIT Unit Unit Object. - -- @param #table Cargotable Table of #CTLD_CARGO objects dropped. - -- @return #CTLD self - - --- FSM Function OnAfterCratesBuild. - -- @function [parent=#CTLD] OnAfterCratesBuild - -- @param #CTLD self - -- @param #string From State. - -- @param #string Event Trigger. - -- @param #string To State. - -- @param Wrapper.Group#GROUP Group Group Object. - -- @param Wrapper.Unit#UNIT Unit Unit Object. - -- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB build. - -- @return #CTLD self - - --- FSM Function OnAfterCratesRepaired. - -- @function [parent=#CTLD] OnAfterCratesRepaired - -- @param #CTLD self - -- @param #string From State. - -- @param #string Event Trigger. - -- @param #string To State. - -- @param Wrapper.Group#GROUP Group Group Object. - -- @param Wrapper.Unit#UNIT Unit Unit Object. - -- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB repaired. - -- @return #CTLD self - - --- FSM Function OnAfterTroopsRTB. - -- @function [parent=#CTLD] OnAfterTroopsRTB - -- @param #CTLD self - -- @param #string From State. - -- @param #string Event Trigger. - -- @param #string To State. - -- @param Wrapper.Group#GROUP Group Group Object. - -- @param Wrapper.Unit#UNIT Unit Unit Object. - - --- FSM Function OnAfterLoad. - -- @function [parent=#CTLD] OnAfterLoad - -- @param #CTLD self - -- @param #string From From state. - -- @param #string Event Event. - -- @param #string To To state. - -- @param #string path (Optional) Path where the file is located. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized. - -- @param #string filename (Optional) File name for loading. Default is "CTLD__Persist.csv". - - --- FSM Function OnAfterSave. - -- @function [parent=#CTLD] OnAfterSave - -- @param #CTLD self - -- @param #string From From state. - -- @param #string Event Event. - -- @param #string To To state. - -- @param #string path (Optional) Path where the file is saved. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized. - -- @param #string filename (Optional) File name for saving. Default is "CTLD__Persist.csv". - - return self - end - - ------------------------------------------------------------------- - -- Helper and User Functions - ------------------------------------------------------------------- - - --- (Internal) Function to get capabilities of a chopper + -- @function [parent=#CTLD] Start -- @param #CTLD self - -- @param Wrapper.Unit#UNIT Unit The unit - -- @return #table Capabilities Table of caps - function CTLD:_GetUnitCapabilities( Unit ) - self:T( self.lid .. " _GetUnitCapabilities" ) - local _unit = Unit -- Wrapper.Unit#UNIT - local unittype = _unit:GetTypeName() - local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities - if not capabilities or capabilities == {} then - -- e.g. ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0}, - capabilities = {} - capabilities.troops = false - capabilities.crates = false - capabilities.cratelimit = 0 - capabilities.trooplimit = 0 - capabilities.type = "generic" - capabilities.length = 20 - end - return capabilities - end - --- (Internal) Function to generate valid UHF Frequencies + --- Triggers the FSM event "Start" after a delay. Starts the CTLD. Initializes parameters and starts event handlers. + -- @function [parent=#CTLD] __Start -- @param #CTLD self - function CTLD:_GenerateUHFrequencies() - self:T( self.lid .. " _GenerateUHFrequencies" ) + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Stop". Stops the CTLD and all its event handlers. + -- @param #CTLD self + + --- Triggers the FSM event "Stop" after a delay. Stops the CTLD and all its event handlers. + -- @function [parent=#CTLD] __Stop + -- @param #CTLD self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Status". + -- @function [parent=#CTLD] Status + -- @param #CTLD self + + --- Triggers the FSM event "Status" after a delay. + -- @function [parent=#CTLD] __Status + -- @param #CTLD self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Load". + -- @function [parent=#CTLD] Load + -- @param #CTLD self + + --- Triggers the FSM event "Load" after a delay. + -- @function [parent=#CTLD] __Load + -- @param #CTLD self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Save". + -- @function [parent=#CTLD] Load + -- @param #CTLD self + + --- Triggers the FSM event "Save" after a delay. + -- @function [parent=#CTLD] __Save + -- @param #CTLD self + -- @param #number delay Delay in seconds. + + --- FSM Function OnAfterTroopsPickedUp. + -- @function [parent=#CTLD] OnAfterTroopsPickedUp + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #CTLD_CARGO Cargo Cargo troops. + -- @return #CTLD self + + --- FSM Function OnAfterTroopsExtracted. + -- @function [parent=#CTLD] OnAfterTroopsExtracted + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #CTLD_CARGO Cargo Cargo troops. + -- @return #CTLD self + + --- FSM Function OnAfterCratesPickedUp. + -- @function [parent=#CTLD] OnAfterCratesPickedUp + -- @param #CTLD self + -- @param #string From State . + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #CTLD_CARGO Cargo Cargo crate. + -- @return #CTLD self + + --- FSM Function OnAfterTroopsDeployed. + -- @function [parent=#CTLD] OnAfterTroopsDeployed + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param Wrapper.Group#GROUP Troops Troops #GROUP Object. + -- @return #CTLD self + + --- FSM Function OnAfterCratesDropped. + -- @function [parent=#CTLD] OnAfterCratesDropped + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #table Cargotable Table of #CTLD_CARGO objects dropped. + -- @return #CTLD self + + --- FSM Function OnAfterCratesBuild. + -- @function [parent=#CTLD] OnAfterCratesBuild + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB build. + -- @return #CTLD self + + --- FSM Function OnAfterCratesRepaired. + -- @function [parent=#CTLD] OnAfterCratesRepaired + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB repaired. + -- @return #CTLD self + + --- FSM Function OnAfterTroopsRTB. + -- @function [parent=#CTLD] OnAfterTroopsRTB + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + + --- FSM Function OnAfterLoad. + -- @function [parent=#CTLD] OnAfterLoad + -- @param #CTLD self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string path (Optional) Path where the file is located. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized. + -- @param #string filename (Optional) File name for loading. Default is "CTLD__Persist.csv". + + --- FSM Function OnAfterSave. + -- @function [parent=#CTLD] OnAfterSave + -- @param #CTLD self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string path (Optional) Path where the file is saved. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized. + -- @param #string filename (Optional) File name for saving. Default is "CTLD__Persist.csv". + + return self +end + +------------------------------------------------------------------- +-- Helper and User Functions +------------------------------------------------------------------- + +--- (Internal) Function to get capabilities of a chopper +-- @param #CTLD self +-- @param Wrapper.Unit#UNIT Unit The unit +-- @return #table Capabilities Table of caps +function CTLD:_GetUnitCapabilities(Unit) + self:T(self.lid .. " _GetUnitCapabilities") + local _unit = Unit -- Wrapper.Unit#UNIT + local unittype = _unit:GetTypeName() + local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + if not capabilities or capabilities == {} then + -- e.g. ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0}, + capabilities = {} + capabilities.troops = false + capabilities.crates = false + capabilities.cratelimit = 0 + capabilities.trooplimit = 0 + capabilities.type = "generic" + capabilities.length = 20 + end + return capabilities +end + + +--- (Internal) Function to generate valid UHF Frequencies +-- @param #CTLD self +function CTLD:_GenerateUHFrequencies() + self:T(self.lid .. " _GenerateUHFrequencies") self.FreeUHFFrequencies = {} - self.FreeUHFFrequencies = UTILS.GenerateUHFrequencies() + self.FreeUHFFrequencies = UTILS.GenerateUHFrequencies() return self - end +end - --- (Internal) Function to generate valid FM Frequencies - -- @param #CTLD self - function CTLD:_GenerateFMFrequencies() - self:T( self.lid .. " _GenerateFMrequencies" ) +--- (Internal) Function to generate valid FM Frequencies +-- @param #CTLD self +function CTLD:_GenerateFMFrequencies() + self:T(self.lid .. " _GenerateFMrequencies") self.FreeFMFrequencies = {} self.FreeFMFrequencies = UTILS.GenerateFMFrequencies() return self - end +end - --- (Internal) Populate table with available VHF beacon frequencies. - -- @param #CTLD self - function CTLD:_GenerateVHFrequencies() - self:T( self.lid .. " _GenerateVHFrequencies" ) - self.FreeVHFFrequencies = {} - self.UsedVHFFrequencies = {} - self.FreeVHFFrequencies = UTILS.GenerateVHFrequencies() - return self - end +--- (Internal) Populate table with available VHF beacon frequencies. +-- @param #CTLD self +function CTLD:_GenerateVHFrequencies() + self:T(self.lid .. " _GenerateVHFrequencies") + self.FreeVHFFrequencies = {} + self.UsedVHFFrequencies = {} + self.FreeVHFFrequencies = UTILS.GenerateVHFrequencies() + return self +end - --- (Internal) Event handler function - -- @param #CTLD self - -- @param Core.Event#EVENTDATA EventData - function CTLD:_EventHandler( EventData ) - self:T( string.format( "%s Event = %d", self.lid, EventData.id ) ) - local event = EventData -- Core.Event#EVENTDATA - if event.id == EVENTS.PlayerEnterAircraft or event.id == EVENTS.PlayerEnterUnit then - local _coalition = event.IniCoalition - if _coalition ~= self.coalition then - return -- ignore! - end - -- check is Helicopter - local _unit = event.IniUnit - local _group = event.IniGroup - if _unit:IsHelicopter() or _group:IsHelicopter() then - local unitname = event.IniUnitName or "none" - self.Loaded_Cargo[unitname] = nil - self:_RefreshF10Menus() - end - -- Herc support - -- self:T_unit:GetTypeName()) - if _unit:GetTypeName() == "Hercules" and self.enableHercules then - self.Loaded_Cargo[unitname] = nil - self:_RefreshF10Menus() - end - return - elseif event.id == EVENTS.PlayerLeaveUnit then - -- remove from pilot table +--- (Internal) Event handler function +-- @param #CTLD self +-- @param Core.Event#EVENTDATA EventData +function CTLD:_EventHandler(EventData) + self:T(string.format("%s Event = %d",self.lid, EventData.id)) + local event = EventData -- Core.Event#EVENTDATA + if event.id == EVENTS.PlayerEnterAircraft or event.id == EVENTS.PlayerEnterUnit then + local _coalition = event.IniCoalition + if _coalition ~= self.coalition then + return --ignore! + end + -- check is Helicopter + local _unit = event.IniUnit + local _group = event.IniGroup + if _unit:IsHelicopter() or _group:IsHelicopter() then local unitname = event.IniUnitName or "none" - self.CtldUnits[unitname] = nil self.Loaded_Cargo[unitname] = nil + self:_RefreshF10Menus() end + -- Herc support + --self:T_unit:GetTypeName()) + if _unit:GetTypeName() == "Hercules" and self.enableHercules then + self.Loaded_Cargo[unitname] = nil + self:_RefreshF10Menus() + end + return + elseif event.id == EVENTS.PlayerLeaveUnit then + -- remove from pilot table + local unitname = event.IniUnitName or "none" + self.CtldUnits[unitname] = nil + self.Loaded_Cargo[unitname] = nil + end + return self +end + +--- (Internal) Function to message a group. +-- @param #CTLD self +-- @param #string Text The text to display. +-- @param #number Time Number of seconds to display the message. +-- @param #boolean Clearscreen Clear screen or not. +-- @param Wrapper.Group#GROUP Group The group receiving the message. +function CTLD:_SendMessage(Text, Time, Clearscreen, Group) + self:T(self.lid .. " _SendMessage") + if not self.suppressmessages then + local m = MESSAGE:New(Text,Time,"CTLD",Clearscreen):ToGroup(Group) + end + return self +end + +--- (Internal) Function to load troops into a heli. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @param #CTLD_CARGO Cargotype +function CTLD:_LoadTroops(Group, Unit, Cargotype) + self:T(self.lid .. " _LoadTroops") + -- check if we have stock + local instock = Cargotype:GetStock() + local cgoname = Cargotype:GetName() + local cgotype = Cargotype:GetType() + if type(instock) == "number" and tonumber(instock) <= 0 and tonumber(instock) ~= -1 then + -- nothing left over + self:_SendMessage(string.format("Sorry, all %s are gone!", cgoname), 10, false, Group) return self end - - --- (Internal) Function to message a group. - -- @param #CTLD self - -- @param #string Text The text to display. - -- @param #number Time Number of seconds to display the message. - -- @param #boolean Clearscreen Clear screen or not. - -- @param Wrapper.Group#GROUP Group The group receiving the message. - function CTLD:_SendMessage( Text, Time, Clearscreen, Group ) - self:T( self.lid .. " _SendMessage" ) - if not self.suppressmessages then - local m = MESSAGE:New( Text, Time, "CTLD", Clearscreen ):ToGroup( Group ) - end - return self + -- landed or hovering over load zone? + local grounded = not self:IsUnitInAir(Unit) + local hoverload = self:CanHoverLoad(Unit) + --local dooropen = UTILS.IsLoadingDoorOpen(Unit:GetName()) and self.pilotmustopendoors + -- check if we are in LOAD zone + local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) + if not inzone then + inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) end - - --- (Internal) Function to load troops into a heli. - -- @param #CTLD self - -- @param Wrapper.Group#GROUP Group - -- @param Wrapper.Unit#UNIT Unit - -- @param #CTLD_CARGO Cargotype - function CTLD:_LoadTroops( Group, Unit, Cargotype ) - self:T( self.lid .. " _LoadTroops" ) - -- check if we have stock - local instock = Cargotype:GetStock() - local cgoname = Cargotype:GetName() - local cgotype = Cargotype:GetType() - if type( instock ) == "number" and tonumber( instock ) <= 0 and tonumber( instock ) ~= -1 then - -- nothing left over - self:_SendMessage( string.format( "Sorry, all %s are gone!", cgoname ), 10, false, Group ) - return self - end - -- landed or hovering over load zone? - local grounded = not self:IsUnitInAir( Unit ) - local hoverload = self:CanHoverLoad( Unit ) - -- local dooropen = UTILS.IsLoadingDoorOpen(Unit:GetName()) and self.pilotmustopendoors - -- check if we are in LOAD zone - local inzone, zonename, zone, distance = self:IsUnitInZone( Unit, CTLD.CargoZoneType.LOAD ) - if not inzone then - inzone, zonename, zone, distance = self:IsUnitInZone( Unit, CTLD.CargoZoneType.SHIP ) - end - if not inzone then - self:_SendMessage( "You are not close enough to a logistics zone!", 10, false, Group ) - if not self.debug then - return self - end - elseif not grounded and not hoverload then - self:_SendMessage( "You need to land or hover in position to load!", 10, false, Group ) - if not self.debug then - return self - end - elseif self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen( Unit:GetName() ) then - self:_SendMessage( "You need to open the door(s) to load troops!", 10, false, Group ) - if not self.debug then - return self - end - end - -- load troops into heli - local group = Group -- Wrapper.Group#GROUP - local unit = Unit -- Wrapper.Unit#UNIT - local unitname = unit:GetName() - local cargotype = Cargotype -- #CTLD_CARGO - local cratename = cargotype:GetName() -- #string - -- see if this heli can load troops - local unittype = unit:GetTypeName() - local capabilities = self:_GetUnitCapabilities( Unit ) - local cantroops = capabilities.troops -- #boolean - local trooplimit = capabilities.trooplimit -- #number - local troopsize = cargotype:GetCratesNeeded() -- #number - -- have we loaded stuff already? - local numberonboard = 0 - local loaded = {} - if self.Loaded_Cargo[unitname] then - loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo - numberonboard = loaded.Troopsloaded or 0 - else - loaded = {} -- #CTLD.LoadedCargo - loaded.Troopsloaded = 0 - loaded.Cratesloaded = 0 - loaded.Cargo = {} - end - if troopsize + numberonboard > trooplimit then - self:_SendMessage( "Sorry, we\'re crammed already!", 10, false, Group ) - return - else - self.CargoCounter = self.CargoCounter + 1 - local loadcargotype = CTLD_CARGO:New( self.CargoCounter, Cargotype.Name, Cargotype.Templates, cgotype, true, true, Cargotype.CratesNeeded, nil, nil, Cargotype.PerCrateMass ) - self:T( { cargotype = loadcargotype } ) - loaded.Troopsloaded = loaded.Troopsloaded + troopsize - table.insert( loaded.Cargo, loadcargotype ) - self.Loaded_Cargo[unitname] = loaded - self:_SendMessage( "Troops boarded!", 10, false, Group ) - self:__TroopsPickedUp( 1, Group, Unit, Cargotype ) - self:_UpdateUnitCargoMass( Unit ) - Cargotype:RemoveStock() - end - return self + if not inzone then + self:_SendMessage("You are not close enough to a logistics zone!", 10, false, Group) + if not self.debug then return self end + elseif not grounded and not hoverload then + self:_SendMessage("You need to land or hover in position to load!", 10, false, Group) + if not self.debug then return self end + elseif self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then + self:_SendMessage("You need to open the door(s) to load troops!", 10, false, Group) + if not self.debug then return self end end + -- load troops into heli + local group = Group -- Wrapper.Group#GROUP + local unit = Unit -- Wrapper.Unit#UNIT + local unitname = unit:GetName() + local cargotype = Cargotype -- #CTLD_CARGO + local cratename = cargotype:GetName() -- #string + -- see if this heli can load troops + local unittype = unit:GetTypeName() + local capabilities = self:_GetUnitCapabilities(Unit) + local cantroops = capabilities.troops -- #boolean + local trooplimit = capabilities.trooplimit -- #number + local troopsize = cargotype:GetCratesNeeded() -- #number + -- have we loaded stuff already? + local numberonboard = 0 + local loaded = {} + if self.Loaded_Cargo[unitname] then + loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo + numberonboard = loaded.Troopsloaded or 0 + else + loaded = {} -- #CTLD.LoadedCargo + loaded.Troopsloaded = 0 + loaded.Cratesloaded = 0 + loaded.Cargo = {} + end + if troopsize + numberonboard > trooplimit then + self:_SendMessage("Sorry, we\'re crammed already!", 10, false, Group) + return + else + self.CargoCounter = self.CargoCounter + 1 + local loadcargotype = CTLD_CARGO:New(self.CargoCounter, Cargotype.Name, Cargotype.Templates, cgotype, true, true, Cargotype.CratesNeeded,nil,nil,Cargotype.PerCrateMass) + self:T({cargotype=loadcargotype}) + loaded.Troopsloaded = loaded.Troopsloaded + troopsize + table.insert(loaded.Cargo,loadcargotype) + self.Loaded_Cargo[unitname] = loaded + self:_SendMessage("Troops boarded!", 10, false, Group) + self:__TroopsPickedUp(1,Group, Unit, Cargotype) + self:_UpdateUnitCargoMass(Unit) + Cargotype:RemoveStock() + end + return self +end - function CTLD:_FindRepairNearby( Group, Unit, Repairtype ) - self:T( self.lid .. " _FindRepairNearby" ) +function CTLD:_FindRepairNearby(Group, Unit, Repairtype) + self:T(self.lid .. " _FindRepairNearby") local unitcoord = Unit:GetCoordinate() - + -- find nearest group of deployed groups local nearestGroup = nil local nearestGroupIndex = -1 local nearestDistance = 10000 - for k, v in pairs( self.DroppedTroops ) do - local distance = self:_GetDistance( v:GetCoordinate(), unitcoord ) - local unit = v:GetUnit( 1 ) -- Wrapper.Unit#UNIT + for k,v in pairs(self.DroppedTroops) do + local distance = self:_GetDistance(v:GetCoordinate(),unitcoord) + local unit = v:GetUnit(1) -- Wrapper.Unit#UNIT local desc = unit:GetDesc() or nil - -- self:I({desc = desc.attributes}) + --self:I({desc = desc.attributes}) if distance < nearestDistance and distance ~= -1 and not desc.attributes.Infantry then nearestGroup = v nearestGroupIndex = k @@ -1532,136 +1528,126 @@ do -- found one and matching distance? if nearestGroup == nil or nearestDistance > self.EngineerSearch then - self:_SendMessage( "No unit close enough to repair!", 10, false, Group ) + self:_SendMessage("No unit close enough to repair!", 10, false, Group) return nil, nil end - + local groupname = nearestGroup:GetName() - + -- helper to find matching template - local function matchstring( String, Table ) + local function matchstring(String,Table) local match = false - if type( Table ) == "table" then - for _, _name in pairs( Table ) do - if string.find( String, _name ) then + if type(Table) == "table" then + for _,_name in pairs (Table) do + if string.find(String,_name) then match = true break end end else - if type( String ) == "string" then - if string.find( String, Table ) then - match = true - end + if type(String) == "string" then + if string.find(String,Table) then match = true end end - end + end return match end - + -- walk through generics and find matching type local Cargotype = nil - for k, v in pairs( self.Cargo_Crates ) do - -- self:I({groupname,v.Templates}) - if matchstring( groupname, v.Templates ) and matchstring( groupname, Repairtype ) then + for k,v in pairs(self.Cargo_Crates) do + --self:I({groupname,v.Templates}) + if matchstring(groupname,v.Templates) and matchstring(groupname,Repairtype) then Cargotype = v -- #CTLD_CARGO break end end if Cargotype == nil then - -- self:_SendMessage("Can't find a matching group for " .. Repairtype, 10, false, Group) + --self:_SendMessage("Can't find a matching group for " .. Repairtype, 10, false, Group) return nil, nil else return nearestGroup, Cargotype end + +end - end - - --- (Internal) Function to repair an object. - -- @param #CTLD self - -- @param Wrapper.Group#GROUP Group - -- @param Wrapper.Unit#UNIT Unit - -- @param #table Crates Table of #CTLD_CARGO objects near the unit. - -- @param #CTLD.Buildable Build Table build object. - -- @param #number Number Number of objects in Crates (found) to limit search. - -- @param #boolean Engineering If true it is an Engineering repair. - function CTLD:_RepairObjectFromCrates( Group, Unit, Crates, Build, Number, Engineering ) - self:T( self.lid .. " _RepairObjectFromCrates" ) - local build = Build -- -- #CTLD.Buildable - -- self:I({Build=Build}) - local Repairtype = build.Template -- #string - local NearestGroup, CargoType = self:_FindRepairNearby( Group, Unit, Repairtype ) -- Wrapper.Group#GROUP, #CTLD_CARGO - -- self:I({Repairtype=Repairtype, CargoType=CargoType, NearestGroup=NearestGroup}) - if NearestGroup ~= nil then - if self.repairtime < 2 then - self.repairtime = 30 - end -- noob catch - if not Engineering then - self:_SendMessage( string.format( "Repair started using %s taking %d secs", build.Name, self.repairtime ), 10, false, Group ) - end - -- now we can build .... - -- NearestGroup:Destroy(false) - local name = CargoType:GetName() - local required = CargoType:GetCratesNeeded() - local template = CargoType:GetTemplates() - local ctype = CargoType:GetType() - local object = {} -- #CTLD.Buildable - object.Name = CargoType:GetName() - object.Required = required - object.Found = required - object.Template = template - object.CanBuild = true - object.Type = ctype -- #CTLD_CARGO.Enum - self:_CleanUpCrates( Crates, Build, Number ) - local desttimer = TIMER:New( function() - NearestGroup:Destroy( false ) - end, self ) - desttimer:Start( self.repairtime - 1 ) - local buildtimer = TIMER:New( self._BuildObjectFromCrates, self, Group, Unit, object, true, NearestGroup:GetCoordinate() ) - buildtimer:Start( self.repairtime ) - -- self:_BuildObjectFromCrates(Group,Unit,object) - else - if not Engineering then - self:_SendMessage( "Can't repair this unit with " .. build.Name, 10, false, Group ) - else - self:T( "Can't repair this unit with " .. build.Name ) - end +--- (Internal) Function to repair an object. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @param #table Crates Table of #CTLD_CARGO objects near the unit. +-- @param #CTLD.Buildable Build Table build object. +-- @param #number Number Number of objects in Crates (found) to limit search. +-- @param #boolean Engineering If true it is an Engineering repair. +function CTLD:_RepairObjectFromCrates(Group,Unit,Crates,Build,Number,Engineering) + self:T(self.lid .. " _RepairObjectFromCrates") + local build = Build -- -- #CTLD.Buildable + --self:I({Build=Build}) + local Repairtype = build.Template -- #string + local NearestGroup, CargoType = self:_FindRepairNearby(Group,Unit,Repairtype) -- Wrapper.Group#GROUP, #CTLD_CARGO + --self:I({Repairtype=Repairtype, CargoType=CargoType, NearestGroup=NearestGroup}) + if NearestGroup ~= nil then + if self.repairtime < 2 then self.repairtime = 30 end -- noob catch + if not Engineering then + self:_SendMessage(string.format("Repair started using %s taking %d secs", build.Name, self.repairtime), 10, false, Group) + end + -- now we can build .... + --NearestGroup:Destroy(false) + local name = CargoType:GetName() + local required = CargoType:GetCratesNeeded() + local template = CargoType:GetTemplates() + local ctype = CargoType:GetType() + local object = {} -- #CTLD.Buildable + object.Name = CargoType:GetName() + object.Required = required + object.Found = required + object.Template = template + object.CanBuild = true + object.Type = ctype -- #CTLD_CARGO.Enum + self:_CleanUpCrates(Crates,Build,Number) + local desttimer = TIMER:New(function() NearestGroup:Destroy(false) end, self) + desttimer:Start(self.repairtime - 1) + local buildtimer = TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,object,true,NearestGroup:GetCoordinate()) + buildtimer:Start(self.repairtime) + --self:_BuildObjectFromCrates(Group,Unit,object) + else + if not Engineering then + self:_SendMessage("Can't repair this unit with " .. build.Name, 10, false, Group) + else + self:T("Can't repair this unit with " .. build.Name) end - return self end + return self +end --- (Internal) Function to extract (load from the field) troops into a heli. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit - function CTLD:_ExtractTroops( Group, Unit ) -- #1574 thanks to @bbirchnz! - self:T( self.lid .. " _ExtractTroops" ) + function CTLD:_ExtractTroops(Group, Unit) -- #1574 thanks to @bbirchnz! + self:T(self.lid .. " _ExtractTroops") -- landed or hovering over load zone? - local grounded = not self:IsUnitInAir( Unit ) - local hoverload = self:CanHoverLoad( Unit ) - + local grounded = not self:IsUnitInAir(Unit) + local hoverload = self:CanHoverLoad(Unit) + if not grounded and not hoverload then - self:_SendMessage( "You need to land or hover in position to load!", 10, false, Group ) - if not self.debug then - return self - end + self:_SendMessage("You need to land or hover in position to load!", 10, false, Group) + if not self.debug then return self end end - if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen( Unit:GetName() ) then - self:_SendMessage( "You need to open the door(s) to extract troops!", 10, false, Group ) - if not self.debug then - return self - end + if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then + self:_SendMessage("You need to open the door(s) to extract troops!", 10, false, Group) + if not self.debug then return self end end -- load troops into heli local unit = Unit -- Wrapper.Unit#UNIT local unitname = unit:GetName() -- see if this heli can load troops local unittype = unit:GetTypeName() - local capabilities = self:_GetUnitCapabilities( Unit ) + local capabilities = self:_GetUnitCapabilities(Unit) local cantroops = capabilities.troops -- #boolean local trooplimit = capabilities.trooplimit -- #number local unitcoord = unit:GetCoordinate() - + -- find nearest group of deployed troops local nearestGroup = nil local nearestGroupIndex = -1 @@ -1669,48 +1655,44 @@ do local nearestList = {} local distancekeys = {} local extractdistance = self.CrateDistance * self.ExtractFactor - for k, v in pairs( self.DroppedTroops ) do - local distance = self:_GetDistance( v:GetCoordinate(), unitcoord ) + for k,v in pairs(self.DroppedTroops) do + local distance = self:_GetDistance(v:GetCoordinate(),unitcoord) if distance <= extractdistance and distance ~= -1 then nearestGroup = v nearestGroupIndex = k nearestDistance = distance - table.insert( nearestList, math.floor( distance ), v ) - distancekeys[#distancekeys + 1] = math.floor( distance ) + table.insert(nearestList, math.floor(distance), v) + distancekeys[#distancekeys+1] = math.floor(distance) end end - + if nearestGroup == nil or nearestDistance > extractdistance then - self:_SendMessage( "No units close enough to extract!", 10, false, Group ) + self:_SendMessage("No units close enough to extract!", 10, false, Group) return self end - + -- sort reference keys - table.sort( distancekeys ) - + table.sort(distancekeys) + local secondarygroups = {} - - for i = 1, #distancekeys do + + for i=1,#distancekeys do local nearestGroup = nearestList[distancekeys[i]] - -- find matching cargo type - local groupType = string.match( nearestGroup:GetName(), "(.+)-(.+)$" ) + -- find matching cargo type + local groupType = string.match(nearestGroup:GetName(), "(.+)-(.+)$") local Cargotype = nil - for k, v in pairs( self.Cargo_Troops ) do + for k,v in pairs(self.Cargo_Troops) do local comparison = "" - if type( v.Templates ) == "string" then - comparison = v.Templates - else - comparison = v.Templates[1] - end + if type(v.Templates) == "string" then comparison = v.Templates else comparison = v.Templates[1] end if comparison == groupType then Cargotype = v break end end if Cargotype == nil then - self:_SendMessage( "Can't onboard " .. groupType, 10, false, Group ) + self:_SendMessage("Can't onboard " .. groupType, 10, false, Group) else - + local troopsize = Cargotype:GetCratesNeeded() -- #number -- have we loaded stuff already? local numberonboard = 0 @@ -1725,38 +1707,38 @@ do loaded.Cargo = {} end if troopsize + numberonboard > trooplimit then - self:_SendMessage( "Sorry, we\'re crammed already!", 10, false, Group ) - -- return self + self:_SendMessage("Sorry, we\'re crammed already!", 10, false, Group) + --return self else self.CargoCounter = self.CargoCounter + 1 - local loadcargotype = CTLD_CARGO:New( self.CargoCounter, Cargotype.Name, Cargotype.Templates, Cargotype.CargoType, true, true, Cargotype.CratesNeeded, nil, nil, Cargotype.PerCrateMass ) - self:T( { cargotype = loadcargotype } ) + local loadcargotype = CTLD_CARGO:New(self.CargoCounter, Cargotype.Name, Cargotype.Templates, Cargotype.CargoType, true, true, Cargotype.CratesNeeded,nil,nil,Cargotype.PerCrateMass) + self:T({cargotype=loadcargotype}) loaded.Troopsloaded = loaded.Troopsloaded + troopsize - table.insert( loaded.Cargo, loadcargotype ) + table.insert(loaded.Cargo,loadcargotype) self.Loaded_Cargo[unitname] = loaded - self:_SendMessage( "Troops boarded!", 10, false, Group ) - self:_UpdateUnitCargoMass( Unit ) - self:__TroopsExtracted( 1, Group, Unit, nearestGroup ) - + self:_SendMessage("Troops boarded!", 10, false, Group) + self:_UpdateUnitCargoMass(Unit) + self:__TroopsExtracted(1,Group, Unit, nearestGroup) + -- clean up: - -- table.remove(self.DroppedTroops, nearestGroupIndex) - if type( Cargotype.Templates ) == "table" and Cargotype.Templates[2] then - -- self:I("*****This CargoType has multiple templates: "..Cargotype.Name) - for _, _key in pairs( Cargotype.Templates ) do - table.insert( secondarygroups, _key ) + --table.remove(self.DroppedTroops, nearestGroupIndex) + if type(Cargotype.Templates) == "table" and Cargotype.Templates[2] then + --self:I("*****This CargoType has multiple templates: "..Cargotype.Name) + for _,_key in pairs (Cargotype.Templates) do + table.insert(secondarygroups,_key) end end - nearestGroup:Destroy( false ) + nearestGroup:Destroy(false) end end end -- clean up secondary groups - for _, _name in pairs( secondarygroups ) do - for _, _group in pairs( nearestList ) do + for _,_name in pairs(secondarygroups) do + for _,_group in pairs(nearestList) do if _group and _group:IsAlive() then - local groupname = string.match( _group:GetName(), "(.+)-(.+)$" ) + local groupname = string.match(_group:GetName(), "(.+)-(.+)$") if _name == groupname then - _group:Destroy( false ) + _group:Destroy(false) end end end @@ -1764,1734 +1746,1712 @@ do self:CleanDroppedTroops() return self end - - --- (Internal) Function to spawn crates in front of the heli. - -- @param #CTLD self - -- @param Wrapper.Group#GROUP Group - -- @param Wrapper.Unit#UNIT Unit - -- @param #CTLD_CARGO Cargo - -- @param #number number Number of crates to generate (for dropping) - -- @param #boolean drop If true we're dropping from heli rather than loading. - function CTLD:_GetCrates( Group, Unit, Cargo, number, drop ) - self:T( self.lid .. " _GetCrates" ) - if not drop then - local cgoname = Cargo:GetName() - -- check if we have stock - local instock = Cargo:GetStock() - if type( instock ) == "number" and tonumber( instock ) <= 0 and tonumber( instock ) ~= -1 then - -- nothing left over - self:_SendMessage( string.format( "Sorry, we ran out of %s", cgoname ), 10, false, Group ) - return self - end - end - -- check if we are in LOAD zone - local inzone = false - local drop = drop or false - local ship = nil - local width = 20 - if not drop then - inzone = self:IsUnitInZone( Unit, CTLD.CargoZoneType.LOAD ) - if not inzone then - inzone, ship, zone, distance, width = self:IsUnitInZone( Unit, CTLD.CargoZoneType.SHIP ) - end - else - if self.dropcratesanywhere then -- #1570 - inzone = true - else - inzone = self:IsUnitInZone( Unit, CTLD.CargoZoneType.DROP ) - end - end - - if not inzone then - self:_SendMessage( "You are not close enough to a logistics zone!", 10, false, Group ) - if not self.debug then - return self - end - end - - -- avoid crate spam - local capabilities = self:_GetUnitCapabilities( Unit ) -- #CTLD.UnitCapabilities - local canloadcratesno = capabilities.cratelimit - local loaddist = self.CrateDistance or 35 - local nearcrates, numbernearby = self:_FindCratesNearby( Group, Unit, loaddist ) - if numbernearby >= canloadcratesno and not drop then - self:_SendMessage( "There are enough crates nearby already! Take care of those first!", 10, false, Group ) + +--- (Internal) Function to spawn crates in front of the heli. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @param #CTLD_CARGO Cargo +-- @param #number number Number of crates to generate (for dropping) +-- @param #boolean drop If true we\'re dropping from heli rather than loading. +function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) + self:T(self.lid .. " _GetCrates") + if not drop then + local cgoname = Cargo:GetName() + -- check if we have stock + local instock = Cargo:GetStock() + if type(instock) == "number" and tonumber(instock) <= 0 and tonumber(instock) ~= -1 then + -- nothing left over + self:_SendMessage(string.format("Sorry, we ran out of %s", cgoname), 10, false, Group) return self end - -- spawn crates in front of helicopter - local IsHerc = self:IsHercules( Unit ) -- Herc - local cargotype = Cargo -- Ops.CTLD#CTLD_CARGO - local number = number or cargotype:GetCratesNeeded() -- #number - local cratesneeded = cargotype:GetCratesNeeded() -- #number - local cratename = cargotype:GetName() - local cratetemplate = "Container" -- #string - local cgotype = cargotype:GetType() - local cgomass = cargotype:GetMass() - local isstatic = false - if cgotype == CTLD_CARGO.Enum.STATIC then - cratetemplate = cargotype:GetTemplates() - isstatic = true + end + -- check if we are in LOAD zone + local inzone = false + local drop = drop or false + local ship = nil + local width = 20 + if not drop then + inzone = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) + if not inzone then + inzone, ship, zone, distance, width = self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) end - -- get position and heading of heli - local position = Unit:GetCoordinate() - local heading = Unit:GetHeading() + 1 - local height = Unit:GetHeight() - local droppedcargo = {} - local cratedistance = 0 - local rheading = 0 - local angleOffNose = 0 - local addon = 0 - if IsHerc then - -- spawn behind the Herc - addon = 180 + else + if self.dropcratesanywhere then -- #1570 + inzone = true + else + inzone = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) end - -- loop crates needed - for i = 1, number do - local cratealias = string.format( "%s-%d", cratetemplate, math.random( 1, 100000 ) ) - if not self.placeCratesAhead then - cratedistance = (i - 1) * 2.5 + capabilities.length - if cratedistance > self.CrateDistance then - cratedistance = self.CrateDistance - end - -- altered heading logic - -- DONE: right standard deviation? - rheading = UTILS.RandomGaussian( 0, 30, -90, 90, 100 ) - rheading = math.fmod( (heading + rheading + addon), 360 ) - else - local initialSpacing = IsHerc and 16 or 12 -- initial spacing of the first crates - local crateSpacing = 4 -- further spacing of remaining crates - local lateralSpacing = 4 -- lateral spacing of crates - local nrSideBySideCrates = 3 -- number of crates that are placed side-by-side - - if cratesneeded == 1 then - -- single crate needed spawns straight ahead - cratedistance = initialSpacing - rheading = heading - else - if (i - 1) % nrSideBySideCrates == 0 then - cratedistance = i == 1 and initialSpacing or cratedistance + crateSpacing - angleOffNose = math.ceil( math.deg( math.atan( lateralSpacing / cratedistance ) ) ) - rheading = heading - angleOffNose - else - rheading = rheading + angleOffNose - end - end - end - local cratecoord = position:Translate( cratedistance, rheading ) - local cratevec2 = cratecoord:GetVec2() - self.CrateCounter = self.CrateCounter + 1 - local basetype = "container_cargo" - if isstatic then - basetype = cratetemplate - end - if type( ship ) == "string" then - self:T( "Spawning on ship " .. ship ) - local Ship = UNIT:FindByName( ship ) - local shipcoord = Ship:GetCoordinate() - local unitcoord = Unit:GetCoordinate() - local dist = shipcoord:Get2DDistance( unitcoord ) - dist = dist - (20 + math.random( 1, 10 )) - local width = width / 2 - local Offy = math.random( -width, width ) - self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType( basetype, "Cargos", self.cratecountry ) -- :InitCoordinate(cratecoord) - :InitCargoMass( cgomass ) - :InitCargo( self.enableslingload ) - :InitLinkToUnit( Ship, dist, Offy, 0 ) - :Spawn( 270, cratealias ) - else - self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType( basetype, "Cargos", self.cratecountry ) - :InitCoordinate( cratecoord ) - :InitCargoMass( cgomass ) - :InitCargo( self.enableslingload ) -- :InitLinkToUnit(Unit,OffsetX,OffsetY,OffsetAngle) - :Spawn( 270, cratealias ) - end - local templ = cargotype:GetTemplates() - local sorte = cargotype:GetType() - self.CargoCounter = self.CargoCounter + 1 - local realcargo = nil - if drop then - realcargo = CTLD_CARGO:New( self.CargoCounter, cratename, templ, sorte, true, false, cratesneeded, self.Spawned_Crates[self.CrateCounter], true, cargotype.PerCrateMass ) - table.insert( droppedcargo, realcargo ) - else - realcargo = CTLD_CARGO:New( self.CargoCounter, cratename, templ, sorte, false, false, cratesneeded, self.Spawned_Crates[self.CrateCounter], true, cargotype.PerCrateMass ) - Cargo:RemoveStock() - end - table.insert( self.Spawned_Cargo, realcargo ) - end - local text = string.format( "Crates for %s have been positioned near you!", cratename ) - if drop then - text = string.format( "Crates for %s have been dropped!", cratename ) - self:__CratesDropped( 1, Group, Unit, droppedcargo ) - end - self:_SendMessage( text, 10, false, Group ) - return self + end + + if not inzone then + self:_SendMessage("You are not close enough to a logistics zone!", 10, false, Group) + if not self.debug then return self end end - --- (Internal) Inject crates and static cargo objects. - -- @param #CTLD self - -- @param Core.Zone#ZONE Zone Zone to spawn in. - -- @param #CTLD_CARGO Cargo The cargo type to spawn. - -- @param #boolean RandomCoord Randomize coordinate. - -- @return #CTLD self - function CTLD:InjectStatics( Zone, Cargo, RandomCoord ) - self:T( self.lid .. " InjectStatics" ) - local cratecoord = Zone:GetCoordinate() - if RandomCoord then - cratecoord = Zone:GetRandomCoordinate( 5, 20 ) + -- avoid crate spam + local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities + local canloadcratesno = capabilities.cratelimit + local loaddist = self.CrateDistance or 35 + local nearcrates, numbernearby = self:_FindCratesNearby(Group,Unit,loaddist) + if numbernearby >= canloadcratesno and not drop then + self:_SendMessage("There are enough crates nearby already! Take care of those first!", 10, false, Group) + return self + end + -- spawn crates in front of helicopter + local IsHerc = self:IsHercules(Unit) -- Herc + local cargotype = Cargo -- Ops.CTLD#CTLD_CARGO + local number = number or cargotype:GetCratesNeeded() --#number + local cratesneeded = cargotype:GetCratesNeeded() --#number + local cratename = cargotype:GetName() + local cratetemplate = "Container"-- #string + local cgotype = cargotype:GetType() + local cgomass = cargotype:GetMass() + local isstatic = false + if cgotype == CTLD_CARGO.Enum.STATIC then + cratetemplate = cargotype:GetTemplates() + isstatic = true + end + -- get position and heading of heli + local position = Unit:GetCoordinate() + local heading = Unit:GetHeading() + 1 + local height = Unit:GetHeight() + local droppedcargo = {} + local cratedistance = 0 + local rheading = 0 + local angleOffNose = 0 + local addon = 0 + if IsHerc then + -- spawn behind the Herc + addon = 180 + end + -- loop crates needed + for i=1,number do + local cratealias = string.format("%s-%d", cratetemplate, math.random(1,100000)) + if not self.placeCratesAhead then + cratedistance = (i-1)*2.5 + capabilities.length + if cratedistance > self.CrateDistance then cratedistance = self.CrateDistance end + -- altered heading logic + -- DONE: right standard deviation? + rheading = UTILS.RandomGaussian(0,30,-90,90,100) + rheading = math.fmod((heading + rheading + addon), 360) + else + local initialSpacing = IsHerc and 16 or 12 -- initial spacing of the first crates + local crateSpacing = 4 -- further spacing of remaining crates + local lateralSpacing = 4 -- lateral spacing of crates + local nrSideBySideCrates = 3 -- number of crates that are placed side-by-side + + if cratesneeded == 1 then + -- single crate needed spawns straight ahead + cratedistance = initialSpacing + rheading = heading + else + if (i - 1) % nrSideBySideCrates == 0 then + cratedistance = i == 1 and initialSpacing or cratedistance + crateSpacing + angleOffNose = math.ceil(math.deg(math.atan(lateralSpacing / cratedistance))) + rheading = heading - angleOffNose + else + rheading = rheading + angleOffNose + end + end end - local surface = cratecoord:GetSurfaceType() - if surface == land.SurfaceType.WATER then - return self - end - local cargotype = Cargo -- #CTLD_CARGO - -- local number = 1 - local cratesneeded = cargotype:GetCratesNeeded() -- #number - local cratetemplate = "Container" -- #string - local cratealias = string.format( "%s-%d", cratetemplate, math.random( 1, 100000 ) ) - local cratename = cargotype:GetName() - local cgotype = cargotype:GetType() - local cgomass = cargotype:GetMass() - local isstatic = false - if cgotype == CTLD_CARGO.Enum.STATIC then - cratetemplate = cargotype:GetTemplates() - isstatic = true - end - local basetype = "container_cargo" + local cratecoord = position:Translate(cratedistance,rheading) + local cratevec2 = cratecoord:GetVec2() + self.CrateCounter = self.CrateCounter + 1 + local basetype = self.basetype or "container_cargo" if isstatic then basetype = cratetemplate end - self.CrateCounter = self.CrateCounter + 1 - self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType( basetype, "Cargos", self.cratecountry ) - :InitCargoMass( cgomass ) - :InitCargo( self.enableslingload ) - :InitCoordinate( cratecoord ) - :Spawn( 270, cratealias ) + if type(ship) == "string" then + self:T("Spawning on ship "..ship) + local Ship = UNIT:FindByName(ship) + local shipcoord = Ship:GetCoordinate() + local unitcoord = Unit:GetCoordinate() + local dist = shipcoord:Get2DDistance(unitcoord) + dist = dist - (20 + math.random(1,10)) + local width = width / 2 + local Offy = math.random(-width,width) + self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) + --:InitCoordinate(cratecoord) + :InitCargoMass(cgomass) + :InitCargo(self.enableslingload) + :InitLinkToUnit(Ship,dist,Offy,0) + :Spawn(270,cratealias) + else + self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) + :InitCoordinate(cratecoord) + :InitCargoMass(cgomass) + :InitCargo(self.enableslingload) + --:InitLinkToUnit(Unit,OffsetX,OffsetY,OffsetAngle) + :Spawn(270,cratealias) + end local templ = cargotype:GetTemplates() local sorte = cargotype:GetType() self.CargoCounter = self.CargoCounter + 1 - cargotype.Positionable = self.Spawned_Crates[self.CrateCounter] - table.insert( self.Spawned_Cargo, cargotype ) - return self - end - - --- (User) Inject static cargo objects. - -- @param #CTLD self - -- @param Core.Zone#ZONE Zone Zone to spawn in. Will be a somewhat random coordinate. - -- @param #string Template Unit(!) name of the static cargo object to be used as template. - -- @param #number Mass Mass of the static in kg. - -- @return #CTLD self - function CTLD:InjectStaticFromTemplate( Zone, Template, Mass ) - self:T( self.lid .. " InjectStaticFromTemplate" ) - local cargotype = self:GetStaticsCargoFromTemplate( Template, Mass ) -- #CTLD_CARGO - self:InjectStatics( Zone, cargotype, true ) - return self - end - - --- (Internal) Function to find and list nearby crates. - -- @param #CTLD self - -- @param Wrapper.Group#GROUP Group - -- @param Wrapper.Unit#UNIT Unit - -- @return #CTLD self - function CTLD:_ListCratesNearby( _group, _unit ) - self:T( self.lid .. " _ListCratesNearby" ) - local finddist = self.CrateDistance or 35 - local crates, number = self:_FindCratesNearby( _group, _unit, finddist ) -- #table - if number > 0 then - local text = REPORT:New( "Crates Found Nearby:" ) - text:Add( "------------------------------------------------------------" ) - for _, _entry in pairs( crates ) do - local entry = _entry -- #CTLD_CARGO - local name = entry:GetName() -- #string - local dropped = entry:WasDropped() - if dropped then - text:Add( string.format( "Dropped crate for %s, %dkg", name, entry.PerCrateMass ) ) - else - text:Add( string.format( "Crate for %s, %dkg", name, entry.PerCrateMass ) ) - end - end - if text:GetCount() == 1 then - text:Add( " N O N E" ) - end - text:Add( "------------------------------------------------------------" ) - self:_SendMessage( text:Text(), 30, true, _group ) + local realcargo = nil + if drop then + realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,true,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass) + table.insert(droppedcargo,realcargo) else - self:_SendMessage( string.format( "No (loadable) crates within %d meters!", finddist ), 10, false, _group ) + realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,false,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass) + Cargo:RemoveStock() end + table.insert(self.Spawned_Cargo, realcargo) + end + local text = string.format("Crates for %s have been positioned near you!",cratename) + if drop then + text = string.format("Crates for %s have been dropped!",cratename) + self:__CratesDropped(1, Group, Unit, droppedcargo) + end + self:_SendMessage(text, 10, false, Group) + return self +end + +--- (Internal) Inject crates and static cargo objects. +-- @param #CTLD self +-- @param Core.Zone#ZONE Zone Zone to spawn in. +-- @param #CTLD_CARGO Cargo The cargo type to spawn. +-- @param #boolean RandomCoord Randomize coordinate. +-- @return #CTLD self +function CTLD:InjectStatics(Zone, Cargo, RandomCoord) + self:T(self.lid .. " InjectStatics") + local cratecoord = Zone:GetCoordinate() + if RandomCoord then + cratecoord = Zone:GetRandomCoordinate(5,20) + end + local surface = cratecoord:GetSurfaceType() + if surface == land.SurfaceType.WATER then return self end + local cargotype = Cargo -- #CTLD_CARGO + --local number = 1 + local cratesneeded = cargotype:GetCratesNeeded() --#number + local cratetemplate = "Container"-- #string + local cratealias = string.format("%s-%d", cratetemplate, math.random(1,100000)) + local cratename = cargotype:GetName() + local cgotype = cargotype:GetType() + local cgomass = cargotype:GetMass() + local isstatic = false + if cgotype == CTLD_CARGO.Enum.STATIC then + cratetemplate = cargotype:GetTemplates() + isstatic = true + end + local basetype = self.basetype or "container_cargo" + if isstatic then + basetype = cratetemplate + end + self.CrateCounter = self.CrateCounter + 1 + self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) + :InitCargoMass(cgomass) + :InitCargo(self.enableslingload) + :InitCoordinate(cratecoord) + :Spawn(270,cratealias) + local templ = cargotype:GetTemplates() + local sorte = cargotype:GetType() + self.CargoCounter = self.CargoCounter + 1 + cargotype.Positionable = self.Spawned_Crates[self.CrateCounter] + table.insert(self.Spawned_Cargo, cargotype) + return self +end - --- (Internal) Return distance in meters between two coordinates. - -- @param #CTLD self - -- @param Core.Point#COORDINATE _point1 Coordinate one - -- @param Core.Point#COORDINATE _point2 Coordinate two - -- @return #number Distance in meters - function CTLD:_GetDistance( _point1, _point2 ) - self:T( self.lid .. " _GetDistance" ) - if _point1 and _point2 then - local distance1 = _point1:Get2DDistance( _point2 ) - local distance2 = _point1:DistanceFromPointVec2( _point2 ) - -- self:I({dist1=distance1, dist2=distance2}) - if distance1 and type( distance1 ) == "number" then - return distance1 - elseif distance2 and type( distance2 ) == "number" then - return distance2 +--- (User) Inject static cargo objects. +-- @param #CTLD self +-- @param Core.Zone#ZONE Zone Zone to spawn in. Will be a somewhat random coordinate. +-- @param #string Template Unit(!) name of the static cargo object to be used as template. +-- @param #number Mass Mass of the static in kg. +-- @return #CTLD self +function CTLD:InjectStaticFromTemplate(Zone, Template, Mass) + self:T(self.lid .. " InjectStaticFromTemplate") + local cargotype = self:GetStaticsCargoFromTemplate(Template,Mass) -- #CTLD_CARGO + self:InjectStatics(Zone,cargotype,true) + return self +end + +--- (Internal) Function to find and list nearby crates. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @return #CTLD self +function CTLD:_ListCratesNearby( _group, _unit) + self:T(self.lid .. " _ListCratesNearby") + local finddist = self.CrateDistance or 35 + local crates,number = self:_FindCratesNearby(_group,_unit, finddist) -- #table + if number > 0 then + local text = REPORT:New("Crates Found Nearby:") + text:Add("------------------------------------------------------------") + for _,_entry in pairs (crates) do + local entry = _entry -- #CTLD_CARGO + local name = entry:GetName() --#string + local dropped = entry:WasDropped() + if dropped then + text:Add(string.format("Dropped crate for %s, %dkg",name, entry.PerCrateMass)) else - self:E( "*****Cannot calculate distance!" ) - self:E( { _point1, _point2 } ) - return -1 + text:Add(string.format("Crate for %s, %dkg",name, entry.PerCrateMass)) end + end + if text:GetCount() == 1 then + text:Add(" N O N E") + end + text:Add("------------------------------------------------------------") + self:_SendMessage(text:Text(), 30, true, _group) + else + self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist), 10, false, _group) + end + return self +end + +--- (Internal) Return distance in meters between two coordinates. +-- @param #CTLD self +-- @param Core.Point#COORDINATE _point1 Coordinate one +-- @param Core.Point#COORDINATE _point2 Coordinate two +-- @return #number Distance in meters +function CTLD:_GetDistance(_point1, _point2) + self:T(self.lid .. " _GetDistance") + if _point1 and _point2 then + local distance1 = _point1:Get2DDistance(_point2) + local distance2 = _point1:DistanceFromPointVec2(_point2) + --self:I({dist1=distance1, dist2=distance2}) + if distance1 and type(distance1) == "number" then + return distance1 + elseif distance2 and type(distance2) == "number" then + return distance2 else - self:E( "******Cannot calculate distance!" ) - self:E( { _point1, _point2 } ) + self:E("*****Cannot calculate distance!") + self:E({_point1,_point2}) return -1 end + else + self:E("******Cannot calculate distance!") + self:E({_point1,_point2}) + return -1 end +end - --- (Internal) Function to find and return nearby crates. - -- @param #CTLD self - -- @param Wrapper.Group#GROUP _group Group - -- @param Wrapper.Unit#UNIT _unit Unit - -- @param #number _dist Distance - -- @return #table Table of crates - -- @return #number Number Number of crates found - function CTLD:_FindCratesNearby( _group, _unit, _dist ) - self:T( self.lid .. " _FindCratesNearby" ) - local finddist = _dist - local location = _group:GetCoordinate() - local existingcrates = self.Spawned_Cargo -- #table - -- cycle - local index = 0 - local found = {} - for _, _cargoobject in pairs( existingcrates ) do - local cargo = _cargoobject -- #CTLD_CARGO - local static = cargo:GetPositionable() -- Wrapper.Static#STATIC -- crates - local staticid = cargo:GetID() - if static and static:IsAlive() then - local staticpos = static:GetCoordinate() - local distance = self:_GetDistance( location, staticpos ) - if distance <= finddist and static then - index = index + 1 - table.insert( found, staticid, cargo ) - end +--- (Internal) Function to find and return nearby crates. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP _group Group +-- @param Wrapper.Unit#UNIT _unit Unit +-- @param #number _dist Distance +-- @return #table Table of crates +-- @return #number Number Number of crates found +function CTLD:_FindCratesNearby( _group, _unit, _dist) + self:T(self.lid .. " _FindCratesNearby") + local finddist = _dist + local location = _group:GetCoordinate() + local existingcrates = self.Spawned_Cargo -- #table + -- cycle + local index = 0 + local found = {} + for _,_cargoobject in pairs (existingcrates) do + local cargo = _cargoobject -- #CTLD_CARGO + local static = cargo:GetPositionable() -- Wrapper.Static#STATIC -- crates + local staticid = cargo:GetID() + if static and static:IsAlive() then + local staticpos = static:GetCoordinate() + local distance = self:_GetDistance(location,staticpos) + if distance <= finddist and static then + index = index + 1 + table.insert(found, staticid, cargo) end end - return found, index end + return found, index +end - --- (Internal) Function to get and load nearby crates. - -- @param #CTLD self - -- @param Wrapper.Group#GROUP Group - -- @param Wrapper.Unit#UNIT Unit - -- @return #CTLD self - function CTLD:_LoadCratesNearby( Group, Unit ) - self:T( self.lid .. " _LoadCratesNearby" ) +--- (Internal) Function to get and load nearby crates. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @return #CTLD self +function CTLD:_LoadCratesNearby(Group, Unit) + self:T(self.lid .. " _LoadCratesNearby") -- load crates into heli - local group = Group -- Wrapper.Group#GROUP - local unit = Unit -- Wrapper.Unit#UNIT - local unitname = unit:GetName() - -- see if this heli can load crates - local unittype = unit:GetTypeName() - local capabilities = self:_GetUnitCapabilities( Unit ) -- #CTLD.UnitCapabilities - -- local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities - local cancrates = capabilities.crates -- #boolean - local cratelimit = capabilities.cratelimit -- #number - local grounded = not self:IsUnitInAir( Unit ) - local canhoverload = self:CanHoverLoad( Unit ) - --- cases ------------------------------- - -- Chopper can't do crates - bark & return - -- Chopper can do crates - - -- --> hover if forcedhover or bark and return - -- --> hover or land if not forcedhover - ----------------------------------------- - if not cancrates then - self:_SendMessage( "Sorry this chopper cannot carry crates!", 10, false, Group ) - elseif self.forcehoverload and not canhoverload then - self:_SendMessage( "Hover over the crates to pick them up!", 10, false, Group ) - elseif not grounded and not canhoverload then - self:_SendMessage( "Land or hover over the crates to pick them up!", 10, false, Group ) + local group = Group -- Wrapper.Group#GROUP + local unit = Unit -- Wrapper.Unit#UNIT + local unitname = unit:GetName() + -- see if this heli can load crates + local unittype = unit:GetTypeName() + local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities + --local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + local cancrates = capabilities.crates -- #boolean + local cratelimit = capabilities.cratelimit -- #number + local grounded = not self:IsUnitInAir(Unit) + local canhoverload = self:CanHoverLoad(Unit) + --- cases ------------------------------- + -- Chopper can\'t do crates - bark & return + -- Chopper can do crates - + -- --> hover if forcedhover or bark and return + -- --> hover or land if not forcedhover + ----------------------------------------- + if not cancrates then + self:_SendMessage("Sorry this chopper cannot carry crates!", 10, false, Group) + elseif self.forcehoverload and not canhoverload then + self:_SendMessage("Hover over the crates to pick them up!", 10, false, Group) + elseif not grounded and not canhoverload then + self:_SendMessage("Land or hover over the crates to pick them up!", 10, false, Group) + else + -- have we loaded stuff already? + local numberonboard = 0 + local massonboard = 0 + local loaded = {} + if self.Loaded_Cargo[unitname] then + loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo + numberonboard = loaded.Cratesloaded or 0 else - -- have we loaded stuff already? - local numberonboard = 0 - local massonboard = 0 - local loaded = {} - if self.Loaded_Cargo[unitname] then - loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo - numberonboard = loaded.Cratesloaded or 0 - else - loaded = {} -- #CTLD.LoadedCargo - loaded.Troopsloaded = 0 - loaded.Cratesloaded = 0 - loaded.Cargo = {} - end - -- get nearby crates - local finddist = self.CrateDistance or 35 - local nearcrates, number = self:_FindCratesNearby( Group, Unit, finddist ) -- #table - if number == 0 and self.hoverautoloading then - return self -- exit - elseif number == 0 then - self:_SendMessage( "Sorry no loadable crates nearby!", 10, false, Group ) - return self -- exit - elseif numberonboard == cratelimit then - self:_SendMessage( "Sorry no fully loaded!", 10, false, Group ) - return self -- exit - else - -- go through crates and load - local capacity = cratelimit - numberonboard - local crateidsloaded = {} - local loops = 0 - while loaded.Cratesloaded < cratelimit and loops < number do - loops = loops + 1 - local crateind = 0 - -- get crate with largest index - for _ind, _crate in pairs( nearcrates ) do - if self.allowcratepickupagain then - if _crate:GetID() > crateind and _crate.Positionable ~= nil then - crateind = _crate:GetID() - end - else - if not _crate:HasMoved() and _crate:WasDropped() and _crate:GetID() > crateind then - crateind = _crate:GetID() - end + loaded = {} -- #CTLD.LoadedCargo + loaded.Troopsloaded = 0 + loaded.Cratesloaded = 0 + loaded.Cargo = {} + end + -- get nearby crates + local finddist = self.CrateDistance or 35 + local nearcrates,number = self:_FindCratesNearby(Group,Unit,finddist) -- #table + if number == 0 and self.hoverautoloading then + return self -- exit + elseif number == 0 then + self:_SendMessage("Sorry no loadable crates nearby!", 10, false, Group) + return self -- exit + elseif numberonboard == cratelimit then + self:_SendMessage("Sorry no fully loaded!", 10, false, Group) + return self -- exit + else + -- go through crates and load + local capacity = cratelimit - numberonboard + local crateidsloaded = {} + local loops = 0 + while loaded.Cratesloaded < cratelimit and loops < number do + loops = loops + 1 + local crateind = 0 + -- get crate with largest index + for _ind,_crate in pairs (nearcrates) do + if self.allowcratepickupagain then + if _crate:GetID() > crateind and _crate.Positionable ~= nil then + crateind = _crate:GetID() + end + else + if not _crate:HasMoved() and _crate:WasDropped() and _crate:GetID() > crateind then + crateind = _crate:GetID() end end - -- load one if we found one - if crateind > 0 then - local crate = nearcrates[crateind] -- #CTLD_CARGO - loaded.Cratesloaded = loaded.Cratesloaded + 1 - crate:SetHasMoved( true ) - crate:SetWasDropped( false ) - table.insert( loaded.Cargo, crate ) - table.insert( crateidsloaded, crate:GetID() ) - -- destroy crate - crate:GetPositionable():Destroy( false ) - crate.Positionable = nil - self:_SendMessage( string.format( "Crate ID %d for %s loaded!", crate:GetID(), crate:GetName() ), 10, false, Group ) - table.remove( nearcrates, crate:GetID() ) - self:__CratesPickedUp( 1, Group, Unit, crate ) - end end - self.Loaded_Cargo[unitname] = loaded - self:_UpdateUnitCargoMass( Unit ) - -- clean up real world crates - self:_CleanupTrackedCrates( crateidsloaded ) + -- load one if we found one + if crateind > 0 then + local crate = nearcrates[crateind] -- #CTLD_CARGO + loaded.Cratesloaded = loaded.Cratesloaded + 1 + crate:SetHasMoved(true) + crate:SetWasDropped(false) + table.insert(loaded.Cargo, crate) + table.insert(crateidsloaded,crate:GetID()) + -- destroy crate + crate:GetPositionable():Destroy(false) + crate.Positionable = nil + self:_SendMessage(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()), 10, false, Group) + table.remove(nearcrates,crate:GetID()) + self:__CratesPickedUp(1, Group, Unit, crate) + end end + self.Loaded_Cargo[unitname] = loaded + self:_UpdateUnitCargoMass(Unit) + -- clean up real world crates + self:_CleanupTrackedCrates(crateidsloaded) end - return self end + return self +end - --- (Internal) Function to clean up tracked cargo crates - function CTLD:_CleanupTrackedCrates( crateIdsToRemove ) - local existingcrates = self.Spawned_Cargo -- #table - local newexcrates = {} - for _, _crate in pairs( existingcrates ) do - local excrate = _crate -- #CTLD_CARGO - local ID = excrate:GetID() - local keep = true - for _, _ID in pairs( crateIdsToRemove ) do - if ID == _ID then - keep = false - end - end - -- remove destroyed crates here too - local static = _crate:GetPositionable() -- Wrapper.Static#STATIC -- crates - if not static or not static:IsAlive() then +--- (Internal) Function to clean up tracked cargo crates +function CTLD:_CleanupTrackedCrates(crateIdsToRemove) + local existingcrates = self.Spawned_Cargo -- #table + local newexcrates = {} + for _,_crate in pairs(existingcrates) do + local excrate = _crate -- #CTLD_CARGO + local ID = excrate:GetID() + local keep = true + for _,_ID in pairs(crateIdsToRemove) do + if ID == _ID then keep = false end - if keep then - table.insert( newexcrates, _crate ) - end end - self.Spawned_Cargo = nil - self.Spawned_Cargo = newexcrates - return self - end - - --- (Internal) Function to get current loaded mass - -- @param #CTLD self - -- @param Wrapper.Unit#UNIT Unit - -- @return #number mass in kgs - function CTLD:_GetUnitCargoMass( Unit ) - self:T( self.lid .. " _GetUnitCargoMass" ) - local unitname = Unit:GetName() - local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo - local loadedmass = 0 -- #number - if self.Loaded_Cargo[unitname] then - local cargotable = loadedcargo.Cargo or {} -- #table - for _, _cargo in pairs( cargotable ) do - local cargo = _cargo -- #CTLD_CARGO - local type = cargo:GetType() -- #CTLD_CARGO.Enum - if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and not cargo:WasDropped() then - loadedmass = loadedmass + (cargo.PerCrateMass * cargo:GetCratesNeeded()) - end - if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and not cargo:WasDropped() then - loadedmass = loadedmass + cargo.PerCrateMass - end - end + -- remove destroyed crates here too + local static = _crate:GetPositionable() -- Wrapper.Static#STATIC -- crates + if not static or not static:IsAlive() then + keep = false end - return loadedmass - end - - --- (Internal) Function to calculate and set Unit internal cargo mass - -- @param #CTLD self - -- @param Wrapper.Unit#UNIT Unit - function CTLD:_UpdateUnitCargoMass( Unit ) - self:T( self.lid .. " _UpdateUnitCargoMass" ) - local calculatedMass = self:_GetUnitCargoMass( Unit ) - Unit:SetUnitInternalCargo( calculatedMass ) - -- local report = REPORT:New("Loadmaster report") - -- report:Add("Carrying " .. calculatedMass .. "Kg") - -- self:_SendMessage(report:Text(),10,false,Unit:GetGroup()) - return self - end - - --- (Internal) Function to list loaded cargo. - -- @param #CTLD self - -- @param Wrapper.Group#GROUP Group - -- @param Wrapper.Unit#UNIT Unit - -- @return #CTLD self - function CTLD:_ListCargo( Group, Unit ) - self:T( self.lid .. " _ListCargo" ) - local unitname = Unit:GetName() - local unittype = Unit:GetTypeName() - local capabilities = self:_GetUnitCapabilities( Unit ) -- #CTLD.UnitCapabilities - local trooplimit = capabilities.trooplimit -- #boolean - local cratelimit = capabilities.cratelimit -- #number - local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo - local loadedmass = self:_GetUnitCargoMass( Unit ) -- #number - if self.Loaded_Cargo[unitname] then - local no_troops = loadedcargo.Troopsloaded or 0 - local no_crates = loadedcargo.Cratesloaded or 0 - local cargotable = loadedcargo.Cargo or {} -- #table - local report = REPORT:New( "Transport Checkout Sheet" ) - report:Add( "------------------------------------------------------------" ) - report:Add( string.format( "Troops: %d(%d), Crates: %d(%d)", no_troops, trooplimit, no_crates, cratelimit ) ) - report:Add( "------------------------------------------------------------" ) - report:Add( " -- TROOPS --" ) - for _, _cargo in pairs( cargotable ) do - local cargo = _cargo -- #CTLD_CARGO - local type = cargo:GetType() -- #CTLD_CARGO.Enum - if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and (not cargo:WasDropped() or self.allowcratepickupagain) then - report:Add( string.format( "Troop: %s size %d", cargo:GetName(), cargo:GetCratesNeeded() ) ) - end - end - if report:GetCount() == 4 then - report:Add( " N O N E" ) - end - report:Add( "------------------------------------------------------------" ) - report:Add( " -- CRATES --" ) - local cratecount = 0 - for _, _cargo in pairs( cargotable ) do - local cargo = _cargo -- #CTLD_CARGO - local type = cargo:GetType() -- #CTLD_CARGO.Enum - if (type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS) and (not cargo:WasDropped() or self.allowcratepickupagain) then - report:Add( string.format( "Crate: %s size 1", cargo:GetName() ) ) - cratecount = cratecount + 1 - end - end - if cratecount == 0 then - report:Add( " N O N E" ) - end - report:Add( "------------------------------------------------------------" ) - report:Add( "Total Mass: " .. loadedmass .. " kg" ) - local text = report:Text() - self:_SendMessage( text, 30, true, Group ) - else - self:_SendMessage( string.format( "Nothing loaded!\nTroop limit: %d | Crate limit %d", trooplimit, cratelimit ), 10, false, Group ) - end - return self - end - - --- (Internal) Function to list loaded cargo. - -- @param #CTLD self - -- @param Wrapper.Group#GROUP Group - -- @param Wrapper.Unit#UNIT Unit - -- @return #CTLD self - function CTLD:_ListInventory( Group, Unit ) - self:T( self.lid .. " _ListInventory" ) - local unitname = Unit:GetName() - local unittype = Unit:GetTypeName() - local cgotypes = self.Cargo_Crates - local trptypes = self.Cargo_Troops - local stctypes = self.Cargo_Statics - - local function countcargo( cgotable ) - local counter = 0 - for _, _cgo in pairs( cgotable ) do - counter = counter + 1 - end - return counter - end - - local crateno = countcargo( cgotypes ) - local troopno = countcargo( trptypes ) - local staticno = countcargo( stctypes ) - - if (crateno > 0 or troopno > 0 or staticno > 0) then - - local report = REPORT:New( "Inventory Sheet" ) - report:Add( "------------------------------------------------------------" ) - report:Add( string.format( "Troops: %d, Cratetypes: %d", troopno, crateno + staticno ) ) - report:Add( "------------------------------------------------------------" ) - report:Add( " -- TROOPS --" ) - for _, _cargo in pairs( trptypes ) do - local cargo = _cargo -- #CTLD_CARGO - local type = cargo:GetType() -- #CTLD_CARGO.Enum - if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and not cargo:WasDropped() then - local stockn = cargo:GetStock() - local stock = "none" - if stockn == -1 then - stock = "unlimited" - elseif stockn > 0 then - stock = tostring( stockn ) - end - report:Add( string.format( "Unit: %s | Soldiers: %d | Stock: %s", cargo:GetName(), cargo:GetCratesNeeded(), stock ) ) - end - end - if report:GetCount() == 4 then - report:Add( " N O N E" ) - end - report:Add( "------------------------------------------------------------" ) - report:Add( " -- CRATES --" ) - local cratecount = 0 - for _, _cargo in pairs( cgotypes ) do - local cargo = _cargo -- #CTLD_CARGO - local type = cargo:GetType() -- #CTLD_CARGO.Enum - if (type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS) and not cargo:WasDropped() then - local stockn = cargo:GetStock() - local stock = "none" - if stockn == -1 then - stock = "unlimited" - elseif stockn > 0 then - stock = tostring( stockn ) - end - report:Add( string.format( "Type: %s | Crates per Set: %d | Stock: %s", cargo:GetName(), cargo:GetCratesNeeded(), stock ) ) - cratecount = cratecount + 1 - end - end - -- Statics - for _, _cargo in pairs( stctypes ) do - local cargo = _cargo -- #CTLD_CARGO - local type = cargo:GetType() -- #CTLD_CARGO.Enum - if (type == CTLD_CARGO.Enum.STATIC) and not cargo:WasDropped() then - local stockn = cargo:GetStock() - local stock = "none" - if stockn == -1 then - stock = "unlimited" - elseif stockn > 0 then - stock = tostring( stockn ) - end - report:Add( string.format( "Type: %s | Stock: %s", cargo:GetName(), stock ) ) - cratecount = cratecount + 1 - end - end - if cratecount == 0 then - report:Add( " N O N E" ) - end - local text = report:Text() - self:_SendMessage( text, 30, true, Group ) - else - self:_SendMessage( string.format( "Nothing in stock!" ), 10, false, Group ) - end - return self - end - - --- (Internal) Function to check if a unit is a Hercules C-130. - -- @param #CTLD self - -- @param Wrapper.Unit#UNIT Unit - -- @return #boolean Outcome - function CTLD:IsHercules( Unit ) - if Unit:GetTypeName() == "Hercules" then - return true - else - return false + if keep then + table.insert(newexcrates,_crate) end end + self.Spawned_Cargo = nil + self.Spawned_Cargo = newexcrates + return self +end - --- (Internal) Function to unload troops from heli. - -- @param #CTLD self - -- @param Wrapper.Group#GROUP Group - -- @param Wrapper.Unit#UNIT Unit - function CTLD:_UnloadTroops( Group, Unit ) - self:T( self.lid .. " _UnloadTroops" ) - -- check if we are in LOAD zone - local droppingatbase = false - local canunload = true - if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen( Unit:GetName() ) then - self:_SendMessage( "You need to open the door(s) to unload troops!", 10, false, Group ) - if not self.debug then - return self +--- (Internal) Function to get current loaded mass +-- @param #CTLD self +-- @param Wrapper.Unit#UNIT Unit +-- @return #number mass in kgs +function CTLD:_GetUnitCargoMass(Unit) + self:T(self.lid .. " _GetUnitCargoMass") + local unitname = Unit:GetName() + local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo + local loadedmass = 0 -- #number + if self.Loaded_Cargo[unitname] then + local cargotable = loadedcargo.Cargo or {} -- #table + for _,_cargo in pairs(cargotable) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and not cargo:WasDropped() then + loadedmass = loadedmass + (cargo.PerCrateMass * cargo:GetCratesNeeded()) + end + if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and not cargo:WasDropped() then + loadedmass = loadedmass + cargo.PerCrateMass end end - local inzone, zonename, zone, distance = self:IsUnitInZone( Unit, CTLD.CargoZoneType.LOAD ) - if not inzone then - inzone, zonename, zone, distance = self:IsUnitInZone( Unit, CTLD.CargoZoneType.SHIP ) - end - if inzone then - droppingatbase = true - end - -- check for hover unload - local hoverunload = self:IsCorrectHover( Unit ) -- if true we're hovering in parameters - local IsHerc = self:IsHercules( Unit ) - if IsHerc then - -- no hover but airdrop here - hoverunload = self:IsCorrectFlightParameters( Unit ) - end - -- check if we're landed - local grounded = not self:IsUnitInAir( Unit ) - -- Get what we have loaded - local unitname = Unit:GetName() - if self.Loaded_Cargo[unitname] and (grounded or hoverunload) then - if not droppingatbase or self.debug then - local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo - -- looking for troops - local cargotable = loadedcargo.Cargo - for _, _cargo in pairs( cargotable ) do - local cargo = _cargo -- #CTLD_CARGO - local type = cargo:GetType() -- #CTLD_CARGO.Enum - if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and not cargo:WasDropped() then - -- unload troops - local name = cargo:GetName() or "none" - local temptable = cargo:GetTemplates() or {} - local position = Group:GetCoordinate() - local zoneradius = 100 -- drop zone radius - local factor = 1 - if IsHerc then - factor = cargo:GetCratesNeeded() or 1 -- spread a bit more if airdropping - zoneradius = Unit:GetVelocityMPS() or 100 - end - local zone = ZONE_GROUP:New( string.format( "Unload zone-%s", unitname ), Group, zoneradius * factor ) - local randomcoord = zone:GetRandomCoordinate( 10, 30 * factor ):GetVec2() - for _, _template in pairs( temptable ) do - self.TroopCounter = self.TroopCounter + 1 - local alias = string.format( "%s-%d", _template, math.random( 1, 100000 ) ) - self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias( _template, alias ) - :InitRandomizeUnits( true, 20, 2 ) - :InitDelayOff() - :SpawnFromVec2( randomcoord ) - if self.movetroopstowpzone and type ~= CTLD_CARGO.Enum.ENGINEERS then - self:_MoveGroupToZone( self.DroppedTroops[self.TroopCounter] ) - end - end -- template loop - cargo:SetWasDropped( true ) - -- engineering group? - -- self:I("Dropped Troop Type: "..type) - if type == CTLD_CARGO.Enum.ENGINEERS then - self.Engineers = self.Engineers + 1 - local grpname = self.DroppedTroops[self.TroopCounter]:GetName() - self.EngineersInField[self.Engineers] = CTLD_ENGINEERING:New( name, grpname ) - self:_SendMessage( string.format( "Dropped Engineers %s into action!", name ), 10, false, Group ) - else - self:_SendMessage( string.format( "Dropped Troops %s into action!", name ), 10, false, Group ) - end - self:__TroopsDeployed( 1, Group, Unit, self.DroppedTroops[self.TroopCounter] ) - end -- if type end - end -- cargotable loop - else -- droppingatbase - self:_SendMessage( "Troops have returned to base!", 10, false, Group ) - self:__TroopsRTB( 1, Group, Unit ) + end + return loadedmass +end + +--- (Internal) Function to calculate and set Unit internal cargo mass +-- @param #CTLD self +-- @param Wrapper.Unit#UNIT Unit +function CTLD:_UpdateUnitCargoMass(Unit) + self:T(self.lid .. " _UpdateUnitCargoMass") + local calculatedMass = self:_GetUnitCargoMass(Unit) + Unit:SetUnitInternalCargo(calculatedMass) + --local report = REPORT:New("Loadmaster report") + --report:Add("Carrying " .. calculatedMass .. "Kg") + --self:_SendMessage(report:Text(),10,false,Unit:GetGroup()) + return self +end + +--- (Internal) Function to list loaded cargo. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @return #CTLD self +function CTLD:_ListCargo(Group, Unit) + self:T(self.lid .. " _ListCargo") + local unitname = Unit:GetName() + local unittype = Unit:GetTypeName() + local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities + local trooplimit = capabilities.trooplimit -- #boolean + local cratelimit = capabilities.cratelimit -- #number + local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo + local loadedmass = self:_GetUnitCargoMass(Unit) -- #number + if self.Loaded_Cargo[unitname] then + local no_troops = loadedcargo.Troopsloaded or 0 + local no_crates = loadedcargo.Cratesloaded or 0 + local cargotable = loadedcargo.Cargo or {} -- #table + local report = REPORT:New("Transport Checkout Sheet") + report:Add("------------------------------------------------------------") + report:Add(string.format("Troops: %d(%d), Crates: %d(%d)",no_troops,trooplimit,no_crates,cratelimit)) + report:Add("------------------------------------------------------------") + report:Add(" -- TROOPS --") + for _,_cargo in pairs(cargotable) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and (not cargo:WasDropped() or self.allowcratepickupagain) then + report:Add(string.format("Troop: %s size %d",cargo:GetName(),cargo:GetCratesNeeded())) end - -- cleanup load list - local loaded = {} -- #CTLD.LoadedCargo - loaded.Troopsloaded = 0 - loaded.Cratesloaded = 0 - loaded.Cargo = {} + end + if report:GetCount() == 4 then + report:Add(" N O N E") + end + report:Add("------------------------------------------------------------") + report:Add(" -- CRATES --") + local cratecount = 0 + for _,_cargo in pairs(cargotable) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if (type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS) and (not cargo:WasDropped() or self.allowcratepickupagain) then + report:Add(string.format("Crate: %s size 1",cargo:GetName())) + cratecount = cratecount + 1 + end + end + if cratecount == 0 then + report:Add(" N O N E") + end + report:Add("------------------------------------------------------------") + report:Add("Total Mass: ".. loadedmass .. " kg") + local text = report:Text() + self:_SendMessage(text, 30, true, Group) + else + self:_SendMessage(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d",trooplimit,cratelimit), 10, false, Group) + end + return self +end + +--- (Internal) Function to list loaded cargo. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @return #CTLD self +function CTLD:_ListInventory(Group, Unit) + self:T(self.lid .. " _ListInventory") + local unitname = Unit:GetName() + local unittype = Unit:GetTypeName() + local cgotypes = self.Cargo_Crates + local trptypes = self.Cargo_Troops + local stctypes = self.Cargo_Statics + + local function countcargo(cgotable) + local counter = 0 + for _,_cgo in pairs(cgotable) do + counter = counter + 1 + end + return counter + end + + local crateno = countcargo(cgotypes) + local troopno = countcargo(trptypes) + local staticno = countcargo(stctypes) + + if (crateno > 0 or troopno > 0 or staticno > 0) then + + local report = REPORT:New("Inventory Sheet") + report:Add("------------------------------------------------------------") + report:Add(string.format("Troops: %d, Cratetypes: %d",troopno,crateno+staticno)) + report:Add("------------------------------------------------------------") + report:Add(" -- TROOPS --") + for _,_cargo in pairs(trptypes) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and not cargo:WasDropped() then + local stockn = cargo:GetStock() + local stock = "none" + if stockn == -1 then + stock = "unlimited" + elseif stockn > 0 then + stock = tostring(stockn) + end + report:Add(string.format("Unit: %s | Soldiers: %d | Stock: %s",cargo:GetName(),cargo:GetCratesNeeded(),stock)) + end + end + if report:GetCount() == 4 then + report:Add(" N O N E") + end + report:Add("------------------------------------------------------------") + report:Add(" -- CRATES --") + local cratecount = 0 + for _,_cargo in pairs(cgotypes) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if (type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS) and not cargo:WasDropped() then + local stockn = cargo:GetStock() + local stock = "none" + if stockn == -1 then + stock = "unlimited" + elseif stockn > 0 then + stock = tostring(stockn) + end + report:Add(string.format("Type: %s | Crates per Set: %d | Stock: %s",cargo:GetName(),cargo:GetCratesNeeded(),stock)) + cratecount = cratecount + 1 + end + end + -- Statics + for _,_cargo in pairs(stctypes) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if (type == CTLD_CARGO.Enum.STATIC) and not cargo:WasDropped() then + local stockn = cargo:GetStock() + local stock = "none" + if stockn == -1 then + stock = "unlimited" + elseif stockn > 0 then + stock = tostring(stockn) + end + report:Add(string.format("Type: %s | Stock: %s",cargo:GetName(),stock)) + cratecount = cratecount + 1 + end + end + if cratecount == 0 then + report:Add(" N O N E") + end + local text = report:Text() + self:_SendMessage(text, 30, true, Group) + else + self:_SendMessage(string.format("Nothing in stock!"), 10, false, Group) + end + return self +end + +--- (Internal) Function to check if a unit is a Hercules C-130. +-- @param #CTLD self +-- @param Wrapper.Unit#UNIT Unit +-- @return #boolean Outcome +function CTLD:IsHercules(Unit) + if Unit:GetTypeName() == "Hercules" then + return true + else + return false + end +end + +--- (Internal) Function to unload troops from heli. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +function CTLD:_UnloadTroops(Group, Unit) + self:T(self.lid .. " _UnloadTroops") + -- check if we are in LOAD zone + local droppingatbase = false + local canunload = true + if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then + self:_SendMessage("You need to open the door(s) to unload troops!", 10, false, Group) + if not self.debug then return self end + end + local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) + if not inzone then + inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) + end + if inzone then + droppingatbase = true + end + -- check for hover unload + local hoverunload = self:IsCorrectHover(Unit) --if true we\'re hovering in parameters + local IsHerc = self:IsHercules(Unit) + if IsHerc then + -- no hover but airdrop here + hoverunload = self:IsCorrectFlightParameters(Unit) + end + -- check if we\'re landed + local grounded = not self:IsUnitInAir(Unit) + -- Get what we have loaded + local unitname = Unit:GetName() + if self.Loaded_Cargo[unitname] and (grounded or hoverunload) then + if not droppingatbase or self.debug then local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo - local cargotable = loadedcargo.Cargo or {} - for _, _cargo in pairs( cargotable ) do - local cargo = _cargo -- #CTLD_CARGO - local type = cargo:GetType() -- #CTLD_CARGO.Enum - local dropped = cargo:WasDropped() - if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and not dropped then - table.insert( loaded.Cargo, _cargo ) - loaded.Cratesloaded = loaded.Cratesloaded + 1 - else - -- add troops back to stock - if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and droppingatbase then - -- find right generic type - local name = cargo:GetName() - local gentroops = self.Cargo_Troops - for _id, _troop in pairs( gentroops ) do -- #number, #CTLD_CARGO - if _troop.Name == name then - local stock = _troop:GetStock() - -- avoid making unlimited stock limited - if stock and tonumber( stock ) >= 0 then - _troop:AddStock() - end - end - end - end - end - end - self.Loaded_Cargo[unitname] = nil - self.Loaded_Cargo[unitname] = loaded - self:_UpdateUnitCargoMass( Unit ) - else - if IsHerc then - self:_SendMessage( "Nothing loaded or not within airdrop parameters!", 10, false, Group ) - else - self:_SendMessage( "Nothing loaded or not hovering within parameters!", 10, false, Group ) - end - end - return self - end - - --- (Internal) Function to unload crates from heli. - -- @param #CTLD self - -- @param Wrapper.Group#GROUP Group - -- @param Wrapper.Unit#UNIT Unit - function CTLD:_UnloadCrates( Group, Unit ) - self:T( self.lid .. " _UnloadCrates" ) - - if not self.dropcratesanywhere then -- #1570 - -- check if we are in DROP zone - local inzone, zonename, zone, distance = self:IsUnitInZone( Unit, CTLD.CargoZoneType.DROP ) - if not inzone then - self:_SendMessage( "You are not close enough to a drop zone!", 10, false, Group ) - if not self.debug then - return self - end - end - end - -- check for hover unload - local hoverunload = self:IsCorrectHover( Unit ) -- if true we're hovering in parameters - local IsHerc = self:IsHercules( Unit ) - if IsHerc then - -- no hover but airdrop here - hoverunload = self:IsCorrectFlightParameters( Unit ) - end - -- check if we're landed - local grounded = not self:IsUnitInAir( Unit ) - -- Get what we have loaded - local unitname = Unit:GetName() - if self.Loaded_Cargo[unitname] and (grounded or hoverunload) then - local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo - -- looking for crate + -- looking for troops local cargotable = loadedcargo.Cargo - for _, _cargo in pairs( cargotable ) do + for _,_cargo in pairs (cargotable) do local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum - if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and (not cargo:WasDropped() or self.allowcratepickupagain) then - -- unload crates - self:_GetCrates( Group, Unit, cargo, 1, true ) - cargo:SetWasDropped( true ) - cargo:SetHasMoved( true ) - end - end - -- cleanup load list - local loaded = {} -- #CTLD.LoadedCargo - loaded.Troopsloaded = 0 - loaded.Cratesloaded = 0 - loaded.Cargo = {} - - for _, _cargo in pairs( cargotable ) do - local cargo = _cargo -- #CTLD_CARGO - local type = cargo:GetType() -- #CTLD_CARGO.Enum - local size = cargo:GetCratesNeeded() - if type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS then - table.insert( loaded.Cargo, _cargo ) - loaded.Troopsloaded = loaded.Troopsloaded + size - end - end - self.Loaded_Cargo[unitname] = nil - self.Loaded_Cargo[unitname] = loaded - - self:_UpdateUnitCargoMass( Unit ) - else - if IsHerc then - self:_SendMessage( "Nothing loaded or not within airdrop parameters!", 10, false, Group ) + if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and not cargo:WasDropped() then + -- unload troops + local name = cargo:GetName() or "none" + local temptable = cargo:GetTemplates() or {} + local position = Group:GetCoordinate() + local zoneradius = 100 -- drop zone radius + local factor = 1 + if IsHerc then + factor = cargo:GetCratesNeeded() or 1 -- spread a bit more if airdropping + zoneradius = Unit:GetVelocityMPS() or 100 + end + local zone = ZONE_GROUP:New(string.format("Unload zone-%s",unitname),Group,zoneradius*factor) + local randomcoord = zone:GetRandomCoordinate(10,30*factor):GetVec2() + for _,_template in pairs(temptable) do + self.TroopCounter = self.TroopCounter + 1 + local alias = string.format("%s-%d", _template, math.random(1,100000)) + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) + :InitRandomizeUnits(true,20,2) + :InitDelayOff() + :SpawnFromVec2(randomcoord) + if self.movetroopstowpzone and type ~= CTLD_CARGO.Enum.ENGINEERS then + self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) + end + end -- template loop + cargo:SetWasDropped(true) + -- engineering group? + --self:I("Dropped Troop Type: "..type) + if type == CTLD_CARGO.Enum.ENGINEERS then + self.Engineers = self.Engineers + 1 + local grpname = self.DroppedTroops[self.TroopCounter]:GetName() + self.EngineersInField[self.Engineers] = CTLD_ENGINEERING:New(name, grpname) + self:_SendMessage(string.format("Dropped Engineers %s into action!",name), 10, false, Group) + else + self:_SendMessage(string.format("Dropped Troops %s into action!",name), 10, false, Group) + end + self:__TroopsDeployed(1, Group, Unit, self.DroppedTroops[self.TroopCounter]) + end -- if type end + end -- cargotable loop + else -- droppingatbase + self:_SendMessage("Troops have returned to base!", 10, false, Group) + self:__TroopsRTB(1, Group, Unit) + end + -- cleanup load list + local loaded = {} -- #CTLD.LoadedCargo + loaded.Troopsloaded = 0 + loaded.Cratesloaded = 0 + loaded.Cargo = {} + local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo + local cargotable = loadedcargo.Cargo or {} + for _,_cargo in pairs (cargotable) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + local dropped = cargo:WasDropped() + if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and not dropped then + table.insert(loaded.Cargo,_cargo) + loaded.Cratesloaded = loaded.Cratesloaded + 1 else - self:_SendMessage( "Nothing loaded or not hovering within parameters!", 10, false, Group ) + -- add troops back to stock + if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and droppingatbase then + -- find right generic type + local name = cargo:GetName() + local gentroops = self.Cargo_Troops + for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO + if _troop.Name == name then + local stock = _troop:GetStock() + -- avoid making unlimited stock limited + if stock and tonumber(stock) >= 0 then _troop:AddStock() end + end + end + end end end - return self + self.Loaded_Cargo[unitname] = nil + self.Loaded_Cargo[unitname] = loaded + self:_UpdateUnitCargoMass(Unit) + else + if IsHerc then + self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) + else + self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) + end end + return self +end - --- (Internal) Function to build nearby crates. - -- @param #CTLD self - -- @param Wrapper.Group#GROUP Group - -- @param Wrapper.Unit#UNIT Unit - -- @param #boolean Engineering If true build is by an engineering team. - function CTLD:_BuildCrates( Group, Unit, Engineering ) - self:T( self.lid .. " _BuildCrates" ) - -- avoid users trying to build from flying Hercs - local type = Unit:GetTypeName() - if type == "Hercules" and self.enableHercules and not Engineering then - local speed = Unit:GetVelocityKMH() - if speed > 1 then - self:_SendMessage( "You need to land / stop to build something, Pilot!", 10, false, Group ) - return self +--- (Internal) Function to unload crates from heli. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +function CTLD:_UnloadCrates(Group, Unit) + self:T(self.lid .. " _UnloadCrates") + + if not self.dropcratesanywhere then -- #1570 + -- check if we are in DROP zone + local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) + if not inzone then + self:_SendMessage("You are not close enough to a drop zone!", 10, false, Group) + if not self.debug then + return self end end - -- get nearby crates - local finddist = self.CrateDistance or 35 - local crates, number = self:_FindCratesNearby( Group, Unit, finddist ) -- #table - local buildables = {} - local foundbuilds = false - local canbuild = false - if number > 0 then - -- get dropped crates - for _, _crate in pairs( crates ) do - local Crate = _crate -- #CTLD_CARGO - if Crate:WasDropped() and not Crate:IsRepair() and not Crate:IsStatic() then - -- we can build these - maybe - local name = Crate:GetName() - local required = Crate:GetCratesNeeded() - local template = Crate:GetTemplates() - local ctype = Crate:GetType() - if not buildables[name] then - local object = {} -- #CTLD.Buildable - object.Name = name - object.Required = required - object.Found = 1 - object.Template = template - object.CanBuild = false - object.Type = ctype -- #CTLD_CARGO.Enum - buildables[name] = object - foundbuilds = true - else - buildables[name].Found = buildables[name].Found + 1 - foundbuilds = true - end - if buildables[name].Found >= buildables[name].Required then - buildables[name].CanBuild = true - canbuild = true - end - self:T( { buildables = buildables } ) - end -- end dropped - end -- end crate loop - -- ok let's list what we have - local report = REPORT:New( "Checklist Buildable Crates" ) - report:Add( "------------------------------------------------------------" ) - for _, _build in pairs( buildables ) do - local build = _build -- Object table from above - local name = build.Name - local needed = build.Required - local found = build.Found - local txtok = "NO" - if build.CanBuild then - txtok = "YES" - end - local text = string.format( "Type: %s | Required %d | Found %d | Can Build %s", name, needed, found, txtok ) - report:Add( text ) - end -- end list buildables - if not foundbuilds then - report:Add( " --- None Found ---" ) - end - report:Add( "------------------------------------------------------------" ) - local text = report:Text() - if not Engineering then - self:_SendMessage( text, 30, true, Group ) - else - self:T( text ) - end - -- let's get going - if canbuild then - -- loop again - for _, _build in pairs( buildables ) do - local build = _build -- #CTLD.Buildable - if build.CanBuild then - self:_CleanUpCrates( crates, build, number ) - self:_BuildObjectFromCrates( Group, Unit, build ) - end - end - end - else - if not Engineering then - self:_SendMessage( string.format( "No crates within %d meters!", finddist ), 10, false, Group ) - end - end -- number > 0 - return self end - - --- (Internal) Function to repair nearby vehicles / FOBs - -- @param #CTLD self - -- @param Wrapper.Group#GROUP Group - -- @param Wrapper.Unit#UNIT Unit - -- @param #boolean Engineering If true, this is an engineering role - function CTLD:_RepairCrates( Group, Unit, Engineering ) - self:T( self.lid .. " _RepairCrates" ) - -- get nearby crates - local finddist = self.CrateDistance or 35 - local crates, number = self:_FindCratesNearby( Group, Unit, finddist ) -- #table - local buildables = {} - local foundbuilds = false - local canbuild = false - if number > 0 then - -- get dropped crates - for _, _crate in pairs( crates ) do - local Crate = _crate -- #CTLD_CARGO - if Crate:WasDropped() and Crate:IsRepair() and not Crate:IsStatic() then - -- we can build these - maybe - local name = Crate:GetName() - local required = Crate:GetCratesNeeded() - local template = Crate:GetTemplates() - local ctype = Crate:GetType() - if not buildables[name] then - local object = {} -- #CTLD.Buildable - object.Name = name - object.Required = required - object.Found = 1 - object.Template = template - object.CanBuild = false - object.Type = ctype -- #CTLD_CARGO.Enum - buildables[name] = object - foundbuilds = true - else - buildables[name].Found = buildables[name].Found + 1 - foundbuilds = true - end - if buildables[name].Found >= buildables[name].Required then - buildables[name].CanBuild = true - canbuild = true - end - self:T( { repair = buildables } ) - end -- end dropped - end -- end crate loop - -- ok let's list what we have - local report = REPORT:New( "Checklist Repairs" ) - report:Add( "------------------------------------------------------------" ) - for _, _build in pairs( buildables ) do - local build = _build -- Object table from above - local name = build.Name - local needed = build.Required - local found = build.Found - local txtok = "NO" - if build.CanBuild then - txtok = "YES" - end - local text = string.format( "Type: %s | Required %d | Found %d | Can Repair %s", name, needed, found, txtok ) - report:Add( text ) - end -- end list buildables - if not foundbuilds then - report:Add( " --- None Found ---" ) - end - report:Add( "------------------------------------------------------------" ) - local text = report:Text() - if not Engineering then - self:_SendMessage( text, 30, true, Group ) - else - self:T( text ) - end - -- let's get going - if canbuild then - -- loop again - for _, _build in pairs( buildables ) do - local build = _build -- #CTLD.Buildable - if build.CanBuild then - self:_RepairObjectFromCrates( Group, Unit, crates, build, number, Engineering ) - end - end - end - else - if not Engineering then - self:_SendMessage( string.format( "No crates within %d meters!", finddist ), 10, false, Group ) - end - end -- number > 0 - return self + -- check for hover unload + local hoverunload = self:IsCorrectHover(Unit) --if true we\'re hovering in parameters + local IsHerc = self:IsHercules(Unit) + if IsHerc then + -- no hover but airdrop here + hoverunload = self:IsCorrectFlightParameters(Unit) end + -- check if we\'re landed + local grounded = not self:IsUnitInAir(Unit) + -- Get what we have loaded + local unitname = Unit:GetName() + if self.Loaded_Cargo[unitname] and (grounded or hoverunload) then + local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo + -- looking for crate + local cargotable = loadedcargo.Cargo + for _,_cargo in pairs (cargotable) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and (not cargo:WasDropped() or self.allowcratepickupagain) then + -- unload crates + self:_GetCrates(Group, Unit, cargo, 1, true) + cargo:SetWasDropped(true) + cargo:SetHasMoved(true) + end + end + -- cleanup load list + local loaded = {} -- #CTLD.LoadedCargo + loaded.Troopsloaded = 0 + loaded.Cratesloaded = 0 + loaded.Cargo = {} + + for _,_cargo in pairs (cargotable) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + local size = cargo:GetCratesNeeded() + if type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS then + table.insert(loaded.Cargo,_cargo) + loaded.Troopsloaded = loaded.Troopsloaded + size + end + end + self.Loaded_Cargo[unitname] = nil + self.Loaded_Cargo[unitname] = loaded + + self:_UpdateUnitCargoMass(Unit) + else + if IsHerc then + self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) + else + self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) + end + end + return self +end - --- (Internal) Function to actually SPAWN buildables in the mission. - -- @param #CTLD self - -- @param Wrapper.Group#GROUP Group - -- @param Wrapper.Group#UNIT Unit - -- @param #CTLD.Buildable Build - -- @param #boolean Repair If true this is a repair and not a new build - -- @param Core.Point#COORDINATE Coordinate Location for repair (e.g. where the destroyed unit was) - function CTLD:_BuildObjectFromCrates( Group, Unit, Build, Repair, RepairLocation ) - self:T( self.lid .. " _BuildObjectFromCrates" ) - -- Spawn-a-crate-content - if Group and Group:IsAlive() then - local position = Unit:GetCoordinate() or Group:GetCoordinate() - local unitname = Unit:GetName() or Group:GetName() - local name = Build.Name - local ctype = Build.Type -- #CTLD_CARGO.Enum - local canmove = false - if ctype == CTLD_CARGO.Enum.VEHICLE then - canmove = true - end - if ctype == CTLD_CARGO.Enum.STATIC then - return self - end - local temptable = Build.Template or {} - if type( temptable ) == "string" then - temptable = { temptable } - end - local zone = ZONE_GROUP:New( string.format( "Unload zone-%s", unitname ), Group, 100 ) - local randomcoord = zone:GetRandomCoordinate( 35 ):GetVec2() - if Repair then - randomcoord = RepairLocation:GetVec2() - end - for _, _template in pairs( temptable ) do - self.TroopCounter = self.TroopCounter + 1 - local alias = string.format( "%s-%d", _template, math.random( 1, 100000 ) ) - if canmove then - self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias( _template, alias ) - :InitRandomizeUnits( true, 20, 2 ) - :InitDelayOff() - :SpawnFromVec2( randomcoord ) - else -- don't random position of e.g. SAM units build as FOB - self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias( _template, alias ) - :InitDelayOff() - :SpawnFromVec2( randomcoord ) - end - if self.movetroopstowpzone and canmove then - self:_MoveGroupToZone( self.DroppedTroops[self.TroopCounter] ) - end - if Repair then - self:__CratesRepaired( 1, Group, Unit, self.DroppedTroops[self.TroopCounter] ) +--- (Internal) Function to build nearby crates. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @param #boolean Engineering If true build is by an engineering team. +function CTLD:_BuildCrates(Group, Unit,Engineering) + self:T(self.lid .. " _BuildCrates") + -- avoid users trying to build from flying Hercs + local type = Unit:GetTypeName() + if type == "Hercules" and self.enableHercules and not Engineering then + local speed = Unit:GetVelocityKMH() + if speed > 1 then + self:_SendMessage("You need to land / stop to build something, Pilot!", 10, false, Group) + return self + end + end + -- get nearby crates + local finddist = self.CrateDistance or 35 + local crates,number = self:_FindCratesNearby(Group,Unit, finddist) -- #table + local buildables = {} + local foundbuilds = false + local canbuild = false + if number > 0 then + -- get dropped crates + for _,_crate in pairs(crates) do + local Crate = _crate -- #CTLD_CARGO + if Crate:WasDropped() and not Crate:IsRepair() and not Crate:IsStatic() then + -- we can build these - maybe + local name = Crate:GetName() + local required = Crate:GetCratesNeeded() + local template = Crate:GetTemplates() + local ctype = Crate:GetType() + if not buildables[name] then + local object = {} -- #CTLD.Buildable + object.Name = name + object.Required = required + object.Found = 1 + object.Template = template + object.CanBuild = false + object.Type = ctype -- #CTLD_CARGO.Enum + buildables[name] = object + foundbuilds = true else - self:__CratesBuild( 1, Group, Unit, self.DroppedTroops[self.TroopCounter] ) + buildables[name].Found = buildables[name].Found + 1 + foundbuilds = true end - end -- template loop - else - self:T( self.lid .. "Group KIA while building!" ) - end - return self - end - - --- (Internal) Function to move group to WP zone. - -- @param #CTLD self - -- @param Wrapper.Group#GROUP Group The Group to move. - function CTLD:_MoveGroupToZone( Group ) - self:T( self.lid .. " _MoveGroupToZone" ) - local groupname = Group:GetName() or "none" - local groupcoord = Group:GetCoordinate() - -- Get closest zone of type - local outcome, name, zone, distance = self:IsUnitInZone( Group, CTLD.CargoZoneType.MOVE ) - -- self:Tstring.format("Closest WP zone %s is %d meters",name,distance)) - if (distance <= self.movetroopsdistance) and zone then - -- yes, we can ;) - local groupname = Group:GetName() - local zonecoord = zone:GetRandomCoordinate( 20, 125 ) -- Core.Point#COORDINATE - local coordinate = zonecoord:GetVec2() - Group:SetAIOn() - Group:OptionAlarmStateAuto() - Group:OptionDisperseOnAttack( 30 ) - Group:OptionROEOpenFirePossible() - Group:RouteToVec2( coordinate, 5 ) - end - return self - end - - --- (Internal) Housekeeping - Cleanup crates when build - -- @param #CTLD self - -- @param #table Crates Table of #CTLD_CARGO objects near the unit. - -- @param #CTLD.Buildable Build Table build object. - -- @param #number Number Number of objects in Crates (found) to limit search. - function CTLD:_CleanUpCrates( Crates, Build, Number ) - self:T( self.lid .. " _CleanUpCrates" ) - -- clean up real world crates - local build = Build -- #CTLD.Buildable - local existingcrates = self.Spawned_Cargo -- #table of exising crates - local newexcrates = {} - -- get right number of crates to destroy - local numberdest = Build.Required - local nametype = Build.Name - local found = 0 - local rounds = Number - local destIDs = {} - - -- loop and find matching IDs in the set - for _, _crate in pairs( Crates ) do - local nowcrate = _crate -- #CTLD_CARGO - local name = nowcrate:GetName() - local thisID = nowcrate:GetID() - if name == nametype then -- matching crate type - table.insert( destIDs, thisID ) - found = found + 1 - nowcrate:GetPositionable():Destroy( false ) - nowcrate.Positionable = nil - nowcrate.HasBeenDropped = false + if buildables[name].Found >= buildables[name].Required then + buildables[name].CanBuild = true + canbuild = true + end + self:T({buildables = buildables}) + end -- end dropped + end -- end crate loop + -- ok let\'s list what we have + local report = REPORT:New("Checklist Buildable Crates") + report:Add("------------------------------------------------------------") + for _,_build in pairs(buildables) do + local build = _build -- Object table from above + local name = build.Name + local needed = build.Required + local found = build.Found + local txtok = "NO" + if build.CanBuild then + txtok = "YES" end - if found == numberdest then - break - end -- got enough + local text = string.format("Type: %s | Required %d | Found %d | Can Build %s", name, needed, found, txtok) + report:Add(text) + end -- end list buildables + if not foundbuilds then report:Add(" --- None Found ---") end + report:Add("------------------------------------------------------------") + local text = report:Text() + if not Engineering then + self:_SendMessage(text, 30, true, Group) + else + self:T(text) end - -- loop and remove from real world representation - self:_CleanupTrackedCrates( destIDs ) - return self - end + -- let\'s get going + if canbuild then + -- loop again + for _,_build in pairs(buildables) do + local build = _build -- #CTLD.Buildable + if build.CanBuild then + self:_CleanUpCrates(crates,build,number) + self:_BuildObjectFromCrates(Group,Unit,build) + end + end + end + else + if not Engineering then self:_SendMessage(string.format("No crates within %d meters!",finddist), 10, false, Group) end + end -- number > 0 + return self +end - --- (Internal) Housekeeping - Function to refresh F10 menus. - -- @param #CTLD self - -- @return #CTLD self - function CTLD:_RefreshF10Menus() - self:T( self.lid .. " _RefreshF10Menus" ) - local PlayerSet = self.PilotGroups -- Core.Set#SET_GROUP - local PlayerTable = PlayerSet:GetSetObjects() -- #table of #GROUP objects - -- rebuild units table - local _UnitList = {} - for _key, _group in pairs( PlayerTable ) do - local _unit = _group:GetUnit( 1 ) -- Wrapper.Unit#UNIT Asume that there is only one unit in the flight for players +--- (Internal) Function to repair nearby vehicles / FOBs +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @param #boolean Engineering If true, this is an engineering role +function CTLD:_RepairCrates(Group, Unit, Engineering) + self:T(self.lid .. " _RepairCrates") + -- get nearby crates + local finddist = self.CrateDistance or 35 + local crates,number = self:_FindCratesNearby(Group,Unit,finddist) -- #table + local buildables = {} + local foundbuilds = false + local canbuild = false + if number > 0 then + -- get dropped crates + for _,_crate in pairs(crates) do + local Crate = _crate -- #CTLD_CARGO + if Crate:WasDropped() and Crate:IsRepair() and not Crate:IsStatic() then + -- we can build these - maybe + local name = Crate:GetName() + local required = Crate:GetCratesNeeded() + local template = Crate:GetTemplates() + local ctype = Crate:GetType() + if not buildables[name] then + local object = {} -- #CTLD.Buildable + object.Name = name + object.Required = required + object.Found = 1 + object.Template = template + object.CanBuild = false + object.Type = ctype -- #CTLD_CARGO.Enum + buildables[name] = object + foundbuilds = true + else + buildables[name].Found = buildables[name].Found + 1 + foundbuilds = true + end + if buildables[name].Found >= buildables[name].Required then + buildables[name].CanBuild = true + canbuild = true + end + self:T({repair = buildables}) + end -- end dropped + end -- end crate loop + -- ok let\'s list what we have + local report = REPORT:New("Checklist Repairs") + report:Add("------------------------------------------------------------") + for _,_build in pairs(buildables) do + local build = _build -- Object table from above + local name = build.Name + local needed = build.Required + local found = build.Found + local txtok = "NO" + if build.CanBuild then + txtok = "YES" + end + local text = string.format("Type: %s | Required %d | Found %d | Can Repair %s", name, needed, found, txtok) + report:Add(text) + end -- end list buildables + if not foundbuilds then report:Add(" --- None Found ---") end + report:Add("------------------------------------------------------------") + local text = report:Text() + if not Engineering then + self:_SendMessage(text, 30, true, Group) + else + self:T(text) + end + -- let\'s get going + if canbuild then + -- loop again + for _,_build in pairs(buildables) do + local build = _build -- #CTLD.Buildable + if build.CanBuild then + self:_RepairObjectFromCrates(Group,Unit,crates,build,number,Engineering) + end + end + end + else + if not Engineering then self:_SendMessage(string.format("No crates within %d meters!",finddist), 10, false, Group) end + end -- number > 0 + return self +end + +--- (Internal) Function to actually SPAWN buildables in the mission. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Group#UNIT Unit +-- @param #CTLD.Buildable Build +-- @param #boolean Repair If true this is a repair and not a new build +-- @param Core.Point#COORDINATE Coordinate Location for repair (e.g. where the destroyed unit was) +function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation) + self:T(self.lid .. " _BuildObjectFromCrates") + -- Spawn-a-crate-content + if Group and Group:IsAlive() then + local position = Unit:GetCoordinate() or Group:GetCoordinate() + local unitname = Unit:GetName() or Group:GetName() + local name = Build.Name + local ctype = Build.Type -- #CTLD_CARGO.Enum + local canmove = false + if ctype == CTLD_CARGO.Enum.VEHICLE then canmove = true end + if ctype == CTLD_CARGO.Enum.STATIC then + return self + end + local temptable = Build.Template or {} + if type(temptable) == "string" then + temptable = {temptable} + end + local zone = ZONE_GROUP:New(string.format("Unload zone-%s",unitname),Group,100) + local randomcoord = zone:GetRandomCoordinate(35):GetVec2() + if Repair then + randomcoord = RepairLocation:GetVec2() + end + for _,_template in pairs(temptable) do + self.TroopCounter = self.TroopCounter + 1 + local alias = string.format("%s-%d", _template, math.random(1,100000)) + if canmove then + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) + :InitRandomizeUnits(true,20,2) + :InitDelayOff() + :SpawnFromVec2(randomcoord) + else -- don't random position of e.g. SAM units build as FOB + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) + :InitDelayOff() + :SpawnFromVec2(randomcoord) + end + if self.movetroopstowpzone and canmove then + self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) + end + if Repair then + self:__CratesRepaired(1,Group,Unit,self.DroppedTroops[self.TroopCounter]) + else + self:__CratesBuild(1,Group,Unit,self.DroppedTroops[self.TroopCounter]) + end + end -- template loop + else + self:T(self.lid.."Group KIA while building!") + end + return self +end + +--- (Internal) Function to move group to WP zone. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group The Group to move. +function CTLD:_MoveGroupToZone(Group) + self:T(self.lid .. " _MoveGroupToZone") + local groupname = Group:GetName() or "none" + local groupcoord = Group:GetCoordinate() + -- Get closest zone of type + local outcome, name, zone, distance = self:IsUnitInZone(Group,CTLD.CargoZoneType.MOVE) + --self:Tstring.format("Closest WP zone %s is %d meters",name,distance)) + if (distance <= self.movetroopsdistance) and zone then + -- yes, we can ;) + local groupname = Group:GetName() + local zonecoord = zone:GetRandomCoordinate(20,125) -- Core.Point#COORDINATE + local coordinate = zonecoord:GetVec2() + Group:SetAIOn() + Group:OptionAlarmStateAuto() + Group:OptionDisperseOnAttack(30) + Group:OptionROEOpenFirePossible() + Group:RouteToVec2(coordinate,5) + end + return self +end + +--- (Internal) Housekeeping - Cleanup crates when build +-- @param #CTLD self +-- @param #table Crates Table of #CTLD_CARGO objects near the unit. +-- @param #CTLD.Buildable Build Table build object. +-- @param #number Number Number of objects in Crates (found) to limit search. +function CTLD:_CleanUpCrates(Crates,Build,Number) + self:T(self.lid .. " _CleanUpCrates") + -- clean up real world crates + local build = Build -- #CTLD.Buildable + local existingcrates = self.Spawned_Cargo -- #table of exising crates + local newexcrates = {} + -- get right number of crates to destroy + local numberdest = Build.Required + local nametype = Build.Name + local found = 0 + local rounds = Number + local destIDs = {} + + -- loop and find matching IDs in the set + for _,_crate in pairs(Crates) do + local nowcrate = _crate -- #CTLD_CARGO + local name = nowcrate:GetName() + local thisID = nowcrate:GetID() + if name == nametype then -- matching crate type + table.insert(destIDs,thisID) + found = found + 1 + nowcrate:GetPositionable():Destroy(false) + nowcrate.Positionable = nil + nowcrate.HasBeenDropped = false + end + if found == numberdest then break end -- got enough + end + -- loop and remove from real world representation + self:_CleanupTrackedCrates(destIDs) + return self +end + +--- (Internal) Housekeeping - Function to refresh F10 menus. +-- @param #CTLD self +-- @return #CTLD self +function CTLD:_RefreshF10Menus() + self:T(self.lid .. " _RefreshF10Menus") + local PlayerSet = self.PilotGroups -- Core.Set#SET_GROUP + local PlayerTable = PlayerSet:GetSetObjects() -- #table of #GROUP objects + -- rebuild units table + local _UnitList = {} + for _key, _group in pairs (PlayerTable) do + local _unit = _group:GetUnit(1) -- Wrapper.Unit#UNIT Asume that there is only one unit in the flight for players + if _unit then + if _unit:IsAlive() and _unit:IsPlayer() then + if _unit:IsHelicopter() or (_unit:GetTypeName() == "Hercules" and self.enableHercules) then --ensure no stupid unit entries here + local unitName = _unit:GetName() + _UnitList[unitName] = unitName + end + end -- end isAlive + end -- end if _unit + end -- end for + self.CtldUnits = _UnitList + + -- build unit menus + local menucount = 0 + local menus = {} + for _, _unitName in pairs(self.CtldUnits) do + if not self.MenusDone[_unitName] then + local _unit = UNIT:FindByName(_unitName) -- Wrapper.Unit#UNIT if _unit then - if _unit:IsAlive() and _unit:IsPlayer() then - if _unit:IsHelicopter() or (_unit:GetTypeName() == "Hercules" and self.enableHercules) then -- ensure no stupid unit entries here - local unitName = _unit:GetName() - _UnitList[unitName] = unitName + local _group = _unit:GetGroup() -- Wrapper.Group#GROUP + if _group then + -- get chopper capabilities + local unittype = _unit:GetTypeName() + local capabilities = self:_GetUnitCapabilities(_unit) -- #CTLD.UnitCapabilities + local cantroops = capabilities.troops + local cancrates = capabilities.crates + -- top menu + local topmenu = MENU_GROUP:New(_group,"CTLD",nil) + local toptroops = MENU_GROUP:New(_group,"Manage Troops",topmenu) + local topcrates = MENU_GROUP:New(_group,"Manage Crates",topmenu) + local listmenu = MENU_GROUP_COMMAND:New(_group,"List boarded cargo",topmenu, self._ListCargo, self, _group, _unit) + local invtry = MENU_GROUP_COMMAND:New(_group,"Inventory",topmenu, self._ListInventory, self, _group, _unit) + local rbcns = MENU_GROUP_COMMAND:New(_group,"List active zone beacons",topmenu, self._ListRadioBeacons, self, _group, _unit) + local smoketopmenu = MENU_GROUP:New(_group,"Smokes & Flares",topmenu) + local smokemenu = MENU_GROUP_COMMAND:New(_group,"Smoke zones nearby",smoketopmenu, self.SmokeZoneNearBy, self, _unit, false) + local smokeself = MENU_GROUP_COMMAND:New(_group,"Drop smoke now",smoketopmenu, self.SmokePositionNow, self, _unit, false) + local flaremenu = MENU_GROUP_COMMAND:New(_group,"Flare zones nearby",smoketopmenu, self.SmokeZoneNearBy, self, _unit, true) + local flareself = MENU_GROUP_COMMAND:New(_group,"Fire flare now",smoketopmenu, self.SmokePositionNow, self, _unit, true):Refresh() + -- sub menus + -- sub menu troops management + if cantroops then + local troopsmenu = MENU_GROUP:New(_group,"Load troops",toptroops) + for _,_entry in pairs(self.Cargo_Troops) do + local entry = _entry -- #CTLD_CARGO + menucount = menucount + 1 + menus[menucount] = MENU_GROUP_COMMAND:New(_group,entry.Name,troopsmenu,self._LoadTroops, self, _group, _unit, entry) + end + local unloadmenu1 = MENU_GROUP_COMMAND:New(_group,"Drop troops",toptroops, self._UnloadTroops, self, _group, _unit):Refresh() + local extractMenu1 = MENU_GROUP_COMMAND:New(_group, "Extract troops", toptroops, self._ExtractTroops, self, _group, _unit):Refresh() end - end -- end isAlive - end -- end if _unit - end -- end for - self.CtldUnits = _UnitList - - -- build unit menus - local menucount = 0 - local menus = {} - for _, _unitName in pairs( self.CtldUnits ) do - if not self.MenusDone[_unitName] then - local _unit = UNIT:FindByName( _unitName ) -- Wrapper.Unit#UNIT - if _unit then - local _group = _unit:GetGroup() -- Wrapper.Group#GROUP - if _group then - -- get chopper capabilities - local unittype = _unit:GetTypeName() - local capabilities = self:_GetUnitCapabilities( _unit ) -- #CTLD.UnitCapabilities - local cantroops = capabilities.troops - local cancrates = capabilities.crates - -- top menu - local topmenu = MENU_GROUP:New( _group, "CTLD", nil ) - local toptroops = MENU_GROUP:New( _group, "Manage Troops", topmenu ) - local topcrates = MENU_GROUP:New( _group, "Manage Crates", topmenu ) - local listmenu = MENU_GROUP_COMMAND:New( _group, "List boarded cargo", topmenu, self._ListCargo, self, _group, _unit ) - local invtry = MENU_GROUP_COMMAND:New( _group, "Inventory", topmenu, self._ListInventory, self, _group, _unit ) - local rbcns = MENU_GROUP_COMMAND:New( _group, "List active zone beacons", topmenu, self._ListRadioBeacons, self, _group, _unit ) - local smoketopmenu = MENU_GROUP:New( _group, "Smokes & Flares", topmenu ) - local smokemenu = MENU_GROUP_COMMAND:New( _group, "Smoke zones nearby", smoketopmenu, self.SmokeZoneNearBy, self, _unit, false ) - local smokeself = MENU_GROUP_COMMAND:New( _group, "Drop smoke now", smoketopmenu, self.SmokePositionNow, self, _unit, false ) - local flaremenu = MENU_GROUP_COMMAND:New( _group, "Flare zones nearby", smoketopmenu, self.SmokeZoneNearBy, self, _unit, true ) - local flareself = MENU_GROUP_COMMAND:New( _group, "Fire flare now", smoketopmenu, self.SmokePositionNow, self, _unit, true ):Refresh() - -- sub menus - -- sub menu troops management - if cantroops then - local troopsmenu = MENU_GROUP:New( _group, "Load troops", toptroops ) - for _, _entry in pairs( self.Cargo_Troops ) do - local entry = _entry -- #CTLD_CARGO - menucount = menucount + 1 - menus[menucount] = MENU_GROUP_COMMAND:New( _group, entry.Name, troopsmenu, self._LoadTroops, self, _group, _unit, entry ) - end - local unloadmenu1 = MENU_GROUP_COMMAND:New( _group, "Drop troops", toptroops, self._UnloadTroops, self, _group, _unit ):Refresh() - local extractMenu1 = MENU_GROUP_COMMAND:New( _group, "Extract troops", toptroops, self._ExtractTroops, self, _group, _unit ):Refresh() + -- sub menu crates management + if cancrates then + local loadmenu = MENU_GROUP_COMMAND:New(_group,"Load crates",topcrates, self._LoadCratesNearby, self, _group, _unit) + local cratesmenu = MENU_GROUP:New(_group,"Get Crates",topcrates) + for _,_entry in pairs(self.Cargo_Crates) do + local entry = _entry -- #CTLD_CARGO + menucount = menucount + 1 + local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) + menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) end - -- sub menu crates management - if cancrates then - local loadmenu = MENU_GROUP_COMMAND:New( _group, "Load crates", topcrates, self._LoadCratesNearby, self, _group, _unit ) - local cratesmenu = MENU_GROUP:New( _group, "Get Crates", topcrates ) - for _, _entry in pairs( self.Cargo_Crates ) do - local entry = _entry -- #CTLD_CARGO - menucount = menucount + 1 - local menutext = string.format( "Crate %s (%dkg)", entry.Name, entry.PerCrateMass or 0 ) - menus[menucount] = MENU_GROUP_COMMAND:New( _group, menutext, cratesmenu, self._GetCrates, self, _group, _unit, entry ) - end - for _, _entry in pairs( self.Cargo_Statics ) do - local entry = _entry -- #CTLD_CARGO - menucount = menucount + 1 - local menutext = string.format( "Crate %s (%dkg)", entry.Name, entry.PerCrateMass or 0 ) - menus[menucount] = MENU_GROUP_COMMAND:New( _group, menutext, cratesmenu, self._GetCrates, self, _group, _unit, entry ) - end - listmenu = MENU_GROUP_COMMAND:New( _group, "List crates nearby", topcrates, self._ListCratesNearby, self, _group, _unit ) - local unloadmenu = MENU_GROUP_COMMAND:New( _group, "Drop crates", topcrates, self._UnloadCrates, self, _group, _unit ) - local buildmenu = MENU_GROUP_COMMAND:New( _group, "Build crates", topcrates, self._BuildCrates, self, _group, _unit ) - local repairmenu = MENU_GROUP_COMMAND:New( _group, "Repair", topcrates, self._RepairCrates, self, _group, _unit ):Refresh() + for _,_entry in pairs(self.Cargo_Statics) do + local entry = _entry -- #CTLD_CARGO + menucount = menucount + 1 + local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) + menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) end - if unittype == "Hercules" then - local hoverpars = MENU_GROUP_COMMAND:New( _group, "Show flight parameters", topmenu, self._ShowFlightParams, self, _group, _unit ):Refresh() - else - local hoverpars = MENU_GROUP_COMMAND:New( _group, "Show hover parameters", topmenu, self._ShowHoverParams, self, _group, _unit ):Refresh() - end - self.MenusDone[_unitName] = true - end -- end group - end -- end unit - else -- menu build check - self:T( self.lid .. " Menus already done for this group!" ) - end -- end menu build check - end -- end for - return self - end + listmenu = MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates, self._ListCratesNearby, self, _group, _unit) + local unloadmenu = MENU_GROUP_COMMAND:New(_group,"Drop crates",topcrates, self._UnloadCrates, self, _group, _unit) + local buildmenu = MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates, self._BuildCrates, self, _group, _unit) + local repairmenu = MENU_GROUP_COMMAND:New(_group,"Repair",topcrates, self._RepairCrates, self, _group, _unit):Refresh() + end + if unittype == "Hercules" then + local hoverpars = MENU_GROUP_COMMAND:New(_group,"Show flight parameters",topmenu, self._ShowFlightParams, self, _group, _unit):Refresh() + else + local hoverpars = MENU_GROUP_COMMAND:New(_group,"Show hover parameters",topmenu, self._ShowHoverParams, self, _group, _unit):Refresh() + end + self.MenusDone[_unitName] = true + end -- end group + end -- end unit + else -- menu build check + self:T(self.lid .. " Menus already done for this group!") + end -- end menu build check + end -- end for + return self + end - --- User function - Add *generic* troop type loadable as cargo. This type will load directly into the heli without crates. - -- @param #CTLD self - -- @param #string Name Unique name of this type of troop. E.g. "Anti-Air Small". - -- @param #table Templates Table of #string names of late activated Wrapper.Group#GROUP making up this troop. - -- @param #CTLD_CARGO.Enum Type Type of cargo, here TROOPS - these will move to a nearby destination zone when dropped/build. - -- @param #number NoTroops Size of the group in number of Units across combined templates (for loading). - -- @param #number PerTroopMass Mass in kg of each soldier - -- @param #number Stock Number of groups in stock. Nil for unlimited. - function CTLD:AddTroopsCargo( Name, Templates, Type, NoTroops, PerTroopMass, Stock ) - self:T( self.lid .. " AddTroopsCargo" ) - self:T( { Name, Templates, Type, NoTroops, PerTroopMass, Stock } ) - self.CargoCounter = self.CargoCounter + 1 - -- Troops are directly loadable - local cargo = CTLD_CARGO:New( self.CargoCounter, Name, Templates, Type, false, true, NoTroops, nil, nil, PerTroopMass, Stock ) - table.insert( self.Cargo_Troops, cargo ) - return self - end +--- User function - Add *generic* troop type loadable as cargo. This type will load directly into the heli without crates. +-- @param #CTLD self +-- @param #string Name Unique name of this type of troop. E.g. "Anti-Air Small". +-- @param #table Templates Table of #string names of late activated Wrapper.Group#GROUP making up this troop. +-- @param #CTLD_CARGO.Enum Type Type of cargo, here TROOPS - these will move to a nearby destination zone when dropped/build. +-- @param #number NoTroops Size of the group in number of Units across combined templates (for loading). +-- @param #number PerTroopMass Mass in kg of each soldier +-- @param #number Stock Number of groups in stock. Nil for unlimited. +function CTLD:AddTroopsCargo(Name,Templates,Type,NoTroops,PerTroopMass,Stock) + self:T(self.lid .. " AddTroopsCargo") + self:T({Name,Templates,Type,NoTroops,PerTroopMass,Stock}) + self.CargoCounter = self.CargoCounter + 1 + -- Troops are directly loadable + local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,true,NoTroops,nil,nil,PerTroopMass,Stock) + table.insert(self.Cargo_Troops,cargo) + return self +end - --- User function - Add *generic* crate-type loadable as cargo. This type will create crates that need to be loaded, moved, dropped and built. - -- @param #CTLD self - -- @param #string Name Unique name of this type of cargo. E.g. "Humvee". - -- @param #table Templates Table of #string names of late activated Wrapper.Group#GROUP building this cargo. - -- @param #CTLD_CARGO.Enum Type Type of cargo. I.e. VEHICLE or FOB. VEHICLE will move to destination zones when dropped/build, FOB stays put. - -- @param #number NoCrates Number of crates needed to build this cargo. - -- @param #number PerCrateMass Mass in kg of each crate - -- @param #number Stock Number of groups in stock. Nil for unlimited. - function CTLD:AddCratesCargo( Name, Templates, Type, NoCrates, PerCrateMass, Stock ) - self:T( self.lid .. " AddCratesCargo" ) - self.CargoCounter = self.CargoCounter + 1 - -- Crates are not directly loadable - local cargo = CTLD_CARGO:New( self.CargoCounter, Name, Templates, Type, false, false, NoCrates, nil, nil, PerCrateMass, Stock ) - table.insert( self.Cargo_Crates, cargo ) - return self - end +--- User function - Add *generic* crate-type loadable as cargo. This type will create crates that need to be loaded, moved, dropped and built. +-- @param #CTLD self +-- @param #string Name Unique name of this type of cargo. E.g. "Humvee". +-- @param #table Templates Table of #string names of late activated Wrapper.Group#GROUP building this cargo. +-- @param #CTLD_CARGO.Enum Type Type of cargo. I.e. VEHICLE or FOB. VEHICLE will move to destination zones when dropped/build, FOB stays put. +-- @param #number NoCrates Number of crates needed to build this cargo. +-- @param #number PerCrateMass Mass in kg of each crate +-- @param #number Stock Number of groups in stock. Nil for unlimited. +function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock) + self:T(self.lid .. " AddCratesCargo") + self.CargoCounter = self.CargoCounter + 1 + -- Crates are not directly loadable + local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,false,NoCrates,nil,nil,PerCrateMass,Stock) + table.insert(self.Cargo_Crates,cargo) + return self +end - --- User function - Add *generic* static-type loadable as cargo. This type will create cargo that needs to be loaded, moved and dropped. - -- @param #CTLD self - -- @param #string Name Unique name of this type of cargo as set in the mission editor (note: UNIT name!), e.g. "Ammunition-1". - -- @param #number Mass Mass in kg of each static in kg, e.g. 100. - -- @param #number Stock Number of groups in stock. Nil for unlimited. - function CTLD:AddStaticsCargo( Name, Mass, Stock ) - self:T( self.lid .. " AddStaticsCargo" ) - self.CargoCounter = self.CargoCounter + 1 - local type = CTLD_CARGO.Enum.STATIC - local template = STATIC:FindByName( Name, true ):GetTypeName() - -- Crates are not directly loadable - local cargo = CTLD_CARGO:New( self.CargoCounter, Name, template, type, false, false, 1, nil, nil, Mass, Stock ) - table.insert( self.Cargo_Statics, cargo ) - return self - end +--- User function - Add *generic* static-type loadable as cargo. This type will create cargo that needs to be loaded, moved and dropped. +-- @param #CTLD self +-- @param #string Name Unique name of this type of cargo as set in the mission editor (note: UNIT name!), e.g. "Ammunition-1". +-- @param #number Mass Mass in kg of each static in kg, e.g. 100. +-- @param #number Stock Number of groups in stock. Nil for unlimited. +function CTLD:AddStaticsCargo(Name,Mass,Stock) + self:T(self.lid .. " AddStaticsCargo") + self.CargoCounter = self.CargoCounter + 1 + local type = CTLD_CARGO.Enum.STATIC + local template = STATIC:FindByName(Name,true):GetTypeName() + -- Crates are not directly loadable + local cargo = CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,Stock) + table.insert(self.Cargo_Statics,cargo) + return self +end - --- User function - Get a *generic* static-type loadable as #CTLD_CARGO object. - -- @param #CTLD self - -- @param #string Name Unique Unit(!) name of this type of cargo as set in the mission editor (not: GROUP name!), e.g. "Ammunition-1". - -- @param #number Mass Mass in kg of each static in kg, e.g. 100. - -- @return #CTLD_CARGO Cargo object - function CTLD:GetStaticsCargoFromTemplate( Name, Mass ) - self:T( self.lid .. " GetStaticsCargoFromTemplate" ) - self.CargoCounter = self.CargoCounter + 1 - local type = CTLD_CARGO.Enum.STATIC - local template = STATIC:FindByName( Name, true ):GetTypeName() - -- Crates are not directly loadable - local cargo = CTLD_CARGO:New( self.CargoCounter, Name, template, type, false, false, 1, nil, nil, Mass, 1 ) - -- table.insert(self.Cargo_Statics,cargo) - return cargo - end +--- User function - Get a *generic* static-type loadable as #CTLD_CARGO object. +-- @param #CTLD self +-- @param #string Name Unique Unit(!) name of this type of cargo as set in the mission editor (not: GROUP name!), e.g. "Ammunition-1". +-- @param #number Mass Mass in kg of each static in kg, e.g. 100. +-- @return #CTLD_CARGO Cargo object +function CTLD:GetStaticsCargoFromTemplate(Name,Mass) + self:T(self.lid .. " GetStaticsCargoFromTemplate") + self.CargoCounter = self.CargoCounter + 1 + local type = CTLD_CARGO.Enum.STATIC + local template = STATIC:FindByName(Name,true):GetTypeName() + -- Crates are not directly loadable + local cargo = CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,1) + --table.insert(self.Cargo_Statics,cargo) + return cargo +end - --- User function - Add *generic* repair crates loadable as cargo. This type will create crates that need to be loaded, moved, dropped and built. - -- @param #CTLD self - -- @param #string Name Unique name of this type of cargo. E.g. "Humvee". - -- @param #string Template Template of VEHICLE or FOB cargo that this can repair. - -- @param #CTLD_CARGO.Enum Type Type of cargo, here REPAIR. - -- @param #number NoCrates Number of crates needed to build this cargo. - -- @param #number PerCrateMass Mass in kg of each crate - -- @param #number Stock Number of groups in stock. Nil for unlimited. - function CTLD:AddCratesRepair( Name, Template, Type, NoCrates, PerCrateMass, Stock ) - self:T( self.lid .. " AddCratesRepair" ) - self.CargoCounter = self.CargoCounter + 1 - -- Crates are not directly loadable - local cargo = CTLD_CARGO:New( self.CargoCounter, Name, Template, Type, false, false, NoCrates, nil, nil, PerCrateMass, Stock ) - table.insert( self.Cargo_Crates, cargo ) - return self - end +--- User function - Add *generic* repair crates loadable as cargo. This type will create crates that need to be loaded, moved, dropped and built. +-- @param #CTLD self +-- @param #string Name Unique name of this type of cargo. E.g. "Humvee". +-- @param #string Template Template of VEHICLE or FOB cargo that this can repair. +-- @param #CTLD_CARGO.Enum Type Type of cargo, here REPAIR. +-- @param #number NoCrates Number of crates needed to build this cargo. +-- @param #number PerCrateMass Mass in kg of each crate +-- @param #number Stock Number of groups in stock. Nil for unlimited. +function CTLD:AddCratesRepair(Name,Template,Type,NoCrates, PerCrateMass,Stock) + self:T(self.lid .. " AddCratesRepair") + self.CargoCounter = self.CargoCounter + 1 + -- Crates are not directly loadable + local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Template,Type,false,false,NoCrates,nil,nil,PerCrateMass,Stock) + table.insert(self.Cargo_Crates,cargo) + return self +end - --- User function - Add a #CTLD.CargoZoneType zone for this CTLD instance. - -- @param #CTLD self - -- @param #CTLD.CargoZone Zone Zone #CTLD.CargoZone describing the zone. - function CTLD:AddZone( Zone ) - self:T( self.lid .. " AddZone" ) - local zone = Zone -- #CTLD.CargoZone - if zone.type == CTLD.CargoZoneType.LOAD then - table.insert( self.pickupZones, zone ) - elseif zone.type == CTLD.CargoZoneType.DROP then - table.insert( self.dropOffZones, zone ) - elseif zone.type == CTLD.CargoZoneType.SHIP then - table.insert( self.shipZones, zone ) - else - table.insert( self.wpZones, zone ) +--- User function - Add a #CTLD.CargoZoneType zone for this CTLD instance. +-- @param #CTLD self +-- @param #CTLD.CargoZone Zone Zone #CTLD.CargoZone describing the zone. +function CTLD:AddZone(Zone) + self:T(self.lid .. " AddZone") + local zone = Zone -- #CTLD.CargoZone + if zone.type == CTLD.CargoZoneType.LOAD then + table.insert(self.pickupZones,zone) + elseif zone.type == CTLD.CargoZoneType.DROP then + table.insert(self.dropOffZones,zone) + elseif zone.type == CTLD.CargoZoneType.SHIP then + table.insert(self.shipZones,zone) + else + table.insert(self.wpZones,zone) + end + return self +end + +--- User function - Activate Name #CTLD.CargoZone.Type ZoneType for this CTLD instance. +-- @param #CTLD self +-- @param #string Name Name of the zone to change in the ME. +-- @param #CTLD.CargoZoneType ZoneType Type of zone this belongs to. +-- @param #boolean NewState (Optional) Set to true to activate, false to switch off. +function CTLD:ActivateZone(Name,ZoneType,NewState) + self:T(self.lid .. " AddZone") + local newstate = true + -- set optional in case we\'re deactivating + if NewState ~= nil then + newstate = NewState + end + + -- get correct table + local table = {} + if ZoneType == CTLD.CargoZoneType.LOAD then + table = self.pickupZones + elseif ZoneType == CTLD.CargoZoneType.DROP then + table = self.dropOffZones + elseif ZoneType == CTLD.CargoZoneType.SHIP then + table = self.shipZones + else + table = self.wpZones + end + -- loop table + for _,_zone in pairs(table) do + local thiszone = _zone --#CTLD.CargoZone + if thiszone.name == Name then + thiszone.active = newstate + break end - return self end + return self +end - --- User function - Activate Name #CTLD.CargoZone.Type ZoneType for this CTLD instance. - -- @param #CTLD self - -- @param #string Name Name of the zone to change in the ME. - -- @param #CTLD.CargoZoneType ZoneType Type of zone this belongs to. - -- @param #boolean NewState (Optional) Set to true to activate, false to switch off. - function CTLD:ActivateZone( Name, ZoneType, NewState ) - self:T( self.lid .. " AddZone" ) - local newstate = true - -- set optional in case we're deactivating - if NewState ~= nil then - newstate = NewState - end - -- get correct table - local table = {} - if ZoneType == CTLD.CargoZoneType.LOAD then - table = self.pickupZones - elseif ZoneType == CTLD.CargoZoneType.DROP then - table = self.dropOffZones - elseif ZoneType == CTLD.CargoZoneType.SHIP then - table = self.shipZones - else - table = self.wpZones - end - -- loop table - for _, _zone in pairs( table ) do - local thiszone = _zone -- #CTLD.CargoZone - if thiszone.name == Name then - thiszone.active = newstate - break - end - end - return self - end +--- User function - Deactivate Name #CTLD.CargoZoneType ZoneType for this CTLD instance. +-- @param #CTLD self +-- @param #string Name Name of the zone to change in the ME. +-- @param #CTLD.CargoZoneType ZoneType Type of zone this belongs to. +function CTLD:DeactivateZone(Name,ZoneType) + self:T(self.lid .. " AddZone") + self:ActivateZone(Name,ZoneType,false) + return self +end - --- User function - Deactivate Name #CTLD.CargoZoneType ZoneType for this CTLD instance. - -- @param #CTLD self - -- @param #string Name Name of the zone to change in the ME. - -- @param #CTLD.CargoZoneType ZoneType Type of zone this belongs to. - function CTLD:DeactivateZone( Name, ZoneType ) - self:T( self.lid .. " AddZone" ) - self:ActivateZone( Name, ZoneType, false ) - return self - end - - --- (Internal) Function to obtain a valid FM frequency. - -- @param #CTLD self - -- @param #string Name Name of zone. - -- @return #CTLD.ZoneBeacon Beacon Beacon table. - function CTLD:_GetFMBeacon( Name ) - self:T( self.lid .. " _GetFMBeacon" ) - local beacon = {} -- #CTLD.ZoneBeacon - if #self.FreeFMFrequencies <= 1 then +--- (Internal) Function to obtain a valid FM frequency. +-- @param #CTLD self +-- @param #string Name Name of zone. +-- @return #CTLD.ZoneBeacon Beacon Beacon table. +function CTLD:_GetFMBeacon(Name) + self:T(self.lid .. " _GetFMBeacon") + local beacon = {} -- #CTLD.ZoneBeacon + if #self.FreeFMFrequencies <= 1 then self.FreeFMFrequencies = self.UsedFMFrequencies self.UsedFMFrequencies = {} - end - -- random - local FM = table.remove( self.FreeFMFrequencies, math.random( #self.FreeFMFrequencies ) ) - table.insert( self.UsedFMFrequencies, FM ) - beacon.name = Name - beacon.frequency = FM / 1000000 - beacon.modulation = radio.modulation.FM - return beacon - end + end + --random + local FM = table.remove(self.FreeFMFrequencies, math.random(#self.FreeFMFrequencies)) + table.insert(self.UsedFMFrequencies, FM) + beacon.name = Name + beacon.frequency = FM / 1000000 + beacon.modulation = radio.modulation.FM + return beacon +end - --- (Internal) Function to obtain a valid UHF frequency. - -- @param #CTLD self - -- @param #string Name Name of zone. - -- @return #CTLD.ZoneBeacon Beacon Beacon table. - function CTLD:_GetUHFBeacon( Name ) - self:T( self.lid .. " _GetUHFBeacon" ) - local beacon = {} -- #CTLD.ZoneBeacon - if #self.FreeUHFFrequencies <= 1 then +--- (Internal) Function to obtain a valid UHF frequency. +-- @param #CTLD self +-- @param #string Name Name of zone. +-- @return #CTLD.ZoneBeacon Beacon Beacon table. +function CTLD:_GetUHFBeacon(Name) + self:T(self.lid .. " _GetUHFBeacon") + local beacon = {} -- #CTLD.ZoneBeacon + if #self.FreeUHFFrequencies <= 1 then self.FreeUHFFrequencies = self.UsedUHFFrequencies self.UsedUHFFrequencies = {} - end - -- random - local UHF = table.remove( self.FreeUHFFrequencies, math.random( #self.FreeUHFFrequencies ) ) - table.insert( self.UsedUHFFrequencies, UHF ) - beacon.name = Name - beacon.frequency = UHF / 1000000 - beacon.modulation = radio.modulation.AM + end + --random + local UHF = table.remove(self.FreeUHFFrequencies, math.random(#self.FreeUHFFrequencies)) + table.insert(self.UsedUHFFrequencies, UHF) + beacon.name = Name + beacon.frequency = UHF / 1000000 + beacon.modulation = radio.modulation.AM - return beacon - end + return beacon +end - --- (Internal) Function to obtain a valid VHF frequency. - -- @param #CTLD self - -- @param #string Name Name of zone. - -- @return #CTLD.ZoneBeacon Beacon Beacon table. - function CTLD:_GetVHFBeacon( Name ) - self:T( self.lid .. " _GetVHFBeacon" ) - local beacon = {} -- #CTLD.ZoneBeacon - if #self.FreeVHFFrequencies <= 3 then +--- (Internal) Function to obtain a valid VHF frequency. +-- @param #CTLD self +-- @param #string Name Name of zone. +-- @return #CTLD.ZoneBeacon Beacon Beacon table. +function CTLD:_GetVHFBeacon(Name) + self:T(self.lid .. " _GetVHFBeacon") + local beacon = {} -- #CTLD.ZoneBeacon + if #self.FreeVHFFrequencies <= 3 then self.FreeVHFFrequencies = self.UsedVHFFrequencies self.UsedVHFFrequencies = {} - end - -- get random - local VHF = table.remove( self.FreeVHFFrequencies, math.random( #self.FreeVHFFrequencies ) ) - table.insert( self.UsedVHFFrequencies, VHF ) - beacon.name = Name - beacon.frequency = VHF / 1000000 - beacon.modulation = radio.modulation.FM - return beacon end + --get random + local VHF = table.remove(self.FreeVHFFrequencies, math.random(#self.FreeVHFFrequencies)) + table.insert(self.UsedVHFFrequencies, VHF) + beacon.name = Name + beacon.frequency = VHF / 1000000 + beacon.modulation = radio.modulation.FM + return beacon +end - --- User function - Crates and adds a #CTLD.CargoZone zone for this CTLD instance. - -- Zones of type LOAD: Players load crates and troops here. - -- Zones of type DROP: Players can drop crates here. Note that troops can be unloaded anywhere. - -- Zone of type MOVE: Dropped troops and vehicles will start moving to the nearest zone of this type (also see options). - -- @param #CTLD self - -- @param #string Name Name of this zone, as in Mission Editor. - -- @param #string Type Type of this zone, #CTLD.CargoZoneType - -- @param #number Color Smoke/Flare color e.g. #SMOKECOLOR.Red - -- @param #string Active Is this zone currently active? - -- @param #string HasBeacon Does this zone have a beacon if it is active? - -- @param #number Shiplength Length of Ship for shipzones - -- @param #number Shipwidth Width of Ship for shipzones - -- @return #CTLD self - function CTLD:AddCTLDZone( Name, Type, Color, Active, HasBeacon, Shiplength, Shipwidth ) - self:T( self.lid .. " AddCTLDZone" ) - local ctldzone = {} -- #CTLD.CargoZone - ctldzone.active = Active or false - ctldzone.color = Color or SMOKECOLOR.Red - ctldzone.name = Name or "NONE" - ctldzone.type = Type or CTLD.CargoZoneType.MOVE -- #CTLD.CargoZoneType - ctldzone.hasbeacon = HasBeacon or false +--- User function - Crates and adds a #CTLD.CargoZone zone for this CTLD instance. +-- Zones of type LOAD: Players load crates and troops here. +-- Zones of type DROP: Players can drop crates here. Note that troops can be unloaded anywhere. +-- Zone of type MOVE: Dropped troops and vehicles will start moving to the nearest zone of this type (also see options). +-- @param #CTLD self +-- @param #string Name Name of this zone, as in Mission Editor. +-- @param #string Type Type of this zone, #CTLD.CargoZoneType +-- @param #number Color Smoke/Flare color e.g. #SMOKECOLOR.Red +-- @param #string Active Is this zone currently active? +-- @param #string HasBeacon Does this zone have a beacon if it is active? +-- @param #number Shiplength Length of Ship for shipzones +-- @param #number Shipwidth Width of Ship for shipzones +-- @return #CTLD self +function CTLD:AddCTLDZone(Name, Type, Color, Active, HasBeacon, Shiplength, Shipwidth) + self:T(self.lid .. " AddCTLDZone") - if HasBeacon then - ctldzone.fmbeacon = self:_GetFMBeacon( Name ) - ctldzone.uhfbeacon = self:_GetUHFBeacon( Name ) - ctldzone.vhfbeacon = self:_GetVHFBeacon( Name ) - else - ctldzone.fmbeacon = nil - ctldzone.uhfbeacon = nil - ctldzone.vhfbeacon = nil - end - - if Type == CTLD.CargoZoneType.SHIP then - ctldzone.shiplength = Shiplength or 100 - ctldzone.shipwidth = Shipwidth or 10 - end - - self:AddZone( ctldzone ) - return self + local ctldzone = {} -- #CTLD.CargoZone + ctldzone.active = Active or false + ctldzone.color = Color or SMOKECOLOR.Red + ctldzone.name = Name or "NONE" + ctldzone.type = Type or CTLD.CargoZoneType.MOVE -- #CTLD.CargoZoneType + ctldzone.hasbeacon = HasBeacon or false + + if HasBeacon then + ctldzone.fmbeacon = self:_GetFMBeacon(Name) + ctldzone.uhfbeacon = self:_GetUHFBeacon(Name) + ctldzone.vhfbeacon = self:_GetVHFBeacon(Name) + else + ctldzone.fmbeacon = nil + ctldzone.uhfbeacon = nil + ctldzone.vhfbeacon = nil end + + if Type == CTLD.CargoZoneType.SHIP then + ctldzone.shiplength = Shiplength or 100 + ctldzone.shipwidth = Shipwidth or 10 + end + + self:AddZone(ctldzone) + return self +end - --- (Internal) Function to show list of radio beacons - -- @param #CTLD self - -- @param Wrapper.Group#GROUP Group - -- @param Wrapper.Unit#UNIT Unit - function CTLD:_ListRadioBeacons( Group, Unit ) - self:T( self.lid .. " _ListRadioBeacons" ) - local report = REPORT:New( "Active Zone Beacons" ) - report:Add( "------------------------------------------------------------" ) - local zones = { [1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones, [4] = self.shipZones } - for i = 1, 4 do - for index, cargozone in pairs( zones[i] ) do - -- Get Beacon object from zone - local czone = cargozone -- #CTLD.CargoZone - if czone.active and czone.hasbeacon then - local FMbeacon = czone.fmbeacon -- #CTLD.ZoneBeacon - local VHFbeacon = czone.vhfbeacon -- #CTLD.ZoneBeacon - local UHFbeacon = czone.uhfbeacon -- #CTLD.ZoneBeacon - local Name = czone.name - local FM = FMbeacon.frequency -- MHz - local VHF = VHFbeacon.frequency * 1000 -- KHz - local UHF = UHFbeacon.frequency -- MHz - report:AddIndent( string.format( " %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ", Name, FM, VHF, UHF ), "|" ) - end +--- (Internal) Function to show list of radio beacons +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +function CTLD:_ListRadioBeacons(Group, Unit) + self:T(self.lid .. " _ListRadioBeacons") + local report = REPORT:New("Active Zone Beacons") + report:Add("------------------------------------------------------------") + local zones = {[1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones, [4] = self.shipZones} + for i=1,4 do + for index,cargozone in pairs(zones[i]) do + -- Get Beacon object from zone + local czone = cargozone -- #CTLD.CargoZone + if czone.active and czone.hasbeacon then + local FMbeacon = czone.fmbeacon -- #CTLD.ZoneBeacon + local VHFbeacon = czone.vhfbeacon -- #CTLD.ZoneBeacon + local UHFbeacon = czone.uhfbeacon -- #CTLD.ZoneBeacon + local Name = czone.name + local FM = FMbeacon.frequency -- MHz + local VHF = VHFbeacon.frequency * 1000 -- KHz + local UHF = UHFbeacon.frequency -- MHz + report:AddIndent(string.format(" %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ", Name, FM, VHF, UHF),"|") end end - if report:GetCount() == 1 then - report:Add( " N O N E" ) - end - report:Add( "------------------------------------------------------------" ) - self:_SendMessage( report:Text(), 30, true, Group ) - return self end - - --- (Internal) Add radio beacon to zone. Runs 30 secs. - -- @param #CTLD self - -- @param #string Name Name of zone. - -- @param #string Sound Name of soundfile. - -- @param #number Mhz Frequency in Mhz. - -- @param #number Modulation Modulation AM or FM. - -- @param #boolean IsShip If true zone is a ship. - function CTLD:_AddRadioBeacon( Name, Sound, Mhz, Modulation, IsShip ) - self:T( self.lid .. " _AddRadioBeacon" ) - local Zone = nil - if IsShip then - Zone = UNIT:FindByName( Name ) - else - Zone = ZONE:FindByName( Name ) - end - local Sound = Sound or "beacon.ogg" - if Zone then - local ZoneCoord = Zone:GetCoordinate() - local ZoneVec3 = ZoneCoord:GetVec3() - local Frequency = Mhz * 1000000 -- Freq in Hertz - local Sound = "l10n/DEFAULT/" .. Sound - trigger.action.radioTransmission( Sound, ZoneVec3, Modulation, false, Frequency, 1000 ) -- Beacon in MP only runs for 30secs straight - end - return self + if report:GetCount() == 1 then + report:Add(" N O N E") end + report:Add("------------------------------------------------------------") + self:_SendMessage(report:Text(), 30, true, Group) + return self +end - --- (Internal) Function to refresh radio beacons - -- @param #CTLD self - function CTLD:_RefreshRadioBeacons() - self:T( self.lid .. " _RefreshRadioBeacons" ) +--- (Internal) Add radio beacon to zone. Runs 30 secs. +-- @param #CTLD self +-- @param #string Name Name of zone. +-- @param #string Sound Name of soundfile. +-- @param #number Mhz Frequency in Mhz. +-- @param #number Modulation Modulation AM or FM. +-- @param #boolean IsShip If true zone is a ship. +function CTLD:_AddRadioBeacon(Name, Sound, Mhz, Modulation, IsShip) + self:T(self.lid .. " _AddRadioBeacon") + local Zone = nil + if IsShip then + Zone = UNIT:FindByName(Name) + else + Zone = ZONE:FindByName(Name) + end + local Sound = Sound or "beacon.ogg" + if Zone then + local ZoneCoord = Zone:GetCoordinate() + local ZoneVec3 = ZoneCoord:GetVec3() + local Frequency = Mhz * 1000000 -- Freq in Hertz + local Sound = "l10n/DEFAULT/"..Sound + trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, Frequency, 1000) -- Beacon in MP only runs for 30secs straight + end + return self +end - local zones = { [1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones, [4] = self.shipZones } - for i = 1, 4 do - local IsShip = false - if i == 4 then - IsShip = true - end - for index, cargozone in pairs( zones[i] ) do - -- Get Beacon object from zone - local czone = cargozone -- #CTLD.CargoZone - local Sound = self.RadioSound - if czone.active and czone.hasbeacon then - local FMbeacon = czone.fmbeacon -- #CTLD.ZoneBeacon - local VHFbeacon = czone.vhfbeacon -- #CTLD.ZoneBeacon - local UHFbeacon = czone.uhfbeacon -- #CTLD.ZoneBeacon - local Name = czone.name - local FM = FMbeacon.frequency -- MHz - local VHF = VHFbeacon.frequency -- KHz - local UHF = UHFbeacon.frequency -- MHz - self:_AddRadioBeacon( Name, Sound, FM, radio.modulation.FM, IsShip ) - self:_AddRadioBeacon( Name, Sound, VHF, radio.modulation.FM, IsShip ) - self:_AddRadioBeacon( Name, Sound, UHF, radio.modulation.AM, IsShip ) - end +--- (Internal) Function to refresh radio beacons +-- @param #CTLD self +function CTLD:_RefreshRadioBeacons() + self:T(self.lid .. " _RefreshRadioBeacons") + + local zones = {[1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones, [4] = self.shipZones} + for i=1,4 do + local IsShip = false + if i == 4 then IsShip = true end + for index,cargozone in pairs(zones[i]) do + -- Get Beacon object from zone + local czone = cargozone -- #CTLD.CargoZone + local Sound = self.RadioSound + if czone.active and czone.hasbeacon then + local FMbeacon = czone.fmbeacon -- #CTLD.ZoneBeacon + local VHFbeacon = czone.vhfbeacon -- #CTLD.ZoneBeacon + local UHFbeacon = czone.uhfbeacon -- #CTLD.ZoneBeacon + local Name = czone.name + local FM = FMbeacon.frequency -- MHz + local VHF = VHFbeacon.frequency -- KHz + local UHF = UHFbeacon.frequency -- MHz + self:_AddRadioBeacon(Name,Sound,FM,radio.modulation.FM, IsShip) + self:_AddRadioBeacon(Name,Sound,VHF,radio.modulation.FM, IsShip) + self:_AddRadioBeacon(Name,Sound,UHF,radio.modulation.AM, IsShip) end end - return self end + return self +end - --- (Internal) Function to see if a unit is in a specific zone type. - -- @param #CTLD self - -- @param Wrapper.Unit#UNIT Unit Unit - -- @param #CTLD.CargoZoneType Zonetype Zonetype - -- @return #boolean Outcome Is in zone or not - -- @return #string name Closest zone name - -- @return Core.Zone#ZONE zone Closest Core.Zone#ZONE object - -- @return #number distance Distance to closest zone - -- @return #number width Radius of zone or width of ship - function CTLD:IsUnitInZone( Unit, Zonetype ) - self:T( self.lid .. " IsUnitInZone" ) - self:T( Zonetype ) - local unitname = Unit:GetName() - local zonetable = {} - local outcome = false - if Zonetype == CTLD.CargoZoneType.LOAD then - zonetable = self.pickupZones -- #table - elseif Zonetype == CTLD.CargoZoneType.DROP then - zonetable = self.dropOffZones -- #table - elseif Zonetype == CTLD.CargoZoneType.SHIP then - zonetable = self.shipZones -- #table - else - zonetable = self.wpZones -- #table - end - --- now see if we're in - local zonecoord = nil - local colorret = nil - local maxdist = 1000000 -- 100km - local zoneret = nil - local zonewret = nil - local zonenameret = nil - for _, _cargozone in pairs( zonetable ) do - local czone = _cargozone -- #CTLD.CargoZone - local unitcoord = Unit:GetCoordinate() - local zonename = czone.name - local active = czone.active - local color = czone.color - local zone = nil - local zoneradius = 100 - local zonewidth = 20 - if Zonetype == CTLD.CargoZoneType.SHIP then - self:T( "Checking Type Ship: " .. zonename ) - zone = UNIT:FindByName( zonename ) - zonecoord = zone:GetCoordinate() - zoneradius = czone.shiplength - zonewidth = czone.shipwidth - else - zone = ZONE:FindByName( zonename ) - zonecoord = zone:GetCoordinate() - zoneradius = zone:GetRadius() - zonewidth = zoneradius - end - local distance = self:_GetDistance( zonecoord, unitcoord ) - if distance <= zoneradius and active then - outcome = true - end - if maxdist > distance then - maxdist = distance - zoneret = zone - zonenameret = zonename - zonewret = zonewidth - colorret = color - end - end - if Zonetype == CTLD.CargoZoneType.SHIP then - return outcome, zonenameret, zoneret, maxdist, zonewret - else - return outcome, zonenameret, zoneret, maxdist - end +--- (Internal) Function to see if a unit is in a specific zone type. +-- @param #CTLD self +-- @param Wrapper.Unit#UNIT Unit Unit +-- @param #CTLD.CargoZoneType Zonetype Zonetype +-- @return #boolean Outcome Is in zone or not +-- @return #string name Closest zone name +-- @return Core.Zone#ZONE zone Closest Core.Zone#ZONE object +-- @return #number distance Distance to closest zone +-- @return #number width Radius of zone or width of ship +function CTLD:IsUnitInZone(Unit,Zonetype) + self:T(self.lid .. " IsUnitInZone") + self:T(Zonetype) + local unitname = Unit:GetName() + local zonetable = {} + local outcome = false + if Zonetype == CTLD.CargoZoneType.LOAD then + zonetable = self.pickupZones -- #table + elseif Zonetype == CTLD.CargoZoneType.DROP then + zonetable = self.dropOffZones -- #table + elseif Zonetype == CTLD.CargoZoneType.SHIP then + zonetable = self.shipZones -- #table + else + zonetable = self.wpZones -- #table end - - --- User function - Drop a smoke or flare at current location. - -- @param #CTLD self - -- @param Wrapper.Unit#UNIT Unit The Unit. - -- @param #boolean Flare If true, flare instead. - function CTLD:SmokePositionNow( Unit, Flare ) - self:T( self.lid .. " SmokePositionNow" ) - local SmokeColor = self.SmokeColor or SMOKECOLOR.Red - local FlareColor = self.FlareColor or FLARECOLOR.Red - -- table of #CTLD.CargoZone table - local unitcoord = Unit:GetCoordinate() -- Core.Point#COORDINATE - local Group = Unit:GetGroup() - if Flare then - unitcoord:Flare( FlareColor, 90 ) - else - local height = unitcoord:GetLandHeight() + 2 - unitcoord.y = height - unitcoord:Smoke( SmokeColor ) - end - return self - end - - --- User function - Start smoke/flare in a zone close to the Unit. - -- @param #CTLD self - -- @param Wrapper.Unit#UNIT Unit The Unit. - -- @param #boolean Flare If true, flare instead. - function CTLD:SmokeZoneNearBy( Unit, Flare ) - self:T( self.lid .. " SmokeZoneNearBy" ) - -- table of #CTLD.CargoZone table + --- now see if we\'re in + local zonecoord = nil + local colorret = nil + local maxdist = 1000000 -- 100km + local zoneret = nil + local zonewret = nil + local zonenameret = nil + for _,_cargozone in pairs(zonetable) do + local czone = _cargozone -- #CTLD.CargoZone local unitcoord = Unit:GetCoordinate() - local Group = Unit:GetGroup() - local smokedistance = self.smokedistance - local smoked = false - local zones = { [1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones, [4] = self.shipZones } - for i = 1, 4 do - for index, cargozone in pairs( zones[i] ) do - local CZone = cargozone -- #CTLD.CargoZone - local zonename = CZone.name - local zone = nil - if i == 4 then - zone = UNIT:FindByName( zonename ) + local zonename = czone.name + local active = czone.active + local color = czone.color + local zone = nil + local zoneradius = 100 + local zonewidth = 20 + if Zonetype == CTLD.CargoZoneType.SHIP then + self:T("Checking Type Ship: "..zonename) + zone = UNIT:FindByName(zonename) + zonecoord = zone:GetCoordinate() + zoneradius = czone.shiplength + zonewidth = czone.shipwidth + else + zone = ZONE:FindByName(zonename) + zonecoord = zone:GetCoordinate() + zoneradius = zone:GetRadius() + zonewidth = zoneradius + end + local distance = self:_GetDistance(zonecoord,unitcoord) + if distance <= zoneradius and active then + outcome = true + end + if maxdist > distance then + maxdist = distance + zoneret = zone + zonenameret = zonename + zonewret = zonewidth + colorret = color + end + end + if Zonetype == CTLD.CargoZoneType.SHIP then + return outcome, zonenameret, zoneret, maxdist, zonewret + else + return outcome, zonenameret, zoneret, maxdist + end +end + +--- User function - Drop a smoke or flare at current location. +-- @param #CTLD self +-- @param Wrapper.Unit#UNIT Unit The Unit. +-- @param #boolean Flare If true, flare instead. +function CTLD:SmokePositionNow(Unit, Flare) + self:T(self.lid .. " SmokePositionNow") + local SmokeColor = self.SmokeColor or SMOKECOLOR.Red + local FlareColor = self.FlareColor or FLARECOLOR.Red + -- table of #CTLD.CargoZone table + local unitcoord = Unit:GetCoordinate() -- Core.Point#COORDINATE + local Group = Unit:GetGroup() + if Flare then + unitcoord:Flare(FlareColor, 90) + else + local height = unitcoord:GetLandHeight() + 2 + unitcoord.y = height + unitcoord:Smoke(SmokeColor) + end + return self +end + +--- User function - Start smoke/flare in a zone close to the Unit. +-- @param #CTLD self +-- @param Wrapper.Unit#UNIT Unit The Unit. +-- @param #boolean Flare If true, flare instead. +function CTLD:SmokeZoneNearBy(Unit, Flare) + self:T(self.lid .. " SmokeZoneNearBy") + -- table of #CTLD.CargoZone table + local unitcoord = Unit:GetCoordinate() + local Group = Unit:GetGroup() + local smokedistance = self.smokedistance + local smoked = false + local zones = {[1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones, [4] = self.shipZones} + for i=1,4 do + for index,cargozone in pairs(zones[i]) do + local CZone = cargozone --#CTLD.CargoZone + local zonename = CZone.name + local zone = nil + if i == 4 then + zone = UNIT:FindByName(zonename) + else + zone = ZONE:FindByName(zonename) + end + local zonecoord = zone:GetCoordinate() + local active = CZone.active + local color = CZone.color + local distance = self:_GetDistance(zonecoord,unitcoord) + if distance < smokedistance and active then + -- smoke zone since we\'re nearby + if not Flare then + zonecoord:Smoke(color or SMOKECOLOR.White) else - zone = ZONE:FindByName( zonename ) - end - local zonecoord = zone:GetCoordinate() - local active = CZone.active - local color = CZone.color - local distance = self:_GetDistance( zonecoord, unitcoord ) - if distance < smokedistance and active then - -- smoke zone since we're nearby - if not Flare then - zonecoord:Smoke( color or SMOKECOLOR.White ) - else - if color == SMOKECOLOR.Blue then - color = FLARECOLOR.White - end - zonecoord:Flare( color or FLARECOLOR.White ) - end - local txt = "smoking" - if Flare then - txt = "flaring" - end - self:_SendMessage( string.format( "Roger, %s zone %s!", txt, zonename ), 10, false, Group ) - smoked = true + if color == SMOKECOLOR.Blue then color = FLARECOLOR.White end + zonecoord:Flare(color or FLARECOLOR.White) end + local txt = "smoking" + if Flare then txt = "flaring" end + self:_SendMessage(string.format("Roger, %s zone %s!",txt, zonename), 10, false, Group) + smoked = true end end - if not smoked then - local distance = UTILS.MetersToNM( self.smokedistance ) - self:_SendMessage( string.format( "Negative, need to be closer than %dnm to a zone!", distance ), 10, false, Group ) - end - return self end + if not smoked then + local distance = UTILS.MetersToNM(self.smokedistance) + self:_SendMessage(string.format("Negative, need to be closer than %dnm to a zone!",distance), 10, false, Group) + end + return self +end --- User - Function to add/adjust unittype capabilities. -- @param #CTLD self @@ -3501,14 +3461,14 @@ do -- @param #number Cratelimit Unit can carry number of crates. Default 0. -- @param #number Trooplimit Unit can carry number of troops. Default 0. -- @param #number Length Unit lenght (in mteres) for the load radius. Default 20. - function CTLD:UnitCapabilities( Unittype, Cancrates, Cantroops, Cratelimit, Trooplimit, Length ) - self:T( self.lid .. " UnitCapabilities" ) - local unittype = nil + function CTLD:UnitCapabilities(Unittype, Cancrates, Cantroops, Cratelimit, Trooplimit, Length) + self:T(self.lid .. " UnitCapabilities") + local unittype = nil local unit = nil - if type( Unittype ) == "string" then + if type(Unittype) == "string" then unittype = Unittype - elseif type( Unittype ) == "table" then - unit = UNIT:FindByName( Unittype ) -- Wrapper.Unit#UNIT + elseif type(Unittype) == "table" then + unit = UNIT:FindByName(Unittype) -- Wrapper.Unit#UNIT unittype = unit:GetTypeName() else return self @@ -3518,22 +3478,22 @@ do capabilities.type = unittype capabilities.crates = Cancrates or false capabilities.troops = Cantroops or false - capabilities.cratelimit = Cratelimit or 0 + capabilities.cratelimit = Cratelimit or 0 capabilities.trooplimit = Trooplimit or 0 capabilities.length = Length or 20 self.UnitTypes[unittype] = capabilities return self end - + --- (Internal) Check if a unit is hovering *in parameters*. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit -- @return #boolean Outcome - function CTLD:IsCorrectHover( Unit ) - self:T( self.lid .. " IsCorrectHover" ) + function CTLD:IsCorrectHover(Unit) + self:T(self.lid .. " IsCorrectHover") local outcome = false -- see if we are in air and within parameters. - if self:IsUnitInAir( Unit ) then + if self:IsUnitInAir(Unit) then -- get speed and height local uspeed = Unit:GetVelocityMPS() local uheight = Unit:GetHeight() @@ -3541,113 +3501,108 @@ do local gheight = ucoord:GetLandHeight() local aheight = uheight - gheight -- height above ground local maxh = self.maximumHoverHeight -- 15 - local minh = self.minimumHoverHeight -- 5 + local minh = self.minimumHoverHeight -- 5 local mspeed = 2 -- 2 m/s - if (uspeed <= mspeed) and (aheight <= maxh) and (aheight >= minh) then + if (uspeed <= mspeed) and (aheight <= maxh) and (aheight >= minh) then -- yep within parameters outcome = true end end return outcome end - - --- (Internal) Check if a Hercules is flying *in parameters* for air drops. + + --- (Internal) Check if a Hercules is flying *in parameters* for air drops. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit -- @return #boolean Outcome - function CTLD:IsCorrectFlightParameters( Unit ) - self:T( self.lid .. " IsCorrectFlightParameters" ) + function CTLD:IsCorrectFlightParameters(Unit) + self:T(self.lid .. " IsCorrectFlightParameters") local outcome = false -- see if we are in air and within parameters. - if self:IsUnitInAir( Unit ) then + if self:IsUnitInAir(Unit) then -- get speed and height local uspeed = Unit:GetVelocityMPS() local uheight = Unit:GetHeight() local ucoord = Unit:GetCoordinate() local gheight = ucoord:GetLandHeight() local aheight = uheight - gheight -- height above ground - local maxh = self.HercMinAngels -- 1500m - local minh = self.HercMaxAngels -- 5000m - local maxspeed = self.HercMaxSpeed -- 77 mps + local maxh = self.HercMinAngels-- 1500m + local minh = self.HercMaxAngels -- 5000m + local maxspeed = self.HercMaxSpeed -- 77 mps -- DONE: TEST - Speed test for Herc, should not be above 280kph/150kn local kmspeed = uspeed * 3.6 local knspeed = kmspeed / 1.86 - self:T( string.format( "%s Unit parameters: at %dm AGL with %dmps | %dkph | %dkn", self.lid, aheight, uspeed, kmspeed, knspeed ) ) - if (aheight <= maxh) and (aheight >= minh) and (uspeed <= maxspeed) then + self:T(string.format("%s Unit parameters: at %dm AGL with %dmps | %dkph | %dkn",self.lid,aheight,uspeed,kmspeed,knspeed)) + if (aheight <= maxh) and (aheight >= minh) and (uspeed <= maxspeed) then -- yep within parameters outcome = true end end return outcome end - + --- (Internal) List if a unit is hovering *in parameters*. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit - function CTLD:_ShowHoverParams( Group, Unit ) - local inhover = self:IsCorrectHover( Unit ) + function CTLD:_ShowHoverParams(Group,Unit) + local inhover = self:IsCorrectHover(Unit) local htxt = "true" - if not inhover then - htxt = "false" - end + if not inhover then htxt = "false" end local text = "" if _SETTINGS:IsMetric() then - text = string.format( "Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 2mps \n - In parameter: %s", self.minimumHoverHeight, self.maximumHoverHeight, htxt ) + text = string.format("Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 2mps \n - In parameter: %s", self.minimumHoverHeight, self.maximumHoverHeight, htxt) else - local minheight = UTILS.MetersToFeet( self.minimumHoverHeight ) - local maxheight = UTILS.MetersToFeet( self.maximumHoverHeight ) - text = string.format( "Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 6fts \n - In parameter: %s", minheight, maxheight, htxt ) + local minheight = UTILS.MetersToFeet(self.minimumHoverHeight) + local maxheight = UTILS.MetersToFeet(self.maximumHoverHeight) + text = string.format("Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 6fts \n - In parameter: %s", minheight, maxheight, htxt) end - self:_SendMessage( text, 10, false, Group ) + self:_SendMessage(text, 10, false, Group) return self end - - --- (Internal) List if a Herc unit is flying *in parameters*. + + --- (Internal) List if a Herc unit is flying *in parameters*. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit - function CTLD:_ShowFlightParams( Group, Unit ) - local inhover = self:IsCorrectFlightParameters( Unit ) + function CTLD:_ShowFlightParams(Group,Unit) + local inhover = self:IsCorrectFlightParameters(Unit) local htxt = "true" - if not inhover then - htxt = "false" - end + if not inhover then htxt = "false" end local text = "" if _SETTINGS:IsImperial() then - local minheight = UTILS.MetersToFeet( self.HercMinAngels ) - local maxheight = UTILS.MetersToFeet( self.HercMaxAngels ) - text = string.format( "Flight parameters (airdrop):\n - Min height %dft \n - Max height %dft \n - In parameter: %s", minheight, maxheight, htxt ) + local minheight = UTILS.MetersToFeet(self.HercMinAngels) + local maxheight = UTILS.MetersToFeet(self.HercMaxAngels) + text = string.format("Flight parameters (airdrop):\n - Min height %dft \n - Max height %dft \n - In parameter: %s", minheight, maxheight, htxt) else local minheight = self.HercMinAngels local maxheight = self.HercMaxAngels - text = string.format( "Flight parameters (airdrop):\n - Min height %dm \n - Max height %dm \n - In parameter: %s", minheight, maxheight, htxt ) + text = string.format("Flight parameters (airdrop):\n - Min height %dm \n - Max height %dm \n - In parameter: %s", minheight, maxheight, htxt) end - self:_SendMessage( text, 10, false, Group ) + self:_SendMessage(text, 10, false, Group) return self end - + + --- (Internal) Check if a unit is in a load zone and is hovering in parameters. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit -- @return #boolean Outcome - function CTLD:CanHoverLoad( Unit ) - self:T( self.lid .. " CanHoverLoad" ) - if self:IsHercules( Unit ) then - return false - end - local outcome = self:IsUnitInZone( Unit, CTLD.CargoZoneType.LOAD ) and self:IsCorrectHover( Unit ) + function CTLD:CanHoverLoad(Unit) + self:T(self.lid .. " CanHoverLoad") + if self:IsHercules(Unit) then return false end + local outcome = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) and self:IsCorrectHover(Unit) if not outcome then - outcome = self:IsUnitInZone( Unit, CTLD.CargoZoneType.SHIP ) -- and self:IsCorrectHover(Unit) + outcome = self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) --and self:IsCorrectHover(Unit) end return outcome end - + --- (Internal) Check if a unit is above ground. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit -- @return #boolean Outcome - function CTLD:IsUnitInAir( Unit ) + function CTLD:IsUnitInAir(Unit) -- get speed and height local minheight = self.minimumHoverHeight if self.enableHercules and Unit:GetTypeName() == "Hercules" then @@ -3663,18 +3618,18 @@ do return false end end - + --- (Internal) Autoload if we can do crates, have capacity free and are in a load zone. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit -- @return #CTLD self - function CTLD:AutoHoverLoad( Unit ) - self:T( self.lid .. " AutoHoverLoad" ) + function CTLD:AutoHoverLoad(Unit) + self:T(self.lid .. " AutoHoverLoad") -- get capabilities and current load local unittype = Unit:GetTypeName() local unitname = Unit:GetName() local Group = Unit:GetGroup() - local capabilities = self:_GetUnitCapabilities( Unit ) -- #CTLD.UnitCapabilities + local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities local cancrates = capabilities.crates -- #boolean local cratelimit = capabilities.cratelimit -- #number if cancrates then @@ -3686,29 +3641,27 @@ do numberonboard = loaded.Cratesloaded or 0 end local load = cratelimit - numberonboard - local canload = self:CanHoverLoad( Unit ) + local canload = self:CanHoverLoad(Unit) if canload and load > 0 then - self:_LoadCratesNearby( Group, Unit ) + self:_LoadCratesNearby(Group,Unit) end end return self end - + --- (Internal) Run through all pilots and see if we autoload. -- @param #CTLD self -- @return #CTLD self function CTLD:CheckAutoHoverload() if self.hoverautoloading then - for _, _pilot in pairs( self.CtldUnits ) do - local Unit = UNIT:FindByName( _pilot ) - if self:CanHoverLoad( Unit ) then - self:AutoHoverLoad( Unit ) - end + for _,_pilot in pairs (self.CtldUnits) do + local Unit = UNIT:FindByName(_pilot) + if self:CanHoverLoad(Unit) then self:AutoHoverLoad(Unit) end end end return self end - + --- (Internal) Run through DroppedTroops and capture alive units -- @param #CTLD self -- @return #CTLD self @@ -3716,8 +3669,8 @@ do -- Troops local troops = self.DroppedTroops local newtable = {} - for _index, _group in pairs( troops ) do - self:T( { _group.ClassName } ) + for _index, _group in pairs (troops) do + self:T({_group.ClassName}) if _group and _group.ClassName == "GROUP" then if _group:IsAlive() then newtable[_index] = _group @@ -3728,9 +3681,9 @@ do -- Engineers local engineers = self.EngineersInField local engtable = {} - for _index, _group in pairs( engineers ) do - self:T( { _group.ClassName } ) - if _group and _group:IsNotStatus( "Stopped" ) then + for _index, _group in pairs (engineers) do + self:T({_group.ClassName}) + if _group and _group:IsNotStatus("Stopped") then engtable[_index] = _group end end @@ -3743,91 +3696,91 @@ do -- @param #string Name Name as defined in the generic cargo. -- @param #number Number Number of units/groups to add. -- @return #CTLD self - function CTLD:AddStockTroops( Name, Number ) + function CTLD:AddStockTroops(Name, Number) local name = Name or "none" local number = Number or 1 -- find right generic type local gentroops = self.Cargo_Troops - for _id, _troop in pairs( gentroops ) do -- #number, #CTLD_CARGO + for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO if _troop.Name == name then - _troop:AddStock( number ) + _troop:AddStock(number) end end end - + --- User - function to add stock of a certain crates type -- @param #CTLD self -- @param #string Name Name as defined in the generic cargo. -- @param #number Number Number of units/groups to add. -- @return #CTLD self - function CTLD:AddStockCrates( Name, Number ) + function CTLD:AddStockCrates(Name, Number) local name = Name or "none" local number = Number or 1 -- find right generic type local gentroops = self.Cargo_Crates - for _id, _troop in pairs( gentroops ) do -- #number, #CTLD_CARGO + for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO if _troop.Name == name then - _troop:AddStock( number ) + _troop:AddStock(number) end end end - + --- User - function to remove stock of a certain troops type -- @param #CTLD self -- @param #string Name Name as defined in the generic cargo. -- @param #number Number Number of units/groups to add. -- @return #CTLD self - function CTLD:RemoveStockTroops( Name, Number ) + function CTLD:RemoveStockTroops(Name, Number) local name = Name or "none" local number = Number or 1 -- find right generic type local gentroops = self.Cargo_Troops - for _id, _troop in pairs( gentroops ) do -- #number, #CTLD_CARGO + for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO if _troop.Name == name then - _troop:RemoveStock( number ) + _troop:RemoveStock(number) end end end - + --- User - function to remove stock of a certain crates type -- @param #CTLD self -- @param #string Name Name as defined in the generic cargo. -- @param #number Number Number of units/groups to add. -- @return #CTLD self - function CTLD:RemoveStockCrates( Name, Number ) + function CTLD:RemoveStockCrates(Name, Number) local name = Name or "none" local number = Number or 1 -- find right generic type local gentroops = self.Cargo_Crates - for _id, _troop in pairs( gentroops ) do -- #number, #CTLD_CARGO + for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO if _troop.Name == name then - _troop:RemoveStock( number ) + _troop:RemoveStock(number) end end return self end - + --- (Internal) Check on engineering teams -- @param #CTLD self -- @return #CTLD self function CTLD:_CheckEngineers() - self:T( self.lid .. " CheckEngineers" ) + self:T(self.lid.." CheckEngineers") local engtable = self.EngineersInField - for _ind, _engineers in pairs( engtable ) do + for _ind,_engineers in pairs (engtable) do local engineers = _engineers -- #CTLD_ENGINEERING local wrenches = engineers.Group -- Wrapper.Group#GROUP - self:T( _engineers.lid .. _engineers:GetStatus() ) + self:T(_engineers.lid .. _engineers:GetStatus()) if wrenches and wrenches:IsAlive() then - if engineers:IsStatus( "Running" ) or engineers:IsStatus( "Searching" ) then - local crates, number = self:_FindCratesNearby( wrenches, nil, self.EngineerSearch ) -- #table - engineers:Search( crates, number ) - elseif engineers:IsStatus( "Moving" ) then + if engineers:IsStatus("Running") or engineers:IsStatus("Searching") then + local crates,number = self:_FindCratesNearby(wrenches,nil, self.EngineerSearch) -- #table + engineers:Search(crates,number) + elseif engineers:IsStatus("Moving") then engineers:Move() - elseif engineers:IsStatus( "Arrived" ) then + elseif engineers:IsStatus("Arrived") then engineers:Build() - local unit = wrenches:GetUnit( 1 ) - self:_BuildCrates( wrenches, unit, true ) - self:_RepairCrates( wrenches, unit, true ) + local unit = wrenches:GetUnit(1) + self:_BuildCrates(wrenches,unit,true) + self:_RepairCrates(wrenches,unit,true) engineers:Done() end else @@ -3836,7 +3789,7 @@ do end return self end - + --- (User) Pre-populate troops in the field. -- @param #CTLD self -- @param Core.Zone#ZONE Zone The zone where to drop the troops. @@ -3849,15 +3802,15 @@ do -- local dropzone = ZONE:New("InjectZone") -- Core.Zone#ZONE -- -- and go: -- my_ctld:InjectTroops(dropzone,InjectTroopsType) - function CTLD:InjectTroops( Zone, Cargo ) - self:T( self.lid .. " InjectTroops" ) + function CTLD:InjectTroops(Zone,Cargo) + self:T(self.lid.." InjectTroops") local cargo = Cargo -- #CTLD_CARGO - - local function IsTroopsMatch( cargo ) + + local function IsTroopsMatch(cargo) local match = false local cgotbl = self.Cargo_Troops local name = cargo:GetName() - for _, _cgo in pairs( cgotbl ) do + for _,_cgo in pairs (cgotbl) do local cname = _cgo:GetName() if name == cname then match = true @@ -3866,14 +3819,14 @@ do end return match end - - if not IsTroopsMatch( cargo ) then + + if not IsTroopsMatch(cargo) then self.CargoCounter = self.CargoCounter + 1 cargo.ID = self.CargoCounter cargo.Stock = 1 - table.insert( self.Cargo_Troops, cargo ) + table.insert(self.Cargo_Troops,cargo) end - + local type = cargo:GetType() -- #CTLD_CARGO.Enum if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) then -- unload @@ -3881,37 +3834,37 @@ do local temptable = cargo:GetTemplates() or {} local factor = 1.5 local zone = Zone - - local randomcoord = zone:GetRandomCoordinate( 10, 30 * factor ):GetVec2() - for _, _template in pairs( temptable ) do + + local randomcoord = zone:GetRandomCoordinate(10,30*factor):GetVec2() + for _,_template in pairs(temptable) do self.TroopCounter = self.TroopCounter + 1 - local alias = string.format( "%s-%d", _template, math.random( 1, 100000 ) ) - self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias( _template, alias ) - :InitRandomizeUnits( true, 20, 2 ) + local alias = string.format("%s-%d", _template, math.random(1,100000)) + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) + :InitRandomizeUnits(true,20,2) :InitDelayOff() - :SpawnFromVec2( randomcoord ) + :SpawnFromVec2(randomcoord) if self.movetroopstowpzone and type ~= CTLD_CARGO.Enum.ENGINEERS then - self:_MoveGroupToZone( self.DroppedTroops[self.TroopCounter] ) + self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) end end -- template loop - cargo:SetWasDropped( true ) + cargo:SetWasDropped(true) -- engineering group? if type == CTLD_CARGO.Enum.ENGINEERS then self.Engineers = self.Engineers + 1 local grpname = self.DroppedTroops[self.TroopCounter]:GetName() - self.EngineersInField[self.Engineers] = CTLD_ENGINEERING:New( name, grpname ) - -- self:I(string.format("%s Injected Engineers %s into action!",self.lid, name)) + self.EngineersInField[self.Engineers] = CTLD_ENGINEERING:New(name, grpname) + --self:I(string.format("%s Injected Engineers %s into action!",self.lid, name)) else - -- self:I(string.format("%s Injected Troops %s into action!",self.lid, name)) + --self:I(string.format("%s Injected Troops %s into action!",self.lid, name)) end if self.eventoninject then - self:__TroopsDeployed( 1, nil, nil, self.DroppedTroops[self.TroopCounter] ) + self:__TroopsDeployed(1,nil,nil,self.DroppedTroops[self.TroopCounter]) end end -- if type end return self end - - --- (User) Pre-populate vehicles in the field. + + --- (User) Pre-populate vehicles in the field. -- @param #CTLD self -- @param Core.Zone#ZONE Zone The zone where to drop the troops. -- @param Ops.CTLD#CTLD_CARGO Cargo The #CTLD_CARGO object to spawn. @@ -3923,15 +3876,15 @@ do -- local dropzone = ZONE:New("InjectZone") -- Core.Zone#ZONE -- -- and go: -- my_ctld:InjectVehicles(dropzone,InjectVehicleType) - function CTLD:InjectVehicles( Zone, Cargo ) - self:T( self.lid .. " InjectVehicles" ) + function CTLD:InjectVehicles(Zone,Cargo) + self:T(self.lid.." InjectVehicles") local cargo = Cargo -- #CTLD_CARGO - - local function IsVehicMatch( cargo ) + + local function IsVehicMatch(cargo) local match = false local cgotbl = self.Cargo_Crates local name = cargo:GetName() - for _, _cgo in pairs( cgotbl ) do + for _,_cgo in pairs (cgotbl) do local cname = _cgo:GetName() if name == cname then match = true @@ -3940,14 +3893,14 @@ do end return match end - - if not IsVehicMatch( cargo ) then + + if not IsVehicMatch(cargo) then self.CargoCounter = self.CargoCounter + 1 cargo.ID = self.CargoCounter cargo.Stock = 1 - table.insert( self.Cargo_Crates, cargo ) + table.insert(self.Cargo_Crates,cargo) end - + local type = cargo:GetType() -- #CTLD_CARGO.Enum if (type == CTLD_CARGO.Enum.VEHICLE or type == CTLD_CARGO.Enum.FOB) then -- unload @@ -3955,39 +3908,37 @@ do local temptable = cargo:GetTemplates() or {} local factor = 1.5 local zone = Zone - local randomcoord = zone:GetRandomCoordinate( 10, 30 * factor ):GetVec2() - cargo:SetWasDropped( true ) + local randomcoord = zone:GetRandomCoordinate(10,30*factor):GetVec2() + cargo:SetWasDropped(true) local canmove = false - if type == CTLD_CARGO.Enum.VEHICLE then - canmove = true - end - for _, _template in pairs( temptable ) do + if type == CTLD_CARGO.Enum.VEHICLE then canmove = true end + for _,_template in pairs(temptable) do self.TroopCounter = self.TroopCounter + 1 - local alias = string.format( "%s-%d", _template, math.random( 1, 100000 ) ) + local alias = string.format("%s-%d", _template, math.random(1,100000)) if canmove then - self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias( _template, alias ) - :InitRandomizeUnits( true, 20, 2 ) + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) + :InitRandomizeUnits(true,20,2) :InitDelayOff() - :SpawnFromVec2( randomcoord ) + :SpawnFromVec2(randomcoord) else -- don't random position of e.g. SAM units build as FOB - self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias( _template, alias ) + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) :InitDelayOff() - :SpawnFromVec2( randomcoord ) + :SpawnFromVec2(randomcoord) end if self.movetroopstowpzone and canmove then - self:_MoveGroupToZone( self.DroppedTroops[self.TroopCounter] ) + self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) end if self.eventoninject then - self:__CratesBuild( 1, nil, nil, self.DroppedTroops[self.TroopCounter] ) + self:__CratesBuild(1,nil,nil,self.DroppedTroops[self.TroopCounter]) end end -- end loop end -- if type end return self end - - ------------------------------------------------------------------- - -- FSM functions - ------------------------------------------------------------------- + +------------------------------------------------------------------- +-- FSM functions +------------------------------------------------------------------- --- (Internal) FSM Function onafterStart. -- @param #CTLD self @@ -3995,31 +3946,31 @@ do -- @param #string Event Trigger. -- @param #string To State. -- @return #CTLD self - function CTLD:onafterStart( From, Event, To ) - self:T( { From, Event, To } ) - self:I( self.lid .. "Started (" .. self.version .. ")" ) + function CTLD:onafterStart(From, Event, To) + self:T({From, Event, To}) + self:I(self.lid .. "Started ("..self.version..")") if self.useprefix or self.enableHercules then local prefix = self.prefixes if self.enableHercules then - self.PilotGroups = SET_GROUP:New():FilterCoalitions( self.coalitiontxt ):FilterPrefixes( prefix ):FilterStart() + self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterStart() else - self.PilotGroups = SET_GROUP:New():FilterCoalitions( self.coalitiontxt ):FilterPrefixes( prefix ):FilterCategories( "helicopter" ):FilterStart() + self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterCategories("helicopter"):FilterStart() end else - self.PilotGroups = SET_GROUP:New():FilterCoalitions( self.coalitiontxt ):FilterCategories( "helicopter" ):FilterStart() + self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategories("helicopter"):FilterStart() end -- Events - self:HandleEvent( EVENTS.PlayerEnterAircraft, self._EventHandler ) - self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventHandler ) - self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventHandler ) - self:__Status( -5 ) - + self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) + self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) + self:HandleEvent(EVENTS.PlayerLeaveUnit, self._EventHandler) + self:__Status(-5) + -- AutoSave if self.enableLoadSave then local interval = self.saveinterval local filename = self.filename local filepath = self.filepath - self:__Save( interval, filepath, filename ) + self:__Save(interval,filepath,filename) end return self end @@ -4030,8 +3981,8 @@ do -- @param #string Event Trigger. -- @param #string To State. -- @return #CTLD self - function CTLD:onbeforeStatus( From, Event, To ) - self:T( { From, Event, To } ) + function CTLD:onbeforeStatus(From, Event, To) + self:T({From, Event, To}) self:CleanDroppedTroops() self:_RefreshF10Menus() self:_RefreshRadioBeacons() @@ -4039,71 +3990,71 @@ do self:_CheckEngineers() return self end - + --- (Internal) FSM Function onafterStatus. -- @param #CTLD self -- @param #string From State. -- @param #string Event Trigger. -- @param #string To State. -- @return #CTLD self - function CTLD:onafterStatus( From, Event, To ) - self:T( { From, Event, To } ) - -- gather some stats + function CTLD:onafterStatus(From, Event, To) + self:T({From, Event, To}) + -- gather some stats -- pilots local pilots = 0 - for _, _pilot in pairs( self.CtldUnits ) do - pilots = pilots + 1 + for _,_pilot in pairs (self.CtldUnits) do + pilots = pilots + 1 end - + -- spawned cargo boxes curr in field local boxes = 0 - for _, _pilot in pairs( self.Spawned_Cargo ) do - boxes = boxes + 1 + for _,_pilot in pairs (self.Spawned_Cargo) do + boxes = boxes + 1 end - - local cc = self.CargoCounter + + local cc = self.CargoCounter local tc = self.TroopCounter - - if self.debug or self.verbose > 0 then - local text = string.format( "%s Pilots %d | Live Crates %d |\nCargo Counter %d | Troop Counter %d", self.lid, pilots, boxes, cc, tc ) - local m = MESSAGE:New( text, 10, "CTLD" ):ToAll() + + if self.debug or self.verbose > 0 then + local text = string.format("%s Pilots %d | Live Crates %d |\nCargo Counter %d | Troop Counter %d", self.lid, pilots, boxes, cc, tc) + local m = MESSAGE:New(text,10,"CTLD"):ToAll() if self.verbose > 0 then - self:I( self.lid .. "Cargo and Troops in Stock:" ) - for _, _troop in pairs( self.Cargo_Crates ) do + self:I(self.lid.."Cargo and Troops in Stock:") + for _,_troop in pairs (self.Cargo_Crates) do local name = _troop:GetName() local stock = _troop:GetStock() - self:I( string.format( "-- %s \t\t\t %d", name, stock ) ) + self:I(string.format("-- %s \t\t\t %d", name, stock)) end - for _, _troop in pairs( self.Cargo_Statics ) do + for _,_troop in pairs (self.Cargo_Statics) do local name = _troop:GetName() local stock = _troop:GetStock() - self:I( string.format( "-- %s \t\t\t %d", name, stock ) ) + self:I(string.format("-- %s \t\t\t %d", name, stock)) end - for _, _troop in pairs( self.Cargo_Troops ) do + for _,_troop in pairs (self.Cargo_Troops) do local name = _troop:GetName() local stock = _troop:GetStock() - self:I( string.format( "-- %s \t\t %d", name, stock ) ) + self:I(string.format("-- %s \t\t %d", name, stock)) end end end - self:__Status( -30 ) + self:__Status(-30) return self end - + --- (Internal) FSM Function onafterStop. -- @param #CTLD self -- @param #string From State. -- @param #string Event Trigger. -- @param #string To State. -- @return #CTLD self - function CTLD:onafterStop( From, Event, To ) - self:T( { From, Event, To } ) - self:UnhandleEvent( EVENTS.PlayerEnterAircraft ) - self:UnhandleEvent( EVENTS.PlayerEnterUnit ) - self:UnhandleEvent( EVENTS.PlayerLeaveUnit ) + function CTLD:onafterStop(From, Event, To) + self:T({From, Event, To}) + self:UnhandleEvent(EVENTS.PlayerEnterAircraft) + self:UnhandleEvent(EVENTS.PlayerEnterUnit) + self:UnhandleEvent(EVENTS.PlayerLeaveUnit) return self end - + --- (Internal) FSM Function onbeforeTroopsPickedUp. -- @param #CTLD self -- @param #string From State. @@ -4113,12 +4064,12 @@ do -- @param Wrapper.Unit#UNIT Unit Unit Object. -- @param #CTLD_CARGO Cargo Cargo crate. -- @return #CTLD self - function CTLD:onbeforeTroopsPickedUp( From, Event, To, Group, Unit, Cargo ) - self:T( { From, Event, To } ) + function CTLD:onbeforeTroopsPickedUp(From, Event, To, Group, Unit, Cargo) + self:T({From, Event, To}) return self end - - --- (Internal) FSM Function onbeforeCratesPickedUp. + + --- (Internal) FSM Function onbeforeCratesPickedUp. -- @param #CTLD self -- @param #string From State . -- @param #string Event Trigger. @@ -4127,25 +4078,26 @@ do -- @param Wrapper.Unit#UNIT Unit Unit Object. -- @param #CTLD_CARGO Cargo Cargo crate. -- @return #CTLD self - function CTLD:onbeforeCratesPickedUp( From, Event, To, Group, Unit, Cargo ) - self:T( { From, Event, To } ) + function CTLD:onbeforeCratesPickedUp(From, Event, To, Group, Unit, Cargo) + self:T({From, Event, To}) return self end - - --- (Internal) FSM Function onbeforeTroopsExtracted. - -- @param #CTLD self - -- @param #string From State. - -- @param #string Event Trigger. - -- @param #string To State. - -- @param Wrapper.Group#GROUP Group Group Object. - -- @param Wrapper.Unit#UNIT Unit Unit Object. - -- @param Wrapper.Group#GROUP Troops Troops #GROUP Object. - -- @return #CTLD self - function CTLD:onbeforeTroopsExtracted( From, Event, To, Group, Unit, Troops ) - self:T( { From, Event, To } ) - return self - end - + + --- (Internal) FSM Function onbeforeTroopsExtracted. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param Wrapper.Group#GROUP Troops Troops #GROUP Object. + -- @return #CTLD self + function CTLD:onbeforeTroopsExtracted(From, Event, To, Group, Unit, Troops) + self:T({From, Event, To}) + return self + end + + --- (Internal) FSM Function onbeforeTroopsDeployed. -- @param #CTLD self -- @param #string From State. @@ -4155,11 +4107,11 @@ do -- @param Wrapper.Unit#UNIT Unit Unit Object. -- @param Wrapper.Group#GROUP Troops Troops #GROUP Object. -- @return #CTLD self - function CTLD:onbeforeTroopsDeployed( From, Event, To, Group, Unit, Troops ) - self:T( { From, Event, To } ) + function CTLD:onbeforeTroopsDeployed(From, Event, To, Group, Unit, Troops) + self:T({From, Event, To}) return self end - + --- (Internal) FSM Function onbeforeCratesDropped. -- @param #CTLD self -- @param #string From State. @@ -4169,11 +4121,11 @@ do -- @param Wrapper.Unit#UNIT Unit Unit Object. -- @param #table Cargotable Table of #CTLD_CARGO objects dropped. -- @return #CTLD self - function CTLD:onbeforeCratesDropped( From, Event, To, Group, Unit, Cargotable ) - self:T( { From, Event, To } ) + function CTLD:onbeforeCratesDropped(From, Event, To, Group, Unit, Cargotable) + self:T({From, Event, To}) return self end - + --- (Internal) FSM Function onbeforeCratesBuild. -- @param #CTLD self -- @param #string From State. @@ -4183,11 +4135,11 @@ do -- @param Wrapper.Unit#UNIT Unit Unit Object. -- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB build. -- @return #CTLD self - function CTLD:onbeforeCratesBuild( From, Event, To, Group, Unit, Vehicle ) - self:T( { From, Event, To } ) + function CTLD:onbeforeCratesBuild(From, Event, To, Group, Unit, Vehicle) + self:T({From, Event, To}) return self end - + --- (Internal) FSM Function onbeforeTroopsRTB. -- @param #CTLD self -- @param #string From State. @@ -4196,11 +4148,11 @@ do -- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Unit#UNIT Unit Unit Object. -- @return #CTLD self - function CTLD:onbeforeTroopsRTB( From, Event, To, Group, Unit ) - self:T( { From, Event, To } ) + function CTLD:onbeforeTroopsRTB(From, Event, To, Group, Unit) + self:T({From, Event, To}) return self end - + --- On before "Save" event. Checks if io and lfs are available. -- @param #CTLD self -- @param #string From From state. @@ -4208,26 +4160,26 @@ do -- @param #string To To state. -- @param #string path (Optional) Path where the file is saved. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized. -- @param #string filename (Optional) File name for saving. Default is "CTLD__Persist.csv". - function CTLD:onbeforeSave( From, Event, To, path, filename ) - self:T( { From, Event, To, path, filename } ) + function CTLD:onbeforeSave(From, Event, To, path, filename) + self:T({From, Event, To, path, filename}) if not self.enableLoadSave then return self end -- Thanks to @FunkyFranky -- Check io module is available. if not io then - self:E( self.lid .. "ERROR: io not desanitized. Can't save current state." ) + self:E(self.lid.."ERROR: io not desanitized. Can't save current state.") return false end - + -- Check default path. - if path == nil and not lfs then - self:E( self.lid .. "WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder." ) + if path==nil and not lfs then + self:E(self.lid.."WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") end - + return true end - + --- On after "Save" event. Player data is saved to file. -- @param #CTLD self -- @param #string From From state. @@ -4235,90 +4187,89 @@ do -- @param #string To To state. -- @param #string path Path where the file is saved. If nil, file is saved in the DCS root installtion directory or your "Saved Games" folder if lfs was desanitized. -- @param #string filename (Optional) File name for saving. Default is Default is "CTLD__Persist.csv". - function CTLD:onafterSave( From, Event, To, path, filename ) - self:T( { From, Event, To, path, filename } ) + function CTLD:onafterSave(From, Event, To, path, filename) + self:T({From, Event, To, path, filename}) -- Thanks to @FunkyFranky if not self.enableLoadSave then return self end --- Function that saves data to file - local function _savefile( filename, data ) - local f = assert( io.open( filename, "wb" ) ) - f:write( data ) + local function _savefile(filename, data) + local f = assert(io.open(filename, "wb")) + f:write(data) f:close() end - + -- Set path or default. if lfs then - path = self.filepath or lfs.writedir() + path=self.filepath or lfs.writedir() end - + -- Set file name. - filename = filename or self.filename - + filename=filename or self.filename + -- Set path. - if path ~= nil then - filename = path .. "\\" .. filename + if path~=nil then + filename=path.."\\"..filename end - + local grouptable = self.DroppedTroops -- #table local cgovehic = self.Cargo_Crates local cgotable = self.Cargo_Troops local stcstable = self.Spawned_Cargo - + local statics = nil local statics = {} - self:I( self.lid .. "Bulding Statics Table for Saving" ) - for _, _cargo in pairs( stcstable ) do + self:T(self.lid.."Bulding Statics Table for Saving") + for _,_cargo in pairs (stcstable) do local cargo = _cargo -- #CTLD_CARGO local object = cargo:GetPositionable() -- Wrapper.Static#STATIC if object and object:IsAlive() and cargo:WasDropped() then - self:I( { _cargo } ) - statics[#statics + 1] = cargo + self:I({_cargo}) + statics[#statics+1] = cargo end end - + -- find matching cargo - local function FindCargoType( name, table ) + local function FindCargoType(name,table) -- name matching a template in the table local match = false local cargo = nil - for _ind, _cargo in pairs( table ) do + for _ind,_cargo in pairs (table) do local thiscargo = _cargo -- #CTLD_CARGO local template = thiscargo:GetTemplates() - if type( template ) == "string" then + if type(template) == "string" then template = { template } end - for _, _name in pairs( template ) do - -- self:I(string.format("*** Saving CTLD: Matching %s with %s",name,_name)) - if string.find( name, _name ) and _cargo:GetType() ~= CTLD_CARGO.Enum.REPAIR then + for _,_name in pairs (template) do + --self:I(string.format("*** Saving CTLD: Matching %s with %s",name,_name)) + if string.find(name,_name) and _cargo:GetType() ~= CTLD_CARGO.Enum.REPAIR then match = true cargo = thiscargo end end - if match then - break - end + if match then break end end return match, cargo end - - -- local data = "LoadedData = {\n" + + + --local data = "LoadedData = {\n" local data = "Group,x,y,z,CargoName,CargoTemplates,CargoType,CratesNeeded,CrateMass\n" local n = 0 - for _, _grp in pairs( grouptable ) do + for _,_grp in pairs(grouptable) do local group = _grp -- Wrapper.Group#GROUP if group and group:IsAlive() then -- get template name local name = group:GetName() - local template = string.gsub( name, "-(.+)$", "" ) - if string.find( template, "#" ) then - template = string.gsub( name, "#(%d+)$", "" ) + local template = string.gsub(name,"-(.+)$","") + if string.find(template,"#") then + template = string.gsub(name,"#(%d+)$","") end - - local match, cargo = FindCargoType( template, cgotable ) + + local match, cargo = FindCargoType(template,cgotable) if not match then - match, cargo = FindCargoType( template, cgovehic ) + match, cargo = FindCargoType(template,cgovehic) end if match then n = n + 1 @@ -4328,54 +4279,56 @@ do local cgotype = cargo.CargoType local cgoneed = cargo.CratesNeeded local cgomass = cargo.PerCrateMass - - if type( cgotemp ) == "table" then + + if type(cgotemp) == "table" then local templates = "{" - for _, _tmpl in pairs( cgotemp ) do + for _,_tmpl in pairs(cgotemp) do templates = templates .. _tmpl .. ";" end templates = templates .. "}" cgotemp = templates end - + local location = group:GetVec3() - local txt = string.format( "%s,%d,%d,%d,%s,%s,%s,%d,%d\n", template, location.x, location.y, location.z, cgoname, cgotemp, cgotype, cgoneed, cgomass ) + local txt = string.format("%s,%d,%d,%d,%s,%s,%s,%d,%d\n" + ,template,location.x,location.y,location.z,cgoname,cgotemp,cgotype,cgoneed,cgomass) data = data .. txt end end end - - for _, _cgo in pairs( statics ) do + + for _,_cgo in pairs(statics) do local object = _cgo -- #CTLD_CARGO local cgoname = object.Name local cgotemp = object.Templates - - if type( cgotemp ) == "table" then + + if type(cgotemp) == "table" then local templates = "{" - for _, _tmpl in pairs( cgotemp ) do + for _,_tmpl in pairs(cgotemp) do templates = templates .. _tmpl .. ";" end templates = templates .. "}" cgotemp = templates end - + local cgotype = object.CargoType local cgoneed = object.CratesNeeded local cgomass = object.PerCrateMass local crateobj = object.Positionable local location = crateobj:GetVec3() - local txt = string.format( "%s,%d,%d,%d,%s,%s,%s,%d,%d\n", "STATIC", location.x, location.y, location.z, cgoname, cgotemp, cgotype, cgoneed, cgomass ) + local txt = string.format("%s,%d,%d,%d,%s,%s,%s,%d,%d\n" + ,"STATIC",location.x,location.y,location.z,cgoname,cgotemp,cgotype,cgoneed,cgomass) data = data .. txt end - - _savefile( filename, data ) - + + _savefile(filename, data) + -- AutoSave if self.enableLoadSave then local interval = self.saveinterval local filename = self.filename local filepath = self.filepath - self:__Save( interval, filepath, filename ) + self:__Save(interval,filepath,filename) end return self end @@ -4387,58 +4340,58 @@ do -- @param #string To To state. -- @param #string path (Optional) Path where the file is located. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized. -- @param #string filename (Optional) File name for loading. Default is "CTLD__Persist.csv". - function CTLD:onbeforeLoad( From, Event, To, path, filename ) - self:T( { From, Event, To, path, filename } ) + function CTLD:onbeforeLoad(From, Event, To, path, filename) + self:T({From, Event, To, path, filename}) if not self.enableLoadSave then return self end --- Function that check if a file exists. - local function _fileexists( name ) - local f = io.open( name, "r" ) - if f ~= nil then - io.close( f ) + local function _fileexists(name) + local f=io.open(name,"r") + if f~=nil then + io.close(f) return true else return false end end - + -- Set file name and path - filename = filename or self.filename + filename=filename or self.filename path = path or self.filepath - + -- Check io module is available. if not io then - self:E( self.lid .. "WARNING: io not desanitized. Cannot load file." ) + self:E(self.lid.."WARNING: io not desanitized. Cannot load file.") return false end - + -- Check default path. - if path == nil and not lfs then - self:E( self.lid .. "WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder." ) + if path==nil and not lfs then + self:E(self.lid.."WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") end - + -- Set path or default. if lfs then - path = path or lfs.writedir() + path=path or lfs.writedir() end - + -- Set path. - if path ~= nil then - filename = path .. "\\" .. filename + if path~=nil then + filename=path.."\\"..filename end - + -- Check if file exists. - local exists = _fileexists( filename ) - + local exists=_fileexists(filename) + if exists then return true else - self:E( self.lid .. string.format( "WARNING: State file %s might not exist.", filename ) ) + self:E(self.lid..string.format("WARNING: State file %s might not exist.", filename)) return false - -- return self + --return self end - + end --- On after "Load" event. Loads dropped units from file. @@ -4448,98 +4401,98 @@ do -- @param #string To To state. -- @param #string path (Optional) Path where the file is located. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized. -- @param #string filename (Optional) File name for loading. Default is "CTLD__Persist.csv". - function CTLD:onafterLoad( From, Event, To, path, filename ) - self:T( { From, Event, To, path, filename } ) + function CTLD:onafterLoad(From, Event, To, path, filename) + self:T({From, Event, To, path, filename}) if not self.enableLoadSave then return self end --- Function that loads data from a file. - local function _loadfile( filename ) - local f = assert( io.open( filename, "rb" ) ) - local data = f:read( "*all" ) + local function _loadfile(filename) + local f=assert(io.open(filename, "rb")) + local data=f:read("*all") f:close() return data end - + -- Set file name and path - filename = filename or self.filename + filename=filename or self.filename path = path or self.filepath - + -- Set path or default. if lfs then - path = path or lfs.writedir() + path=path or lfs.writedir() end - + -- Set path. - if path ~= nil then - filename = path .. "\\" .. filename + if path~=nil then + filename=path.."\\"..filename end - + -- Info message. - local text = string.format( "Loading CTLD state from file %s", filename ) - MESSAGE:New( text, 10 ):ToAllIf( self.Debug ) - self:I( self.lid .. text ) - - local file = assert( io.open( filename, "rb" ) ) - + local text=string.format("Loading CTLD state from file %s", filename) + MESSAGE:New(text,10):ToAllIf(self.Debug) + self:I(self.lid..text) + + local file=assert(io.open(filename, "rb")) + local loadeddata = {} for line in file:lines() do - -- self:I({line=type(line)}) - loadeddata[#loadeddata + 1] = line + --self:I({line=type(line)}) + loadeddata[#loadeddata+1] = line end file:close() - + -- remove header - table.remove( loadeddata, 1 ) - - for _id, _entry in pairs( loadeddata ) do - local dataset = UTILS.Split( _entry, "," ) + table.remove(loadeddata, 1) + + for _id,_entry in pairs (loadeddata) do + local dataset = UTILS.Split(_entry,",") -- 1=Group,2=x,3=y,4=z,5=CargoName,6=CargoTemplates,7=CargoType,8=CratesNeeded,9=CrateMass local groupname = dataset[1] local vec2 = {} - vec2.x = tonumber( dataset[2] ) - vec2.y = tonumber( dataset[4] ) + vec2.x = tonumber(dataset[2]) + vec2.y = tonumber(dataset[4]) local cargoname = dataset[5] local cargotype = dataset[7] - if type( groupname ) == "string" and groupname ~= "STATIC" then + if type(groupname) == "string" and groupname ~= "STATIC" then local cargotemplates = dataset[6] - cargotemplates = string.gsub( cargotemplates, "{", "" ) - cargotemplates = string.gsub( cargotemplates, "}", "" ) - cargotemplates = UTILS.Split( cargotemplates, ";" ) - local size = tonumber( dataset[8] ) - local mass = tonumber( dataset[9] ) - -- self:I({groupname,vec3,cargoname,cargotemplates,cargotype,size,mass}) + cargotemplates = string.gsub(cargotemplates,"{","") + cargotemplates = string.gsub(cargotemplates,"}","") + cargotemplates = UTILS.Split(cargotemplates,";") + local size = tonumber(dataset[8]) + local mass = tonumber(dataset[9]) + --self:I({groupname,vec3,cargoname,cargotemplates,cargotype,size,mass}) -- inject at Vec2 - local dropzone = ZONE_RADIUS:New( "DropZone", vec2, 20 ) + local dropzone = ZONE_RADIUS:New("DropZone",vec2,20) if cargotype == CTLD_CARGO.Enum.VEHICLE or cargotype == CTLD_CARGO.Enum.FOB then - local injectvehicle = CTLD_CARGO:New( nil, cargoname, cargotemplates, cargotype, true, true, size, nil, true, mass ) - self:InjectVehicles( dropzone, injectvehicle ) + local injectvehicle = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) + self:InjectVehicles(dropzone,injectvehicle) elseif cargotype == CTLD_CARGO.Enum.TROOPS or cargotype == CTLD_CARGO.Enum.ENGINEERS then - local injecttroops = CTLD_CARGO:New( nil, cargoname, cargotemplates, cargotype, true, true, size, nil, true, mass ) - self:InjectTroops( dropzone, injecttroops ) + local injecttroops = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) + self:InjectTroops(dropzone,injecttroops) end - elseif (type( groupname ) == "string" and groupname == "STATIC") or cargotype == CTLD_CARGO.Enum.REPAIR then + elseif (type(groupname) == "string" and groupname == "STATIC") or cargotype == CTLD_CARGO.Enum.REPAIR then local cargotemplates = dataset[6] - local size = tonumber( dataset[8] ) - local mass = tonumber( dataset[9] ) - local dropzone = ZONE_RADIUS:New( "DropZone", vec2, 20 ) + local size = tonumber(dataset[8]) + local mass = tonumber(dataset[9]) + local dropzone = ZONE_RADIUS:New("DropZone",vec2,20) -- STATIC,-84037,154,834021,Humvee,{Humvee;},Vehicle,1,100 -- STATIC,-84036,154,834018,Ammunition-1,ammo_cargo,Static,1,500 local injectstatic = nil if cargotype == CTLD_CARGO.Enum.VEHICLE or cargotype == CTLD_CARGO.Enum.FOB then - cargotemplates = string.gsub( cargotemplates, "{", "" ) - cargotemplates = string.gsub( cargotemplates, "}", "" ) - cargotemplates = UTILS.Split( cargotemplates, ";" ) - injectstatic = CTLD_CARGO:New( nil, cargoname, cargotemplates, cargotype, true, true, size, nil, true, mass ) + cargotemplates = string.gsub(cargotemplates,"{","") + cargotemplates = string.gsub(cargotemplates,"}","") + cargotemplates = UTILS.Split(cargotemplates,";") + injectstatic = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) elseif cargotype == CTLD_CARGO.Enum.STATIC or cargotype == CTLD_CARGO.Enum.REPAIR then - injectstatic = CTLD_CARGO:New( nil, cargoname, cargotemplates, cargotype, true, true, size, nil, true, mass ) + injectstatic = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) end if injectstatic then - self:InjectStatics( dropzone, injectstatic ) + self:InjectStatics(dropzone,injectstatic) end - end + end end - + return self end end -- end do From 41c9c15ae50c39a7425f84cbbce573e4fd853413 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 16 Jan 2022 11:39:19 +0100 Subject: [PATCH 054/200] CTLD, CSAR - added support for UH-60L --- Moose Development/Moose/Ops/CSAR.lua | 1931 +++++++++++++------------- Moose Development/Moose/Ops/CTLD.lua | 5 +- 2 files changed, 967 insertions(+), 969 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index df96af4c4..859c2e3c2 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -1,17 +1,17 @@ --- **Ops** -- Combat Search and Rescue. -- -- === --- +-- -- **CSAR** - MOOSE based Helicopter CSAR Operations. --- +-- -- === --- +-- -- ## Missions: -- -- ### [CSAR - Combat Search & Rescue](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20CSAR) --- +-- -- === --- +-- -- **Main Features:** -- -- * MOOSE-based Helicopter CSAR Operations for Players. @@ -40,22 +40,22 @@ -- ![Banner Image](OPS_CSAR.jpg) -- -- # CSAR Concept --- +-- -- * MOOSE-based Helicopter CSAR Operations for Players. --- * Object oriented refactoring of Ciribob's fantastic CSAR script. --- * No need for extra MIST loading. +-- * Object oriented refactoring of Ciribob\'s fantastic CSAR script. +-- * No need for extra MIST loading. -- * Additional events to tailor your mission. --- * Optional SpawnCASEVAC to create casualties without beacon (e.g. handling dead ground vehicles and create CASEVAC requests). --- +-- * Optional SpawnCASEVAC to create casualties without beacon (e.g. handling dead ground vehicles and create CASVAC requests). +-- -- ## 0. Prerequisites --- --- You need to load an .ogg sound file for the pilot's beacons into the mission, e.g. "beacon.ogg", use a once trigger, "sound to country" for that. +-- +-- You need to load an .ogg soundfile for the pilot\'s beacons into the mission, e.g. "beacon.ogg", use a once trigger, "sound to country" for that. -- Create a late-activated single infantry unit as template in the mission editor and name it e.g. "Downed Pilot". --- +-- -- ## 1. Basic Setup --- +-- -- A basic setup example is the following: --- +-- -- -- Instantiate and start a CSAR for the blue side, with template "Downed Pilot" and alias "Luftrettung" -- local my_csar = CSAR:New(coalition.side.BLUE,"Downed Pilot","Luftrettung") -- -- options @@ -63,35 +63,35 @@ -- my_csar.invisiblecrew = false -- downed pilot spawn is visible -- -- start the FSM -- my_csar:__Start(5) --- +-- -- ## 2. Options --- +-- -- The following options are available (with their defaults). Only set the ones you want changed: -- --- self.allowDownedPilotCAcontrol = false -- Set to false if you don't want to allow control by Combined Arms. +-- self.allowDownedPilotCAcontrol = false -- Set to false if you don\'t want to allow control by Combined Arms. -- self.allowFARPRescue = true -- allows pilots to be rescued by landing at a FARP or Airbase. Else MASH only! -- self.FARPRescueDistance = 1000 -- you need to be this close to a FARP or Airport for the pilot to be rescued. --- self.autosmoke = false -- automatically smoke a downed pilot's location when a helicopter is near. --- self.autosmokedistance = 1000 -- distance in meters for automatic smoke deployment +-- self.autosmoke = false -- automatically smoke a downed pilot\'s location when a heli is near. +-- self.autosmokedistance = 1000 -- distance for autosmoke -- self.coordtype = 1 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. -- self.csarOncrash = false -- (WIP) If set to true, will generate a downed pilot when a plane crashes as well. -- self.enableForAI = false -- set to false to disable AI pilots from being rescued. --- self.pilotRuntoExtractPoint = true -- Downed pilot will run to the rescue helicopter up to self.extractDistance in meters. +-- self.pilotRuntoExtractPoint = true -- Downed pilot will run to the rescue helicopter up to self.extractDistance in meters. -- self.extractDistance = 500 -- Distance the downed pilot will start to run to the rescue helicopter. -- self.immortalcrew = true -- Set to true to make wounded crew immortal. --- self.invisiblecrew = false -- Set to true to make wounded crew invisible. +-- self.invisiblecrew = false -- Set to true to make wounded crew insvisible. -- self.loadDistance = 75 -- configure distance for pilots to get into helicopter in meters. -- self.mashprefix = {"MASH"} -- prefixes of #GROUP objects used as MASHes. -- self.max_units = 6 -- max number of pilots that can be carried if #CSAR.AircraftType is undefined. -- self.messageTime = 15 -- Time to show messages for in seconds. Doubled for long messages. --- self.radioSound = "beacon.ogg" -- the name of the sound file to use for the pilots' radio beacons. --- self.smokecolor = 4 -- Color of smoke marker, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue. +-- self.radioSound = "beacon.ogg" -- the name of the sound file to use for the pilots\' radio beacons. +-- self.smokecolor = 4 -- Color of smokemarker, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue. -- self.useprefix = true -- Requires CSAR helicopter #GROUP names to have the prefix(es) defined below. --- self.csarPrefix = { "helicargo", "MEDEVAC"} -- #GROUP name prefixes used for useprefix=true - DO NOT use # in helicopter names in the Mission Editor! +-- self.csarPrefix = { "helicargo", "MEDEVAC"} -- #GROUP name prefixes used for useprefix=true - DO NOT use # in helicopter names in the Mission Editor! -- self.verbose = 0 -- set to > 1 for stats output for debugging. -- -- (added 0.1.4) limit amount of downed pilots spawned by **ejection** events -- self.limitmaxdownedpilots = true --- self.maxdownedpilots = 10 +-- self.maxdownedpilots = 10 -- -- (added 0.1.8) - allow to set far/near distance for approach and optionally pilot must open doors -- self.approachdist_far = 5000 -- switch do 10 sec interval approach mode, meters -- self.approachdist_near = 3000 -- switch to 5 sec interval approach mode, meters @@ -106,13 +106,13 @@ -- self.countryblue= country.id.USA -- self.countryred = country.id.RUSSIA -- self.countryneutral = country.id.UN_PEACEKEEPERS --- +-- -- ## 2.1 Experimental Features --- --- WARNING - Here'll be dragons! +-- +-- WARNING - Here\'ll be dragons! -- DANGER - For this to work you need to de-sanitize your mission environment (all three entries) in \Scripts\MissionScripting.lua -- Needs SRS => 1.9.6 to work (works on the **server** side of SRS) --- self.useSRS = false -- Set true to use FF's SRS integration +-- self.useSRS = false -- Set true to use FF\'s SRS integration -- self.SRSPath = "E:\\Progra~1\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your SRS installation -- server(!) -- self.SRSchannel = 300 -- radio channel -- self.SRSModulation = radio.modulation.AM -- modulation @@ -120,78 +120,78 @@ -- self.csarUsePara = false -- If set to true, will use the LandingAfterEjection Event instead of Ejection --shagrat -- -- ## 3. Results --- +-- -- Number of successful landings with save pilots and aggregated number of saved pilots is stored in these variables in the object: --- +-- -- self.rescues -- number of successful landings *with* saved pilots -- self.rescuedpilots -- aggregated number of pilots rescued from the field (of *all* players) --- +-- -- ## 4. Events -- -- The class comes with a number of FSM-based events that missions designers can use to shape their mission. -- These are: --- --- ### 4.1. PilotDown. --- +-- +-- ### 4.1. PilotDown. +-- -- The event is triggered when a new downed pilot is detected. Use e.g. `function my_csar:OnAfterPilotDown(...)` to link into this event: --- +-- -- function my_csar:OnAfterPilotDown(from, event, to, spawnedgroup, frequency, groupname, coordinates_text) -- ... your code here ... -- end --- --- ### 4.2. Approach. --- --- A CSAR helicopter is closing in on a downed pilot. Use e.g. `function my_csar:OnAfterApproach(...)` to link into this event: --- +-- +-- ### 4.2. Approach. +-- +-- A CSAR helicpoter is closing in on a downed pilot. Use e.g. `function my_csar:OnAfterApproach(...)` to link into this event: +-- -- function my_csar:OnAfterApproach(from, event, to, heliname, groupname) -- ... your code here ... -- end --- --- ### 4.3. Boarded. --- +-- +-- ### 4.3. Boarded. +-- -- The pilot has been boarded to the helicopter. Use e.g. `function my_csar:OnAfterBoarded(...)` to link into this event: --- +-- -- function my_csar:OnAfterBoarded(from, event, to, heliname, groupname) -- ... your code here ... -- end --- --- ### 4.4. Returning. --- +-- +-- ### 4.4. Returning. +-- -- The CSAR helicopter is ready to return to an Airbase, FARP or MASH. Use e.g. `function my_csar:OnAfterReturning(...)` to link into this event: --- +-- -- function my_csar:OnAfterReturning(from, event, to, heliname, groupname) -- ... your code here ... -- end --- --- ### 4.5. Rescued. --- +-- +-- ### 4.5. Rescued. +-- -- The CSAR helicopter has landed close to an Airbase/MASH/FARP and the pilots are safe. Use e.g. `function my_csar:OnAfterRescued(...)` to link into this event: --- +-- -- function my_csar:OnAfterRescued(from, event, to, heliunit, heliname, pilotssaved) -- ... your code here ... --- end +-- end -- -- ## 5. Spawn downed pilots at location to be picked up. --- +-- -- If missions designers want to spawn downed pilots into the field, e.g. at mission begin to give the helicopter guys works, they can do this like so: --- +-- -- -- Create downed "Pilot Wagner" in #ZONE "CSAR_Start_1" at a random point for the blue coalition -- my_csar:SpawnCSARAtZone( "CSAR_Start_1", coalition.side.BLUE, "Pilot Wagner", true ) -- -- --Create a casualty and CASEVAC request from a "Point" (VEC2) for the blue coalition --shagrat --- my_csar:SpawnCASEVAC(Point, coalition.side.BLUE) +-- my_csar:SpawnCASEVAC(Point, coalition.side.BLUE) -- -- @field #CSAR CSAR = { - ClassName = "CSAR", - verbose = 0, - lid = "", - coalition = 1, - coalitiontxt = "blue", + ClassName = "CSAR", + verbose = 0, + lid = "", + coalition = 1, + coalitiontxt = "blue", FreeVHFFrequencies = {}, UsedVHFFrequencies = {}, takenOff = {}, - csarUnits = {}, -- table of unit names + csarUnits = {}, -- table of unit names downedPilots = {}, woundedGroups = {}, landedStatus = {}, @@ -201,11 +201,11 @@ CSAR = { smokeMarkers = {}, -- tracks smoke markers for groups heliVisibleMessage = {}, -- tracks if the first message has been sent of the heli being visible heliCloseMessage = {}, -- tracks heli close message ie heli < 500m distance - max_units = 6, -- number of pilots that can be carried + max_units = 6, --number of pilots that can be carried hoverStatus = {}, -- tracks status of a helis hover above a downed pilot pilotDisabled = {}, -- tracks what aircraft a pilot is disabled for pilotLives = {}, -- tracks how many lives a pilot has - useprefix = true, -- Use the Prefix defined below, requires Unit to have the Prefix defined + useprefix = true, -- Use the Prefixed defined below, Requires Unit have the Prefix defined below csarPrefix = {}, template = nil, mash = {}, @@ -240,14 +240,15 @@ CSAR.AircraftType["SA342L"] = 4 CSAR.AircraftType["SA342M"] = 4 CSAR.AircraftType["UH-1H"] = 8 CSAR.AircraftType["Mi-8MTV2"] = 12 -CSAR.AircraftType["Mi-8MT"] = 12 -CSAR.AircraftType["Mi-24P"] = 8 +CSAR.AircraftType["Mi-8MT"] = 12 +CSAR.AircraftType["Mi-24P"] = 8 CSAR.AircraftType["Mi-24V"] = 8 -CSAR.AircraftType["Bell-47"] = 2 +CSAR.AircraftType["Bell-47"] = 2 +CSAR.AircraftType["UH-60L"] = 10 --- CSAR class version. -- @field #string version -CSAR.version = "1.0.1r1" +CSAR.version="1.0.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -266,49 +267,49 @@ CSAR.version = "1.0.1r1" -- @param #string Template Name of the late activated infantry unit standing in for the downed pilot. -- @param #string Alias An *optional* alias how this object is called in the logs etc. -- @return #CSAR self -function CSAR:New( Coalition, Template, Alias ) - +function CSAR:New(Coalition, Template, Alias) + -- Inherit everything from FSM class. - local self = BASE:Inherit( self, FSM:New() ) -- #CSAR - - -- set Coalition - if Coalition and type( Coalition ) == "string" then - if Coalition == "blue" then - self.coalition = coalition.side.BLUE + local self=BASE:Inherit(self, FSM:New()) -- #CSAR + + --set Coalition + if Coalition and type(Coalition)=="string" then + if Coalition=="blue" then + self.coalition=coalition.side.BLUE self.coalitiontxt = Coalition - elseif Coalition == "red" then - self.coalition = coalition.side.RED + elseif Coalition=="red" then + self.coalition=coalition.side.RED self.coalitiontxt = Coalition - elseif Coalition == "neutral" then - self.coalition = coalition.side.NEUTRAL + elseif Coalition=="neutral" then + self.coalition=coalition.side.NEUTRAL self.coalitiontxt = Coalition else - self:E( "ERROR: Unknown coalition in CSAR!" ) + self:E("ERROR: Unknown coalition in CSAR!") end else self.coalition = Coalition - self.coalitiontxt = string.lower( UTILS.GetCoalitionName( self.coalition ) ) + self.coalitiontxt = string.lower(UTILS.GetCoalitionName(self.coalition)) end - + -- Set alias. if Alias then - self.alias = tostring( Alias ) + self.alias=tostring(Alias) else - self.alias = "Red Cross" + self.alias="Red Cross" if self.coalition then - if self.coalition == coalition.side.RED then - self.alias = "IFRC" - elseif self.coalition == coalition.side.BLUE then - self.alias = "CSAR" + if self.coalition==coalition.side.RED then + self.alias="IFRC" + elseif self.coalition==coalition.side.BLUE then + self.alias="CSAR" end end end - + -- Set some string id for output to DCS.log file. - self.lid = string.format( "%s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName( self.coalition ) or "unknown" ) - + self.lid=string.format("%s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown") + -- Start State. - self:SetStartState( "Stopped" ) + self:SetStartState("Stopped") -- Add FSM transitions. -- From State --> Event --> To State @@ -339,70 +340,70 @@ function CSAR:New( Coalition, Template, Alias ) self.woundedGroups = {} -- contains the new group of units self.downedPilots = {} -- Replacement woundedGroups self.downedpilotcounter = 1 - + -- settings, counters etc self.rescues = 0 -- counter for successful rescue landings at FARP/AFB/MASH self.rescuedpilots = 0 -- counter for saved pilots self.csarOncrash = false -- If set to true, will generate a csar when a plane crashes as well. - self.allowDownedPilotCAcontrol = false -- Set to false if you don't want to allow control by Combined arms. + self.allowDownedPilotCAcontrol = false -- Set to false if you don\'t want to allow control by Combined arms. self.enableForAI = false -- set to false to disable AI units from being rescued. - self.smokecolor = 4 -- Color of smoke marker for blue side, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue + self.smokecolor = 4 -- Color of smokemarker for blue side, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue self.coordtype = 2 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. self.immortalcrew = true -- Set to true to make wounded crew immortal - self.invisiblecrew = false -- Set to true to make wounded crew invisible - self.messageTime = 15 -- Time to show longer messages for in seconds - self.pilotRuntoExtractPoint = true -- Downed Pilot will run to the rescue helicopter up to self.extractDistance METERS. + self.invisiblecrew = false -- Set to true to make wounded crew insvisible + self.messageTime = 15 -- Time to show longer messages for in seconds + self.pilotRuntoExtractPoint = true -- Downed Pilot will run to the rescue helicopter up to self.extractDistance METERS self.loadDistance = 75 -- configure distance for pilot to get in helicopter in meters. - self.extractDistance = 500 -- Distance the Downed pilot will run to the rescue helicopter. + self.extractDistance = 500 -- Distance the Downed pilot will run to the rescue helicopter self.loadtimemax = 135 -- seconds - self.radioSound = "beacon.ogg" -- the name of the sound file to use for the Pilot radio beacons. If this isn't added to the mission BEACONS WONT WORK! + self.radioSound = "beacon.ogg" -- the name of the sound file to use for the Pilot radio beacons. If this isnt added to the mission BEACONS WONT WORK! self.beaconRefresher = 29 -- seconds - self.allowFARPRescue = true -- allows pilot to be rescued by landing at a FARP or Airbase + self.allowFARPRescue = true --allows pilot to be rescued by landing at a FARP or Airbase self.FARPRescueDistance = 1000 -- you need to be this close to a FARP or Airport for the pilot to be rescued. - self.max_units = 6 -- max number of pilots that can be carried - self.useprefix = true -- Use the Prefixed defined below, Requires Unit have the Prefix defined below. - self.csarPrefix = { "helicargo", "MEDEVAC" } -- prefixes used for useprefix=true - DON'T use # in names! + self.max_units = 6 --max number of pilots that can be carried + self.useprefix = true -- Use the Prefixed defined below, Requires Unit have the Prefix defined below + self.csarPrefix = { "helicargo", "MEDEVAC"} -- prefixes used for useprefix=true - DON\'T use # in names! self.template = Template or "generic" -- template for downed pilot - self.mashprefix = { "MASH" } -- prefixes used to find MASHes - + self.mashprefix = {"MASH"} -- prefixes used to find MASHes + self.autosmoke = false -- automatically smoke location when heli is near - self.autosmokedistance = 2000 -- distance in meters for automatic smoke deployment + self.autosmokedistance = 2000 -- distance for autosmoke -- added 0.1.4 self.limitmaxdownedpilots = true self.maxdownedpilots = 25 -- generate Frequencies self:_GenerateVHFrequencies() -- added 0.1.8 - self.approachdist_far = 5000 -- switch to 10 sec interval approach mode, meters + self.approachdist_far = 5000 -- switch do 10 sec interval approach mode, meters self.approachdist_near = 3000 -- switch to 5 sec interval approach mode, meters self.pilotmustopendoors = false -- switch to true to enable check on open doors self.suppressmessages = false - + -- added 0.1.11r1 self.rescuehoverheight = 20 self.rescuehoverdistance = 10 - + -- added 0.1.12 - self.countryblue = country.id.USA + self.countryblue= country.id.USA self.countryred = country.id.RUSSIA self.countryneutral = country.id.UN_PEACEKEEPERS - + -- added 0.1.3 self.csarUsePara = false -- shagrat set to true, will use the LandingAfterEjection Event instead of Ejection - - -- WARNING - here'll be dragons + + -- WARNING - here\'ll be dragons -- for this to work you need to de-sanitize your mission environment in \Scripts\MissionScripting.lua -- needs SRS => 1.9.6 to work (works on the *server* side) - self.useSRS = false -- Use FF's SRS integration + self.useSRS = false -- Use FF\'s SRS integration self.SRSPath = "E:\\Progra~1\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your server(!) self.SRSchannel = 300 -- radio channel self.SRSModulation = radio.modulation.AM -- modulation - + ------------------------ --- Pseudo Functions --- ------------------------ - - --- Triggers the FSM event "Start". Starts the CSAR. Initializes parameters and starts event handlers. + + --- Triggers the FSM event "Start". Starts the CSAR. Initializes parameters and starts event handlers. -- @function [parent=#CSAR] Start -- @param #CSAR self @@ -427,7 +428,7 @@ function CSAR:New( Coalition, Template, Alias ) -- @function [parent=#CSAR] __Status -- @param #CSAR self -- @param #number delay Delay in seconds. - + --- On After "PilotDown" event. Downed Pilot detected. -- @function [parent=#CSAR] OnAfterPilotDown -- @param #CSAR self @@ -438,7 +439,7 @@ function CSAR:New( Coalition, Template, Alias ) -- @param #number Frequency Beacon frequency in kHz. -- @param #string Leadername Name of the #UNIT of the downed pilot. -- @param #string CoordinatesText String of the position of the pilot. Format determined by self.coordtype. - + --- On After "Aproach" event. Heli close to downed Pilot. -- @function [parent=#CSAR] OnAfterApproach -- @param #CSAR self @@ -446,27 +447,27 @@ function CSAR:New( Coalition, Template, Alias ) -- @param #string Event Event. -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. - -- @param #string Woundedgroupname Name of the downed pilot's group. - - --- On After "Boarded" event. Downed pilot boarded heli. + -- @param #string Woundedgroupname Name of the downed pilot\'s group. + + --- On After "Boarded" event. Downed pilot boarded heli. -- @function [parent=#CSAR] OnAfterBoarded -- @param #CSAR self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. - -- @param #string Woundedgroupname Name of the downed pilot's group. + -- @param #string Woundedgroupname Name of the downed pilot\'s group. - --- On After "Returning" event. Heli can return home with downed pilot(s). + --- On After "Returning" event. Heli can return home with downed pilot(s). -- @function [parent=#CSAR] OnAfterReturning -- @param #CSAR self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. - -- @param #string Woundedgroupname Name of the downed pilot's group. - - --- On After "Rescued" event. Pilot(s) have been brought to the MASH/FARP/AFB. + -- @param #string Woundedgroupname Name of the downed pilot\'s group. + + --- On After "Rescued" event. Pilot(s) have been brought to the MASH/FARP/AFB. -- @function [parent=#CSAR] OnAfterRescued -- @param #CSAR self -- @param #string From From state. @@ -475,7 +476,7 @@ function CSAR:New( Coalition, Template, Alias ) -- @param Wrapper.Unit#UNIT HeliUnit Unit of the helicopter. -- @param #string HeliName Name of the helicopter group. -- @param #number PilotsSaved Number of the saved pilots on board when landing. - + --- On After "KIA" event. Pilot is dead. -- @function [parent=#CSAR] OnAfterKIA -- @param #CSAR self @@ -483,7 +484,7 @@ function CSAR:New( Coalition, Template, Alias ) -- @param #string Event Event. -- @param #string To To state. -- @param #string Pilotname Name of the pilot KIA. - + return self end @@ -502,9 +503,9 @@ end -- @param #number Frequency Frequency of the NDB in Hz -- @param #string Playername Name of Player (if applicable) -- @return #CSAR self. -function CSAR:_CreateDownedPilotTrack( Group, Groupname, Side, OriginalUnit, Description, Typename, Frequency, Playername ) - self:T( { "_CreateDownedPilotTrack", Groupname, Side, OriginalUnit, Description, Typename, Frequency, Playername } ) - +function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername) + self:T({"_CreateDownedPilotTrack",Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername}) + -- create new entry local DownedPilot = {} -- #CSAR.DownedPilot DownedPilot.desc = Description or "" @@ -518,16 +519,16 @@ function CSAR:_CreateDownedPilotTrack( Group, Groupname, Side, OriginalUnit, Des DownedPilot.group = Group DownedPilot.timestamp = 0 DownedPilot.alive = true - + -- Add Pilot local PilotTable = self.downedPilots local counter = self.downedpilotcounter PilotTable[counter] = {} PilotTable[counter] = DownedPilot - self:T( { Table = PilotTable } ) + self:T({Table=PilotTable}) self.downedPilots = PilotTable -- Increase counter - self.downedpilotcounter = self.downedpilotcounter + 1 + self.downedpilotcounter = self.downedpilotcounter+1 return self end @@ -535,13 +536,13 @@ end -- @param #CSAR self -- @param #string _heliName -- @return #number count -function CSAR:_PilotsOnboard( _heliName ) - self:T( self.lid .. " _PilotsOnboard" ) - local count = 0 +function CSAR:_PilotsOnboard(_heliName) + self:T(self.lid .. " _PilotsOnboard") + local count = 0 if self.inTransitGroups[_heliName] then - for _, _group in pairs( self.inTransitGroups[_heliName] ) do - count = count + 1 - end + for _, _group in pairs(self.inTransitGroups[_heliName]) do + count = count + 1 + end end return count end @@ -550,16 +551,16 @@ end -- @param #CSAR self -- @param #string _unitname Name of unit. -- @return #boolean Outcome -function CSAR:_DoubleEjection( _unitname ) - if self.lastCrash[_unitname] then - local _time = self.lastCrash[_unitname] - if timer.getTime() - _time < 10 then - self:E( self.lid .. "Caught double ejection!" ) - return true +function CSAR:_DoubleEjection(_unitname) + if self.lastCrash[_unitname] then + local _time = self.lastCrash[_unitname] + if timer.getTime() - _time < 10 then + self:E(self.lid.."Caught double ejection!") + return true + end end - end - self.lastCrash[_unitname] = timer.getTime() - return false + self.lastCrash[_unitname] = timer.getTime() + return false end --- (Internal) Spawn a downed pilot @@ -569,18 +570,16 @@ end -- @param #number frequency Frequency of the pilot's beacon -- @return Wrapper.Group#GROUP group The #GROUP object. -- @return #string alias The alias name. -function CSAR:_SpawnPilotInField( country, point, frequency ) - self:T( { country, point, frequency } ) +function CSAR:_SpawnPilotInField(country,point,frequency) + self:T({country,point,frequency}) local freq = frequency or 1000 local freq = freq / 1000 -- kHz - for i = 1, 10 do - math.random( i, 10000 ) - end - if point:IsSurfaceTypeWater() then - point.y = 0 + for i=1,10 do + math.random(i,10000) end + if point:IsSurfaceTypeWater() then point.y = 0 end local template = self.template - local alias = string.format( "Pilot %.2fkHz-%d", freq, math.random( 1, 99 ) ) + local alias = string.format("Pilot %.2fkHz-%d", freq, math.random(1,99)) local coalition = self.coalition local pilotcacontrol = self.allowDownedPilotCAcontrol -- Switch AI on/oof - is this really correct for CA? local _spawnedGroup = SPAWN @@ -597,32 +596,32 @@ end --- (Internal) Add options to a downed pilot -- @param #CSAR self -- @param Wrapper.Group#GROUP group Group to use. -function CSAR:_AddSpecialOptions( group ) - self:T( self.lid .. " _AddSpecialOptions" ) - self:T( { group } ) - +function CSAR:_AddSpecialOptions(group) + self:T(self.lid.." _AddSpecialOptions") + self:T({group}) + local immortalcrew = self.immortalcrew local invisiblecrew = self.invisiblecrew if immortalcrew then local _setImmortal = { - id = 'SetImmortal', - params = { - value = true, - }, + id = 'SetImmortal', + params = { + value = true + } } - group:SetCommand( _setImmortal ) + group:SetCommand(_setImmortal) end if invisiblecrew then local _setInvisible = { - id = 'SetInvisible', - params = { - value = true, - }, + id = 'SetInvisible', + params = { + value = true + } } - group:SetCommand( _setInvisible ) + group:SetCommand(_setInvisible) end - + group:OptionAlarmStateGreen() group:OptionROEHoldFire() return self @@ -640,61 +639,59 @@ end -- @param #boolean noMessage -- @param #string _description Description -- @param #boolean forcedesc Use the description only for the pilot track entry -function CSAR:_AddCsar( _coalition, _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description, forcedesc ) - self:T( self.lid .. " _AddCsar" ) - self:T( { _coalition, _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description } ) +function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description, forcedesc ) + self:T(self.lid .. " _AddCsar") + self:T({_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description}) local template = self.template - + if not _freq then _freq = self:_GenerateADFFrequency() - if not _freq then - _freq = 333000 - end -- noob catch - end - - local _spawnedGroup, _alias = self:_SpawnPilotInField( _country, _point, _freq ) - + if not _freq then _freq = 333000 end --noob catch + end + + local _spawnedGroup, _alias = self:_SpawnPilotInField(_country,_point,_freq) + local _typeName = _typeName or "Pilot" - + if not noMessage then - if _freq ~= 0 then -- shagrat different CASEVAC msg - self:_DisplayToAllSAR( "MAYDAY MAYDAY! " .. _typeName .. " is down. ", self.coalition, self.messageTime ) - else - self:_DisplayToAllSAR( "Troops In Contact. " .. _typeName .. " requests CASEVAC. ", self.coalition, self.messageTime ) - end + if _freq ~= 0 then --shagrat different CASEVAC msg + self:_DisplayToAllSAR("MAYDAY MAYDAY! " .. _typeName .. " is down. ", self.coalition, self.messageTime) + else + self:_DisplayToAllSAR("Troops In Contact. " .. _typeName .. " requests CASEVAC. ", self.coalition, self.messageTime) end - - if (_freq and _freq ~= 0) then -- shagrat only add beacon if _freq is NOT 0 - self:_AddBeaconToGroup( _spawnedGroup, _freq ) end - - self:_AddSpecialOptions( _spawnedGroup ) + + if (_freq and _freq ~= 0) then --shagrat only add beacon if _freq is NOT 0 + self:_AddBeaconToGroup(_spawnedGroup, _freq) + end + + self:_AddSpecialOptions(_spawnedGroup) local _text = _description if not forcedesc then if _playerName ~= nil then - if _freq ~= 0 then -- shagrat - _text = "Pilot " .. _playerName - else - _text = "TIC - " .. _playerName - end + if _freq ~= 0 then --shagrat + _text = "Pilot " .. _playerName + else + _text = "TIC - " .. _playerName + end elseif _unitName ~= nil then - if _freq ~= 0 then -- shagrat - _text = "AI Pilot of " .. _unitName - else - _text = "TIC - " .. _unitName - end + if _freq ~= 0 then --shagrat + _text = "AI Pilot of " .. _unitName + else + _text = "TIC - " .. _unitName end end - self:T( { _spawnedGroup, _alias } ) - + end + self:T({_spawnedGroup, _alias}) + local _GroupName = _spawnedGroup:GetName() or _alias - self:_CreateDownedPilotTrack( _spawnedGroup, _GroupName, _coalition, _unitName, _text, _typeName, _freq, _playerName ) - - self:_InitSARForPilot( _spawnedGroup, _unitName, _freq, noMessage ) -- shagrat use unitName to have the aircraft callsign / descriptive "name" etc. + self:_CreateDownedPilotTrack(_spawnedGroup,_GroupName,_coalition,_unitName,_text,_typeName,_freq,_playerName) + self:_InitSARForPilot(_spawnedGroup, _unitName, _freq, noMessage) --shagrat use unitName to have the aircraft callsign / descriptive "name" etc. + return self end @@ -704,31 +701,31 @@ end -- @param #number _coalition Coalition. -- @param #string _description (optional) Description. -- @param #boolean _randomPoint (optional) Random yes or no. --- @param #boolean _nomessage (optional) If true, don't send a message to SAR. +-- @param #boolean _nomessage (optional) If true, don\'t send a message to SAR. -- @param #string unitname (optional) Name of the lost unit. -- @param #string typename (optional) Type of plane. -- @param #boolean forcedesc (optional) Force to use the description passed only for the pilot track entry. Use to have fully custom names. -function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _nomessage, unitname, typename, forcedesc ) - self:T( self.lid .. " _SpawnCsarAtZone" ) +function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _nomessage, unitname, typename, forcedesc) + self:T(self.lid .. " _SpawnCsarAtZone") local freq = self:_GenerateADFFrequency() - local _triggerZone = ZONE:New( _zone ) -- trigger to use as reference position + local _triggerZone = ZONE:New(_zone) -- trigger to use as reference position if _triggerZone == nil then - self:E( self.lid .. "ERROR: Can\'t find zone called " .. _zone, 10 ) + self:E(self.lid.."ERROR: Can\'t find zone called " .. _zone, 10) return end - + local _description = _description or "PoW" local unitname = unitname or "Old Rusty" local typename = typename or "Phantom II" - + local pos = {} if _randomPoint then - local _pos = _triggerZone:GetRandomPointVec3() - pos = COORDINATE:NewFromVec3( _pos ) + local _pos = _triggerZone:GetRandomPointVec3() + pos = COORDINATE:NewFromVec3(_pos) else - pos = _triggerZone:GetCoordinate() + pos = _triggerZone:GetCoordinate() end - + local _country = 0 if _coalition == coalition.side.BLUE then _country = self.countryblue @@ -737,9 +734,9 @@ function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _ else _country = self.countryneutral end - - self:_AddCsar( _coalition, _country, pos, typename, unitname, _description, freq, _nomessage, _description, forcedesc ) - + + self:_AddCsar(_coalition, _country, pos, typename, unitname, _description, freq, _nomessage, _description, forcedesc) + return self end @@ -749,7 +746,7 @@ end -- @param #number Coalition Coalition. -- @param #string Description (optional) Description. -- @param #boolean RandomPoint (optional) Random yes or no. --- @param #boolean Nomessage (optional) If true, don't send a message to SAR. +-- @param #boolean Nomessage (optional) If true, don\'t send a message to SAR. -- @param #string Unitname (optional) Name of the lost unit. -- @param #string Typename (optional) Type of plane. -- @param #boolean Forcedesc (optional) Force to use the **description passed only** for the pilot track entry. Use to have fully custom names. @@ -757,8 +754,8 @@ end -- -- -- Create downed "Pilot Wagner" in #ZONE "CSAR_Start_1" at a random point for the blue coalition -- my_csar:SpawnCSARAtZone( "CSAR_Start_1", coalition.side.BLUE, "Wagner", true, false, "Charly-1-1", "F5E" ) -function CSAR:SpawnCSARAtZone( Zone, Coalition, Description, RandomPoint, Nomessage, Unitname, Typename, Forcedesc ) - self:_SpawnCsarAtZone( Zone, Coalition, Description, RandomPoint, Nomessage, Unitname, Typename, Forcedesc ) +function CSAR:SpawnCSARAtZone(Zone, Coalition, Description, RandomPoint, Nomessage, Unitname, Typename, Forcedesc) + self:_SpawnCsarAtZone(Zone, Coalition, Description, RandomPoint, Nomessage, Unitname, Typename, Forcedesc) return self end @@ -767,20 +764,20 @@ end -- @param #string _Point a POINT_VEC2. -- @param #number _coalition Coalition. -- @param #string _description (optional) Description. --- @param #boolean _nomessage (optional) If true, don't send a message to SAR. +-- @param #boolean _nomessage (optional) If true, don\'t send a message to SAR. -- @param #string unitname (optional) Name of the lost unit. -- @param #string typename (optional) Type of plane. -- @param #boolean forcedesc (optional) Force to use the description passed only for the pilot track entry. Use to have fully custom names. -function CSAR:_SpawnCASEVAC( _Point, _coalition, _description, _nomessage, unitname, typename, forcedesc ) -- shagrat added internal Function _SpawnCASEVAC - self:T( self.lid .. " _SpawnCASEVAC" ) - +function CSAR:_SpawnCASEVAC( _Point, _coalition, _description, _nomessage, unitname, typename, forcedesc) --shagrat added internal Function _SpawnCASEVAC + self:T(self.lid .. " _SpawnCASEVAC") + local _description = _description or "CASEVAC" local unitname = unitname or "CASEVAC" local typename = typename or "Ground Commander" - + local pos = {} - pos = _Point - + pos = _Point + local _country = 0 if _coalition == coalition.side.BLUE then _country = self.countryblue @@ -789,9 +786,9 @@ function CSAR:_SpawnCASEVAC( _Point, _coalition, _description, _nomessage, unitn else _country = self.countryneutral end - -- shagrat set frequency to 0 as "flag" for no beacon - self:_AddCsar( _coalition, _country, pos, typename, unitname, _description, 0, _nomessage, _description, forcedesc ) - + --shagrat set frequency to 0 as "flag" for no beacon + self:_AddCsar(_coalition, _country, pos, typename, unitname, _description, 0, _nomessage, _description, forcedesc) + return self end @@ -801,204 +798,204 @@ end -- @param #number Coalition Coalition. -- @param #string Description (optional) Description. -- @param #boolean addBeacon (optional) yes or no. --- @param #boolean Nomessage (optional) If true, don't send a message to SAR. +-- @param #boolean Nomessage (optional) If true, don\'t send a message to SAR. -- @param #string Unitname (optional) Name of the lost unit. -- @param #string Typename (optional) Type of plane. -- @param #boolean Forcedesc (optional) Force to use the **description passed only** for the pilot track entry. Use to have fully custom names. -- @usage If missions designers want to spawn downed pilots into the field, e.g. at mission begin, to give the helicopter guys work, they can do this like so: --- +-- -- -- Create casualty "CASEVAC" at Point #POINT_VEC2 for the blue coalition. -- my_csar:SpawnCASEVAC( POINT_VEC2, coalition.side.BLUE ) -function CSAR:SpawnCASEVAC( Point, Coalition, Description, Nomessage, Unitname, Typename, Forcedesc ) - self:_SpawnCASEVAC( Point, Coalition, Description, Nomessage, Unitname, Typename, Forcedesc ) +function CSAR:SpawnCASEVAC(Point, Coalition, Description, Nomessage, Unitname, Typename, Forcedesc) + self:_SpawnCASEVAC(Point, Coalition, Description, Nomessage, Unitname, Typename, Forcedesc) return self -end -- shagrat end added CASEVAC +end --shagrat end added CASEVAC --- (Internal) Event handler. -- @param #CSAR self -function CSAR:_EventHandler( EventData ) - self:T( self.lid .. " _EventHandler" ) - self:T( { Event = EventData.id } ) - +function CSAR:_EventHandler(EventData) + self:T(self.lid .. " _EventHandler") + self:T({Event = EventData.id}) + local _event = EventData -- Core.Event#EVENTDATA - - -- no Player + + -- no Player if self.enableForAI == false and _event.IniPlayerName == nil then - return - end - - -- no event + return + end + + -- no event if _event == nil or _event.initiator == nil then return false - - -- take off + + -- take off elseif _event.id == EVENTS.Takeoff then -- taken off - self:T( self.lid .. " Event unit - Takeoff" ) - + self:T(self.lid .. " Event unit - Takeoff") + local _coalition = _event.IniCoalition if _coalition ~= self.coalition then - return -- ignore! + return --ignore! end - + if _event.IniGroupName then - self.takenOff[_event.IniUnitName] = true + self.takenOff[_event.IniUnitName] = true end - + return true - - -- player enter unit - elseif _event.id == EVENTS.PlayerEnterAircraft or _event.id == EVENTS.PlayerEnterUnit then -- player entered unit - self:T( self.lid .. " Event unit - Player Enter" ) - + + -- player enter unit + elseif _event.id == EVENTS.PlayerEnterAircraft or _event.id == EVENTS.PlayerEnterUnit then --player entered unit + self:T(self.lid .. " Event unit - Player Enter") + local _coalition = _event.IniCoalition if _coalition ~= self.coalition then - return -- ignore! + return --ignore! end - + if _event.IniPlayerName then - self.takenOff[_event.IniPlayerName] = nil + self.takenOff[_event.IniPlayerName] = nil end - + local _unit = _event.IniUnit local _group = _event.IniGroup if _unit:IsHelicopter() or _group:IsHelicopter() then self:_AddMedevacMenuItem() - end - + end + return true - + elseif (_event.id == EVENTS.PilotDead and self.csarOncrash == false) then - -- Pilot dead + -- Pilot dead + + self:T(self.lid .. " Event unit - Pilot Dead") + + local _unit = _event.IniUnit + local _unitname = _event.IniUnitName + local _group = _event.IniGroup + + if _unit == nil then + return -- error! + end + + local _coalition = _event.IniCoalition + if _coalition ~= self.coalition then + return --ignore! + end + + -- Catch multiple events here? + if self.takenOff[_event.IniUnitName] == true or _group:IsAirborne() then + if self:_DoubleEjection(_unitname) then + return + end - self:T( self.lid .. " Event unit - Pilot Dead" ) - - local _unit = _event.IniUnit - local _unitname = _event.IniUnitName - local _group = _event.IniGroup - - if _unit == nil then - return -- error! - end - - local _coalition = _event.IniCoalition - if _coalition ~= self.coalition then - return -- ignore! - end - - -- Catch multiple events here? - if self.takenOff[_event.IniUnitName] == true or _group:IsAirborne() then - if self:_DoubleEjection( _unitname ) then - return + else + self:T(self.lid .. " Pilot has not taken off, ignore") + end + + return + + elseif _event.id == EVENTS.PilotDead or _event.id == EVENTS.Ejection then + if _event.id == EVENTS.PilotDead and self.csarOncrash == false then + return + end + self:T(self.lid .. " Event unit - Pilot Ejected") + + local _unit = _event.IniUnit + local _unitname = _event.IniUnitName + local _group = _event.IniGroup + + if _unit == nil then + return -- error! + end + + local _coalition = _unit:GetCoalition() + if _coalition ~= self.coalition then + return --ignore! end - else - self:T( self.lid .. " Pilot has not taken off, ignore" ) + if not self.takenOff[_event.IniUnitName] and not _group:IsAirborne() then + self:T(self.lid .. " Pilot has not taken off, ignore") + return -- give up, pilot hasnt taken off + end + + if self:_DoubleEjection(_unitname) then + return + end + + -- limit no of pilots in the field. + if self.limitmaxdownedpilots and self:_ReachedPilotLimit() then + return + end + + -- all checks passed, get going. + if self.csarUsePara == false then --shagrat check parameter LandingAfterEjection, if true don't spawn a Pilot from EJECTION event, wait for the Chute to land + local _freq = self:_GenerateADFFrequency() + self:_AddCsar(_coalition, _unit:GetCountry(), _unit:GetCoordinate() , _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, false, "none") + return true end - - return - - elseif _event.id == EVENTS.PilotDead or _event.id == EVENTS.Ejection then - if _event.id == EVENTS.PilotDead and self.csarOncrash == false then - return - end - self:T( self.lid .. " Event unit - Pilot Ejected" ) - - local _unit = _event.IniUnit - local _unitname = _event.IniUnitName - local _group = _event.IniGroup - - if _unit == nil then - return -- error! - end - - local _coalition = _unit:GetCoalition() - if _coalition ~= self.coalition then - return -- ignore! - end - - if not self.takenOff[_event.IniUnitName] and not _group:IsAirborne() then - self:T( self.lid .. " Pilot has not taken off, ignore" ) - return -- give up, pilot hasnt taken off - end - - if self:_DoubleEjection( _unitname ) then - return - end - - -- limit no of pilots in the field. - if self.limitmaxdownedpilots and self:_ReachedPilotLimit() then - return - end - - -- all checks passed, get going. - if self.csarUsePara == false then -- shagrat check parameter LandingAfterEjection, if true don't spawn a Pilot from EJECTION event, wait for the Chute to land - local _freq = self:_GenerateADFFrequency() - self:_AddCsar( _coalition, _unit:GetCountry(), _unit:GetCoordinate(), _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, false, "none" ) - return true - end - + ---- shagrat on event LANDING_AFTER_EJECTION spawn pilot at parachute location elseif (_event.id == EVENTS.LandingAfterEjection and self.csarUsePara == true) then - self:I( { EVENT = _event } ) - local _LandingPos = COORDINATE:NewFromVec3( _event.initiator:getPosition().p ) - local _unitname = "Aircraft" -- _event.initiator:getName() or "Aircraft" --shagrat Optional use of Object name which is unfortunately 'f15_Pilot_Parachute' - local _typename = "Ejected Pilot" -- _event.Initiator.getTypeName() or "Ejected Pilot" + self:I({EVENT=_event}) + local _LandingPos = COORDINATE:NewFromVec3(_event.initiator:getPosition().p) + local _unitname = "Aircraft" --_event.initiator:getName() or "Aircraft" --shagrat Optional use of Object name which is unfortunately 'f15_Pilot_Parachute' + local _typename = "Ejected Pilot" --_event.Initiator.getTypeName() or "Ejected Pilot" local _country = _event.initiator:getCountry() local _coalition = coalition.getCountryCoalition( _country ) if _coalition == self.coalition then local _freq = self:_GenerateADFFrequency() - self:I( { coalition = _coalition, country = _country, coord = _LandingPos, name = _unitname, player = _event.IniPlayerName, freq = _freq } ) - self:_AddCsar( _coalition, _country, _LandingPos, nil, _unitname, _event.IniPlayerName, _freq, false, "none" ) -- shagrat add CSAR at Parachute location. - - Unit.destroy( _event.initiator ) -- shagrat remove static Pilot model - end + self:I({coalition=_coalition,country= _country, coord=_LandingPos, name=_unitname, player=_event.IniPlayerName, freq=_freq}) + self:_AddCsar(_coalition, _country, _LandingPos, nil, _unitname, _event.IniPlayerName, _freq, false, "none")--shagrat add CSAR at Parachute location. + + Unit.destroy(_event.initiator) -- shagrat remove static Pilot model + end return true - + elseif _event.id == EVENTS.Land then - self:T( self.lid .. " Landing" ) - - if _event.IniUnitName then - self.takenOff[_event.IniUnitName] = nil - end - - if self.allowFARPRescue then - - local _unit = _event.IniUnit -- Wrapper.Unit#UNIT - - if _unit == nil then - self:T( self.lid .. " Unit nil on landing" ) - return -- error! + self:T(self.lid .. " Landing") + + if _event.IniUnitName then + self.takenOff[_event.IniUnitName] = nil end - - local _coalition = _event.IniCoalition - if _coalition ~= self.coalition then - return -- ignore! + + if self.allowFARPRescue then + + local _unit = _event.IniUnit -- Wrapper.Unit#UNIT + + if _unit == nil then + self:T(self.lid .. " Unit nil on landing") + return -- error! + end + + local _coalition = _event.IniCoalition + if _coalition ~= self.coalition then + return --ignore! + end + + self.takenOff[_event.IniUnitName] = nil + + local _place = _event.Place -- Wrapper.Airbase#AIRBASE + + if _place == nil then + self:T(self.lid .. " Landing Place Nil") + return -- error! + end + + -- anyone on board? + if self.inTransitGroups[_event.IniUnitName] == nil then + -- ignore + return + end + + if _place:GetCoalition() == self.coalition or _place:GetCoalition() == coalition.side.NEUTRAL then + self:_ScheduledSARFlight(_event.IniUnitName,_event.IniGroupName,true) + else + self:T(string.format("Airfield %d, Unit %d", _place:GetCoalition(), _unit:GetCoalition())) + end + end + + return true end - - self.takenOff[_event.IniUnitName] = nil - - local _place = _event.Place -- Wrapper.Airbase#AIRBASE - - if _place == nil then - self:T( self.lid .. " Landing Place Nil" ) - return -- error! - end - - -- anyone on board? - if self.inTransitGroups[_event.IniUnitName] == nil then - -- ignore - return - end - - if _place:GetCoalition() == self.coalition or _place:GetCoalition() == coalition.side.NEUTRAL then - self:_ScheduledSARFlight( _event.IniUnitName, _event.IniGroupName, true ) - else - self:T( string.format( "Airfield %d, Unit %d", _place:GetCoalition(), _unit:GetCoalition() ) ) - end - end - - return true - end return self end @@ -1008,31 +1005,31 @@ end -- @param #string _GroupName Name of the Group -- @param #number _freq Beacon frequency. -- @param #boolean _nomessage Send message true or false. -function CSAR:_InitSARForPilot( _downedGroup, _GroupName, _freq, _nomessage ) - self:T( self.lid .. " _InitSARForPilot" ) - local _leader = _downedGroup:GetUnit( 1 ) +function CSAR:_InitSARForPilot(_downedGroup, _GroupName, _freq, _nomessage) + self:T(self.lid .. " _InitSARForPilot") + local _leader = _downedGroup:GetUnit(1) local _groupName = _GroupName local _freqk = _freq / 1000 - local _coordinatesText = self:_GetPositionOfWounded( _downedGroup ) + local _coordinatesText = self:_GetPositionOfWounded(_downedGroup) local _leadername = _leader:GetName() - + if not _nomessage then - if _freq ~= 0 then -- shagrat - local _text = string.format( "%s requests SAR at %s, beacon at %.2f KHz", _groupName, _coordinatesText, _freqk ) -- shagrat _groupName to prevent 'f15_Pilot_Parachute' - self:_DisplayToAllSAR( _text, self.coalition, self.messageTime ) - else -- shagrat CASEVAC msg - local _text = string.format( "Pickup Zone at %s.", _coordinatesText ) - self:_DisplayToAllSAR( _text, self.coalition, self.messageTime ) - end + if _freq ~= 0 then --shagrat + local _text = string.format("%s requests SAR at %s, beacon at %.2f KHz", _groupName, _coordinatesText, _freqk)--shagrat _groupName to prevent 'f15_Pilot_Parachute' + self:_DisplayToAllSAR(_text,self.coalition,self.messageTime) + else --shagrat CASEVAC msg + local _text = string.format("Pickup Zone at %s.", _coordinatesText ) + self:_DisplayToAllSAR(_text,self.coalition,self.messageTime) + end + end + + for _,_heliName in pairs(self.csarUnits) do + self:_CheckWoundedGroupStatus(_heliName, _groupName) end - for _, _heliName in pairs( self.csarUnits ) do - self:_CheckWoundedGroupStatus( _heliName, _groupName ) - end - - -- trigger FSM event - self:__PilotDown( 2, _downedGroup, _freqk, _groupName, _coordinatesText ) - + -- trigger FSM event + self:__PilotDown(2,_downedGroup, _freqk, _groupName, _coordinatesText) + return self end @@ -1041,16 +1038,16 @@ end -- @param #string name Name to search for. -- @return #boolean Outcome. -- @return #CSAR.DownedPilot Table if found else nil. -function CSAR:_CheckNameInDownedPilots( name ) - local PilotTable = self.downedPilots -- #CSAR.DownedPilot +function CSAR:_CheckNameInDownedPilots(name) + local PilotTable = self.downedPilots --#CSAR.DownedPilot local found = false local table = nil - for _, _pilot in pairs( PilotTable ) do + for _,_pilot in pairs(PilotTable) do if _pilot.name == name and _pilot.alive == true then found = true table = _pilot break - end + end end return found, table end @@ -1060,10 +1057,10 @@ end -- @param #string name Name to search for. -- @param #boolean force Force removal. -- @return #boolean Outcome. -function CSAR:_RemoveNameFromDownedPilots( name, force ) - local PilotTable = self.downedPilots -- #CSAR.DownedPilot +function CSAR:_RemoveNameFromDownedPilots(name,force) + local PilotTable = self.downedPilots --#CSAR.DownedPilot local found = false - for _index, _pilot in pairs( PilotTable ) do + for _index,_pilot in pairs(PilotTable) do if _pilot.name == name then self.downedPilots[_index].alive = false end @@ -1075,95 +1072,95 @@ end -- @param #CSAR self -- @param #string heliname heliname -- @param #string woundedgroupname woundedgroupname -function CSAR:_CheckWoundedGroupStatus( heliname, woundedgroupname ) - self:T( self.lid .. " _CheckWoundedGroupStatus" ) +function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) + self:T(self.lid .. " _CheckWoundedGroupStatus") local _heliName = heliname local _woundedGroupName = woundedgroupname - self:T( { Heli = _heliName, Downed = _woundedGroupName } ) + self:T({Heli = _heliName, Downed = _woundedGroupName}) -- if wounded group is not here then message already been sent to SARs -- stop processing any further - local _found, _downedpilot = self:_CheckNameInDownedPilots( _woundedGroupName ) + local _found, _downedpilot = self:_CheckNameInDownedPilots(_woundedGroupName) if not _found then - self:T( "...not found in list!" ) + self:T("...not found in list!") return end - + local _woundedGroup = _downedpilot.group - if _woundedGroup ~= nil and _woundedGroup:IsAlive() then - local _heliUnit = self:_GetSARHeli( _heliName ) -- Wrapper.Unit#UNIT - - local _lookupKeyHeli = _heliName .. "_" .. _woundedGroupName -- lookup key for message state tracking - + if _woundedGroup ~= nil and _woundedGroup:IsAlive() then + local _heliUnit = self:_GetSARHeli(_heliName) -- Wrapper.Unit#UNIT + + local _lookupKeyHeli = _heliName .. "_" .. _woundedGroupName --lookup key for message state tracking + if _heliUnit == nil then self.heliVisibleMessage[_lookupKeyHeli] = nil self.heliCloseMessage[_lookupKeyHeli] = nil self.landedStatus[_lookupKeyHeli] = nil - self:T( "...helinunit nil!" ) + self:T("...helinunit nil!") return end - + local _heliCoord = _heliUnit:GetCoordinate() local _leaderCoord = _woundedGroup:GetCoordinate() - local _distance = self:_GetDistance( _heliCoord, _leaderCoord ) + local _distance = self:_GetDistance(_heliCoord,_leaderCoord) -- autosmoke if (self.autosmoke == true) and (_distance < self.autosmokedistance) and (_distance ~= -1) then - self:_PopSmokeForGroup( _woundedGroupName, _woundedGroup ) + self:_PopSmokeForGroup(_woundedGroupName, _woundedGroup) end - + if _distance < self.approachdist_near and _distance > 0 then - if self:_CheckCloseWoundedGroup( _distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName ) == true then - -- we're close, reschedule + if self:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName) == true then + -- we\'re close, reschedule _downedpilot.timestamp = timer.getAbsTime() - self:__Approach( -5, heliname, woundedgroupname ) + self:__Approach(-5,heliname,woundedgroupname) end elseif _distance >= self.approachdist_near and _distance < self.approachdist_far then -- message once if self.heliVisibleMessage[_lookupKeyHeli] == nil then - local _pilotName = _downedpilot.desc - if self.autosmoke == true then - local dist = self.autosmokedistance / 1000 - local disttext = string.format( "%.0fkm", dist ) - if _SETTINGS:IsImperial() then - local dist = UTILS.MetersToNM( self.autosmokedistance ) - disttext = string.format( "%.0fnm", dist ) + local _pilotName = _downedpilot.desc + if self.autosmoke == true then + local dist = self.autosmokedistance / 1000 + local disttext = string.format("%.0fkm",dist) + if _SETTINGS:IsImperial() then + local dist = UTILS.MetersToNM(self.autosmokedistance) + disttext = string.format("%.0fnm",dist) + end + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Finally, that is music in my ears!\nI'll pop a smoke when you are %s away.\nLand or hover by the smoke.", _heliName, _pilotName, disttext), self.messageTime,false,true) + else + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Finally, that is music in my ears!\nRequest a flare or smoke if you need.", _heliName, _pilotName), self.messageTime,false,true) end - self:_DisplayMessageToSAR( _heliUnit, string.format( "%s: %s. I hear you! Finally, that is music in my ears!\nI'll pop a smoke when you are %s away.\nLand or hover by the smoke.", _heliName, _pilotName, disttext ), self.messageTime, false, true ) - else - self:_DisplayMessageToSAR( _heliUnit, string.format( "%s: %s. I hear you! Finally, that is music in my ears!\nRequest a flare or smoke if you need.", _heliName, _pilotName ), self.messageTime, false, true ) - end - -- mark as shown for THIS heli and THIS group - self.heliVisibleMessage[_lookupKeyHeli] = true - end + --mark as shown for THIS heli and THIS group + self.heliVisibleMessage[_lookupKeyHeli] = true + end self.heliCloseMessage[_lookupKeyHeli] = nil self.landedStatus[_lookupKeyHeli] = nil - -- reschedule as units aren't dead yet , schedule for a bit slower though as we're far away + --reschedule as units aren\'t dead yet , schedule for a bit slower though as we\'re far away _downedpilot.timestamp = timer.getAbsTime() - self:__Approach( -10, heliname, woundedgroupname ) + self:__Approach(-10,heliname,woundedgroupname) end else - self:T( "...Downed Pilot KIA?!" ) - if not _downedpilot.alive then - -- self:__KIA(1,_downedpilot.name) - self:_RemoveNameFromDownedPilots( _downedpilot.name, true ) - end + self:T("...Downed Pilot KIA?!") + if not _downedpilot.alive then + --self:__KIA(1,_downedpilot.name) + self:_RemoveNameFromDownedPilots(_downedpilot.name, true) + end end return self end ---- (Internal) Function to pop a smoke at a wounded pilot's positions. +--- (Internal) Function to pop a smoke at a wounded pilot\'s positions. -- @param #CSAR self -- @param #string _woundedGroupName Name of the group. -- @param Wrapper.Group#GROUP _woundedLeader Object of the group. -function CSAR:_PopSmokeForGroup( _woundedGroupName, _woundedLeader ) - self:T( self.lid .. " _PopSmokeForGroup" ) +function CSAR:_PopSmokeForGroup(_woundedGroupName, _woundedLeader) + self:T(self.lid .. " _PopSmokeForGroup") -- have we popped smoke already in the last 5 mins local _lastSmoke = self.smokeMarkers[_woundedGroupName] if _lastSmoke == nil or timer.getTime() > _lastSmoke then - - local _smokecolor = self.smokecolor - local _smokecoord = _woundedLeader:GetCoordinate():Translate( 6, math.random( 1, 360 ) ) -- shagrat place smoke at a random 6 m distance, so smoke does not obscure the pilot - _smokecoord:Smoke( _smokecolor ) - self.smokeMarkers[_woundedGroupName] = timer.getTime() + 300 -- next smoke time + + local _smokecolor = self.smokecolor + local _smokecoord = _woundedLeader:GetCoordinate():Translate( 6, math.random( 1, 360) ) --shagrat place smoke at a random 6 m distance, so smoke does not obscure the pilot + _smokecoord:Smoke(_smokecolor) + self.smokeMarkers[_woundedGroupName] = timer.getTime() + 300 -- next smoke time end return self end @@ -1174,40 +1171,47 @@ end -- @param #string _pilotName Name of the pilot. -- @param Wrapper.Group#GROUP _woundedGroup Object of the group. -- @param #string _woundedGroupName Name of the group. -function CSAR:_PickupUnit( _heliUnit, _pilotName, _woundedGroup, _woundedGroupName ) - self:T( self.lid .. " _PickupUnit" ) +function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) + self:T(self.lid .. " _PickupUnit") -- board local _heliName = _heliUnit:GetName() local _groups = self.inTransitGroups[_heliName] - local _unitsInHelicopter = self:_PilotsOnboard( _heliName ) - + local _unitsInHelicopter = self:_PilotsOnboard(_heliName) + -- init table if there is none for this helicopter if not _groups then - self.inTransitGroups[_heliName] = {} - _groups = self.inTransitGroups[_heliName] + self.inTransitGroups[_heliName] = {} + _groups = self.inTransitGroups[_heliName] end - + -- if the heli can\'t pick them up, show a message and return local _maxUnits = self.AircraftType[_heliUnit:GetTypeName()] if _maxUnits == nil then _maxUnits = self.max_units end if _unitsInHelicopter + 1 > _maxUnits then - self:_DisplayMessageToSAR( _heliUnit, string.format( "%s, %s. We\'re already crammed with %d guys! Sorry!", _pilotName, _heliName, _unitsInHelicopter, _unitsInHelicopter ), self.messageTime ) - return true + self:_DisplayMessageToSAR(_heliUnit, string.format("%s, %s. We\'re already crammed with %d guys! Sorry!", _pilotName, _heliName, _unitsInHelicopter, _unitsInHelicopter), self.messageTime) + return true end + + local found,downedgrouptable = self:_CheckNameInDownedPilots(_woundedGroupName) + local grouptable = downedgrouptable --#CSAR.DownedPilot + self.inTransitGroups[_heliName][_woundedGroupName] = + { + originalUnit = grouptable.originalUnit, + woundedGroup = _woundedGroupName, + side = self.coalition, + desc = grouptable.desc, + player = grouptable.player, + } - local found, downedgrouptable = self:_CheckNameInDownedPilots( _woundedGroupName ) - local grouptable = downedgrouptable -- #CSAR.DownedPilot - self.inTransitGroups[_heliName][_woundedGroupName] = { originalUnit = grouptable.originalUnit, woundedGroup = _woundedGroupName, side = self.coalition, desc = grouptable.desc, player = grouptable.player } - - _woundedGroup:Destroy( false ) - self:_RemoveNameFromDownedPilots( _woundedGroupName, true ) - - self:_DisplayMessageToSAR( _heliUnit, string.format( "%s: %s I\'m in! Get to the MASH ASAP! ", _heliName, _pilotName ), self.messageTime, true, true ) - - self:__Boarded( 5, _heliName, _woundedGroupName ) - + _woundedGroup:Destroy(false) + self:_RemoveNameFromDownedPilots(_woundedGroupName,true) + + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s I\'m in! Get to the MASH ASAP! ", _heliName, _pilotName), self.messageTime,true,true) + + self:__Boarded(5,_heliName,_woundedGroupName) + return true end @@ -1215,23 +1219,24 @@ end -- @param #CSAR self -- @param Wrapper.Group#GROUP _leader -- @param Core.Point#COORDINATE _destination -function CSAR:_OrderGroupToMoveToPoint( _leader, _destination ) - self:T( self.lid .. " _OrderGroupToMoveToPoint" ) +function CSAR:_OrderGroupToMoveToPoint(_leader, _destination) + self:T(self.lid .. " _OrderGroupToMoveToPoint") local group = _leader local coordinate = _destination:GetVec2() group:SetAIOn() - group:RouteToVec2( coordinate, 5 ) + group:RouteToVec2(coordinate,5) return self end + --- (internal) Function to check if the heli door(s) are open. Thanks to Shadowze. -- @param #CSAR self -- @param #string unit_name Name of unit. -- @return #boolean outcome The outcome. function CSAR:_IsLoadingDoorOpen( unit_name ) - self:T( self.lid .. " _IsLoadingDoorOpen" ) - return UTILS.IsLoadingDoorOpen( unit_name ) + self:T(self.lid .. " _IsLoadingDoorOpen") + return UTILS.IsLoadingDoorOpen(unit_name) end --- (Internal) Function to check if heli is close to group. @@ -1242,123 +1247,122 @@ end -- @param Wrapper.Group#GROUP _woundedGroup -- @param #string _woundedGroupName -- @return #boolean Outcome -function CSAR:_CheckCloseWoundedGroup( _distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName ) - self:T( self.lid .. " _CheckCloseWoundedGroup" ) +function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName) + self:T(self.lid .. " _CheckCloseWoundedGroup") local _woundedLeader = _woundedGroup - local _lookupKeyHeli = _heliUnit:GetName() .. "_" .. _woundedGroupName -- lookup key for message state tracking - - local _found, _pilotable = self:_CheckNameInDownedPilots( _woundedGroupName ) -- #boolean, #CSAR.DownedPilot + local _lookupKeyHeli = _heliUnit:GetName() .. "_" .. _woundedGroupName --lookup key for message state tracking + + local _found, _pilotable = self:_CheckNameInDownedPilots(_woundedGroupName) -- #boolean, #CSAR.DownedPilot local _pilotName = _pilotable.desc + local _reset = true - + if (_distance < 500) then - - if self.heliCloseMessage[_lookupKeyHeli] == nil then - if self.autosmoke == true then - self:_DisplayMessageToSAR( _heliUnit, string.format( "%s: %s. You\'re close now! Land or hover at the smoke.", _heliName, _pilotName ), self.messageTime, false, true ) - else - self:_DisplayMessageToSAR( _heliUnit, string.format( "%s: %s. You\'re close now! Land in a safe place, I will go there ", _heliName, _pilotName ), self.messageTime, false, true ) - end - self.heliCloseMessage[_lookupKeyHeli] = true - end - - -- have we landed close enough? - if not _heliUnit:InAir() then - - if self.pilotRuntoExtractPoint == true then - if (_distance < self.extractDistance) then - local _time = self.landedStatus[_lookupKeyHeli] - if _time == nil then - self.landedStatus[_lookupKeyHeli] = math.floor( (_distance - self.loadDistance) / 3.6 ) - _time = self.landedStatus[_lookupKeyHeli] - self:_OrderGroupToMoveToPoint( _woundedGroup, _heliUnit:GetCoordinate() ) - self:_DisplayMessageToSAR( _heliUnit, "Wait till " .. _pilotName .. " gets in. \nETA " .. _time .. " more seconds.", self.messageTime, false ) + + if self.heliCloseMessage[_lookupKeyHeli] == nil then + if self.autosmoke == true then + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land or hover at the smoke.", _heliName, _pilotName), self.messageTime,false,true) else - _time = self.landedStatus[_lookupKeyHeli] - 10 - self.landedStatus[_lookupKeyHeli] = _time + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land in a safe place, I will go there ", _heliName, _pilotName), self.messageTime,false,true) end - if _time <= 0 or _distance < self.loadDistance then - if self.pilotmustopendoors and not self:_IsLoadingDoorOpen( _heliName ) then - self:_DisplayMessageToSAR( _heliUnit, "Open the door to let me in!", self.messageTime, true ) - return true - else - self.landedStatus[_lookupKeyHeli] = nil - self:_PickupUnit( _heliUnit, _pilotName, _woundedGroup, _woundedGroupName ) - return false - end - end - end - else - if (_distance < self.loadDistance) then - if self.pilotmustopendoors and not self:_IsLoadingDoorOpen( _heliName ) then - self:_DisplayMessageToSAR( _heliUnit, "Open the door to let me in!", self.messageTime, true ) - return true - else - self:_PickupUnit( _heliUnit, _pilotName, _woundedGroup, _woundedGroupName ) - return false - end - end + self.heliCloseMessage[_lookupKeyHeli] = true end - else - - local _unitsInHelicopter = self:_PilotsOnboard( _heliName ) - local _maxUnits = self.AircraftType[_heliUnit:GetTypeName()] - if _maxUnits == nil then - _maxUnits = self.max_units - end - - if _heliUnit:InAir() and _unitsInHelicopter + 1 <= _maxUnits then - -- TODO - make variable - if _distance < self.rescuehoverdistance then - - -- check height! - local leaderheight = _woundedLeader:GetHeight() - if leaderheight < 0 then - leaderheight = 0 - end - local _height = _heliUnit:GetHeight() - leaderheight - - -- TODO - make variable - if _height <= self.rescuehoverheight then - - local _time = self.hoverStatus[_lookupKeyHeli] - - if _time == nil then - self.hoverStatus[_lookupKeyHeli] = 10 - _time = 10 - else - _time = self.hoverStatus[_lookupKeyHeli] - 10 - self.hoverStatus[_lookupKeyHeli] = _time - end - - if _time > 0 then - self:_DisplayMessageToSAR( _heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you\'re too far away!", self.messageTime, true ) - else - if self.pilotmustopendoors and not self:_IsLoadingDoorOpen( _heliName ) then - self:_DisplayMessageToSAR( _heliUnit, "Open the door to let me in!", self.messageTime, true ) - return true + + -- have we landed close enough? + if not _heliUnit:InAir() then + + if self.pilotRuntoExtractPoint == true then + if (_distance < self.extractDistance) then + local _time = self.landedStatus[_lookupKeyHeli] + if _time == nil then + self.landedStatus[_lookupKeyHeli] = math.floor( (_distance - self.loadDistance) / 3.6 ) + _time = self.landedStatus[_lookupKeyHeli] + self:_OrderGroupToMoveToPoint(_woundedGroup, _heliUnit:GetCoordinate()) + self:_DisplayMessageToSAR(_heliUnit, "Wait till " .. _pilotName .. " gets in. \nETA " .. _time .. " more seconds.", self.messageTime, false) else - self.hoverStatus[_lookupKeyHeli] = nil - self:_PickupUnit( _heliUnit, _pilotName, _woundedGroup, _woundedGroupName ) - return false + _time = self.landedStatus[_lookupKeyHeli] - 10 + self.landedStatus[_lookupKeyHeli] = _time + end + if _time <= 0 or _distance < self.loadDistance then + if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then + self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true) + return true + else + self.landedStatus[_lookupKeyHeli] = nil + self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) + return false + end end end - _reset = false - else - self:_DisplayMessageToSAR( _heliUnit, "Too high to winch " .. _pilotName .. " \nReduce height and hover for 10 seconds!", self.messageTime, true, true ) + else + if (_distance < self.loadDistance) then + if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then + self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true) + return true + else + self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) + return false + end end end - + else + + local _unitsInHelicopter = self:_PilotsOnboard(_heliName) + local _maxUnits = self.AircraftType[_heliUnit:GetTypeName()] + if _maxUnits == nil then + _maxUnits = self.max_units + end + + if _heliUnit:InAir() and _unitsInHelicopter + 1 <= _maxUnits then + -- TODO - make variable + if _distance < self.rescuehoverdistance then + + --check height! + local leaderheight = _woundedLeader:GetHeight() + if leaderheight < 0 then leaderheight = 0 end + local _height = _heliUnit:GetHeight() - leaderheight + + -- TODO - make variable + if _height <= self.rescuehoverheight then + + local _time = self.hoverStatus[_lookupKeyHeli] + + if _time == nil then + self.hoverStatus[_lookupKeyHeli] = 10 + _time = 10 + else + _time = self.hoverStatus[_lookupKeyHeli] - 10 + self.hoverStatus[_lookupKeyHeli] = _time + end + + if _time > 0 then + self:_DisplayMessageToSAR(_heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you\'re too far away!", self.messageTime, true) + else + if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then + self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true) + return true + else + self.hoverStatus[_lookupKeyHeli] = nil + self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) + return false + end + end + _reset = false + else + self:_DisplayMessageToSAR(_heliUnit, "Too high to winch " .. _pilotName .. " \nReduce height and hover for 10 seconds!", self.messageTime, true,true) + end + end + + end end - end end - + if _reset then - self.hoverStatus[_lookupKeyHeli] = nil + self.hoverStatus[_lookupKeyHeli] = nil end - + if _distance < 500 then return true else @@ -1371,65 +1375,65 @@ end -- @param #string heliname Heli name -- @param #string groupname Group name -- @param #boolean isairport If true, EVENT.Landing took place at an airport or FARP -function CSAR:_ScheduledSARFlight( heliname, groupname, isairport ) - self:T( self.lid .. " _ScheduledSARFlight" ) - self:T( { heliname, groupname } ) - local _heliUnit = self:_GetSARHeli( heliname ) +function CSAR:_ScheduledSARFlight(heliname,groupname, isairport) + self:T(self.lid .. " _ScheduledSARFlight") + self:T({heliname,groupname}) + local _heliUnit = self:_GetSARHeli(heliname) local _woundedGroupName = groupname if (_heliUnit == nil) then - -- helicopter crashed? - self.inTransitGroups[heliname] = nil - return + --helicopter crashed? + self.inTransitGroups[heliname] = nil + return end if self.inTransitGroups[heliname] == nil or self.inTransitGroups[heliname][_woundedGroupName] == nil then - -- Groups already rescued - return + -- Groups already rescued + return end - local _dist = self:_GetClosestMASH( _heliUnit ) + local _dist = self:_GetClosestMASH(_heliUnit) if _dist == -1 then - return + return end - if (_dist < self.FARPRescueDistance or isairport) and _heliUnit:InAir() == false then - if self.pilotmustopendoors and self:_IsLoadingDoorOpen( heliname ) == false then - self:_DisplayMessageToSAR( _heliUnit, "Open the door to let me out!", self.messageTime, true ) + if ( _dist < self.FARPRescueDistance or isairport ) and _heliUnit:InAir() == false then + if self.pilotmustopendoors and self:_IsLoadingDoorOpen(heliname) == false then + self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me out!", self.messageTime, true) else - self:_RescuePilots( _heliUnit ) + self:_RescuePilots(_heliUnit) return end end - -- queue up - self:__Returning( -5, heliname, _woundedGroupName, isairport ) + --queue up + self:__Returning(-5,heliname,_woundedGroupName, isairport) return self end --- (Internal) Mark pilot as rescued and remove from tables. -- @param #CSAR self -- @param Wrapper.Unit#UNIT _heliUnit -function CSAR:_RescuePilots( _heliUnit ) - self:T( self.lid .. " _RescuePilots" ) +function CSAR:_RescuePilots(_heliUnit) + self:T(self.lid .. " _RescuePilots") local _heliName = _heliUnit:GetName() local _rescuedGroups = self.inTransitGroups[_heliName] - + if _rescuedGroups == nil then - -- Groups already rescued - return + -- Groups already rescued + return end - local PilotsSaved = self:_PilotsOnboard( _heliName ) - + local PilotsSaved = self:_PilotsOnboard(_heliName) + self.inTransitGroups[_heliName] = nil - - local _txt = string.format( "%s: The %d pilot(s) have been taken to the\nmedical clinic. Good job!", _heliName, PilotsSaved ) - - self:_DisplayMessageToSAR( _heliUnit, _txt, self.messageTime ) + + local _txt = string.format("%s: The %d pilot(s) have been taken to the\nmedical clinic. Good job!", _heliName, PilotsSaved) + + self:_DisplayMessageToSAR(_heliUnit, _txt, self.messageTime) -- trigger event - self:__Rescued( -1, _heliUnit, _heliName, PilotsSaved ) + self:__Rescued(-1,_heliUnit,_heliName, PilotsSaved) return self end @@ -1437,9 +1441,9 @@ end -- @param #CSAR self -- @param #string _unitname Name of Unit -- @return Wrapper.Unit#UNIT The unit or nil -function CSAR:_GetSARHeli( _unitName ) - self:T( self.lid .. " _GetSARHeli" ) - local unit = UNIT:FindByName( _unitName ) +function CSAR:_GetSARHeli(_unitName) + self:T(self.lid .. " _GetSARHeli") + local unit = UNIT:FindByName(_unitName) if unit and unit:IsAlive() then return unit else @@ -1455,43 +1459,43 @@ end -- @param #boolean _clear (optional) Clear screen. -- @param #boolean _speak (optional) Speak message via SRS. -- @param #boolean _override (optional) Override message suppression -function CSAR:_DisplayMessageToSAR( _unit, _text, _time, _clear, _speak, _override ) - self:T( self.lid .. " _DisplayMessageToSAR" ) +function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear, _speak, _override) + self:T(self.lid .. " _DisplayMessageToSAR") local group = _unit:GetGroup() local _clear = _clear or nil local _time = _time or self.messageTime if _override or not self.suppressmessages then - local m = MESSAGE:New( _text, _time, "Info", _clear ):ToGroup( group ) + local m = MESSAGE:New(_text,_time,"Info",_clear):ToGroup(group) end -- integrate SRS if _speak and self.useSRS then - local srstext = SOUNDTEXT:New( _text ) + local srstext = SOUNDTEXT:New(_text) local path = self.SRSPath local modulation = self.SRSModulation local channel = self.SRSchannel - local msrs = MSRS:New( path, channel, modulation ) - msrs:PlaySoundText( srstext, 2 ) + local msrs = MSRS:New(path,channel,modulation) + msrs:PlaySoundText(srstext, 2) end return self end ---- (Internal) Function to get string of a group's position. +--- (Internal) Function to get string of a group\'s position. -- @param #CSAR self -- @param Wrapper.Controllable#CONTROLLABLE _woundedGroup Group or Unit object. -- @return #string Coordinates as Text -function CSAR:_GetPositionOfWounded( _woundedGroup ) - self:T( self.lid .. " _GetPositionOfWounded" ) +function CSAR:_GetPositionOfWounded(_woundedGroup) + self:T(self.lid .. " _GetPositionOfWounded") local _coordinate = _woundedGroup:GetCoordinate() local _coordinatesText = "None" if _coordinate then if self.coordtype == 0 then -- Lat/Long DMTM _coordinatesText = _coordinate:ToStringLLDDM() elseif self.coordtype == 1 then -- Lat/Long DMS - _coordinatesText = _coordinate:ToStringLLDMS() + _coordinatesText = _coordinate:ToStringLLDMS() elseif self.coordtype == 2 then -- MGRS - _coordinatesText = _coordinate:ToStringMGRS() + _coordinatesText = _coordinate:ToStringMGRS() else -- Bullseye Metric --(medevac.coordtype == 4 or 3) - _coordinatesText = _coordinate:ToStringBULLS( self.coalition ) + _coordinatesText = _coordinate:ToStringBULLS(self.coalition) end end return _coordinatesText @@ -1500,55 +1504,55 @@ end --- (Internal) Display active SAR tasks to player. -- @param #CSAR self -- @param #string _unitName Unit to display to -function CSAR:_DisplayActiveSAR( _unitName ) - self:T( self.lid .. " _DisplayActiveSAR" ) - local _msg = "Active MEDEVAC/SAR:" - local _heli = self:_GetSARHeli( _unitName ) -- Wrapper.Unit#UNIT +function CSAR:_DisplayActiveSAR(_unitName) + self:T(self.lid .. " _DisplayActiveSAR") + local _msg = "Active MEDEVAC/SAR:" + local _heli = self:_GetSARHeli(_unitName) -- Wrapper.Unit#UNIT if _heli == nil then - return + return end - + local _heliSide = self.coalition local _csarList = {} - + local _DownedPilotTable = self.downedPilots - self:T( { Table = _DownedPilotTable } ) - for _, _value in pairs( _DownedPilotTable ) do + self:T({Table=_DownedPilotTable}) + for _, _value in pairs(_DownedPilotTable) do local _groupName = _value.name - self:T( string.format( "Display Active Pilot: %s", tostring( _groupName ) ) ) - self:T( { Table = _value } ) + self:T(string.format("Display Active Pilot: %s", tostring(_groupName))) + self:T({Table=_value}) local _woundedGroup = _value.group - if _woundedGroup and _value.alive then - local _coordinatesText = self:_GetPositionOfWounded( _woundedGroup ) - local _helicoord = _heli:GetCoordinate() - local _woundcoord = _woundedGroup:GetCoordinate() - local _distance = self:_GetDistance( _helicoord, _woundcoord ) - self:T( { _distance = _distance } ) - local distancetext = "" - if _SETTINGS:IsImperial() then - distancetext = string.format( "%.1fnm", UTILS.MetersToNM( _distance ) ) - else - distancetext = string.format( "%.1fkm", _distance / 1000.0 ) - end - if _value.frequency == 0 then -- shagrat insert CASEVAC without Frequency - table.insert( _csarList, { dist = _distance, msg = string.format( "%s at %s - %s ", _value.desc, _coordinatesText, distancetext ) } ) - else - table.insert( _csarList, { dist = _distance, msg = string.format( "%s at %s - %.2f KHz ADF - %s ", _value.desc, _coordinatesText, _value.frequency / 1000, distancetext ) } ) - end + if _woundedGroup and _value.alive then + local _coordinatesText = self:_GetPositionOfWounded(_woundedGroup) + local _helicoord = _heli:GetCoordinate() + local _woundcoord = _woundedGroup:GetCoordinate() + local _distance = self:_GetDistance(_helicoord, _woundcoord) + self:T({_distance = _distance}) + local distancetext = "" + if _SETTINGS:IsImperial() then + distancetext = string.format("%.1fnm",UTILS.MetersToNM(_distance)) + else + distancetext = string.format("%.1fkm", _distance/1000.0) + end + if _value.frequency == 0 then--shagrat insert CASEVAC without Frequency + table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %s ", _value.desc, _coordinatesText, distancetext) }) + else + table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %s ", _value.desc, _coordinatesText, _value.frequency / 1000, distancetext) }) + end end end - - local function sortDistance( a, b ) - return a.dist < b.dist + + local function sortDistance(a, b) + return a.dist < b.dist end - - table.sort( _csarList, sortDistance ) - - for _, _line in pairs( _csarList ) do - _msg = _msg .. "\n" .. _line.msg + + table.sort(_csarList, sortDistance) + + for _, _line in pairs(_csarList) do + _msg = _msg .. "\n" .. _line.msg end - - self:_DisplayMessageToSAR( _heli, _msg, self.messageTime * 2, false, false, true ) + + self:_DisplayMessageToSAR(_heli, _msg, self.messageTime*2, false, false, true) return self end @@ -1556,38 +1560,38 @@ end -- @param #CSAR self -- @param Wrapper.Unit#UNIT _heli Helicopter #UNIT -- @return #table Table of results -function CSAR:_GetClosestDownedPilot( _heli ) - self:T( self.lid .. " _GetClosestDownedPilot" ) +function CSAR:_GetClosestDownedPilot(_heli) + self:T(self.lid .. " _GetClosestDownedPilot") local _side = self.coalition local _closestGroup = nil local _shortestDistance = -1 local _distance = 0 local _closestGroupInfo = nil local _heliCoord = _heli:GetCoordinate() or _heli:GetCoordinate() - - if _heliCoord == nil then - self:E( "****Error obtaining coordinate!" ) - return nil + + if _heliCoord == nil then + self:E("****Error obtaining coordinate!") + return nil end - + local DownedPilotsTable = self.downedPilots + + for _, _groupInfo in UTILS.spairs(DownedPilotsTable) do + --for _, _groupInfo in pairs(DownedPilotsTable) do + local _woundedName = _groupInfo.name + local _tempWounded = _groupInfo.group + + -- check group exists and not moving to someone else + if _tempWounded then + local _tempCoord = _tempWounded:GetCoordinate() + _distance = self:_GetDistance(_heliCoord, _tempCoord) - for _, _groupInfo in UTILS.spairs( DownedPilotsTable ) do - -- for _, _groupInfo in pairs(DownedPilotsTable) do - local _woundedName = _groupInfo.name - local _tempWounded = _groupInfo.group - - -- check group exists and not moving to someone else - if _tempWounded then - local _tempCoord = _tempWounded:GetCoordinate() - _distance = self:_GetDistance( _heliCoord, _tempCoord ) - - if _distance ~= nil and (_shortestDistance == -1 or _distance < _shortestDistance) then - _shortestDistance = _distance - _closestGroup = _tempWounded - _closestGroupInfo = _groupInfo + if _distance ~= nil and (_shortestDistance == -1 or _distance < _shortestDistance) then + _shortestDistance = _distance + _closestGroup = _tempWounded + _closestGroupInfo = _groupInfo + end end - end end return { pilot = _closestGroup, distance = _shortestDistance, groupInfo = _closestGroupInfo } @@ -1596,40 +1600,38 @@ end --- (Internal) Fire a flare at the point of a downed pilot. -- @param #CSAR self -- @param #string _unitName Name of the unit. -function CSAR:_SignalFlare( _unitName ) - self:T( self.lid .. " _SignalFlare" ) - local _heli = self:_GetSARHeli( _unitName ) +function CSAR:_SignalFlare(_unitName) + self:T(self.lid .. " _SignalFlare") + local _heli = self:_GetSARHeli(_unitName) if _heli == nil then - return + return end - - local _closest = self:_GetClosestDownedPilot( _heli ) + + local _closest = self:_GetClosestDownedPilot(_heli) local smokedist = 8000 - if self.approachdist_far > smokedist then - smokedist = self.approachdist_far - end + if self.approachdist_far > smokedist then smokedist = self.approachdist_far end if _closest ~= nil and _closest.pilot ~= nil and _closest.distance > 0 and _closest.distance < smokedist then - - local _clockDir = self:_GetClockDirection( _heli, _closest.pilot ) - local _distance = 0 - if _SETTINGS:IsImperial() then - _distance = string.format( "%.1fnm", UTILS.MetersToNM( _closest.distance ) ) - else - _distance = string.format( "%.1fkm", _closest.distance ) - end - local _msg = string.format( "%s - Popping signal flare at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance ) - self:_DisplayMessageToSAR( _heli, _msg, self.messageTime, false, true, true ) - - local _coord = _closest.pilot:GetCoordinate() - _coord:FlareRed( _clockDir ) + + local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) + local _distance = 0 + if _SETTINGS:IsImperial() then + _distance = string.format("%.1fnm",UTILS.MetersToNM(_closest.distance)) + else + _distance = string.format("%.1fkm",_closest.distance) + end + local _msg = string.format("%s - Popping signal flare at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance) + self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true) + + local _coord = _closest.pilot:GetCoordinate() + _coord:FlareRed(_clockDir) else - local _distance = smokedist - if _SETTINGS:IsImperial() then - _distance = string.format( "%.1fnm", UTILS.MetersToNM( smokedist ) ) - else - _distance = string.format( "%.1fkm", smokedist / 1000 ) - end - self:_DisplayMessageToSAR( _heli, string.format( "No Pilots within %s", _distance ), self.messageTime, false, false, true ) + local _distance = smokedist + if _SETTINGS:IsImperial() then + _distance = string.format("%.1fnm",UTILS.MetersToNM(smokedist)) + else + _distance = string.format("%.1fkm",smokedist/1000) + end + self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",_distance), self.messageTime, false, false, true) end return self end @@ -1639,53 +1641,51 @@ end -- @param #string _message Message to display. -- @param #number _side Coalition of message. -- @param #number _messagetime How long to show. -function CSAR:_DisplayToAllSAR( _message, _side, _messagetime ) - self:T( self.lid .. " _DisplayToAllSAR" ) +function CSAR:_DisplayToAllSAR(_message, _side, _messagetime) + self:T(self.lid .. " _DisplayToAllSAR") local messagetime = _messagetime or self.messageTime - for _, _unitName in pairs( self.csarUnits ) do - local _unit = self:_GetSARHeli( _unitName ) + for _, _unitName in pairs(self.csarUnits) do + local _unit = self:_GetSARHeli(_unitName) if _unit and not self.suppressmessages then - self:_DisplayMessageToSAR( _unit, _message, _messagetime ) + self:_DisplayMessageToSAR(_unit, _message, _messagetime) end end return self end ---(Internal) Request smoke at closest downed pilot. --- @param #CSAR self --- @param #string _unitName Name of the helicopter +--@param #CSAR self +--@param #string _unitName Name of the helicopter function CSAR:_Reqsmoke( _unitName ) - self:T( self.lid .. " _Reqsmoke" ) - local _heli = self:_GetSARHeli( _unitName ) + self:T(self.lid .. " _Reqsmoke") + local _heli = self:_GetSARHeli(_unitName) if _heli == nil then - return + return end local smokedist = 8000 - if smokedist < self.approachdist_far then - smokedist = self.approachdist_far - end - local _closest = self:_GetClosestDownedPilot( _heli ) + if smokedist < self.approachdist_far then smokedist = self.approachdist_far end + local _closest = self:_GetClosestDownedPilot(_heli) if _closest ~= nil and _closest.pilot ~= nil and _closest.distance > 0 and _closest.distance < smokedist then - local _clockDir = self:_GetClockDirection( _heli, _closest.pilot ) - local _distance = 0 - if _SETTINGS:IsImperial() then - _distance = string.format( "%.1fnm", UTILS.MetersToNM( _closest.distance ) ) - else - _distance = string.format( "%.1fkm", _closest.distance / 1000 ) - end - local _msg = string.format( "%s - Popping smoke at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance ) - self:_DisplayMessageToSAR( _heli, _msg, self.messageTime, false, true, true ) - local _coord = _closest.pilot:GetCoordinate() - local color = self.smokecolor - _coord:Smoke( color ) + local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) + local _distance = 0 + if _SETTINGS:IsImperial() then + _distance = string.format("%.1fnm",UTILS.MetersToNM(_closest.distance)) + else + _distance = string.format("%.1fkm",_closest.distance/1000) + end + local _msg = string.format("%s - Popping smoke at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance) + self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true) + local _coord = _closest.pilot:GetCoordinate() + local color = self.smokecolor + _coord:Smoke(color) else - local _distance = 0 - if _SETTINGS:IsImperial() then - _distance = string.format( "%.1fnm", UTILS.MetersToNM( smokedist ) ) - else - _distance = string.format( "%.1fkm", smokedist / 1000 ) - end - self:_DisplayMessageToSAR( _heli, string.format( "No Pilots within %s", _distance ), self.messageTime, false, false, true ) + local _distance = 0 + if _SETTINGS:IsImperial() then + _distance = string.format("%.1fnm",UTILS.MetersToNM(smokedist)) + else + _distance = string.format("%.1fkm",smokedist/1000) + end + self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",_distance), self.messageTime, false, false, true) end return self end @@ -1694,120 +1694,120 @@ end -- @param #CSAR self -- @param Wrapper.Unit#UNIT _heli Helicopter #UNIT -- @retunr -function CSAR:_GetClosestMASH( _heli ) - self:T( self.lid .. " _GetClosestMASH" ) +function CSAR:_GetClosestMASH(_heli) + self:T(self.lid .. " _GetClosestMASH") local _mashset = self.mash -- Core.Set#SET_GROUP local _mashes = _mashset:GetSetObjects() -- #table local _shortestDistance = -1 local _distance = 0 local _helicoord = _heli:GetCoordinate() - - local function GetCloseAirbase( coordinate, Coalition, Category ) - - local a = coordinate:GetVec3() - local distmin = math.huge - local airbase = nil - for DCSairbaseID, DCSairbase in pairs( world.getAirbases( Coalition ) ) do - local b = DCSairbase:getPoint() - - local c = UTILS.VecSubstract( a, b ) - local dist = UTILS.VecNorm( c ) - - if dist < distmin and (Category == nil or Category == DCSairbase:getDesc().category) then - distmin = dist - airbase = DCSairbase - end - - end - return distmin + + local function GetCloseAirbase(coordinate,Coalition,Category) + + local a=coordinate:GetVec3() + local distmin=math.huge + local airbase=nil + for DCSairbaseID, DCSairbase in pairs(world.getAirbases(Coalition)) do + local b=DCSairbase:getPoint() + + local c=UTILS.VecSubstract(a,b) + local dist=UTILS.VecNorm(c) + + if dist 0 then - local PilotTable = self.downedPilots - for _, _pilot in pairs( PilotTable ) do - self:T( { _pilot } ) - local pilot = _pilot -- #CSAR.DownedPilot - local group = pilot.group - local frequency = pilot.frequency or 0 -- thanks to @Thrud - if group and group:IsAlive() and frequency > 0 then - self:_AddBeaconToGroup( group, frequency ) + self:T(self.lid .. " _RefreshRadioBeacons") + if self:_CountActiveDownedPilots() > 0 then + local PilotTable = self.downedPilots + for _,_pilot in pairs (PilotTable) do + self:T({_pilot}) + local pilot = _pilot -- #CSAR.DownedPilot + local group = pilot.group + local frequency = pilot.frequency or 0 -- thanks to @Thrud + if group and group:IsAlive() and frequency > 0 then + self:_AddBeaconToGroup(group,frequency) + end end end - end - return self + return self end --- (Internal) Helper function to count active downed pilots. -- @param #CSAR self -- @return #number Number of pilots in the field. function CSAR:_CountActiveDownedPilots() - self:T( self.lid .. " _CountActiveDownedPilots" ) + self:T(self.lid .. " _CountActiveDownedPilots") local PilotsInFieldN = 0 - for _, _unitName in pairs( self.downedPilots ) do - self:T( { _unitName.desc } ) + for _, _unitName in pairs(self.downedPilots) do + self:T({_unitName.desc}) if _unitName.alive == true then PilotsInFieldN = PilotsInFieldN + 1 end @@ -1956,45 +1952,45 @@ end -- @param #CSAR self -- @return #boolean True or false. function CSAR:_ReachedPilotLimit() - self:T( self.lid .. " _ReachedPilotLimit" ) - local limit = self.maxdownedpilots - local islimited = self.limitmaxdownedpilots - local count = self:_CountActiveDownedPilots() - if islimited and (count >= limit) then - return true - else - return false - end + self:T(self.lid .. " _ReachedPilotLimit") + local limit = self.maxdownedpilots + local islimited = self.limitmaxdownedpilots + local count = self:_CountActiveDownedPilots() + if islimited and (count >= limit) then + return true + else + return false + end end ------------------------------- ---- FSM internal Functions --- ------------------------------- + ------------------------------ + --- FSM internal Functions --- + ------------------------------ --- (Internal) Function called after Start() event. -- @param #CSAR self. -- @param #string From From state. -- @param #string Event Event triggered. -- @param #string To To state. -function CSAR:onafterStart( From, Event, To ) - self:T( { From, Event, To } ) - self:I( self.lid .. "Started." ) +function CSAR:onafterStart(From, Event, To) + self:T({From, Event, To}) + self:I(self.lid .. "Started.") -- event handler - self:HandleEvent( EVENTS.Takeoff, self._EventHandler ) - self:HandleEvent( EVENTS.Land, self._EventHandler ) - self:HandleEvent( EVENTS.Ejection, self._EventHandler ) - self:HandleEvent( EVENTS.LandingAfterEjection, self._EventHandler ) -- shagrat - self:HandleEvent( EVENTS.PlayerEnterAircraft, self._EventHandler ) - self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventHandler ) - self:HandleEvent( EVENTS.PilotDead, self._EventHandler ) + self:HandleEvent(EVENTS.Takeoff, self._EventHandler) + self:HandleEvent(EVENTS.Land, self._EventHandler) + self:HandleEvent(EVENTS.Ejection, self._EventHandler) + self:HandleEvent(EVENTS.LandingAfterEjection, self._EventHandler) --shagrat + self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) + self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) + self:HandleEvent(EVENTS.PilotDead, self._EventHandler) if self.useprefix then local prefixes = self.csarPrefix or {} - self.allheligroupset = SET_GROUP:New():FilterCoalitions( self.coalitiontxt ):FilterPrefixes( prefixes ):FilterCategoryHelicopter():FilterStart() + self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefixes):FilterCategoryHelicopter():FilterStart() else - self.allheligroupset = SET_GROUP:New():FilterCoalitions( self.coalitiontxt ):FilterCategoryHelicopter():FilterStart() + self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart() end - self.mash = SET_GROUP:New():FilterCoalitions( self.coalitiontxt ):FilterPrefixes( self.mashprefix ):FilterStart() -- currently only GROUP objects, maybe support STATICs also? - self:__Status( -10 ) + self.mash = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart() -- currently only GROUP objects, maybe support STATICs also? + self:__Status(-10) return self end @@ -2003,14 +1999,14 @@ end function CSAR:_CheckDownedPilotTable() local pilots = self.downedPilots local npilots = {} - - for _ind, _entry in pairs( pilots ) do + + for _ind,_entry in pairs(pilots) do local _group = _entry.group if _group:IsAlive() then - npilots[_ind] = _entry + npilots[_ind] = _entry else if _entry.alive then - self:__KIA( 1, _entry.desc ) + self:__KIA(1,_entry.desc) end end end @@ -2023,27 +2019,27 @@ end -- @param #string From From state. -- @param #string Event Event triggered. -- @param #string To To state. -function CSAR:onbeforeStatus( From, Event, To ) - self:T( { From, Event, To } ) +function CSAR:onbeforeStatus(From, Event, To) + self:T({From, Event, To}) -- housekeeping self:_AddMedevacMenuItem() - + if not self.BeaconTimer or (self.BeaconTimer and not self.BeaconTimer:IsRunning()) then - self.BeaconTimer = TIMER:New( self._RefreshRadioBeacons, self ) - self.BeaconTimer:Start( 2, self.beaconRefresher ) + self.BeaconTimer = TIMER:New(self._RefreshRadioBeacons,self) + self.BeaconTimer:Start(2,self.beaconRefresher) end - + self:_CheckDownedPilotTable() - for _, _sar in pairs( self.csarUnits ) do + for _,_sar in pairs (self.csarUnits) do local PilotTable = self.downedPilots - for _, _entry in pairs( PilotTable ) do + for _,_entry in pairs (PilotTable) do if _entry.alive then local entry = _entry -- #CSAR.DownedPilot local name = entry.name local timestamp = entry.timestamp or 0 local now = timer.getAbsTime() - if now - timestamp > 17 then -- only check if we're not in approach mode, which is iterations of 5 and 10. - self:_CheckWoundedGroupStatus( _sar, name ) + if now - timestamp > 17 then -- only check if we\'re not in approach mode, which is iterations of 5 and 10. + self:_CheckWoundedGroupStatus(_sar,name) end end end @@ -2056,34 +2052,35 @@ end -- @param #string From From state. -- @param #string Event Event triggered. -- @param #string To To state. -function CSAR:onafterStatus( From, Event, To ) - self:T( { From, Event, To } ) +function CSAR:onafterStatus(From, Event, To) + self:T({From, Event, To}) -- collect some stats local NumberOfSARPilots = 0 - for _, _unitName in pairs( self.csarUnits ) do + for _, _unitName in pairs(self.csarUnits) do NumberOfSARPilots = NumberOfSARPilots + 1 end local PilotsInFieldN = self:_CountActiveDownedPilots() - + local PilotsBoarded = 0 - for _, _unitName in pairs( self.inTransitGroups ) do - for _, _units in pairs( _unitName ) do + for _, _unitName in pairs(self.inTransitGroups) do + for _,_units in pairs(_unitName) do PilotsBoarded = PilotsBoarded + 1 end end - + if self.verbose > 0 then - local text = string.format( "%s Active SAR: %d | Downed Pilots in field: %d (max %d) | Pilots boarded: %d | Landings: %d | Pilots rescued: %d", self.lid, NumberOfSARPilots, PilotsInFieldN, self.maxdownedpilots, PilotsBoarded, self.rescues, self.rescuedpilots ) - self:T( text ) + local text = string.format("%s Active SAR: %d | Downed Pilots in field: %d (max %d) | Pilots boarded: %d | Landings: %d | Pilots rescued: %d", + self.lid,NumberOfSARPilots,PilotsInFieldN,self.maxdownedpilots,PilotsBoarded,self.rescues,self.rescuedpilots) + self:T(text) if self.verbose < 2 then - self:I( text ) + self:I(text) elseif self.verbose > 1 then - self:I( text ) - local m = MESSAGE:New( text, "10", "Status", true ):ToCoalition( self.coalition ) + self:I(text) + local m = MESSAGE:New(text,"10","Status",true):ToCoalition(self.coalition) end end - self:__Status( -20 ) + self:__Status(-20) return self end @@ -2092,17 +2089,17 @@ end -- @param #string From From state. -- @param #string Event Event triggered. -- @param #string To To state. -function CSAR:onafterStop( From, Event, To ) - self:T( { From, Event, To } ) +function CSAR:onafterStop(From, Event, To) + self:T({From, Event, To}) -- event handler - self:UnHandleEvent( EVENTS.Takeoff ) - self:UnHandleEvent( EVENTS.Land ) - self:UnHandleEvent( EVENTS.Ejection ) - self:UnHandleEvent( EVENTS.LandingAfterEjection ) -- shagrat - self:UnHandleEvent( EVENTS.PlayerEnterUnit ) - self:UnHandleEvent( EVENTS.PlayerEnterAircraft ) - self:UnHandleEvent( EVENTS.PilotDead ) - self:T( self.lid .. "Stopped." ) + self:UnHandleEvent(EVENTS.Takeoff) + self:UnHandleEvent(EVENTS.Land) + self:UnHandleEvent(EVENTS.Ejection) + self:UnHandleEvent(EVENTS.LandingAfterEjection) -- shagrat + self:UnHandleEvent(EVENTS.PlayerEnterUnit) + self:UnHandleEvent(EVENTS.PlayerEnterAircraft) + self:UnHandleEvent(EVENTS.PilotDead) + self:T(self.lid .. "Stopped.") return self end @@ -2112,10 +2109,10 @@ end -- @param #string Event Event triggered. -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. --- @param #string Woundedgroupname Name of the downed pilot's group. -function CSAR:onbeforeApproach( From, Event, To, Heliname, Woundedgroupname ) - self:T( { From, Event, To, Heliname, Woundedgroupname } ) - self:_CheckWoundedGroupStatus( Heliname, Woundedgroupname ) +-- @param #string Woundedgroupname Name of the downed pilot\'s group. +function CSAR:onbeforeApproach(From, Event, To, Heliname, Woundedgroupname) + self:T({From, Event, To, Heliname, Woundedgroupname}) + self:_CheckWoundedGroupStatus(Heliname,Woundedgroupname) return self end @@ -2125,10 +2122,10 @@ end -- @param #string Event Event triggered. -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. --- @param #string Woundedgroupname Name of the downed pilot's group. -function CSAR:onbeforeBoarded( From, Event, To, Heliname, Woundedgroupname ) - self:T( { From, Event, To, Heliname, Woundedgroupname } ) - self:_ScheduledSARFlight( Heliname, Woundedgroupname ) +-- @param #string Woundedgroupname Name of the downed pilot\'s group. +function CSAR:onbeforeBoarded(From, Event, To, Heliname, Woundedgroupname) + self:T({From, Event, To, Heliname, Woundedgroupname}) + self:_ScheduledSARFlight(Heliname,Woundedgroupname) return self end @@ -2138,11 +2135,11 @@ end -- @param #string Event Event triggered. -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. --- @param #string Woundedgroupname Name of the downed pilot's group. +-- @param #string Woundedgroupname Name of the downed pilot\'s group. -- @param #boolean IsAirport True if heli has landed on an AFB (from event land). -function CSAR:onbeforeReturning( From, Event, To, Heliname, Woundedgroupname, IsAirPort ) - self:T( { From, Event, To, Heliname, Woundedgroupname } ) - self:_ScheduledSARFlight( Heliname, Woundedgroupname, IsAirPort ) +function CSAR:onbeforeReturning(From, Event, To, Heliname, Woundedgroupname, IsAirPort) + self:T({From, Event, To, Heliname, Woundedgroupname}) + self:_ScheduledSARFlight(Heliname,Woundedgroupname, IsAirPort) return self end @@ -2154,8 +2151,8 @@ end -- @param Wrapper.Unit#UNIT HeliUnit Unit of the helicopter. -- @param #string HeliName Name of the helicopter group. -- @param #number PilotsSaved Number of the saved pilots on board when landing. -function CSAR:onbeforeRescued( From, Event, To, HeliUnit, HeliName, PilotsSaved ) - self:T( { From, Event, To, HeliName, HeliUnit } ) +function CSAR:onbeforeRescued(From, Event, To, HeliUnit, HeliName, PilotsSaved) + self:T({From, Event, To, HeliName, HeliUnit}) self.rescues = self.rescues + 1 self.rescuedpilots = self.rescuedpilots + PilotsSaved return self @@ -2170,8 +2167,8 @@ end -- @param #number Frequency Beacon frequency in kHz. -- @param #string Leadername Name of the #UNIT of the downed pilot. -- @param #string CoordinatesText String of the position of the pilot. Format determined by self.coordtype. -function CSAR:onbeforePilotDown( From, Event, To, Group, Frequency, Leadername, CoordinatesText ) - self:T( { From, Event, To, Group, Frequency, Leadername, CoordinatesText } ) +function CSAR:onbeforePilotDown(From, Event, To, Group, Frequency, Leadername, CoordinatesText) + self:T({From, Event, To, Group, Frequency, Leadername, CoordinatesText}) return self end -------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 04fb227d2..a1bc0e99c 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -696,7 +696,7 @@ do -- ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18}, -- ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18}, -- ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25}, --- +-- ["UH-60L"] = {type="UH-60L", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16}, -- -- ### 2.1.2 Activate and deactivate zones -- @@ -987,11 +987,12 @@ CTLD.UnitTypes = { ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18}, ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25}, -- 19t cargo, 64 paratroopers. --Actually it's longer, but the center coord is off-center of the model. + ["UH-60L"] = {type="UH-60L", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16}, -- 4t cargo, 20 (unsec) seats } --- CTLD class version. -- @field #string version -CTLD.version="1.0.2" +CTLD.version="1.0.3" --- Instantiate a new CTLD. -- @param #CTLD self From c7bbb091958cb0be2a31982926d0d6482770da2a Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 16 Jan 2022 17:07:44 +0100 Subject: [PATCH 055/200] Added doors check for UH-60L --- Moose Development/Moose/Utilities/Utils.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 6ef1b9f68..6e5ffa48b 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1694,7 +1694,17 @@ function UTILS.IsLoadingDoorOpen( unit_name ) BASE:T( unit_name .. " door is open" ) ret_val = true end + + if string.find(type_name, "UH-60L") and (unit:getDrawArgumentValue(401) == 1) or (unit:getDrawArgumentValue(402) == 1) then + BASE:T(unit_name .. " cargo door is open") + ret_val = true + end + if string.find(type_name, "UH-60L" ) and unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(400) == 1 then + BASE:T(unit_name .. " front door(s) are open") + ret_val = true + end + if ret_val == false then BASE:T( unit_name .. " all doors are closed" ) end From 7bfa05f47d3bd67a9f85f69bab5dccddc455d332 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 19 Jan 2022 07:52:59 +0100 Subject: [PATCH 056/200] DETECTION - corrected call for Vec2 in zone --- Moose Development/Moose/Functional/Detection.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Functional/Detection.lua b/Moose Development/Moose/Functional/Detection.lua index eb9b2e982..73b84429c 100644 --- a/Moose Development/Moose/Functional/Detection.lua +++ b/Moose Development/Moose/Functional/Detection.lua @@ -714,7 +714,7 @@ do -- DETECTION_BASE if self.RejectZones then for RejectZoneID, RejectZone in pairs( self.RejectZones ) do local RejectZone = RejectZone -- Core.Zone#ZONE_BASE - if RejectZone:IsPointVec2InZone( DetectedObjectVec2 ) == true then + if RejectZone:IsVec2InZone( DetectedObjectVec2 ) == true then DetectionAccepted = false end end @@ -759,7 +759,7 @@ do -- DETECTION_BASE local ZoneProbability = ZoneData[2] -- #number ZoneProbability = ZoneProbability * 30 / 300 - if ZoneObject:IsPointVec2InZone( DetectedObjectVec2 ) == true then + if ZoneObject:IsVec2InZone( DetectedObjectVec2 ) == true then local Probability = math.random() -- Selects a number between 0 and 1 --self:T( { Probability, ZoneProbability } ) if Probability > ZoneProbability then From d7a44a639d8c1221b226a212d0a5b62b11270221 Mon Sep 17 00:00:00 2001 From: TommyC81 Date: Sun, 23 Jan 2022 14:21:59 +0400 Subject: [PATCH 057/200] Update Detection.lua (#1685) Code formatting. Fix minor typos, errors, and references in documentation. --- .../Moose/Functional/Detection.lua | 1792 ++++++++--------- 1 file changed, 866 insertions(+), 926 deletions(-) diff --git a/Moose Development/Moose/Functional/Detection.lua b/Moose Development/Moose/Functional/Detection.lua index 73b84429c..642748128 100644 --- a/Moose Development/Moose/Functional/Detection.lua +++ b/Moose Development/Moose/Functional/Detection.lua @@ -1,43 +1,42 @@ --- **Functional** -- Models the detection of enemy units by FACs or RECCEs and group them according various methods. --- +-- -- === --- +-- -- ## Features: --- +-- -- * Detection of targets by recce units. -- * Group detected targets per unit, type or area (zone). -- * Keep persistency of detected targets, if when detection is lost. -- * Provide an indication of detected targets. -- * Report detected targets. -- * Refresh detection upon specified time intervals. --- +-- -- === --- +-- -- ## Missions: --- +-- -- [DET - Detection](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/DET%20-%20Detection) --- +-- -- === --- --- Facilitate the detection of enemy units within the battle zone executed by FACs (Forward Air Controllers) or RECCEs (Reconnassance Units). +-- +-- Facilitate the detection of enemy units within the battle zone executed by FACs (Forward Air Controllers) or RECCEs (Reconnaissance Units). -- It uses the in-built detection capabilities of DCS World, but adds new functionalities. --- +-- -- === --- --- ### Contributions: --- +-- +-- ### Contributions: +-- -- * Mechanist : Early concept of DETECTION_AREAS. --- --- ### Authors: --- +-- +-- ### Authors: +-- -- * FlightControl : Analysis, Design, Programming, Testing --- +-- -- === --- +-- -- @module Functional.Detection -- @image Detection.JPG - do -- DETECTION_BASE --- @type DETECTION_BASE @@ -47,220 +46,218 @@ do -- DETECTION_BASE -- @field #table DetectedObjectsIdentified Map of the DetectedObjects identified. -- @field #number DetectionRun -- @extends Core.Fsm#FSM - + --- Defines the core functions to administer detected objects. -- The DETECTION_BASE class will detect objects within the battle zone for a list of @{Wrapper.Group}s detecting targets following (a) detection method(s). - -- + -- -- ## DETECTION_BASE constructor - -- + -- -- Construct a new DETECTION_BASE instance using the @{#DETECTION_BASE.New}() method. - -- + -- -- ## Initialization - -- + -- -- By default, detection will return detected objects with all the detection sensors available. - -- However, you can ask how the objects were found with specific detection methods. + -- However, you can ask how the objects were found with specific detection methods. -- If you use one of the below methods, the detection will work with the detection method specified. -- You can specify to apply multiple detection methods. - -- + -- -- Use the following functions to report the objects it detected using the methods Visual, Optical, Radar, IRST, RWR, DLINK: - -- + -- -- * @{#DETECTION_BASE.InitDetectVisual}(): Detected using Visual. -- * @{#DETECTION_BASE.InitDetectOptical}(): Detected using Optical. -- * @{#DETECTION_BASE.InitDetectRadar}(): Detected using Radar. -- * @{#DETECTION_BASE.InitDetectIRST}(): Detected using IRST. -- * @{#DETECTION_BASE.InitDetectRWR}(): Detected using RWR. -- * @{#DETECTION_BASE.InitDetectDLINK}(): Detected using DLINK. - -- + -- -- ## **Filter** detected units based on **category of the unit** - -- - -- Filter the detected units based on Unit.Category using the method @{#DETECTION_BASE.FilterCategories}(). + -- + -- Filter the detected units based on Unit.Category using the method @{#DETECTION_BASE.FilterCategories}(). -- The different values of Unit.Category can be: - -- + -- -- * Unit.Category.AIRPLANE -- * Unit.Category.GROUND_UNIT -- * Unit.Category.HELICOPTER -- * Unit.Category.SHIP -- * Unit.Category.STRUCTURE - -- + -- -- Multiple Unit.Category entries can be given as a table and then these will be evaluated as an OR expression. - -- + -- -- Example to filter a single category (Unit.Category.AIRPLANE). - -- - -- DetectionObject:FilterCategories( Unit.Category.AIRPLANE ) - -- + -- + -- DetectionObject:FilterCategories( Unit.Category.AIRPLANE ) + -- -- Example to filter multiple categories (Unit.Category.AIRPLANE, Unit.Category.HELICOPTER). Note the {}. - -- + -- -- DetectionObject:FilterCategories( { Unit.Category.AIRPLANE, Unit.Category.HELICOPTER } ) - -- - -- + -- -- ## **DETECTION_ derived classes** group the detected units into a **DetectedItems[]** list - -- - -- DETECTION_BASE derived classes build a list called DetectedItems[], which is essentially a first later + -- + -- DETECTION_BASE derived classes build a list called DetectedItems[], which is essentially a first later -- of grouping of detected units. Each DetectedItem within the DetectedItems[] list contains -- a SET_UNIT object that contains the detected units that belong to that group. - -- - -- Derived classes will apply different methods to group the detected units. + -- + -- Derived classes will apply different methods to group the detected units. -- Examples are per area, per quadrant, per distance, per type. - -- See further the derived DETECTION classes on which grouping methods are currently supported. - -- + -- See further the derived DETECTION classes on which grouping methods are currently supported. + -- -- Various methods exist how to retrieve the grouped items from a DETECTION_BASE derived class: - -- + -- -- * The method @{Functional.Detection#DETECTION_BASE.GetDetectedItems}() retrieves the DetectedItems[] list. -- * A DetectedItem from the DetectedItems[] list can be retrieved using the method @{Functional.Detection#DETECTION_BASE.GetDetectedItem}( DetectedItemIndex ). -- Note that this method returns a DetectedItem element from the list, that contains a Set variable and further information -- about the DetectedItem that is set by the DETECTION_BASE derived classes, used to group the DetectedItem. -- * A DetectedSet from the DetectedItems[] list can be retrieved using the method @{Functional.Detection#DETECTION_BASE.GetDetectedSet}( DetectedItemIndex ). -- This method retrieves the Set from a DetectedItem element from the DetectedItem list (DetectedItems[ DetectedItemIndex ].Set ). - -- + -- -- ## **Visual filters** to fine-tune the probability of the detected objects - -- + -- -- By default, DCS World will return any object that is in LOS and within "visual reach", or detectable through one of the electronic detection means. -- That being said, the DCS World detection algorithm can sometimes be unrealistic. -- Especially for a visual detection, DCS World is able to report within 1 second a detailed detection of a group of 20 units (including types of the units) that are 10 kilometers away, using only visual capabilities. -- Additionally, trees and other obstacles are not accounted during the DCS World detection. - -- + -- -- Therefore, an additional (optional) filtering has been built into the DETECTION_BASE class, that can be set for visual detected units. -- For electronic detection, this filtering is not applied, only for visually detected targets. - -- + -- -- The following additional filtering can be applied for visual filtering: - -- + -- -- * A probability factor per kilometer distance. -- * A probability factor based on the alpha angle between the detected object and the unit detecting. -- A detection from a higher altitude allows for better detection than when on the ground. -- * Define a probability factor for "cloudy zones", which are zones where forests or villages are located. In these zones, detection will be much more difficult. - -- The mission designer needs to define these cloudy zones within the mission, and needs to register these zones in the DETECTION_ objects additing a probability factor per zone. - -- + -- The mission designer needs to define these cloudy zones within the mission, and needs to register these zones in the DETECTION_ objects adding a probability factor per zone. + -- -- I advise however, that, when you first use the DETECTION derived classes, that you don't use these filters. - -- Only when you experience unrealistic behaviour in your missions, these filters could be applied. - -- - -- + -- Only when you experience unrealistic behavior in your missions, these filters could be applied. + -- -- ### Distance visual detection probability - -- + -- -- Upon a **visual** detection, the further away a detected object is, the less likely it is to be detected properly. -- Also, the speed of accurate detection plays a role. - -- + -- -- A distance probability factor between 0 and 1 can be given, that will model a linear extrapolated probability over 10 km distance. - -- + -- -- For example, if a probability factor of 0.6 (60%) is given, the extrapolated probabilities over 15 kilometers would like like: -- 1 km: 96%, 2 km: 92%, 3 km: 88%, 4 km: 84%, 5 km: 80%, 6 km: 76%, 7 km: 72%, 8 km: 68%, 9 km: 64%, 10 km: 60%, 11 km: 56%, 12 km: 52%, 13 km: 48%, 14 km: 44%, 15 km: 40%. - -- + -- -- Note that based on this probability factor, not only the detection but also the **type** of the unit will be applied! - -- + -- -- Use the method @{Functional.Detection#DETECTION_BASE.SetDistanceProbability}() to set the probability factor upon a 10 km distance. - -- + -- -- ### Alpha Angle visual detection probability - -- + -- -- Upon a **visual** detection, the higher the unit is during the detecting process, the more likely the detected unit is to be detected properly. -- A detection at a 90% alpha angle is the most optimal, a detection at 10% is less and a detection at 0% is less likely to be correct. - -- + -- -- A probability factor between 0 and 1 can be given, that will model a progressive extrapolated probability if the target would be detected at a 0° angle. - -- + -- -- For example, if a alpha angle probability factor of 0.7 is given, the extrapolated probabilities of the different angles would look like: -- 0°: 70%, 10°: 75,21%, 20°: 80,26%, 30°: 85%, 40°: 89,28%, 50°: 92,98%, 60°: 95,98%, 70°: 98,19%, 80°: 99,54%, 90°: 100% - -- + -- -- Use the method @{Functional.Detection#DETECTION_BASE.SetAlphaAngleProbability}() to set the probability factor if 0°. - -- + -- -- ### Cloudy Zones detection probability - -- + -- -- Upon a **visual** detection, the more a detected unit is within a cloudy zone, the less likely the detected unit is to be detected successfully. -- The Cloudy Zones work with the ZONE_BASE derived classes. The mission designer can define within the mission -- zones that reflect cloudy areas where detected units may not be so easily visually detected. - -- + -- -- Use the method @{Functional.Detection#DETECTION_BASE.SetZoneProbability}() to set for a defined number of zones, the probability factors. - -- + -- -- Note however, that the more zones are defined to be "cloudy" within a detection, the more performance it will take -- from the DETECTION_BASE to calculate the presence of the detected unit within each zone. - -- Expecially for ZONE_POLYGON, try to limit the amount of nodes of the polygon! - -- - -- Typically, this kind of filter would be applied for very specific areas were a detection needs to be very realisting for + -- Especially for ZONE_POLYGON, try to limit the amount of nodes of the polygon! + -- + -- Typically, this kind of filter would be applied for very specific areas where a detection needs to be very realistic for -- AI not to detect so easily targets within a forrest or village rich area. - -- + -- -- ## Accept / Reject detected units - -- - -- DETECTION_BASE can accept or reject successful detections based on the location of the detected object, + -- + -- DETECTION_BASE can accept or reject successful detections based on the location of the detected object, -- if it is located in range or located inside or outside of specific zones. - -- + -- -- ### Detection acceptance of within range limit - -- + -- -- A range can be set that will limit a successful detection for a unit. -- Use the method @{Functional.Detection#DETECTION_BASE.SetAcceptRange}() to apply a range in meters till where detected units will be accepted. - -- + -- -- local SetGroup = SET_GROUP:New():FilterPrefixes( "FAC" ):FilterStart() -- Build a SetGroup of Forward Air Controllers. - -- + -- -- -- Build a detect object. -- local Detection = DETECTION_UNITS:New( SetGroup ) - -- + -- -- -- This will accept detected units if the range is below 5000 meters. - -- Detection:SetAcceptRange( 5000 ) - -- + -- Detection:SetAcceptRange( 5000 ) + -- -- -- Start the Detection. -- Detection:Start() - -- - -- + -- + -- -- ### Detection acceptance if within zone(s). - -- + -- -- Specific ZONE_BASE object(s) can be given as a parameter, which will only accept a detection if the unit is within the specified ZONE_BASE object(s). -- Use the method @{Functional.Detection#DETECTION_BASE.SetAcceptZones}() will accept detected units if they are within the specified zones. - -- + -- -- local SetGroup = SET_GROUP:New():FilterPrefixes( "FAC" ):FilterStart() -- Build a SetGroup of Forward Air Controllers. - -- + -- -- -- Search fo the zones where units are to be accepted. -- local ZoneAccept1 = ZONE:New( "AcceptZone1" ) -- local ZoneAccept2 = ZONE:New( "AcceptZone2" ) - -- + -- -- -- Build a detect object. -- local Detection = DETECTION_UNITS:New( SetGroup ) - -- + -- -- -- This will accept detected units by Detection when the unit is within ZoneAccept1 OR ZoneAccept2. -- Detection:SetAcceptZones( { ZoneAccept1, ZoneAccept2 } ) - -- + -- -- -- Start the Detection. -- Detection:Start() - -- - -- ### Detection rejectance if within zone(s). - -- + -- + -- ### Detection rejection if within zone(s). + -- -- Specific ZONE_BASE object(s) can be given as a parameter, which will reject detection if the unit is within the specified ZONE_BASE object(s). -- Use the method @{Functional.Detection#DETECTION_BASE.SetRejectZones}() will reject detected units if they are within the specified zones. -- An example of how to use the method is shown below. - -- + -- -- local SetGroup = SET_GROUP:New():FilterPrefixes( "FAC" ):FilterStart() -- Build a SetGroup of Forward Air Controllers. - -- + -- -- -- Search fo the zones where units are to be rejected. -- local ZoneReject1 = ZONE:New( "RejectZone1" ) -- local ZoneReject2 = ZONE:New( "RejectZone2" ) - -- + -- -- -- Build a detect object. -- local Detection = DETECTION_UNITS:New( SetGroup ) - -- + -- -- -- This will reject detected units by Detection when the unit is within ZoneReject1 OR ZoneReject2. - -- Detection:SetRejectZones( { ZoneReject1, ZoneReject2 } ) - -- + -- Detection:SetRejectZones( { ZoneReject1, ZoneReject2 } ) + -- -- -- Start the Detection. -- Detection:Start() - -- + -- -- ## Detection of Friendlies Nearby - -- + -- -- Use the method @{Functional.Detection#DETECTION_BASE.SetFriendliesRange}() to set the range what will indicate when friendlies are nearby -- a DetectedItem. The default range is 6000 meters. For air detections, it is advisory to use about 30.000 meters. - -- + -- -- ## DETECTION_BASE is a Finite State Machine -- -- Various Events and State Transitions can be tailored using DETECTION_BASE. - -- + -- -- ### DETECTION_BASE States - -- + -- -- * **Detecting**: The detection is running. -- * **Stopped**: The detection is stopped. - -- + -- -- ### DETECTION_BASE Events - -- + -- -- * **Start**: Start the detection process. -- * **Detect**: Detect new units. -- * **Detected**: New units have been detected. -- * **Stop**: Stop the detection process. - -- + -- -- @field #DETECTION_BASE DETECTION_BASE - -- + -- DETECTION_BASE = { ClassName = "DETECTION_BASE", DetectionSetGroup = nil, @@ -269,12 +266,12 @@ do -- DETECTION_BASE DetectionRun = 0, DetectedObjectsIdentified = {}, DetectedItems = {}, - DetectedItemsByIndex = {}, + DetectedItemsByIndex = {}, } - + --- @type DETECTION_BASE.DetectedObjects -- @list <#DETECTION_BASE.DetectedObject> - + --- @type DETECTION_BASE.DetectedObject -- @field #string Name -- @field #boolean IsVisible @@ -287,11 +284,10 @@ do -- DETECTION_BASE -- @field #boolean LastPos -- @field #number LastVelocity - --- @type DETECTION_BASE.DetectedItems -- @list <#DETECTION_BASE.DetectedItem> - - --- Detected item data structrue. + + --- Detected item data structure. -- @type DETECTION_BASE.DetectedItem -- @field #boolean IsDetected Indicates if the DetectedItem has been detected or not. -- @field Core.Set#SET_UNIT Set The Set of Units in the detected area. @@ -302,7 +298,7 @@ do -- DETECTION_BASE -- @field #boolean FriendliesNearBy Indicates if there are friendlies within the detected area. -- @field Wrapper.Unit#UNIT NearestFAC The nearest FAC near the Area. -- @field Core.Point#COORDINATE Coordinate The last known coordinate of the DetectedItem. - -- @field Core.Point#COORDINATE InterceptCoord Intercept coordiante. + -- @field Core.Point#COORDINATE InterceptCoord Intercept coordinate. -- @field #number DistanceRecce Distance in meters of the Recce. -- @field #number Index Detected item key. Could also be a string. -- @field #string ItemID ItemPrefix .. "." .. self.DetectedItemMax. @@ -310,7 +306,7 @@ do -- DETECTION_BASE -- @field #table PlayersNearBy Table of nearby players. -- @field #table FriendliesDistance Table of distances to friendly units. -- @field #string TypeName Type name of the detected unit. - -- @field #string CategoryName Catetory name of the detected unit. + -- @field #string CategoryName Category name of the detected unit. -- @field #string Name Name of the detected object. -- @field #boolean IsVisible If true, detected object is visible. -- @field #number LastTime Last time the detected item was seen. @@ -319,31 +315,31 @@ do -- DETECTION_BASE -- @field #boolean KnowType Type of detected item is known. -- @field #boolean KnowDistance Distance to the detected item is known. -- @field #number Distance Distance to the detected item. - + --- DETECTION constructor. -- @param #DETECTION_BASE self -- @param Core.Set#SET_GROUP DetectionSet The @{Set} of @{Group}s that is used to detect the units. -- @return #DETECTION_BASE self function DETECTION_BASE:New( DetectionSet ) - + -- Inherits from BASE local self = BASE:Inherit( self, FSM:New() ) -- #DETECTION_BASE - + 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, @@ -351,15 +347,15 @@ do -- DETECTION_BASE Unit.Category.SHIP, Unit.Category.STRUCTURE } ) - + self:SetFriendliesRange( 6000 ) - + -- Create FSM transitions. - + self:SetStartState( "Stopped" ) - - self:AddTransition( "Stopped", "Start", "Detecting") - + + self:AddTransition( "Stopped", "Start", "Detecting" ) + --- OnLeave Transition Handler for State Stopped. -- @function [parent=#DETECTION_BASE] OnLeaveStopped -- @param #DETECTION_BASE self @@ -367,14 +363,14 @@ do -- DETECTION_BASE -- @param #string Event The Event string. -- @param #string To The To State string. -- @return #boolean Return false to cancel Transition. - + --- OnEnter Transition Handler for State Stopped. -- @function [parent=#DETECTION_BASE] OnEnterStopped -- @param #DETECTION_BASE self -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. - + --- OnBefore Transition Handler for Event Start. -- @function [parent=#DETECTION_BASE] OnBeforeStart -- @param #DETECTION_BASE self @@ -382,23 +378,23 @@ do -- DETECTION_BASE -- @param #string Event The Event string. -- @param #string To The To State string. -- @return #boolean Return false to cancel Transition. - + --- OnAfter Transition Handler for Event Start. -- @function [parent=#DETECTION_BASE] OnAfterStart -- @param #DETECTION_BASE self -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. - + --- Synchronous Event Trigger for Event Start. -- @function [parent=#DETECTION_BASE] Start -- @param #DETECTION_BASE self - + --- Asynchronous Event Trigger for Event Start. -- @function [parent=#DETECTION_BASE] __Start -- @param #DETECTION_BASE self -- @param #number Delay The delay in seconds. - + --- OnLeave Transition Handler for State Detecting. -- @function [parent=#DETECTION_BASE] OnLeaveDetecting -- @param #DETECTION_BASE self @@ -406,17 +402,17 @@ do -- DETECTION_BASE -- @param #string Event The Event string. -- @param #string To The To State string. -- @return #boolean Return false to cancel Transition. - + --- OnEnter Transition Handler for State Detecting. -- @function [parent=#DETECTION_BASE] OnEnterDetecting -- @param #DETECTION_BASE self -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. - + self:AddTransition( "Detecting", "Detect", "Detecting" ) self:AddTransition( "Detecting", "Detection", "Detecting" ) - + --- OnBefore Transition Handler for Event Detect. -- @function [parent=#DETECTION_BASE] OnBeforeDetect -- @param #DETECTION_BASE self @@ -424,26 +420,25 @@ do -- DETECTION_BASE -- @param #string Event The Event string. -- @param #string To The To State string. -- @return #boolean Return false to cancel Transition. - + --- OnAfter Transition Handler for Event Detect. -- @function [parent=#DETECTION_BASE] OnAfterDetect -- @param #DETECTION_BASE self -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. - + --- Synchronous Event Trigger for Event Detect. -- @function [parent=#DETECTION_BASE] Detect -- @param #DETECTION_BASE self - + --- Asynchronous Event Trigger for Event Detect. -- @function [parent=#DETECTION_BASE] __Detect -- @param #DETECTION_BASE self -- @param #number Delay The delay in seconds. - - + self:AddTransition( "Detecting", "Detected", "Detecting" ) - + --- OnBefore Transition Handler for Event Detected. -- @function [parent=#DETECTION_BASE] OnBeforeDetected -- @param #DETECTION_BASE self @@ -451,7 +446,7 @@ do -- DETECTION_BASE -- @param #string Event The Event string. -- @param #string To The To State string. -- @return #boolean Return false to cancel Transition. - + --- OnAfter Transition Handler for Event Detected. -- @function [parent=#DETECTION_BASE] OnAfterDetected -- @param #DETECTION_BASE self @@ -459,20 +454,20 @@ do -- DETECTION_BASE -- @param #string Event The Event string. -- @param #string To The To State string. -- @param #table Units Table of detected units. - + --- Synchronous Event Trigger for Event Detected. -- @function [parent=#DETECTION_BASE] Detected -- @param #DETECTION_BASE self -- @param #table Units Table of detected units. - + --- Asynchronous Event Trigger for Event Detected. -- @function [parent=#DETECTION_BASE] __Detected -- @param #DETECTION_BASE self -- @param #number Delay The delay in seconds. -- @param #table Units Table of detected units. - + self:AddTransition( "Detecting", "DetectedItem", "Detecting" ) - + --- OnAfter Transition Handler for Event DetectedItem. -- @function [parent=#DETECTION_BASE] OnAfterDetectedItem -- @param #DETECTION_BASE self @@ -480,9 +475,9 @@ do -- DETECTION_BASE -- @param #string Event The Event string. -- @param #string To The To State string. -- @param #table DetectedItem The DetectedItem. - + self:AddTransition( "*", "Stop", "Stopped" ) - + --- OnBefore Transition Handler for Event Stop. -- @function [parent=#DETECTION_BASE] OnBeforeStop -- @param #DETECTION_BASE self @@ -490,23 +485,23 @@ do -- DETECTION_BASE -- @param #string Event The Event string. -- @param #string To The To State string. -- @return #boolean Return false to cancel Transition. - + --- OnAfter Transition Handler for Event Stop. -- @function [parent=#DETECTION_BASE] OnAfterStop -- @param #DETECTION_BASE self -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. - + --- Synchronous Event Trigger for Event Stop. -- @function [parent=#DETECTION_BASE] Stop -- @param #DETECTION_BASE self - + --- Asynchronous Event Trigger for Event Stop. -- @function [parent=#DETECTION_BASE] __Stop -- @param #DETECTION_BASE self -- @param #number Delay The delay in seconds. - + --- OnLeave Transition Handler for State Stopped. -- @function [parent=#DETECTION_BASE] OnLeaveStopped -- @param #DETECTION_BASE self @@ -514,24 +509,24 @@ do -- DETECTION_BASE -- @param #string Event The Event string. -- @param #string To The To State string. -- @return #boolean Return false to cancel Transition. - + --- OnEnter Transition Handler for State Stopped. -- @function [parent=#DETECTION_BASE] OnEnterStopped -- @param #DETECTION_BASE self -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. - + return self end - + do -- State Transition Handling - + --- @param #DETECTION_BASE self -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. - function DETECTION_BASE:onafterStart(From,Event,To) + function DETECTION_BASE:onafterStart( From, Event, To ) self:__Detect( 1 ) end @@ -539,18 +534,18 @@ do -- DETECTION_BASE -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. - function DETECTION_BASE:onafterDetect(From,Event,To) + function DETECTION_BASE:onafterDetect( From, Event, To ) local DetectDelay = 0.1 self.DetectionCount = 0 self.DetectionRun = 0 self:UnIdentifyAllDetectedObjects() -- Resets the DetectedObjectsIdentified table - + local DetectionTimeStamp = timer.getTime() - + -- Reset detection cache for the next detection run. for DetectionObjectName, DetectedObjectData in pairs( self.DetectedObjects ) do - + self.DetectedObjects[DetectionObjectName].IsDetected = false self.DetectedObjects[DetectionObjectName].IsVisible = false self.DetectedObjects[DetectionObjectName].KnowDistance = nil @@ -558,23 +553,21 @@ do -- DETECTION_BASE self.DetectedObjects[DetectionObjectName].LastPos = nil self.DetectedObjects[DetectionObjectName].LastVelocity = nil self.DetectedObjects[DetectionObjectName].Distance = 10000000 - + end - + -- Count alive(!) groups only. Solves issue #1173 https://github.com/FlightControl-Master/MOOSE/issues/1173 self.DetectionCount = self:CountAliveRecce() - - local DetectionInterval = self.DetectionCount / ( self.RefreshTimeInterval - 1 ) - - self:ForEachAliveRecce( - function( DetectionGroup ) - self:__Detection( DetectDelay, DetectionGroup, DetectionTimeStamp ) -- Process each detection asynchronously. - DetectDelay = DetectDelay + DetectionInterval - end - ) - + + local DetectionInterval = self.DetectionCount / (self.RefreshTimeInterval - 1) + + self:ForEachAliveRecce( function( DetectionGroup ) + self:__Detection( DetectDelay, DetectionGroup, DetectionTimeStamp ) -- Process each detection asynchronously. + DetectDelay = DetectDelay + DetectionInterval + end ) + self:__Detect( -self.RefreshTimeInterval ) - + end --- @param #DETECTION_BASE self @@ -583,41 +576,40 @@ do -- DETECTION_BASE return self.DetectionSet:CountAlive() - end - + end + --- @param #DETECTION_BASE self function DETECTION_BASE:ForEachAliveRecce( IteratorFunction, ... ) self:F2( arg ) - + self.DetectionSet:ForEachGroupAlive( IteratorFunction, arg ) - + return self end - - + --- @param #DETECTION_BASE self -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. -- @param Wrapper.Group#GROUP DetectionGroup The Group detecting. -- @param #number DetectionTimeStamp Time stamp of detection event. - function DETECTION_BASE:onafterDetection( From, Event, To, Detection, DetectionTimeStamp ) - - --self:F( { DetectedObjects = self.DetectedObjects } ) - + function DETECTION_BASE:onafterDetection( From, Event, To, Detection, DetectionTimeStamp ) + + -- self:F( { DetectedObjects = self.DetectedObjects } ) + self.DetectionRun = self.DetectionRun + 1 - + local HasDetectedObjects = false - + if Detection and Detection:IsAlive() then - - --self:T( { "DetectionGroup is Alive", DetectionGroup:GetName() } ) - + + -- self:T( { "DetectionGroup is Alive", DetectionGroup:GetName() } ) + local DetectionGroupName = Detection:GetName() - local DetectionUnit = Detection:GetUnit(1) - + local DetectionUnit = Detection:GetUnit( 1 ) + local DetectedUnits = {} - + local DetectedTargets = Detection:GetDetectedTargets( self.DetectVisual, self.DetectOptical, @@ -626,29 +618,29 @@ do -- DETECTION_BASE self.DetectRWR, self.DetectDLINK ) - + self:F( { DetectedTargets = DetectedTargets } ) - + for DetectionObjectID, Detection in pairs( DetectedTargets ) do local DetectedObject = Detection.object -- DCS#Object - + if DetectedObject and DetectedObject:isExist() and DetectedObject.id_ < 50000000 then -- and ( DetectedObject:getCategory() == Object.Category.UNIT or DetectedObject:getCategory() == Object.Category.STATIC ) then local DetectedObjectName = DetectedObject:getName() if not self.DetectedObjects[DetectedObjectName] then - self.DetectedObjects[DetectedObjectName] = self.DetectedObjects[DetectedObjectName] or {} + 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( + + local TargetIsDetected, TargetIsVisible, TargetLastTime, TargetKnowType, TargetKnowDistance, TargetLastPos, TargetLastVelocity = DetectionUnit:IsTargetDetected( DetectedObject, self.DetectVisual, self.DetectOptical, @@ -657,177 +649,176 @@ do -- DETECTION_BASE self.DetectRWR, self.DetectDLINK ) - - --self:T2( { TargetIsDetected = TargetIsDetected, TargetIsVisible = TargetIsVisible, TargetLastTime = TargetLastTime, TargetKnowType = TargetKnowType, TargetKnowDistance = TargetKnowDistance, TargetLastPos = TargetLastPos, TargetLastVelocity = TargetLastVelocity } ) - + + -- self:T2( { TargetIsDetected = TargetIsDetected, TargetIsVisible = TargetIsVisible, TargetLastTime = TargetLastTime, TargetKnowType = TargetKnowType, TargetKnowDistance = TargetKnowDistance, TargetLastPos = TargetLastPos, TargetLastVelocity = TargetLastVelocity } ) + -- Only process if the target is visible. Detection also returns invisible units. - --if Detection.visible == true then - - 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 - - --self:F( { "Detected Target:", DetectionGroupName, DetectedObjectName, DetectedObjectType, Distance, DetectedUnitCategory } ) - - -- Calculate Acceptance - - DetectionAccepted = self._.FilterCategories[DetectedUnitCategory] ~= nil and DetectionAccepted or false - - -- if Distance > 15000 then - -- if DetectedUnitCategory == Unit.Category.GROUND_UNIT or DetectedUnitCategory == Unit.Category.SHIP then - -- if DetectedObject:hasSensors( Unit.SensorType.RADAR, Unit.RadarType.AS ) == false then - -- DetectionAccepted = false - -- end - -- end - -- end - - if self.AcceptRange and Distance * 1000 > self.AcceptRange then + -- if Detection.visible == true then + + 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 + + -- self:F( { "Detected Target:", DetectionGroupName, DetectedObjectName, DetectedObjectType, Distance, DetectedUnitCategory } ) + + -- Calculate Acceptance + + DetectionAccepted = self._.FilterCategories[DetectedUnitCategory] ~= nil and DetectionAccepted or false + + -- if Distance > 15000 then + -- if DetectedUnitCategory == Unit.Category.GROUND_UNIT or DetectedUnitCategory == Unit.Category.SHIP then + -- if DetectedObject:hasSensors( Unit.SensorType.RADAR, Unit.RadarType.AS ) == false then + -- DetectionAccepted = false + -- end + -- end + -- end + + 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 -- Core.Zone#ZONE_BASE + if AcceptZone:IsVec2InZone( DetectedObjectVec2 ) then + AnyZoneDetection = true + end + end + if not AnyZoneDetection then DetectionAccepted = false end - - if self.AcceptZones then - local AnyZoneDetection = false - for AcceptZoneID, AcceptZone in pairs( self.AcceptZones ) do - local AcceptZone = AcceptZone -- Core.Zone#ZONE_BASE - if AcceptZone:IsVec2InZone( DetectedObjectVec2 ) then - AnyZoneDetection = true - end - end - if not AnyZoneDetection then - DetectionAccepted = false + end + + if self.RejectZones then + for RejectZoneID, RejectZone in pairs( self.RejectZones ) do + local RejectZone = RejectZone -- Core.Zone#ZONE_BASE + if RejectZone:IsVec2InZone( DetectedObjectVec2 ) == true then + DetectionAccepted = false end end - - if self.RejectZones then - for RejectZoneID, RejectZone in pairs( self.RejectZones ) do - local RejectZone = RejectZone -- Core.Zone#ZONE_BASE - if RejectZone:IsVec2InZone( DetectedObjectVec2 ) == true then + end + + -- Calculate additional probabilities + + 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() -- Selects a number between 0 and 1 + -- self:T( { Probability, DistanceProbability } ) + 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() -- Selects a number between 0 and 1 + -- self:T( { Probability, AlphaAngleProbability } ) + 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] -- Core.Zone#ZONE_BASE + local ZoneProbability = ZoneData[2] -- #number + ZoneProbability = ZoneProbability * 30 / 300 + + if ZoneObject:IsVec2InZone( DetectedObjectVec2 ) == true then + local Probability = math.random() -- Selects a number between 0 and 1 + -- self:T( { Probability, ZoneProbability } ) + if Probability > ZoneProbability then DetectionAccepted = false + break end end end - - -- Calculate additional probabilities - - 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() -- Selects a number between 0 and 1 - --self:T( { Probability, DistanceProbability } ) - if Probability > DistanceProbability then - DetectionAccepted = false - 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 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() -- Selects a number between 0 and 1 - --self:T( { Probability, AlphaAngleProbability } ) - if Probability > AlphaAngleProbability then - DetectionAccepted = false - end - + + if TargetIsDetected and TargetIsVisible and TargetIsVisible == true then + self.DetectedObjects[DetectedObjectName].IsVisible = TargetIsDetected and TargetIsVisible 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] -- Core.Zone#ZONE_BASE - local ZoneProbability = ZoneData[2] -- #number - ZoneProbability = ZoneProbability * 30 / 300 - - if ZoneObject:IsVec2InZone( DetectedObjectVec2 ) == true then - local Probability = math.random() -- Selects a number between 0 and 1 - --self:T( { Probability, ZoneProbability } ) - if Probability > ZoneProbability then - DetectionAccepted = false - break - end - end - end + + if TargetIsDetected and not self.DetectedObjects[DetectedObjectName].KnowType then + self.DetectedObjects[DetectedObjectName].KnowType = TargetIsDetected and TargetKnowType 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 -- Detection.distance -- 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 - -- if beyond the DetectionRange then nullify... - self:F( { DetectedObject = "No more detection for " .. DetectedObjectName } ) - if self.DetectedObjects[DetectedObjectName] then - self.DetectedObjects[DetectedObjectName] = nil - end + self.DetectedObjects[DetectedObjectName].KnowDistance = TargetKnowDistance -- Detection.distance -- 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:T2( self.DetectedObjects ) + + self.DetectedObjects[DetectedObjectName].DetectionTimeStamp = DetectionTimeStamp + + self:F( { DetectedObject = self.DetectedObjects[DetectedObjectName] } ) + + local DetectedUnit = UNIT:FindByName( DetectedObjectName ) + + DetectedUnits[DetectedObjectName] = DetectedUnit + else + -- if beyond the DetectionRange then nullify... + self:F( { DetectedObject = "No more detection for " .. DetectedObjectName } ) + if self.DetectedObjects[DetectedObjectName] then + self.DetectedObjects[DetectedObjectName] = nil + end + end + + -- self:T2( self.DetectedObjects ) else -- The previously detected object does not exist anymore, delete from the cache. 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 - + -- First check if all DetectedObjects were detected. -- This is important. When there are DetectedObjects in the list, but were not detected, -- And these remain undetected for more than 60 seconds, then these DetectedObjects will be flagged as not Detected. @@ -840,45 +831,43 @@ do -- DETECTION_BASE end self:CreateDetectionItems() -- Polymorphic call to Create/Update the DetectionItems list for the DETECTION_ class grouping method. - + for DetectedItemID, DetectedItem in pairs( self.DetectedItems ) do - + self:UpdateDetectedItemDetection( DetectedItem ) - + self:CleanDetectionItem( DetectedItem, DetectedItemID ) -- Any DetectionItem that has a Set with zero elements in it, must be removed from the DetectionItems list. - + if DetectedItem then self:__DetectedItem( 0.1, DetectedItem ) end - + end end - end - - + end - + do -- DetectionItems Creation - + --- Clean the DetectedItem table. -- @param #DETECTION_BASE self -- @return #DETECTION_BASE function DETECTION_BASE:CleanDetectionItem( DetectedItem, DetectedItemID ) - + -- We clean all DetectedItems. -- if there are any remaining DetectedItems with no Set Objects then the Item in the DetectedItems must be deleted. - + local DetectedSet = DetectedItem.Set - + if DetectedSet:Count() == 0 then self:RemoveDetectedItem( DetectedItemID ) end return self end - + --- Forget a Unit from a DetectionItem -- @param #DETECTION_BASE self -- @param #string UnitName The UnitName that needs to be forgotten from the DetectionItem Sets. @@ -886,7 +875,7 @@ do -- DETECTION_BASE function DETECTION_BASE:ForgetDetectedUnit( UnitName ) local DetectedItems = self:GetDetectedItems() - + for DetectedItemIndex, DetectedItem in pairs( DetectedItems ) do local DetectedSet = self:GetDetectedItemSet( DetectedItem ) if DetectedSet then @@ -896,96 +885,95 @@ do -- DETECTION_BASE return self end - + --- Make a DetectionSet table. This function will be overridden in the derived clsses. -- @param #DETECTION_BASE self -- @return #DETECTION_BASE function DETECTION_BASE:CreateDetectionItems() - + self:F( "Error, in DETECTION_BASE class..." ) return self end - end - + do -- Initialization methods - + --- Detect Visual. -- @param #DETECTION_BASE self -- @param #boolean DetectVisual -- @return #DETECTION_BASE self function DETECTION_BASE:InitDetectVisual( DetectVisual ) - + self.DetectVisual = DetectVisual - + return self end - + --- Detect Optical. -- @param #DETECTION_BASE self -- @param #boolean DetectOptical -- @return #DETECTION_BASE self function DETECTION_BASE:InitDetectOptical( DetectOptical ) - self:F2() - + self:F2() + self.DetectOptical = DetectOptical - + return self end - + --- Detect Radar. -- @param #DETECTION_BASE self -- @param #boolean DetectRadar -- @return #DETECTION_BASE self function DETECTION_BASE:InitDetectRadar( DetectRadar ) self:F2() - + self.DetectRadar = DetectRadar - + return self end - + --- Detect IRST. -- @param #DETECTION_BASE self -- @param #boolean DetectIRST -- @return #DETECTION_BASE self function DETECTION_BASE:InitDetectIRST( DetectIRST ) self:F2() - + self.DetectIRST = DetectIRST - + return self end - + --- Detect RWR. -- @param #DETECTION_BASE self -- @param #boolean DetectRWR -- @return #DETECTION_BASE self function DETECTION_BASE:InitDetectRWR( DetectRWR ) self:F2() - + self.DetectRWR = DetectRWR - + return self end - + --- Detect DLINK. -- @param #DETECTION_BASE self -- @param #boolean DetectDLINK -- @return #DETECTION_BASE self function DETECTION_BASE:InitDetectDLINK( DetectDLINK ) self:F2() - + self.DetectDLINK = DetectDLINK - + return self end - + end - + do -- Filter methods - + --- Filter the detected units based on Unit.Category -- The different values of Unit.Category can be: -- @@ -1010,35 +998,35 @@ do -- DETECTION_BASE -- @return #DETECTION_BASE self 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 + end else self._.FilterCategories[FilterCategories] = FilterCategories end return self - + end end do - + --- Set the detection interval time in seconds. -- @param #DETECTION_BASE self -- @param #number RefreshTimeInterval Interval in seconds. -- @return #DETECTION_BASE self function DETECTION_BASE:SetRefreshTimeInterval( RefreshTimeInterval ) self:F2() - + self.RefreshTimeInterval = RefreshTimeInterval - + return self end - + end do -- Friendlies Radius @@ -1047,18 +1035,18 @@ do -- DETECTION_BASE -- @param #DETECTION_BASE self -- @param #number FriendliesRange Radius to use when checking if Friendlies are nearby. -- @return #DETECTION_BASE self - function DETECTION_BASE:SetFriendliesRange( FriendliesRange ) --R2.2 Friendlies range + function DETECTION_BASE:SetFriendliesRange( FriendliesRange ) -- R2.2 Friendlies range self:F2() - + self.FriendliesRange = FriendliesRange - + return self end - + end - + do -- Intercept Point - + --- Set the parameters to calculate to optimal intercept point. -- @param #DETECTION_BASE self -- @param #boolean Intercept Intercept is true if an intercept point is calculated. Intercept is false if it is disabled. The default Intercept is false. @@ -1066,36 +1054,36 @@ do -- DETECTION_BASE -- @return #DETECTION_BASE self function DETECTION_BASE:SetIntercept( Intercept, InterceptDelay ) self:F2() - + self.Intercept = Intercept self.InterceptDelay = InterceptDelay - + return self end end - + do -- Accept / Reject detected units - + --- Accept detections if within a range in meters. -- @param #DETECTION_BASE self -- @param #number AcceptRange Accept a detection if the unit is within the AcceptRange in meters. -- @return #DETECTION_BASE self function DETECTION_BASE:SetAcceptRange( AcceptRange ) self:F2() - + self.AcceptRange = AcceptRange - + return self end - + --- Accept detections if within the specified zone(s). -- @param #DETECTION_BASE self -- @param Core.Zone#ZONE_BASE AcceptZones Can be a list or ZONE_BASE objects, or a single ZONE_BASE object. -- @return #DETECTION_BASE self function DETECTION_BASE:SetAcceptZones( AcceptZones ) self:F2() - + if type( AcceptZones ) == "table" then if AcceptZones.ClassName and AcceptZones:IsInstanceOf( ZONE_BASE ) then self.AcceptZones = { AcceptZones } @@ -1106,17 +1094,17 @@ do -- DETECTION_BASE self:F( { "AcceptZones must be a list of ZONE_BASE derived objects or one ZONE_BASE derived object", AcceptZones } ) error() end - + return self end - + --- Reject detections if within the specified zone(s). -- @param #DETECTION_BASE self -- @param Core.Zone#ZONE_BASE RejectZones Can be a list or ZONE_BASE objects, or a single ZONE_BASE object. -- @return #DETECTION_BASE self function DETECTION_BASE:SetRejectZones( RejectZones ) self:F2() - + if type( RejectZones ) == "table" then if RejectZones.ClassName and RejectZones:IsInstanceOf( ZONE_BASE ) then self.RejectZones = { RejectZones } @@ -1127,14 +1115,14 @@ do -- DETECTION_BASE 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 -- Probability methods - + --- Upon a **visual** detection, the further away a detected object is, the less likely it is to be detected properly. -- Also, the speed of accurate detection plays a role. -- A distance probability factor between 0 and 1 can be given, that will model a linear extrapolated probability over 10 km distance. @@ -1145,13 +1133,12 @@ do -- DETECTION_BASE -- @return #DETECTION_BASE self function DETECTION_BASE:SetDistanceProbability( DistanceProbability ) self:F2() - + self.DistanceProbability = DistanceProbability - + return self end - - + --- Upon a **visual** detection, the higher the unit is during the detecting process, the more likely the detected unit is to be detected properly. -- A detection at a 90% alpha angle is the most optimal, a detection at 10% is less and a detection at 0% is less likely to be correct. -- @@ -1164,12 +1151,12 @@ do -- DETECTION_BASE -- @return #DETECTION_BASE self function DETECTION_BASE:SetAlphaAngleProbability( AlphaAngleProbability ) self:F2() - + self.AlphaAngleProbability = AlphaAngleProbability - + return self end - + --- Upon a **visual** detection, the more a detected unit is within a cloudy zone, the less likely the detected unit is to be detected successfully. -- The Cloudy Zones work with the ZONE_BASE derived classes. The mission designer can define within the mission -- zones that reflect cloudy areas where detected units may not be so easily visually detected. @@ -1178,26 +1165,25 @@ do -- DETECTION_BASE -- @return #DETECTION_BASE self function DETECTION_BASE:SetZoneProbability( ZoneArray ) self:F2() - - self.ZoneProbability = ZoneArray - + + self.ZoneProbability = ZoneArray + return self end - - + end - + do -- Change processing - + --- Accepts changes from the detected item. -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem -- @return #DETECTION_BASE self function DETECTION_BASE:AcceptChanges( DetectedItem ) - + DetectedItem.Changed = false DetectedItem.Changes = {} - + return self end @@ -1207,21 +1193,20 @@ do -- DETECTION_BASE -- @param #string ChangeCode -- @return #DETECTION_BASE self 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 - - + --- Add a change to the detected zone. -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem @@ -1229,25 +1214,25 @@ do -- DETECTION_BASE -- @param #string ChangeUnitType -- @return #DETECTION_BASE self 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 -- Friendly calculations - + --- This will allow during friendly search any recce or detection unit to be also considered as a friendly. -- By default, recce aren't considered friendly, because that would mean that a recce would be also an attacking friendly, -- and this is wrong. @@ -1266,9 +1251,9 @@ do -- DETECTION_BASE self:F( { FriendlyPrefix = Prefix } ) self.FriendlyPrefixes[Prefix] = Prefix end - return self + return self end - + --- This will allow during friendly search only units of the specified list of categories. -- @param #DETECTION_BASE self -- @param #string FriendlyCategories A list of unit categories. @@ -1276,113 +1261,112 @@ do -- DETECTION_BASE -- @usage -- -- Only allow Ships and Vehicles to be part of the friendly team. -- Detection:SetFriendlyCategories( { Unit.Category.SHIP, Unit.Category.GROUND_UNIT } ) - + --- Returns if there are friendlies nearby the FAC units ... -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem -- @param DCS#Unit.Category Category The category of the unit. -- @return #boolean true if there are friendlies nearby function DETECTION_BASE:IsFriendliesNearBy( DetectedItem, Category ) --- self:F( { "FriendliesNearBy Test", DetectedItem.FriendliesNearBy } ) - return ( DetectedItem.FriendliesNearBy and DetectedItem.FriendliesNearBy[Category] ~= nil ) or false + -- self:F( { "FriendliesNearBy Test", DetectedItem.FriendliesNearBy } ) + return (DetectedItem.FriendliesNearBy and DetectedItem.FriendliesNearBy[Category] ~= nil) or false end - + --- Returns friendly units nearby the FAC units ... -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem -- @param DCS#Unit.Category Category The category of the unit. - -- @return #map<#string,Wrapper.Unit#UNIT> The map of Friendly UNITs. + -- @return #map<#string,Wrapper.Unit#UNIT> The map of Friendly UNITs. function DETECTION_BASE:GetFriendliesNearBy( DetectedItem, Category ) - + return DetectedItem.FriendliesNearBy and DetectedItem.FriendliesNearBy[Category] end - + --- Returns if there are friendlies nearby the intercept ... -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem -- @return #boolean trhe if there are friendlies near the intercept. function DETECTION_BASE:IsFriendliesNearIntercept( DetectedItem ) - + return DetectedItem.FriendliesNearIntercept ~= nil or false end - + --- Returns friendly units nearby the intercept point ... -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem The detected item. -- @return #map<#string,Wrapper.Unit#UNIT> The map of Friendly UNITs. function DETECTION_BASE:GetFriendliesNearIntercept( DetectedItem ) - + return DetectedItem.FriendliesNearIntercept end - - --- Returns the distance used to identify friendlies near the deteted item ... + + --- Returns the distance used to identify friendlies near the detected item ... -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem The detected item. -- @return #table A table of distances to friendlies. function DETECTION_BASE:GetFriendliesDistance( DetectedItem ) - + return DetectedItem.FriendliesDistance end - + --- Returns if there are friendlies nearby the FAC units ... -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem - -- @return #boolean trhe if there are friendlies nearby + -- @return #boolean true if there are friendlies nearby function DETECTION_BASE:IsPlayersNearBy( DetectedItem ) - + return DetectedItem.PlayersNearBy ~= nil end - + --- Returns friendly units nearby the FAC units ... -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem The detected item. - -- @return #map<#string,Wrapper.Unit#UNIT> The map of Friendly UNITs. + -- @return #map<#string,Wrapper.Unit#UNIT> The map of Friendly UNITs. function DETECTION_BASE:GetPlayersNearBy( DetectedItem ) - + return DetectedItem.PlayersNearBy end - + --- Background worker function to determine if there are friendlies nearby ... -- @param #DETECTION_BASE self -- @param #table TargetData function DETECTION_BASE:ReportFriendliesNearBy( TargetData ) - --self:F( { "Search Friendlies", DetectedItem = TargetData.DetectedItem } ) - - local DetectedItem = TargetData.DetectedItem --#DETECTION_BASE.DetectedItem + -- self:F( { "Search Friendlies", DetectedItem = TargetData.DetectedItem } ) + + local DetectedItem = TargetData.DetectedItem -- #DETECTION_BASE.DetectedItem local DetectedSet = TargetData.DetectedItem.Set local DetectedUnit = DetectedSet:GetFirst() -- Wrapper.Unit#UNIT - + DetectedItem.FriendliesNearBy = nil -- We need to ensure that the DetectedUnit is alive! if DetectedUnit and DetectedUnit:IsAlive() then - + local DetectedUnitCoord = DetectedUnit:GetCoordinate() local InterceptCoord = TargetData.InterceptCoord or DetectedUnitCoord - + local SphereSearch = { - id = world.VolumeType.SPHERE, + id = world.VolumeType.SPHERE, params = { - point = InterceptCoord:GetVec3(), - radius = self.FriendliesRange, + point = InterceptCoord:GetVec3(), + radius = self.FriendliesRange, } - - } - + } + --- @param DCS#Unit FoundDCSUnit -- @param Wrapper.Group#GROUP ReportGroup -- @param Core.Set#SET_GROUP ReportSetGroup local FindNearByFriendlies = function( FoundDCSUnit, ReportGroupData ) - - local DetectedItem = ReportGroupData.DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem + + local DetectedItem = ReportGroupData.DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem local DetectedSet = ReportGroupData.DetectedItem.Set local DetectedUnit = DetectedSet:GetFirst() -- Wrapper.Unit#UNIT 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() @@ -1390,25 +1374,25 @@ do -- DETECTION_BASE local EnemyUnitName = DetectedUnit:GetName() local FoundUnitInReportSetGroup = ReportSetGroup:FindGroup( FoundUnitGroupName ) ~= nil - --self:T( { "Friendlies search:", FoundUnitName, FoundUnitCoalition, EnemyUnitName, EnemyCoalition, FoundUnitInReportSetGroup } ) - + -- self:T( { "Friendlies search:", FoundUnitName, FoundUnitCoalition, EnemyUnitName, EnemyCoalition, FoundUnitInReportSetGroup } ) + if FoundUnitInReportSetGroup == true then -- If the recce was part of the friendlies found, then check if the recce is part of the allowed friendly unit prefixes. for PrefixID, Prefix in pairs( self.FriendlyPrefixes or {} ) do - --self:F( { "Friendly Prefix:", Prefix = Prefix } ) + -- self:F( { "Friendly Prefix:", Prefix = Prefix } ) -- In case a match is found (so a recce unit name is part of the friendly prefixes), then report that recce to be part of the friendlies. -- This is important if CAP planes (so planes using their own radar) to be scanning for targets as part of the EWR network. -- But CAP planes are also attackers, so they need to be considered friendlies too! -- I chose to use prefixes because it is the fastest way to check. - if string.find( FoundUnitName, Prefix:gsub ("-", "%%-"), 1 ) then + if string.find( FoundUnitName, Prefix:gsub( "-", "%%-" ), 1 ) then FoundUnitInReportSetGroup = false break end end end - - --self:F( { "Friendlies near Target:", FoundUnitName, FoundUnitCoalition, EnemyUnitName, EnemyCoalition, FoundUnitInReportSetGroup } ) - + + -- self:F( { "Friendlies near Target:", FoundUnitName, FoundUnitCoalition, EnemyUnitName, EnemyCoalition, FoundUnitInReportSetGroup } ) + if FoundUnitCoalition ~= EnemyCoalition and FoundUnitInReportSetGroup == false then local FriendlyUnit = UNIT:Find( FoundDCSUnit ) local FriendlyUnitName = FriendlyUnit:GetName() @@ -1422,65 +1406,64 @@ do -- DETECTION_BASE local Distance = DetectedUnitCoord:Get2DDistance( FriendlyUnit:GetCoordinate() ) DetectedItem.FriendliesDistance = DetectedItem.FriendliesDistance or {} DetectedItem.FriendliesDistance[Distance] = FriendlyUnit - --self:F( { "Friendlies Found:", FriendlyUnitName = FriendlyUnitName, Distance = Distance, FriendlyUnitCategory = FriendlyUnitCategory, FriendliesCategory = self.FriendliesCategory } ) + -- self:F( { "Friendlies Found:", FriendlyUnitName = FriendlyUnitName, Distance = Distance, FriendlyUnitCategory = FriendlyUnitCategory, FriendliesCategory = self.FriendliesCategory } ) return true end - + return true end - + world.searchObjects( Object.Category.UNIT, SphereSearch, FindNearByFriendlies, TargetData ) DetectedItem.PlayersNearBy = nil - + _DATABASE:ForEachPlayer( - --- @param Wrapper.Unit#UNIT PlayerUnit - function( PlayerUnitName ) - local PlayerUnit = UNIT:FindByName( PlayerUnitName ) + --- @param Wrapper.Unit#UNIT PlayerUnit + function( PlayerUnitName ) + local PlayerUnit = UNIT:FindByName( PlayerUnitName ) - -- Fix for issue https://github.com/FlightControl-Master/MOOSE/issues/1225 - if PlayerUnit and PlayerUnit:IsAlive() then - local coord=PlayerUnit:GetCoordinate() - - if coord and coord:IsInRadius( DetectedUnitCoord, self.FriendliesRange ) then + -- Fix for issue https://github.com/FlightControl-Master/MOOSE/issues/1225 + 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 + + -- Friendlies are sorted per unit category. + 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 - 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 - - -- Friendlies are sorted per unit category. - 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 + end ) + end self:F( { Friendlies = DetectedItem.FriendliesNearBy, Players = DetectedItem.PlayersNearBy } ) - + end - + end - + --- Determines if a detected object has already been identified during detection processing. -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedObject DetectedObject -- @return #boolean true if already identified. function DETECTION_BASE:IsDetectedObjectIdentified( DetectedObject ) - + local DetectedObjectName = DetectedObject.Name if DetectedObjectName then local DetectedObjectIdentified = self.DetectedObjectsIdentified[DetectedObjectName] == true @@ -1489,71 +1472,70 @@ do -- DETECTION_BASE return nil end end - + --- Identifies a detected object during detection processing. -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedObject DetectedObject function DETECTION_BASE:IdentifyDetectedObject( DetectedObject ) - --self:F( { "Identified:", DetectedObject.Name } ) - + -- self:F( { "Identified:", DetectedObject.Name } ) + local DetectedObjectName = DetectedObject.Name self.DetectedObjectsIdentified[DetectedObjectName] = true end - + --- UnIdentify a detected object during detection processing. -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedObject DetectedObject function DETECTION_BASE:UnIdentifyDetectedObject( DetectedObject ) - + local DetectedObjectName = DetectedObject.Name self.DetectedObjectsIdentified[DetectedObjectName] = false end - + --- UnIdentify all detected objects during detection processing. -- @param #DETECTION_BASE self function DETECTION_BASE:UnIdentifyAllDetectedObjects() - + self.DetectedObjectsIdentified = {} -- Table will be garbage collected. end - + --- Gets a detected object with a given name. -- @param #DETECTION_BASE self -- @param #string ObjectName -- @return #DETECTION_BASE.DetectedObject function DETECTION_BASE:GetDetectedObject( ObjectName ) - self:F2( { ObjectName = ObjectName } ) - + self:F2( { ObjectName = ObjectName } ) + if ObjectName then local DetectedObject = self.DetectedObjects[ObjectName] - + if DetectedObject then - --self:F( { DetectedObjects = self.DetectedObjects } ) + -- self:F( { DetectedObjects = self.DetectedObjects } ) -- Only return detected objects that are alive! local DetectedUnit = UNIT:FindByName( ObjectName ) if DetectedUnit and DetectedUnit:IsAlive() then if self:IsDetectedObjectIdentified( DetectedObject ) == false then - --self:F( { DetectedObject = DetectedObject } ) + -- self:F( { DetectedObject = DetectedObject } ) return DetectedObject end end end end - + return nil end - --- Gets a detected unit type name, taking into account the detection results. -- @param #DETECTION_BASE self -- @param Wrapper.Unit#UNIT DetectedUnit -- @return #string The type name function DETECTION_BASE:GetDetectedUnitTypeName( DetectedUnit ) - --self:F2( ObjectName ) - + -- self:F2( ObjectName ) + 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() @@ -1566,11 +1548,10 @@ do -- DETECTION_BASE else return "Dead:" .. DetectedUnit:GetName() end - + return "Undetected:" .. DetectedUnit:GetName() end - --- Adds a new DetectedItem to the DetectedItems list. -- The DetectedItem is a table and contains a SET_UNIT in the field Set. -- @param #DETECTION_BASE self @@ -1579,29 +1560,28 @@ do -- DETECTION_BASE -- @param Core.Set#SET_UNIT Set (optional) The Set of Units to be added. -- @return #DETECTION_BASE.DetectedItem function DETECTION_BASE:AddDetectedItem( ItemPrefix, DetectedItemKey, Set ) - - local DetectedItem = {} --#DETECTION_BASE.DetectedItem + + local DetectedItem = {} -- #DETECTION_BASE.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 - + --- Adds a new DetectedItem to the DetectedItems list. -- The DetectedItem is a table and contains a SET_UNIT in the field Set. -- @param #DETECTION_BASE self @@ -1610,22 +1590,22 @@ do -- DETECTION_BASE -- @param Core.Zone#ZONE_UNIT Zone (optional) The Zone to be added where the Units are located. -- @return #DETECTION_BASE.DetectedItem 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 - + --- Removes an existing DetectedItem from the DetectedItems list. -- The DetectedItem is a table and contains a SET_UNIT in the field Set. -- @param #DETECTION_BASE self -- @param DetectedItemKey The key in the DetectedItems list where the item needs to be removed. function DETECTION_BASE:RemoveDetectedItem( DetectedItemKey ) - + local DetectedItem = self.DetectedItems[DetectedItemKey] if DetectedItem then @@ -1635,125 +1615,124 @@ do -- DETECTION_BASE self.DetectedItems[DetectedItemKey] = nil end end - - + --- Get the DetectedItems by Key. -- This will return the DetectedItems collection, indexed by the Key, which can be any object that acts as the key of the detection. -- @param #DETECTION_BASE self -- @return #DETECTION_BASE.DetectedItems function DETECTION_BASE:GetDetectedItems() - + return self.DetectedItems end - + --- Get the DetectedItems by Index. -- This will return the DetectedItems collection, indexed by an internal numerical Index. -- @param #DETECTION_BASE self -- @return #DETECTION_BASE.DetectedItems function DETECTION_BASE:GetDetectedItemsByIndex() - + return self.DetectedItemsByIndex end - + --- Get the amount of SETs with detected objects. -- @param #DETECTION_BASE self - -- @return #number The amount of detected items. Note that the amount of detected items can differ with the reality, because detections are not real-time but doen in intervals! + -- @return #number The amount of detected items. Note that the amount of detected items can differ with the reality, because detections are not real-time but done in intervals! function DETECTION_BASE:GetDetectedItemsCount() - + local DetectedCount = self.DetectedItemCount return DetectedCount end - + --- Get a detected item using a given Key. -- @param #DETECTION_BASE self -- @param Key -- @return #DETECTION_BASE.DetectedItem function DETECTION_BASE:GetDetectedItemByKey( Key ) - + self:F( { DetectedItems = self.DetectedItems } ) - + local DetectedItem = self.DetectedItems[Key] if DetectedItem then return DetectedItem end - + return nil end - + --- Get a detected item using a given numeric index. -- @param #DETECTION_BASE self -- @param #number Index -- @return #DETECTION_BASE.DetectedItem function DETECTION_BASE:GetDetectedItemByIndex( Index ) - + self:F( { self.DetectedItemsByIndex } ) - + local DetectedItem = self.DetectedItemsByIndex[Index] if DetectedItem then return DetectedItem end - + return nil end - + --- Get a detected ItemID using a given numeric index. -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem. -- @return #string DetectedItemID - function DETECTION_BASE:GetDetectedItemID( DetectedItem ) --R2.1 - + function DETECTION_BASE:GetDetectedItemID( DetectedItem ) -- R2.1 + return DetectedItem and DetectedItem.ItemID or "" end - + --- Get a detected ID using a given numeric index. -- @param #DETECTION_BASE self -- @param #number Index -- @return #string DetectedItemID - function DETECTION_BASE:GetDetectedID( Index ) --R2.1 - + function DETECTION_BASE:GetDetectedID( Index ) -- R2.1 + local DetectedItem = self.DetectedItemsByIndex[Index] if DetectedItem then return DetectedItem.ID end - + return "" end - - --- Get the @{Core.Set#SET_UNIT} of a detecttion area using a given numeric index. + + --- Get the @{Core.Set#SET_UNIT} of a detection area using a given numeric index. -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem -- @return Core.Set#SET_UNIT DetectedSet function DETECTION_BASE:GetDetectedItemSet( DetectedItem ) - + local DetectedSetUnit = DetectedItem and DetectedItem.Set if DetectedSetUnit then return DetectedSetUnit end - + return nil end - + --- Set IsDetected flag for the DetectedItem, which can have more units. -- @param #DETECTION_BASE self -- @return #DETECTION_BASE.DetectedItem DetectedItem -- @return #boolean true if at least one UNIT is detected from the DetectedSet, false if no UNIT was detected from the DetectedSet. 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}) + 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 @@ -1761,31 +1740,30 @@ do -- DETECTION_BASE -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem -- @return #boolean true if at least one UNIT is detected from the DetectedSet, false if no UNIT was detected from the DetectedSet. - function DETECTION_BASE:IsDetectedItemDetected( DetectedItem ) - + function DETECTION_BASE:IsDetectedItemDetected( DetectedItem ) + return DetectedItem.IsDetected end - do -- Zones - + --- Get the @{Core.Zone#ZONE_UNIT} of a detection area using a given numeric index. -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem. -- @return Core.Zone#ZONE_UNIT DetectedZone function DETECTION_BASE:GetDetectedItemZone( DetectedItem ) - + local DetectedZone = DetectedItem and DetectedItem.Zone if DetectedZone then return DetectedZone end - + local Detected - + return nil end - end + end --- Lock the detected items when created and lock all existing detected items. -- @param #DETECTION_BASE self @@ -1800,7 +1778,6 @@ do -- DETECTION_BASE return self end - --- Unlock the detected items when created and unlock all existing detected items. -- @param #DETECTION_BASE self -- @return #DETECTION_BASE @@ -1813,7 +1790,7 @@ do -- DETECTION_BASE return self end - + --- Validate if the detected item is locked. -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem. @@ -1823,7 +1800,6 @@ do -- DETECTION_BASE return self.Locking and DetectedItem.Locked == true end - --- Lock a detected item. -- @param #DETECTION_BASE self @@ -1847,9 +1823,6 @@ do -- DETECTION_BASE return self end - - - --- Set the detected item coordinate. -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem to set the coordinate at. @@ -1858,7 +1831,7 @@ do -- DETECTION_BASE -- @return #DETECTION_BASE function DETECTION_BASE:SetDetectedItemCoordinate( DetectedItem, Coordinate, DetectedItemUnit ) self:F( { Coordinate = Coordinate } ) - + if DetectedItem then if DetectedItemUnit then DetectedItem.Coordinate = Coordinate @@ -1869,18 +1842,17 @@ do -- DETECTION_BASE end end - --- Get the detected item coordinate. -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem to set the coordinate at. -- @return Core.Point#COORDINATE function DETECTION_BASE:GetDetectedItemCoordinate( DetectedItem ) self:F( { DetectedItem = DetectedItem } ) - + if DetectedItem then return DetectedItem.Coordinate end - + return nil end @@ -1888,30 +1860,28 @@ do -- DETECTION_BASE -- @param #DETECTION_BASE self -- @return #table A table of Core.Point#COORDINATE function DETECTION_BASE:GetDetectedItemCoordinates() - + local Coordinates = {} - + for DetectedItemID, DetectedItem in pairs( self:GetDetectedItems() ) do Coordinates[DetectedItem] = self:GetDetectedItemCoordinate( DetectedItem ) end - + return Coordinates end - --- Set the detected item threatlevel. + --- Set the detected item threat level. -- @param #DETECTION_BASE self - -- @param #DETECTION_BASE.DetectedItem The DetectedItem to calculate the threatlevel for. + -- @param #DETECTION_BASE.DetectedItem The DetectedItem to calculate the threat level for. -- @return #DETECTION_BASE function DETECTION_BASE:SetDetectedItemThreatLevel( DetectedItem ) - + local DetectedSet = DetectedItem.Set - + if DetectedItem then DetectedItem.ThreatLevel, DetectedItem.ThreatText = DetectedSet:CalculateThreatLevelA2G() end end - - --- Get the detected item coordinate. -- @param #DETECTION_BASE self @@ -1919,15 +1889,14 @@ do -- DETECTION_BASE -- @return #number ThreatLevel 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 - --- Report summary of a detected item using a given numeric index. -- @param #DETECTION_BASE self @@ -1939,8 +1908,8 @@ do -- DETECTION_BASE self:F() return nil end - - --- Report detailed of a detectedion result. + + --- Report detailed of a detection result. -- @param #DETECTION_BASE self -- @param Wrapper.Group#GROUP AttackGroup The group to generate the report for. -- @return #string @@ -1948,25 +1917,25 @@ do -- DETECTION_BASE self:F() return nil end - + --- Get the Detection Set. -- @param #DETECTION_BASE self -- @return #DETECTION_BASE self function DETECTION_BASE:GetDetectionSet() - + local DetectionSet = self.DetectionSet return DetectionSet end - - --- Find the nearest Recce of the DetectedItem. + + --- Find the nearest Recce of the DetectedItem. -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem -- @return Wrapper.Unit#UNIT The nearest FAC unit function DETECTION_BASE:NearestRecce( DetectedItem ) - + local NearestRecce = nil local DistanceRecce = 1000000000 -- Units are not further than 1000000 km away from an area :-) - + for RecceGroupName, RecceGroup in pairs( self.DetectionSet:GetSet() ) do if RecceGroup and RecceGroup:IsAlive() then for RecceUnit, RecceUnit in pairs( RecceGroup:GetUnits() ) do @@ -1981,14 +1950,12 @@ do -- DETECTION_BASE end end end - + DetectedItem.NearestFAC = NearestRecce DetectedItem.DistanceRecce = DistanceRecce - + end - - - + --- Schedule the DETECTION construction. -- @param #DETECTION_BASE self -- @param #number DelayTime The delay in seconds to wait the reporting. @@ -1996,10 +1963,10 @@ do -- DETECTION_BASE -- @return #DETECTION_BASE self function DETECTION_BASE:Schedule( DelayTime, RepeatInterval ) self:F2() - + self.ScheduleDelayTime = DelayTime self.ScheduleRepeatInterval = RepeatInterval - + self.DetectionScheduler = SCHEDULER:New( self, self._DetectionScheduler, { self, "Detection" }, DelayTime, RepeatInterval ) return self end @@ -2013,31 +1980,31 @@ do -- DETECTION_UNITS -- @extends Functional.Detection#DETECTION_BASE --- Will detect units within the battle zone. - -- + -- -- It will build a DetectedItems list filled with DetectedItems. Each DetectedItem will contain a field Set, which contains a @{Core.Set#SET_UNIT} containing ONE @{UNIT} object reference. - -- Beware that when the amount of units detected is large, the DetectedItems list will be large also. - -- + -- Beware that when the amount of units detected is large, the DetectedItems list will be large also. + -- -- @field #DETECTION_UNITS DETECTION_UNITS = { ClassName = "DETECTION_UNITS", DetectionRange = nil, } - + --- DETECTION_UNITS constructor. -- @param Functional.Detection#DETECTION_UNITS self -- @param Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. -- @return Functional.Detection#DETECTION_UNITS self function DETECTION_UNITS:New( DetectionSetGroup ) - + -- Inherits from DETECTION_BASE local self = BASE:Inherit( self, DETECTION_BASE:New( DetectionSetGroup ) ) -- #DETECTION_UNITS - + self._SmokeDetectedUnits = false self._FlareDetectedUnits = false self._SmokeDetectedZones = false self._FlareDetectedZones = false self._BoundDetectedZones = false - + return self end @@ -2047,70 +2014,69 @@ do -- DETECTION_UNITS -- @return #string The Changes text function DETECTION_UNITS:GetChangeText( DetectedItem ) self:F( DetectedItem ) - + local MT = {} - + for ChangeCode, ChangeData in pairs( DetectedItem.Changes ) do - + if ChangeCode == "AU" then local MTUT = {} for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do - if ChangeUnitType ~= "ID" then - MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType + if ChangeUnitType ~= "ID" then + MTUT[#MTUT + 1] = ChangeUnitCount .. " of " .. ChangeUnitType end end - MT[#MT+1] = " New target(s) detected: " .. table.concat( MTUT, ", " ) .. "." + MT[#MT + 1] = " New target(s) detected: " .. table.concat( MTUT, ", " ) .. "." end - + if ChangeCode == "RU" then local MTUT = {} for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do - if ChangeUnitType ~= "ID" then - MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType + if ChangeUnitType ~= "ID" then + MTUT[#MTUT + 1] = ChangeUnitCount .. " of " .. ChangeUnitType end end - MT[#MT+1] = " Invisible or destroyed target(s): " .. table.concat( MTUT, ", " ) .. "." + MT[#MT + 1] = " Invisible or destroyed target(s): " .. table.concat( MTUT, ", " ) .. "." end - + end - + return table.concat( MT, "\n" ) - + end - - - --- Create the DetectedItems list from the DetectedObjects table. + + --- Create the DetectedItems list from the DetectedObjects table. -- For each DetectedItem, a one field array is created containing the Unit detected. -- @param #DETECTION_UNITS self -- @return #DETECTION_UNITS self function DETECTION_UNITS:CreateDetectionItems() -- Loop the current detected items, and check if each object still exists and is detected. - + for DetectedItemKey, _DetectedItem in pairs( self.DetectedItems ) do - local DetectedItem=_DetectedItem --#DETECTION_BASE.DetectedItem - + local DetectedItem = _DetectedItem -- #DETECTION_BASE.DetectedItem + local DetectedItemSet = DetectedItem.Set -- Core.Set#SET_UNIT - + for DetectedUnitName, DetectedUnitData in pairs( DetectedItemSet:GetSet() ) do local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT local DetectedObject = nil - --self:F( DetectedUnit ) + -- self:F( DetectedUnit ) if DetectedUnit:IsAlive() then - --self:F(DetectedUnit:GetName()) + -- self:F(DetectedUnit:GetName()) DetectedObject = self:GetDetectedObject( DetectedUnit:GetName() ) end if DetectedObject then - + -- Yes, the DetectedUnit is still detected or exists. Flag as identified. self:IdentifyDetectedObject( DetectedObject ) - + self:F( { "**DETECTED**", IsVisible = DetectedObject.IsVisible } ) -- Update the detection with the new data provided. - DetectedItem.TypeName = DetectedUnit:GetTypeName() - DetectedItem.CategoryName = DetectedUnit:GetCategoryName() + DetectedItem.TypeName = DetectedUnit:GetTypeName() + DetectedItem.CategoryName = DetectedUnit:GetCategoryName() DetectedItem.Name = DetectedObject.Name - DetectedItem.IsVisible = DetectedObject.IsVisible + DetectedItem.IsVisible = DetectedObject.IsVisible DetectedItem.LastTime = DetectedObject.LastTime DetectedItem.LastPos = DetectedObject.LastPos DetectedItem.LastVelocity = DetectedObject.LastVelocity @@ -2130,25 +2096,24 @@ do -- DETECTION_UNITS end end - -- Now we need to loop through the unidentified detected units and add these... These are all new items. for DetectedUnitName, DetectedObjectData in pairs( self.DetectedObjects ) do - + local DetectedObject = self:GetDetectedObject( DetectedUnitName ) if DetectedObject then self:T( { "Detected Unit #", DetectedUnitName } ) - + local DetectedUnit = UNIT:FindByName( DetectedUnitName ) -- Wrapper.Unit#UNIT - + if DetectedUnit then local DetectedTypeName = DetectedUnit:GetTypeName() local DetectedItem = self:GetDetectedItemByKey( DetectedUnitName ) if not DetectedItem then self:T( "Added new DetectedItem" ) DetectedItem = self:AddDetectedItem( "UNIT", DetectedUnitName ) - DetectedItem.TypeName = DetectedUnit:GetTypeName() + DetectedItem.TypeName = DetectedUnit:GetTypeName() DetectedItem.Name = DetectedObject.Name - DetectedItem.IsVisible = DetectedObject.IsVisible + DetectedItem.IsVisible = DetectedObject.IsVisible DetectedItem.LastTime = DetectedObject.LastTime DetectedItem.LastPos = DetectedObject.LastPos DetectedItem.LastVelocity = DetectedObject.LastVelocity @@ -2156,18 +2121,18 @@ do -- DETECTION_UNITS DetectedItem.KnowDistance = DetectedObject.KnowDistance DetectedItem.Distance = DetectedObject.Distance end - + DetectedItem.Set:AddUnit( DetectedUnit ) self:AddChangeUnit( DetectedItem, "AU", DetectedTypeName ) end - end + end end - + for DetectedItemID, DetectedItemData in pairs( self.DetectedItems ) do - + local DetectedItem = DetectedItemData -- #DETECTION_BASE.DetectedItem local DetectedSet = DetectedItem.Set - + -- Set the last known coordinate. local DetectedFirstUnit = DetectedSet:GetFirst() local DetectedFirstUnitCoord = DetectedFirstUnit:GetCoordinate() @@ -2176,12 +2141,11 @@ do -- DETECTION_UNITS self:ReportFriendliesNearBy( { DetectedItem = DetectedItem, ReportSetGroup = self.DetectionSet } ) -- Fill the Friendlies table self:SetDetectedItemThreatLevel( DetectedItem ) self:NearestRecce( DetectedItem ) - + end - + end - --- Report summary of a DetectedItem using a given numeric index. -- @param #DETECTION_UNITS self -- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem. @@ -2190,14 +2154,14 @@ do -- DETECTION_UNITS -- @return Core.Report#REPORT The report of the detection items. function DETECTION_UNITS:DetectedItemReportSummary( DetectedItem, AttackGroup, Settings ) self:F( { DetectedItem = DetectedItem } ) - + local DetectedItemID = self:GetDetectedItemID( DetectedItem ) - + if DetectedItem then local ReportSummary = "" local UnitDistanceText = "" local UnitCategoryText = "" - + if DetectedItem.KnowType then local UnitCategoryName = DetectedItem.CategoryName if UnitCategoryName then @@ -2209,7 +2173,7 @@ do -- DETECTION_UNITS else UnitCategoryText = "Unknown" end - + if DetectedItem.KnowDistance then if DetectedItem.IsVisible then UnitDistanceText = " at " .. string.format( "%.2f", DetectedItem.Distance ) .. " km" @@ -2219,33 +2183,32 @@ do -- DETECTION_UNITS UnitDistanceText = " at +/- " .. string.format( "%.0f", DetectedItem.Distance ) .. " km" end end - - --TODO: solve Index reference + + -- TODO: solve Index reference local DetectedItemCoordinate = self:GetDetectedItemCoordinate( DetectedItem ) local DetectedItemCoordText = DetectedItemCoordinate:ToString( AttackGroup, Settings ) - + local ThreatLevelA2G = self:GetDetectedItemThreatLevel( DetectedItem ) - + local Report = REPORT:New() - Report:Add(DetectedItemID .. ", " .. DetectedItemCoordText) - Report:Add( string.format( "Threat: [%s]", string.rep( "■", ThreatLevelA2G ), string.rep( "□", 10-ThreatLevelA2G ) ) ) - Report:Add( string.format("Type: %s%s", UnitCategoryText, UnitDistanceText ) ) - Report:Add( string.format("Visible: %s", DetectedItem.IsVisible and "yes" or "no" ) ) - Report:Add( string.format("Detected: %s", DetectedItem.IsDetected and "yes" or "no" ) ) - Report:Add( string.format("Distance: %s", DetectedItem.KnowDistance and "yes" or "no" ) ) + Report:Add( DetectedItemID .. ", " .. DetectedItemCoordText ) + Report:Add( string.format( "Threat: [%s]", string.rep( "■", ThreatLevelA2G ), string.rep( "□", 10 - ThreatLevelA2G ) ) ) + Report:Add( string.format( "Type: %s%s", UnitCategoryText, UnitDistanceText ) ) + Report:Add( string.format( "Visible: %s", DetectedItem.IsVisible and "yes" or "no" ) ) + Report:Add( string.format( "Detected: %s", DetectedItem.IsDetected and "yes" or "no" ) ) + Report:Add( string.format( "Distance: %s", DetectedItem.KnowDistance and "yes" or "no" ) ) return Report end return nil end - --- Report detailed of a detection result. -- @param #DETECTION_UNITS self -- @param Wrapper.Group#GROUP AttackGroup The group to generate the report for. -- @return #string function DETECTION_UNITS:DetectedReportDetailed( AttackGroup ) self:F() - + local Report = REPORT:New() for DetectedItemIndex, DetectedItem in pairs( self.DetectedItems ) do local DetectedItem = DetectedItem -- #DETECTION_BASE.DetectedItem @@ -2253,9 +2216,9 @@ do -- DETECTION_UNITS Report:SetTitle( "Detected units:" ) Report:Add( ReportSummary:Text() ) end - + local ReportText = Report:Text() - + return ReportText end @@ -2267,31 +2230,31 @@ do -- DETECTION_TYPES -- @extends Functional.Detection#DETECTION_BASE --- Will detect units within the battle zone. - -- It will build a DetectedItems[] list filled with DetectedItems, grouped by the type of units detected. + -- It will build a DetectedItems[] list filled with DetectedItems, grouped by the type of units detected. -- Each DetectedItem will contain a field Set, which contains a @{Core.Set#SET_UNIT} containing ONE @{UNIT} object reference. - -- Beware that when the amount of different types detected is large, the DetectedItems[] list will be large also. - -- + -- Beware that when the amount of different types detected is large, the DetectedItems[] list will be large also. + -- -- @field #DETECTION_TYPES DETECTION_TYPES = { ClassName = "DETECTION_TYPES", DetectionRange = nil, } - + --- DETECTION_TYPES constructor. -- @param Functional.Detection#DETECTION_TYPES self -- @param Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Recce role. -- @return Functional.Detection#DETECTION_TYPES self function DETECTION_TYPES:New( DetectionSetGroup ) - + -- Inherits from DETECTION_BASE local self = BASE:Inherit( self, DETECTION_BASE:New( DetectionSetGroup ) ) -- #DETECTION_TYPES - + self._SmokeDetectedUnits = false self._FlareDetectedUnits = false self._SmokeDetectedZones = false self._FlareDetectedZones = false self._BoundDetectedZones = false - + return self end @@ -2301,61 +2264,60 @@ do -- DETECTION_TYPES -- @return #string The Changes text function DETECTION_TYPES:GetChangeText( DetectedItem ) self:F( DetectedItem ) - + local MT = {} - + for ChangeCode, ChangeData in pairs( DetectedItem.Changes ) do - + if ChangeCode == "AU" then local MTUT = {} for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do - if ChangeUnitType ~= "ID" then - MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType + if ChangeUnitType ~= "ID" then + MTUT[#MTUT + 1] = ChangeUnitCount .. " of " .. ChangeUnitType end end - MT[#MT+1] = " New target(s) detected: " .. table.concat( MTUT, ", " ) .. "." + MT[#MT + 1] = " New target(s) detected: " .. table.concat( MTUT, ", " ) .. "." end - + if ChangeCode == "RU" then local MTUT = {} for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do - if ChangeUnitType ~= "ID" then - MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType + if ChangeUnitType ~= "ID" then + MTUT[#MTUT + 1] = ChangeUnitCount .. " of " .. ChangeUnitType end end - MT[#MT+1] = " Invisible or destroyed target(s): " .. table.concat( MTUT, ", " ) .. "." + MT[#MT + 1] = " Invisible or destroyed target(s): " .. table.concat( MTUT, ", " ) .. "." end - + end - + return table.concat( MT, "\n" ) - + end - - - --- Create the DetectedItems list from the DetectedObjects table. + + --- Create the DetectedItems list from the DetectedObjects table. -- For each DetectedItem, a one field array is created containing the Unit detected. -- @param #DETECTION_TYPES self -- @return #DETECTION_TYPES self function DETECTION_TYPES:CreateDetectionItems() - + -- Loop the current detected items, and check if each object still exists and is detected. - + for DetectedItemKey, DetectedItem in pairs( self.DetectedItems ) do - + local DetectedItemSet = DetectedItem.Set -- Core.Set#SET_UNIT local DetectedTypeName = DetectedItem.TypeName - + for DetectedUnitName, DetectedUnitData in pairs( DetectedItemSet:GetSet() ) do local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT local DetectedObject = nil if DetectedUnit:IsAlive() then - --self:F(DetectedUnit:GetName()) + -- self:F(DetectedUnit:GetName()) DetectedObject = self:GetDetectedObject( DetectedUnit:GetName() ) end if DetectedObject then - + -- Yes, the DetectedUnit is still detected or exists. Flag as identified. self:IdentifyDetectedObject( DetectedObject ) else @@ -2371,16 +2333,15 @@ do -- DETECTION_TYPES end end - -- Now we need to loop through the unidentified detected units and add these... These are all new items. for DetectedUnitName, DetectedObjectData in pairs( self.DetectedObjects ) do - + local DetectedObject = self:GetDetectedObject( DetectedUnitName ) if DetectedObject then self:T( { "Detected Unit #", DetectedUnitName } ) - + local DetectedUnit = UNIT:FindByName( DetectedUnitName ) -- Wrapper.Unit#UNIT - + if DetectedUnit then local DetectedTypeName = DetectedUnit:GetTypeName() local DetectedItem = self:GetDetectedItemByKey( DetectedTypeName ) @@ -2388,21 +2349,19 @@ do -- DETECTION_TYPES DetectedItem = self:AddDetectedItem( "TYPE", DetectedTypeName ) DetectedItem.TypeName = DetectedTypeName end - + DetectedItem.Set:AddUnit( DetectedUnit ) self:AddChangeUnit( DetectedItem, "AU", DetectedTypeName ) end - end + end end - - -- Check if there are any friendlies nearby. for DetectedItemID, DetectedItemData in pairs( self.DetectedItems ) do - + local DetectedItem = DetectedItemData -- #DETECTION_BASE.DetectedItem local DetectedSet = DetectedItem.Set - + -- Set the last known coordinate. local DetectedFirstUnit = DetectedSet:GetFirst() local DetectedUnitCoord = DetectedFirstUnit:GetCoordinate() @@ -2412,8 +2371,6 @@ do -- DETECTION_TYPES self:SetDetectedItemThreatLevel( DetectedItem ) self:NearestRecce( DetectedItem ) end - - end @@ -2425,35 +2382,35 @@ do -- DETECTION_TYPES -- @return Core.Report#REPORT The report of the detection items. function DETECTION_TYPES:DetectedItemReportSummary( DetectedItem, AttackGroup, Settings ) self:F( { DetectedItem = DetectedItem } ) - + local DetectedSet = self:GetDetectedItemSet( DetectedItem ) local DetectedItemID = self:GetDetectedItemID( DetectedItem ) - + self:T( DetectedItem ) if DetectedItem then local ThreatLevelA2G = self:GetDetectedItemThreatLevel( DetectedItem ) local DetectedItemsCount = DetectedSet:Count() local DetectedItemType = DetectedItem.TypeName - + local DetectedItemCoordinate = self:GetDetectedItemCoordinate( DetectedItem ) local DetectedItemCoordText = DetectedItemCoordinate:ToString( AttackGroup, Settings ) local Report = REPORT:New() - Report:Add(DetectedItemID .. ", " .. DetectedItemCoordText) - Report:Add( string.format( "Threat: [%s%s]", string.rep( "■", ThreatLevelA2G ), string.rep( "□", 10-ThreatLevelA2G ) ) ) - Report:Add( string.format("Type: %2d of %s", DetectedItemsCount, DetectedItemType ) ) + Report:Add( DetectedItemID .. ", " .. DetectedItemCoordText ) + Report:Add( string.format( "Threat: [%s%s]", string.rep( "■", ThreatLevelA2G ), string.rep( "□", 10 - ThreatLevelA2G ) ) ) + Report:Add( string.format( "Type: %2d of %s", DetectedItemsCount, DetectedItemType ) ) return Report end end - + --- Report detailed of a detection result. -- @param #DETECTION_TYPES self -- @param Wrapper.Group#GROUP AttackGroup The group to generate the report for. -- @return #string function DETECTION_TYPES:DetectedReportDetailed( AttackGroup ) self:F() - + local Report = REPORT:New() for DetectedItemIndex, DetectedItem in pairs( self.DetectedItems ) do local DetectedItem = DetectedItem -- #DETECTION_BASE.DetectedItem @@ -2461,15 +2418,14 @@ do -- DETECTION_TYPES Report:SetTitle( "Detected types:" ) Report:Add( ReportSummary:Text() ) end - + local ReportText = Report:Text() - + return ReportText end end - do -- DETECTION_AREAS --- @type DETECTION_AREAS @@ -2477,65 +2433,63 @@ do -- DETECTION_AREAS -- @field #DETECTION_BASE.DetectedItems DetectedItems A list of areas containing the set of @{Wrapper.Unit}s, @{Zone}s, the center @{Wrapper.Unit} within the zone, and ID of each area that was detected within a DetectionZoneRange. -- @extends Functional.Detection#DETECTION_BASE - --- Detect units within the battle zone for a list of @{Wrapper.Group}s detecting targets following (a) detection method(s), + --- Detect units within the battle zone for a list of @{Wrapper.Group}s detecting targets following (a) detection method(s), -- and will build a list (table) of @{Core.Set#SET_UNIT}s containing the @{Wrapper.Unit#UNIT}s detected. -- The class is group the detected units within zones given a DetectedZoneRange parameter. -- A set with multiple detected zones will be created as there are groups of units detected. - -- + -- -- ## 4.1) Retrieve the Detected Unit Sets and Detected Zones - -- - -- The methods to manage the DetectedItems[].Set(s) are implemented in @{Functional.Detection#DECTECTION_BASE} and + -- + -- The methods to manage the DetectedItems[].Set(s) are implemented in @{Functional.Detection#DETECTION_BASE} and -- the methods to manage the DetectedItems[].Zone(s) is implemented in @{Functional.Detection#DETECTION_AREAS}. - -- + -- -- Retrieve the DetectedItems[].Set with the method @{Functional.Detection#DETECTION_BASE.GetDetectedSet}(). A @{Core.Set#SET_UNIT} object will be returned. - -- + -- -- Retrieve the formed @{Zone@ZONE_UNIT}s as a result of the grouping the detected units within the DetectionZoneRange, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZones}(). - -- To understand the amount of zones created, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZoneCount}(). + -- To understand the amount of zones created, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZoneCount}(). -- If you want to obtain a specific zone from the DetectedZones, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZone}() with a given index. - -- + -- -- ## 4.4) Flare or Smoke detected units - -- + -- -- Use the methods @{Functional.Detection#DETECTION_AREAS.FlareDetectedUnits}() or @{Functional.Detection#DETECTION_AREAS.SmokeDetectedUnits}() to flare or smoke the detected units when a new detection has taken place. - -- + -- -- ## 4.5) Flare or Smoke or Bound detected zones - -- + -- -- Use the methods: - -- - -- * @{Functional.Detection#DETECTION_AREAS.FlareDetectedZones}() to flare in a color + -- + -- * @{Functional.Detection#DETECTION_AREAS.FlareDetectedZones}() to flare in a color -- * @{Functional.Detection#DETECTION_AREAS.SmokeDetectedZones}() to smoke in a color -- * @{Functional.Detection#DETECTION_AREAS.SmokeDetectedZones}() to bound with a tire with a white flag - -- + -- -- the detected zones when a new detection has taken place. - -- + -- -- @field #DETECTION_AREAS DETECTION_AREAS = { ClassName = "DETECTION_AREAS", DetectionZoneRange = nil, } - - + --- DETECTION_AREAS constructor. -- @param #DETECTION_AREAS self -- @param Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. -- @param DCS#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. -- @return #DETECTION_AREAS function DETECTION_AREAS:New( DetectionSetGroup, DetectionZoneRange ) - + -- Inherits from DETECTION_BASE local self = BASE:Inherit( self, DETECTION_BASE:New( DetectionSetGroup ) ) - + self.DetectionZoneRange = DetectionZoneRange - + self._SmokeDetectedUnits = false self._FlareDetectedUnits = false self._SmokeDetectedZones = false self._FlareDetectedZones = false self._BoundDetectedZones = false - + return self end - --- Report summary of a detected item using a given numeric index. -- @param #DETECTION_AREAS self -- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem. @@ -2544,26 +2498,26 @@ do -- DETECTION_AREAS -- @return Core.Report#REPORT The report of the detection items. function DETECTION_AREAS:DetectedItemReportMenu( DetectedItem, AttackGroup, Settings ) self:F( { DetectedItem = DetectedItem } ) - + local DetectedItemID = self:GetDetectedItemID( DetectedItem ) - + if DetectedItem then local DetectedSet = self:GetDetectedItemSet( DetectedItem ) local ReportSummaryItem - + local DetectedZone = self:GetDetectedItemZone( DetectedItem ) local DetectedItemCoordinate = DetectedZone:GetCoordinate() local DetectedItemCoordText = DetectedItemCoordinate:ToString( AttackGroup, Settings ) local ThreatLevelA2G = self:GetDetectedItemThreatLevel( DetectedItem ) - + local Report = REPORT:New() Report:Add( DetectedItemID ) - Report:Add( string.format( "Threat: [%s%s]", string.rep( "■", ThreatLevelA2G ), string.rep( "□", 10-ThreatLevelA2G ) ) ) - + Report:Add( string.format( "Threat: [%s%s]", string.rep( "■", ThreatLevelA2G ), string.rep( "□", 10 - ThreatLevelA2G ) ) ) + return Report end - + return nil end @@ -2575,14 +2529,14 @@ do -- DETECTION_AREAS -- @return Core.Report#REPORT The report of the detection items. function DETECTION_AREAS:DetectedItemReportSummary( DetectedItem, AttackGroup, Settings ) self:F( { DetectedItem = DetectedItem } ) - + local DetectedItemID = self:GetDetectedItemID( DetectedItem ) - + if DetectedItem then local DetectedSet = self:GetDetectedItemSet( DetectedItem ) local ReportSummaryItem - - --local DetectedZone = self:GetDetectedItemZone( DetectedItem ) + + -- local DetectedZone = self:GetDetectedItemZone( DetectedItem ) local DetectedItemCoordinate = self:GetDetectedItemCoordinate( DetectedItem ) local DetectedAir = DetectedSet:HasAirUnits() local DetectedAltitude = self:GetDetectedItemCoordinate( DetectedItem ) @@ -2592,21 +2546,20 @@ do -- DETECTION_AREAS 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 ) ) - --Report:Add( string.format("Detected: %s", DetectedItem.IsDetected and "yes" or "no" ) ) - + 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 ) ) + -- Report:Add( string.format("Detected: %s", DetectedItem.IsDetected and "yes" or "no" ) ) + return Report end - + return nil end @@ -2614,9 +2567,9 @@ do -- DETECTION_AREAS -- @param #DETECTION_AREAS self -- @param Wrapper.Group#GROUP AttackGroup The group to generate the report for. -- @return #string - function DETECTION_AREAS:DetectedReportDetailed( AttackGroup ) --R2.1 Fixed missing report + function DETECTION_AREAS:DetectedReportDetailed( AttackGroup ) -- R2.1 Fixed missing report self:F() - + local Report = REPORT:New() for DetectedItemIndex, DetectedItem in pairs( self.DetectedItems ) do local DetectedItem = DetectedItem -- #DETECTION_BASE.DetectedItem @@ -2624,13 +2577,12 @@ do -- DETECTION_AREAS Report:SetTitle( "Detected areas:" ) Report:Add( ReportSummary:Text() ) end - + local ReportText = Report:Text() - + return ReportText end - --- Calculate the optimal intercept point of the DetectedItem. -- @param #DETECTION_AREAS self -- @param #DETECTION_BASE.DetectedItem DetectedItem @@ -2643,56 +2595,54 @@ do -- DETECTION_AREAS if self.Intercept then local DetectedSet = DetectedItem.Set -- todo: speed - + local TranslateDistance = DetectedSpeed * self.InterceptDelay - + local InterceptCoord = DetectedCoord:Translate( TranslateDistance, DetectedHeading ) - + DetectedItem.InterceptCoord = InterceptCoord else DetectedItem.InterceptCoord = DetectedCoord end - + end - - --- Smoke the detected units -- @param #DETECTION_AREAS self -- @return #DETECTION_AREAS self function DETECTION_AREAS:SmokeDetectedUnits() self:F2() - + self._SmokeDetectedUnits = true return self end - + --- Flare the detected units -- @param #DETECTION_AREAS self -- @return #DETECTION_AREAS self function DETECTION_AREAS:FlareDetectedUnits() self:F2() - + self._FlareDetectedUnits = true return self end - + --- Smoke the detected zones -- @param #DETECTION_AREAS self -- @return #DETECTION_AREAS self function DETECTION_AREAS:SmokeDetectedZones() self:F2() - + self._SmokeDetectedZones = true return self end - + --- Flare the detected zones -- @param #DETECTION_AREAS self -- @return #DETECTION_AREAS self function DETECTION_AREAS:FlareDetectedZones() self:F2() - + self._FlareDetectedZones = true return self end @@ -2702,127 +2652,123 @@ do -- DETECTION_AREAS -- @return #DETECTION_AREAS self function DETECTION_AREAS:BoundDetectedZones() self:F2() - + self._BoundDetectedZones = true return self end - + --- Make text documenting the changes of the detected zone. -- @param #DETECTION_AREAS self -- @param #DETECTION_BASE.DetectedItem DetectedItem -- @return #string The Changes text 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 .. "." + 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." + 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 .. "." + 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." + 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 + 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, ", " ) .. "." + 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 + 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, ", " ) .. "." + MT[#MT + 1] = "Removed for area " .. ChangeData.ID .. " invisible or destroyed target(s) " .. table.concat( MTUT, ", " ) .. "." end - + end - + return table.concat( MT, "\n" ) - + end - - - --- Make a DetectionSet table. This function will be overridden in the derived clsses. + + --- Make a DetectionSet table. This function will be overridden in the derived classes. -- @param #DETECTION_AREAS self -- @return #DETECTION_AREAS self function DETECTION_AREAS:CreateDetectionItems() - - + self:F( "Checking Detected Items for new Detected Units ..." ) - --self:F( { DetectedObjects = self.DetectedObjects } ) - + -- self:F( { DetectedObjects = self.DetectedObjects } ) + -- First go through all detected sets, and check if there are new detected units, match all existing detected units and identify undetected units. -- Regroup when needed, split groups when needed. for DetectedItemID, DetectedItemData in pairs( self.DetectedItems ) do - + local DetectedItem = DetectedItemData -- #DETECTION_BASE.DetectedItem - + if DetectedItem then - + self:T2( { "Detected Item ID: ", DetectedItemID } ) - + local DetectedSet = DetectedItem.Set - + local AreaExists = false -- This flag will determine of the detected area is still existing. - + -- First test if the center unit is detected in the detection area. 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 - - --self:IdentifyDetectedObject( DetectedZoneObject ) + + -- self:IdentifyDetectedObject( DetectedZoneObject ) AreaExists = true - - - + else -- The center object of the detected area has not been detected. Find an other unit of the set to become the center of the area. -- First remove the center unit from the set. DetectedSet:RemoveUnitsByName( DetectedItem.Zone.ZoneUNIT.UnitName ) - + self:AddChangeItem( DetectedItem, 'RAU', self:GetDetectedUnitTypeName( DetectedItem.Zone.ZoneUNIT ) ) - + -- Then search for a new center area unit within the set. Note that the new area unit candidate must be within the area range. for DetectedUnitName, DetectedUnitData in pairs( DetectedSet:GetSet() ) do - + local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT - local DetectedObject = self:GetDetectedObject( DetectedUnit.UnitName ) + local DetectedObject = self:GetDetectedObject( DetectedUnit.UnitName ) local DetectedUnitTypeName = self:GetDetectedUnitTypeName( DetectedUnit ) - + -- The DetectedObject can be nil when the DetectedUnit is not alive anymore or it is not in the DetectedObjects map. -- If the DetectedUnit was already identified, DetectedObject will be nil. if DetectedObject then self:IdentifyDetectedObject( DetectedObject ) AreaExists = true - - --DetectedItem.Zone:BoundZone( 12, self.CountryID, true) - + + -- DetectedItem.Zone:BoundZone( 12, self.CountryID, true) + -- Assign the Unit as the new center unit of the detected area. DetectedItem.Zone = ZONE_UNIT:New( DetectedUnit:GetName(), DetectedUnit, self.DetectionZoneRange ) - + self:AddChangeItem( DetectedItem, "AAU", DetectedUnitTypeName ) - + -- We don't need to add the DetectedObject to the area set, because it is already there ... break else @@ -2831,30 +2777,30 @@ do -- DETECTION_AREAS end end end - + -- Now we've determined the center unit of the area, now we can iterate the units in the detected area. -- Note that the position of the area may have moved due to the center unit repositioning. -- If no center unit was identified, then the detected area does not exist anymore and should be deleted, as there are no valid units that can be the center unit. if AreaExists then - + -- ok, we found the center unit of the area, now iterate through the detected area set and see which units are still within the center unit zone ... -- Those units within the zone are flagged as Identified. -- If a unit was not found in the set, remove it from the set. This may be added later to other existing or new sets. for DetectedUnitName, DetectedUnitData in pairs( DetectedSet:GetSet() ) do - + local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT local DetectedUnitTypeName = self:GetDetectedUnitTypeName( DetectedUnit ) local DetectedObject = nil if DetectedUnit:IsAlive() then - --self:F(DetectedUnit:GetName()) + -- self:F(DetectedUnit:GetName()) DetectedObject = self:GetDetectedObject( DetectedUnit:GetName() ) end if DetectedObject then -- Check if the DetectedUnit is within the DetectedItem.Zone if DetectedUnit:IsInZone( DetectedItem.Zone ) then - + -- Yes, the DetectedUnit is within the DetectedItem.Zone, no changes, DetectedUnit can be kept within the Set. self:IdentifyDetectedObject( DetectedObject ) DetectedSet:AddUnit( DetectedUnit ) @@ -2864,47 +2810,45 @@ do -- DETECTION_AREAS DetectedSet:Remove( DetectedUnitName ) self:AddChangeUnit( DetectedItem, "RU", DetectedUnitTypeName ) end - + else -- There was no DetectedObject, remove DetectedUnit from the Set. self:AddChangeUnit( DetectedItem, "RU", "destroyed target" ) DetectedSet:Remove( DetectedUnitName ) - + -- The DetectedObject has been identified, because it does not exist ... -- self:IdentifyDetectedObject( DetectedObject ) end end else - --DetectedItem.Zone:BoundZone( 12, self.CountryID, true) + -- DetectedItem.Zone:BoundZone( 12, self.CountryID, true) self:RemoveDetectedItem( DetectedItemID ) self:AddChangeItem( DetectedItem, "RA" ) end end end - - - + -- We iterated through the existing detection areas and: -- - We checked which units are still detected in each detection area. Those units were flagged as Identified. - -- - We recentered the detection area to new center units where it was needed. + -- - We re-centered the detection area to new center units where it was needed. -- -- Now we need to loop through the unidentified detected units and see where they belong: -- - They can be added to a new detection area and become the new center unit. -- - They can be added to a new detection area. for DetectedUnitName, DetectedObjectData in pairs( self.DetectedObjects ) do - + local DetectedObject = self:GetDetectedObject( DetectedUnitName ) - + if DetectedObject then - + -- We found an unidentified unit outside of any existing detection area. local DetectedUnit = UNIT:FindByName( DetectedUnitName ) -- Wrapper.Unit#UNIT local DetectedUnitTypeName = self:GetDetectedUnitTypeName( DetectedUnit ) - + local AddedToDetectionArea = false - + for DetectedItemID, DetectedItemData in pairs( self.DetectedItems ) do - + local DetectedItem = DetectedItemData -- #DETECTION_BASE.DetectedItem if DetectedItem then local DetectedSet = DetectedItem.Set @@ -2916,74 +2860,71 @@ do -- DETECTION_AREAS end end end - + if AddedToDetectionArea == false then - + -- New detection area - local DetectedItem = self:AddDetectedItemZone( "AREA", nil, + local DetectedItem = self:AddDetectedItemZone( "AREA", nil, SET_UNIT:New():FilterDeads():FilterCrashes(), ZONE_UNIT:New( DetectedUnitName, DetectedUnit, self.DetectionZoneRange ) ) - --self:F( DetectedItem.Zone.ZoneUNIT.UnitName ) + -- self:F( DetectedItem.Zone.ZoneUNIT.UnitName ) DetectedItem.Set:AddUnit( DetectedUnit ) self:AddChangeItem( DetectedItem, "AA", DetectedUnitTypeName ) - end + end end end - + -- Now all the tests should have been build, now make some smoke and flares... -- We also report here the friendlies within the detected areas. - + for DetectedItemID, DetectedItemData in pairs( self.DetectedItems ) do - + local DetectedItem = DetectedItemData -- #DETECTION_BASE.DetectedItem local DetectedSet = DetectedItem.Set local DetectedFirstUnit = DetectedSet:GetFirst() local DetectedZone = DetectedItem.Zone - + -- Set the last known coordinate to the detection item. local DetectedZoneCoord = DetectedZone:GetCoordinate() self:SetDetectedItemCoordinate( DetectedItem, DetectedZoneCoord, DetectedFirstUnit ) - + self:CalculateIntercept( DetectedItem ) - + -- We search for friendlies nearby. -- If there weren't any friendlies nearby, and now there are friendlies nearby, we flag the area as "changed". -- If there were friendlies nearby, and now there aren't any friendlies nearby, we flag the area as "changed". -- This is for the A2G dispatcher to detect if there is a change in the tactical situation. local OldFriendliesNearbyGround = self:IsFriendliesNearBy( DetectedItem, Unit.Category.GROUND_UNIT ) self:ReportFriendliesNearBy( { DetectedItem = DetectedItem, ReportSetGroup = self.DetectionSet } ) -- Fill the Friendlies table - local NewFriendliesNearbyGround = self:IsFriendliesNearBy( DetectedItem, Unit.Category.GROUND_UNIT ) + local NewFriendliesNearbyGround = self:IsFriendliesNearBy( DetectedItem, Unit.Category.GROUND_UNIT ) if OldFriendliesNearbyGround ~= NewFriendliesNearbyGround then DetectedItem.Changed = true end - self:SetDetectedItemThreatLevel( DetectedItem ) -- Calculate A2G threat level + self:SetDetectedItemThreatLevel( DetectedItem ) -- Calculate A2G threat level self:NearestRecce( DetectedItem ) - if DETECTION_AREAS._SmokeDetectedUnits or self._SmokeDetectedUnits then DetectedZone.ZoneUNIT:SmokeRed() end - - --DetectedSet:Flush( self ) - - DetectedSet:ForEachUnit( - --- @param Wrapper.Unit#UNIT DetectedUnit - function( DetectedUnit ) - if DetectedUnit:IsAlive() then - --self:T( "Detected Set #" .. DetectedItem.ID .. ":" .. DetectedUnit:GetName() ) - if DETECTION_AREAS._FlareDetectedUnits or self._FlareDetectedUnits then - DetectedUnit:FlareGreen() - end - if DETECTION_AREAS._SmokeDetectedUnits or self._SmokeDetectedUnits then - DetectedUnit:SmokeGreen() - end + + -- DetectedSet:Flush( self ) + + DetectedSet:ForEachUnit( --- @param Wrapper.Unit#UNIT DetectedUnit + function( DetectedUnit ) + if DetectedUnit:IsAlive() then + -- self:T( "Detected Set #" .. DetectedItem.ID .. ":" .. DetectedUnit:GetName() ) + 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 ) ) + DetectedZone:FlareZone( SMOKECOLOR.White, 30, math.random( 0, 90 ) ) end if DETECTION_AREAS._SmokeDetectedZones or self._SmokeDetectedZones then DetectedZone:SmokeZone( SMOKECOLOR.White, 30 ) @@ -2994,9 +2935,8 @@ do -- DETECTION_AREAS DetectedZone:BoundZone( 12, self.CountryID ) end end - - end - -end - + end + +end + From 2d4f90d5eb0811a2d039d7701674ef4909789f8e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 23 Jan 2022 11:37:07 +0100 Subject: [PATCH 058/200] Added new Callsigns as per 2.7.9 --- Moose Development/Moose/Utilities/Utils.lua | 171 +++++++++++++------- 1 file changed, 114 insertions(+), 57 deletions(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 6e5ffa48b..84799fb18 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1,4 +1,4 @@ ---- This module contains derived utilities taken from the MIST framework, which are excellent tools to be reused in an OO environment. +--- This module contains derived utilities taken from the MIST framework, as well as a lot of added helpers from the MOOSE community. -- -- ### Authors: -- @@ -7,6 +7,7 @@ -- ### Contributions: -- -- * FlightControl : Rework to OO framework. +-- * And many more -- -- @module Utils -- @image MOOSE.JPG @@ -62,73 +63,129 @@ DCSMAP = { --- See [DCS_enum_callsigns](https://wiki.hoggitworld.com/view/DCS_enum_callsigns) -- @type CALLSIGN -CALLSIGN = { +CALLSIGN={ -- Aircraft - Aircraft = { - Enfield = 1, - Springfield = 2, - Uzi = 3, - Colt = 4, - Dodge = 5, - Ford = 6, - Chevy = 7, - Pontiac = 8, + Aircraft={ + Enfield=1, + Springfield=2, + Uzi=3, + Colt=4, + Dodge=5, + Ford=6, + Chevy=7, + Pontiac=8, -- A-10A or A-10C - Hawg = 9, - Boar = 10, - Pig = 11, - Tusk = 12, + Hawg=9, + Boar=10, + Pig=11, + Tusk=12, }, -- AWACS - AWACS = { - Overlord = 1, - Magic = 2, - Wizard = 3, - Focus = 4, - Darkstar = 5, + AWACS={ + Overlord=1, + Magic=2, + Wizard=3, + Focus=4, + Darkstar=5, }, -- Tanker - Tanker = { - Texaco = 1, - Arco = 2, - Shell = 3, + Tanker={ + Texaco=1, + Arco=2, + Shell=3, }, -- JTAC - 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, + 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 - FARP = { - London = 1, - Dallas = 2, - Paris = 3, - Moscow = 4, - Berlin = 5, - Rome = 6, - Madrid = 7, - Warsaw = 8, - Dublin = 9, - Perth = 10, + FARP={ + London=1, + Dallas=2, + Paris=3, + Moscow=4, + Berlin=5, + Rome=6, + Madrid=7, + Warsaw=8, + Dublin=9, + Perth=10, }, -} -- #CALLSIGN + F16={ + Viper=9, + Venom=10, + Lobo=11, + Cowboy=12, + Python=13, + Rattler=14, + Panther=15, + Wolf=16, + Weasel=17, + Wild=18, + Ninja=19, + Jedi=20, + }, + F18={ + Hornet=9, + Squid=10, + Ragin=11, + Roman=12, + Sting=13, + Jury=14, + Jokey=15, + Ram=16, + Hawk=17, + Devil=18, + Check=19, + Snake=20, + }, + F15E={ + Dude=9, + Thud=10, + Gunny=11, + Trek=12, + Sniper=13, + Sled=14, + Best=15, + Jazz=16, + Rage=17, + Tahoe=18, + }, + B1B={ + Bone=9, + Dark=10, + Vader=11 + }, + B52={ + Buff=9, + Dump=10, + Kenworth=11, + }, + TransportAircraft={ + Heavy=9, + Trash=10, + Cargo=11, + Ascot=12, + }, +} --#CALLSIGN --- Utilities static class. -- @type UTILS From 8ac06979f031a59ff19142a6db497e3cfae9ae4d Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 23 Jan 2022 11:42:16 +0100 Subject: [PATCH 059/200] CTLD added color options for smoke drops, droppable beacons w/ timer --- Moose Development/Moose/Ops/CTLD.lua | 153 ++++++++++++++++++++++----- 1 file changed, 125 insertions(+), 28 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index a1bc0e99c..dcf99dae1 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -334,13 +334,13 @@ CTLD_CARGO = { -- @type CTLD_CARGO.Enum -- @field #string Type Type of Cargo. CTLD_CARGO.Enum = { - ["VEHICLE"] = "Vehicle", -- #string vehicles - ["TROOPS"] = "Troops", -- #string troops - ["FOB"] = "FOB", -- #string FOB - ["CRATE"] = "Crate", -- #string crate - ["REPAIR"] = "Repair", -- #string repair - ["ENGINEERS"] = "Engineers", -- #string engineers - ["STATIC"] = "Static", -- #string engineers + VEHICLE = "Vehicle", -- #string vehicles + TROOPS = "Troops", -- #string troops + FOB = "FOB", -- #string FOB + CRATE = "Crate", -- #string crate + REPAIR = "Repair", -- #string repair + ENGINEERS = "Engineers", -- #string engineers + STATIC = "Static", -- #string engineers } --- Function to create new CTLD_CARGO object. @@ -670,9 +670,10 @@ do -- my_ctld.allowcratepickupagain = true -- allow re-pickup crates that were dropped. -- my_ctld.enableslingload = false -- allow cargos to be slingloaded - might not work for all cargo types -- my_ctld.pilotmustopendoors = false -- force opening of doors --- my_ctld.SmokeColor = SMOKECOLOR.Red -- color to use when dropping smoke from heli +-- my_ctld.SmokeColor = SMOKECOLOR.Red -- default color to use when dropping smoke from heli -- my_ctld.FlareColor = FLARECOLOR.Red -- color to use when flaring from heli -- my_ctld.basetype = "container_cargo" -- default shape of the cargo container +-- my_ctld.droppedbeacontimeout = 600 -- dropped beacon lasts 10 minutes -- -- ## 2.1 User functions -- @@ -826,7 +827,7 @@ do -- -- Lists what you have loaded. Shows load capabilities for number of crates and number of seats for troops. -- --- ## 4.4 Smoke & Flare zones nearby or drop smoke or flare from Heli +-- ## 4.4 Smoke & Flare zones nearby or drop smoke, beacon or flare from Heli -- -- Does what it says. -- @@ -948,6 +949,7 @@ CTLD = { -- @field #table vhfbeacon Beacon info as #CTLD.ZoneBeacon -- @field #number shiplength For ships - length of ship -- @field #number shipwidth For ships - width of ship +-- @field #number timestamp For dropped beacons - time this was created --- Zone Type Info. -- @type CTLD.CargoZoneType @@ -956,6 +958,7 @@ CTLD.CargoZoneType = { DROP = "drop", MOVE = "move", SHIP = "ship", + BEACON = "beacon", } --- Buildable table info. @@ -992,7 +995,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="1.0.3" +CTLD.version="1.0.4" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1081,6 +1084,9 @@ function CTLD:New(Coalition, Prefixes, Alias) self.dropOffZones = {} self.wpZones = {} self.shipZones = {} + self.droppedBeacons = {} + self.droppedbeaconref = {} + self.droppedbeacontimeout = 600 -- Cargo self.Cargo_Crates = {} @@ -2920,11 +2926,17 @@ function CTLD:_RefreshF10Menus() local listmenu = MENU_GROUP_COMMAND:New(_group,"List boarded cargo",topmenu, self._ListCargo, self, _group, _unit) local invtry = MENU_GROUP_COMMAND:New(_group,"Inventory",topmenu, self._ListInventory, self, _group, _unit) local rbcns = MENU_GROUP_COMMAND:New(_group,"List active zone beacons",topmenu, self._ListRadioBeacons, self, _group, _unit) - local smoketopmenu = MENU_GROUP:New(_group,"Smokes & Flares",topmenu) + local smoketopmenu = MENU_GROUP:New(_group,"Smokes, Flares, Beacons",topmenu) local smokemenu = MENU_GROUP_COMMAND:New(_group,"Smoke zones nearby",smoketopmenu, self.SmokeZoneNearBy, self, _unit, false) - local smokeself = MENU_GROUP_COMMAND:New(_group,"Drop smoke now",smoketopmenu, self.SmokePositionNow, self, _unit, false) + local smokeself = MENU_GROUP:New(_group,"Drop smoke now",smoketopmenu) + local smokeselfred = MENU_GROUP_COMMAND:New(_group,"Red smoke",smokeself, self.SmokePositionNow, self, _unit, false,SMOKECOLOR.Red) + local smokeselfblue = MENU_GROUP_COMMAND:New(_group,"Blue smoke",smokeself, self.SmokePositionNow, self, _unit, false,SMOKECOLOR.Blue) + local smokeselfgreen = MENU_GROUP_COMMAND:New(_group,"Green smoke",smokeself, self.SmokePositionNow, self, _unit, false,SMOKECOLOR.Green) + local smokeselforange = MENU_GROUP_COMMAND:New(_group,"Orange smoke",smokeself, self.SmokePositionNow, self, _unit, false,SMOKECOLOR.Orange) + local smokeselfwhite = MENU_GROUP_COMMAND:New(_group,"White smoke",smokeself, self.SmokePositionNow, self, _unit, false,SMOKECOLOR.White) local flaremenu = MENU_GROUP_COMMAND:New(_group,"Flare zones nearby",smoketopmenu, self.SmokeZoneNearBy, self, _unit, true) - local flareself = MENU_GROUP_COMMAND:New(_group,"Fire flare now",smoketopmenu, self.SmokePositionNow, self, _unit, true):Refresh() + local flareself = MENU_GROUP_COMMAND:New(_group,"Fire flare now",smoketopmenu, self.SmokePositionNow, self, _unit, true) + local beaconself = MENU_GROUP_COMMAND:New(_group,"Drop beacon now",smoketopmenu, self.DropBeaconNow, self, _unit):Refresh() -- sub menus -- sub menu troops management if cantroops then @@ -3068,7 +3080,9 @@ function CTLD:AddZone(Zone) elseif zone.type == CTLD.CargoZoneType.DROP then table.insert(self.dropOffZones,zone) elseif zone.type == CTLD.CargoZoneType.SHIP then - table.insert(self.shipZones,zone) + table.insert(self.shipZones,zone) + elseif zone.type == CTLD.CargoZoneType.BEACON then + table.insert(self.droppedBeacons,zone) else table.insert(self.wpZones,zone) end @@ -3081,7 +3095,7 @@ end -- @param #CTLD.CargoZoneType ZoneType Type of zone this belongs to. -- @param #boolean NewState (Optional) Set to true to activate, false to switch off. function CTLD:ActivateZone(Name,ZoneType,NewState) - self:T(self.lid .. " AddZone") + self:T(self.lid .. " ActivateZone") local newstate = true -- set optional in case we\'re deactivating if NewState ~= nil then @@ -3116,7 +3130,7 @@ end -- @param #string Name Name of the zone to change in the ME. -- @param #CTLD.CargoZoneType ZoneType Type of zone this belongs to. function CTLD:DeactivateZone(Name,ZoneType) - self:T(self.lid .. " AddZone") + self:T(self.lid .. " DeactivateZone") self:ActivateZone(Name,ZoneType,false) return self end @@ -3225,6 +3239,72 @@ function CTLD:AddCTLDZone(Name, Type, Color, Active, HasBeacon, Shiplength, Ship return self end + +--- (Internal) Function to create a dropped beacon +-- @param #CTLD self +-- @param Wrapper.Unit#UNIT Unit +-- @return #CTLD self +function CTLD:DropBeaconNow(Unit) + self:T(self.lid .. " DropBeaconNow") + + local ctldzone = {} -- #CTLD.CargoZone + ctldzone.active = true + ctldzone.color = math.random(0,4) -- random color + ctldzone.name = "Beacon " .. math.random(1,10000) + ctldzone.type = CTLD.CargoZoneType.BEACON -- #CTLD.CargoZoneType + ctldzone.hasbeacon = true + + ctldzone.fmbeacon = self:_GetFMBeacon(ctldzone.name) + ctldzone.uhfbeacon = self:_GetUHFBeacon(ctldzone.name) + ctldzone.vhfbeacon = self:_GetVHFBeacon(ctldzone.name) + ctldzone.timestamp = timer.getTime() + + self.droppedbeaconref[ctldzone.name] = Unit:GetCoordinate() + + self:AddZone(ctldzone) + + local FMbeacon = ctldzone.fmbeacon -- #CTLD.ZoneBeacon + local VHFbeacon = ctldzone.vhfbeacon -- #CTLD.ZoneBeacon + local UHFbeacon = ctldzone.uhfbeacon -- #CTLD.ZoneBeacon + local Name = ctldzone.name + local FM = FMbeacon.frequency -- MHz + local VHF = VHFbeacon.frequency * 1000 -- KHz + local UHF = UHFbeacon.frequency -- MHz + local text = string.format("Dropped %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ", Name, FM, VHF, UHF) + + self:_SendMessage(text,15,false,Unit:GetGroup()) + + return self +end + +--- (Internal) Housekeeping dropped beacons. +-- @param #CTLD self +-- @return #CTLD self +function CTLD:CheckDroppedBeacons() + self:T(self.lid .. " CheckDroppedBeacons") + + -- check for timeout + local timeout = self.droppedbeacontimeout or 600 + local livebeacontable = {} + + for _,_beacon in pairs (self.droppedBeacons) do + local beacon = _beacon -- #CTLD.CargoZone + local T0 = beacon.timestamp + if timer.getTime() - T0 > timeout then + local name = beacon.name + self.droppedbeaconref[name] = nil + _beacon = nil + else + table.insert(livebeacontable,beacon) + end + end + + self.droppedBeacons = nil + self.droppedBeacons = livebeacontable + + return self +end + --- (Internal) Function to show list of radio beacons -- @param #CTLD self -- @param Wrapper.Group#GROUP Group @@ -3233,8 +3313,8 @@ function CTLD:_ListRadioBeacons(Group, Unit) self:T(self.lid .. " _ListRadioBeacons") local report = REPORT:New("Active Zone Beacons") report:Add("------------------------------------------------------------") - local zones = {[1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones, [4] = self.shipZones} - for i=1,4 do + local zones = {[1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones, [4] = self.shipZones, [5] = self.droppedBeacons} + for i=1,5 do for index,cargozone in pairs(zones[i]) do -- Get Beacon object from zone local czone = cargozone -- #CTLD.CargoZone @@ -3265,16 +3345,25 @@ end -- @param #number Mhz Frequency in Mhz. -- @param #number Modulation Modulation AM or FM. -- @param #boolean IsShip If true zone is a ship. -function CTLD:_AddRadioBeacon(Name, Sound, Mhz, Modulation, IsShip) +-- @param #boolean IsDropped If true, this isn't a zone but a dropped beacon +function CTLD:_AddRadioBeacon(Name, Sound, Mhz, Modulation, IsShip, IsDropped) self:T(self.lid .. " _AddRadioBeacon") local Zone = nil if IsShip then Zone = UNIT:FindByName(Name) + elseif IsDropped then + Zone = self.droppedbeaconref[Name] else Zone = ZONE:FindByName(Name) end local Sound = Sound or "beacon.ogg" - if Zone then + if IsDropped and Zone then + local ZoneCoord = Zone + local ZoneVec3 = ZoneCoord:GetVec3() + local Frequency = Mhz * 1000000 -- Freq in Hertz + local Sound = "l10n/DEFAULT/"..Sound + trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, Frequency, 1000) -- Beacon in MP only runs for 30secs straight + elseif Zone then local ZoneCoord = Zone:GetCoordinate() local ZoneVec3 = ZoneCoord:GetVec3() local Frequency = Mhz * 1000000 -- Freq in Hertz @@ -3289,10 +3378,12 @@ end function CTLD:_RefreshRadioBeacons() self:T(self.lid .. " _RefreshRadioBeacons") - local zones = {[1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones, [4] = self.shipZones} - for i=1,4 do + local zones = {[1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones, [4] = self.shipZones, [5] = self.droppedBeacons} + for i=1,5 do local IsShip = false if i == 4 then IsShip = true end + local IsDropped = false + if i == 5 then IsDropped = true end for index,cargozone in pairs(zones[i]) do -- Get Beacon object from zone local czone = cargozone -- #CTLD.CargoZone @@ -3305,9 +3396,9 @@ function CTLD:_RefreshRadioBeacons() local FM = FMbeacon.frequency -- MHz local VHF = VHFbeacon.frequency -- KHz local UHF = UHFbeacon.frequency -- MHz - self:_AddRadioBeacon(Name,Sound,FM,radio.modulation.FM, IsShip) - self:_AddRadioBeacon(Name,Sound,VHF,radio.modulation.FM, IsShip) - self:_AddRadioBeacon(Name,Sound,UHF,radio.modulation.AM, IsShip) + self:_AddRadioBeacon(Name,Sound,FM,radio.modulation.FM, IsShip, IsDropped) + self:_AddRadioBeacon(Name,Sound,VHF,radio.modulation.FM, IsShip, IsDropped) + self:_AddRadioBeacon(Name,Sound,UHF,radio.modulation.AM, IsShip, IsDropped) end end end @@ -3362,6 +3453,7 @@ function CTLD:IsUnitInZone(Unit,Zonetype) zonewidth = czone.shipwidth else zone = ZONE:FindByName(zonename) + self:T("Checking Zone: "..zonename) zonecoord = zone:GetCoordinate() zoneradius = zone:GetRadius() zonewidth = zoneradius @@ -3389,9 +3481,13 @@ end -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit The Unit. -- @param #boolean Flare If true, flare instead. -function CTLD:SmokePositionNow(Unit, Flare) +-- @param #number SmokeColor Color enumerator for smoke, e.g. SMOKECOLOR.Red +function CTLD:SmokePositionNow(Unit, Flare, SmokeColor) self:T(self.lid .. " SmokePositionNow") - local SmokeColor = self.SmokeColor or SMOKECOLOR.Red + local Smokecolor = self.SmokeColor or SMOKECOLOR.Red + if SmokeColor then + Smokecolor = SmokeColor + end local FlareColor = self.FlareColor or FLARECOLOR.Red -- table of #CTLD.CargoZone table local unitcoord = Unit:GetCoordinate() -- Core.Point#COORDINATE @@ -3401,7 +3497,7 @@ function CTLD:SmokePositionNow(Unit, Flare) else local height = unitcoord:GetLandHeight() + 2 unitcoord.y = height - unitcoord:Smoke(SmokeColor) + unitcoord:Smoke(Smokecolor) end return self end @@ -3986,6 +4082,7 @@ end self:T({From, Event, To}) self:CleanDroppedTroops() self:_RefreshF10Menus() + self:CheckDroppedBeacons() self:_RefreshRadioBeacons() self:CheckAutoHoverload() self:_CheckEngineers() From 2aeebf280b1550b05d86b19851310627426ff746 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 24 Jan 2022 09:54:16 +0100 Subject: [PATCH 060/200] AI Dispatchers - add ability to add/remove resources to/from a squad --- .../Moose/AI/AI_A2A_Dispatcher.lua | 26 ++++++++++++++++++- .../Moose/AI/AI_A2G_Dispatcher.lua | 26 ++++++++++++++++++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua index fb9e31513..960bfce89 100644 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua @@ -3876,7 +3876,31 @@ do function AI_A2A_DISPATCHER:SchedulerCAP( SquadronName ) self:CAP( SquadronName ) end - + + --- Add resources to a Squadron + -- @param #AI_A2A_DISPATCHER self + -- @param #string Squadron The squadron name. + -- @param #number Amount Number of resources to add. + function AI_A2A_DISPATCHER:AddToSquadron(Squadron,Amount) + local Squadron = self:GetSquadron(Squadron) + if Squadron.ResourceCount then + Squadron.ResourceCount = Squadron.ResourceCount + Amount + end + self:T({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount}) + end + + --- Remove resources from a Squadron + -- @param #AI_A2A_DISPATCHER self + -- @param #string Squadron The squadron name. + -- @param #number Amount Number of resources to remove. + function AI_A2A_DISPATCHER:RemoveFromSquadron(Squadron,Amount) + local Squadron = self:GetSquadron(Squadron) + if Squadron.ResourceCount then + Squadron.ResourceCount = Squadron.ResourceCount - Amount + end + self:T({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount}) + end + end do diff --git a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua index 9d6fd1573..b030466e4 100644 --- a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua @@ -4728,6 +4728,30 @@ do local PatrolTaskType = PatrolTaskTypes[math.random(1,3)] self:Patrol( SquadronName, PatrolTaskType ) end - + + --- Add resources to a Squadron + -- @param #AI_A2G_DISPATCHER self + -- @param #string Squadron The squadron name. + -- @param #number Amount Number of resources to add. + function AI_A2G_DISPATCHER:AddToSquadron(Squadron,Amount) + local Squadron = self:GetSquadron(Squadron) + if Squadron.ResourceCount then + Squadron.ResourceCount = Squadron.ResourceCount + Amount + end + self:T({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount}) + end + + --- Remove resources from a Squadron + -- @param #AI_A2G_DISPATCHER self + -- @param #string Squadron The squadron name. + -- @param #number Amount Number of resources to remove. + function AI_A2G_DISPATCHER:RemoveFromSquadron(Squadron,Amount) + local Squadron = self:GetSquadron(Squadron) + if Squadron.ResourceCount then + Squadron.ResourceCount = Squadron.ResourceCount - Amount + end + self:T({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount}) + end + end From b7adc6add60a64fc4d6f51cead63d8b065082873 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 30 Jan 2022 09:47:11 +0100 Subject: [PATCH 061/200] POINT - added function to name/stop fires and smoke --- Moose Development/Moose/Core/Point.lua | 1610 +++++++++++++----------- 1 file changed, 901 insertions(+), 709 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 9fdd5f659..93327ab62 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -26,18 +26,31 @@ -- -- ### Authors: -- --- * FlightControl : Design & Programming +-- * FlightControl (Design & Programming) -- -- ### Contributions: +-- +-- * funkyfranky +-- * Applevangelist +-- +-- === -- -- @module Core.Point -- @image Core_Coordinate.JPG + do -- COORDINATE --- @type COORDINATE + -- @field #string ClassName Name of the class + -- @field #number x Component of the 3D vector. + -- @field #number y Component of the 3D vector. + -- @field #number z Component of the 3D vector. + -- @field #number Heading Heading in degrees. Needs to be set first. + -- @field #number Velocity Velocity in meters per second. Needs to be set first. -- @extends Core.Base#BASE + --- Defines a 3D point in the simulator and with its methods, you can use or manipulate the point in 3D space. -- -- # 1) Create a COORDINATE object. @@ -48,6 +61,7 @@ do -- COORDINATE -- * @{#COORDINATE.NewFromVec2}(): from a @{DCS#Vec2} and possible altitude. -- * @{#COORDINATE.NewFromVec3}(): from a @{DCS#Vec3}. -- + -- -- # 2) Smoke, flare, explode, illuminate at the coordinate. -- -- At the point a smoke, flare, explosion and illumination bomb can be triggered. Use the following methods: @@ -77,9 +91,10 @@ do -- COORDINATE -- -- * @{#COORDINATE.IlluminationBomb}(): To illuminate the point. -- + -- -- # 3) Create markings on the map. -- - -- Place markers (text boxes with clarifications for briefings, target locations or any other reference point) + -- Place markers (text boxes with clarifications for briefings, target locations or any other reference point) -- on the map for all players, coalitions or specific groups: -- -- * @{#COORDINATE.MarkToAll}(): Place a mark to all players. @@ -122,8 +137,8 @@ do -- COORDINATE -- ## 4.6) LOS between coordinates. -- -- Calculate if the coordinate has Line of Sight (LOS) with the other given coordinate. - -- Mountains, trees and other objects can be positioned between the two 3D points, preventing visibility in a straight continuous line. - -- The method @{#COORDINATE.IsLOS}() returns if the two coordinates have LOS. + -- Mountains, trees and other objects can be positioned between the two 3D points, preventing visibilty in a straight continuous line. + -- The method @{#COORDINATE.IsLOS}() returns if the two coodinates have LOS. -- -- ## 4.7) Check the coordinate position. -- @@ -133,6 +148,8 @@ do -- COORDINATE -- * @{#COORDINATE.IsInSphere}(): is in a given sphere. -- * @{#COORDINATE.IsAtCoordinate2D}(): is in a given coordinate within a specific precision. -- + -- + -- -- # 5) Measure the simulation environment at the coordinate. -- -- ## 5.1) Weather specific. @@ -177,7 +194,7 @@ do -- COORDINATE -- ## 9) Coordinate text generation -- -- * @{#COORDINATE.ToStringBR}(): Generates a Bearing & Range text in the format of DDD for DI where DDD is degrees and DI is distance. - -- * @{#COORDINATE.ToStringLL}(): Generates a Latitude & Longitude text. + -- * @{#COORDINATE.ToStringLL}(): Generates a Latutude & Longutude text. -- -- ## 10) Drawings on F10 map -- @@ -193,43 +210,69 @@ do -- COORDINATE ClassName = "COORDINATE", } - --- @field COORDINATE.WaypointAltType + --- Waypoint altitude types. + -- @type COORDINATE.WaypointAltType + -- @field #string BARO Barometric altitude. + -- @field #string RADIO Radio altitude. COORDINATE.WaypointAltType = { - BARO = "BARO", + BARO = "BARO", RADIO = "RADIO", } - --- @field COORDINATE.WaypointAction + --- Waypoint actions. + -- @type COORDINATE.WaypointAction + -- @field #string TurningPoint Turning point. + -- @field #string FlyoverPoint Fly over point. + -- @field #string FromParkingArea From parking area. + -- @field #string FromParkingAreaHot From parking area hot. + -- @field #string FromGroundAreaHot From ground area hot. + -- @field #string FromGroundArea From ground area. + -- @field #string FromRunway From runway. + -- @field #string Landing Landing. + -- @field #string LandingReFuAr Landing and refuel and rearm. COORDINATE.WaypointAction = { TurningPoint = "Turning Point", FlyoverPoint = "Fly Over Point", FromParkingArea = "From Parking Area", FromParkingAreaHot = "From Parking Area Hot", + FromGroundAreaHot = "From Ground Area Hot", + FromGroundArea = "From Ground Area", FromRunway = "From Runway", Landing = "Landing", LandingReFuAr = "LandingReFuAr", } - --- @field COORDINATE.WaypointType + --- Waypoint types. + -- @type COORDINATE.WaypointType + -- @field #string TakeOffParking Take of parking. + -- @field #string TakeOffParkingHot Take of parking hot. + -- @field #string TakeOff Take off parking hot. + -- @field #string TakeOffGroundHot Take of from ground hot. + -- @field #string TurningPoint Turning point. + -- @field #string Land Landing point. + -- @field #string LandingReFuAr Landing and refuel and rearm. COORDINATE.WaypointType = { TakeOffParking = "TakeOffParking", TakeOffParkingHot = "TakeOffParkingHot", TakeOff = "TakeOffParkingHot", + TakeOffGroundHot = "TakeOffGroundHot", + TakeOffGround = "TakeOffGround", TurningPoint = "Turning Point", Land = "Land", - LandingReFuAr = "LandingReFuAr", + LandingReFuAr = "LandingReFuAr", } + --- COORDINATE constructor. -- @param #COORDINATE self -- @param DCS#Distance x The x coordinate of the Vec3 point, pointing to the North. - -- @param DCS#Distance y The y coordinate of the Vec3 point, pointing to the Right. - -- @param DCS#Distance z The z coordinate of the Vec3 point, pointing to the Right. - -- @return #COORDINATE + -- @param DCS#Distance y The y coordinate of the Vec3 point, pointing to up. + -- @param DCS#Distance z The z coordinate of the Vec3 point, pointing to the right. + -- @return #COORDINATE self function COORDINATE:New( x, y, z ) - -- env.info("FF COORDINATE New") - local self = BASE:Inherit( self, BASE:New() ) -- #COORDINATE + local self=BASE:Inherit(self, BASE:New()) -- #COORDINATE + self.x = x self.y = y self.z = z @@ -240,7 +283,7 @@ do -- COORDINATE --- COORDINATE constructor. -- @param #COORDINATE self -- @param #COORDINATE Coordinate. - -- @return #COORDINATE + -- @return #COORDINATE self function COORDINATE:NewFromCoordinate( Coordinate ) local self = BASE:Inherit( self, BASE:New() ) -- #COORDINATE @@ -255,7 +298,7 @@ do -- COORDINATE -- @param #COORDINATE self -- @param DCS#Vec2 Vec2 The Vec2 point. -- @param DCS#Distance LandHeightAdd (Optional) The default height if required to be evaluated will be the land height of the x, y coordinate. You can specify an extra height to be added to the land height. - -- @return #COORDINATE + -- @return #COORDINATE self function COORDINATE:NewFromVec2( Vec2, LandHeightAdd ) local LandHeight = land.getHeight( Vec2 ) @@ -265,8 +308,6 @@ do -- COORDINATE local self = self:New( Vec2.x, LandHeight, Vec2.y ) -- #COORDINATE - self:F2( self ) - return self end @@ -274,7 +315,7 @@ do -- COORDINATE --- Create a new COORDINATE object from Vec3 coordinates. -- @param #COORDINATE self -- @param DCS#Vec3 Vec3 The Vec3 point. - -- @return #COORDINATE + -- @return #COORDINATE self function COORDINATE:NewFromVec3( Vec3 ) local self = self:New( Vec3.x, Vec3.y, Vec3.z ) -- #COORDINATE @@ -283,6 +324,29 @@ do -- COORDINATE return self end + + --- Create a new COORDINATE object from a waypoint. This uses the components + -- + -- * `waypoint.x` + -- * `waypoint.alt` + -- * `waypoint.y` + -- + -- @param #COORDINATE self + -- @param DCS#Waypoint Waypoint The waypoint. + -- @return #COORDINATE self + function COORDINATE:NewFromWaypoint(Waypoint) + + local self=self:New(Waypoint.x, Waypoint.alt, Waypoint.y) -- #COORDINATE + + return self + end + + --- Return the coordinates itself. Sounds stupid but can be useful for compatibility. + -- @param #COORDINATE self + -- @return #COORDINATE self + function COORDINATE:GetCoordinate() + return self + end --- Return the coordinates of the COORDINATE in Vec3 format. -- @param #COORDINATE self @@ -291,6 +355,7 @@ do -- COORDINATE return { x = self.x, y = self.y, z = self.z } end + --- Return the coordinates of the COORDINATE in Vec2 format. -- @param #COORDINATE self -- @return DCS#Vec2 The Vec2 format coordinate. @@ -302,11 +367,11 @@ do -- COORDINATE -- @param #COORDINATE self -- @param DCS#Vec3 Vec3 The 3D vector with x,y,z components. -- @return #COORDINATE The modified COORDINATE itself. - function COORDINATE:UpdateFromVec3( Vec3 ) + function COORDINATE:UpdateFromVec3(Vec3) - self.x = Vec3.x - self.y = Vec3.y - self.z = Vec3.z + self.x=Vec3.x + self.y=Vec3.y + self.z=Vec3.z return self end @@ -315,11 +380,11 @@ do -- COORDINATE -- @param #COORDINATE self -- @param #COORDINATE Coordinate The coordinate with the new x,y,z positions. -- @return #COORDINATE The modified COORDINATE itself. - function COORDINATE:UpdateFromCoordinate( Coordinate ) + function COORDINATE:UpdateFromCoordinate(Coordinate) - self.x = Coordinate.x - self.y = Coordinate.y - self.z = Coordinate.z + self.x=Coordinate.x + self.y=Coordinate.y + self.z=Coordinate.z return self end @@ -328,38 +393,40 @@ do -- COORDINATE -- @param #COORDINATE self -- @param DCS#Vec2 Vec2 The 2D vector with x,y components. x is overwriting COORDINATE.x while y is overwriting COORDINATE.z. -- @return #COORDINATE The modified COORDINATE itself. - function COORDINATE:UpdateFromVec2( Vec2 ) + function COORDINATE:UpdateFromVec2(Vec2) - self.x = Vec2.x - self.z = Vec2.y + self.x=Vec2.x + self.z=Vec2.y return self end + --- Returns the coordinate from the latitude and longitude given in decimal degrees. -- @param #COORDINATE self -- @param #number latitude Latitude in decimal degrees. -- @param #number longitude Longitude in decimal degrees. -- @param #number altitude (Optional) Altitude in meters. Default is the land height at the coordinate. -- @return #COORDINATE - function COORDINATE:NewFromLLDD( latitude, longitude, altitude ) + function COORDINATE:NewFromLLDD( latitude, longitude, altitude) -- Returns a point from latitude and longitude in the vec3 format. - local vec3 = coord.LLtoLO( latitude, longitude ) + local vec3=coord.LLtoLO(latitude, longitude) -- Convert vec3 to coordinate object. - local _coord = self:NewFromVec3( vec3 ) + local _coord=self:NewFromVec3(vec3) -- Adjust height - if altitude == nil then - _coord.y = self:GetLandHeight() + if altitude==nil then + _coord.y=self:GetLandHeight() else - _coord.y = altitude + _coord.y=altitude end return _coord end + --- Returns if the 2 coordinates are at the same 2D position. -- @param #COORDINATE self -- @param #COORDINATE Coordinate @@ -385,53 +452,53 @@ do -- COORDINATE -- @return #boolean True if units were found. -- @return #boolean True if statics were found. -- @return #boolean True if scenery objects were found. - -- @return #table Table of MOOSE @[#Wrapper.Unit#UNIT} objects found. + -- @return #table Table of MOOSE @{Wrapper.Unit#UNIT} objects found. -- @return #table Table of DCS static objects found. -- @return #table Table of DCS scenery objects found. - function COORDINATE:ScanObjects( radius, scanunits, scanstatics, scanscenery ) - self:F( string.format( "Scanning in radius %.1f m.", radius or 100 ) ) + 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 = { + params = { point = self:GetVec3(), radius = radius, - }, - } + } + } -- Defaults - radius = radius or 100 - if scanunits == nil then - scanunits = true + radius=radius or 100 + if scanunits==nil then + scanunits=true end - if scanstatics == nil then - scanstatics = true + if scanstatics==nil then + scanstatics=true end - if scanscenery == nil then - scanscenery = false + if scanscenery==nil then + scanscenery=false end - -- {Object.Category.UNIT, Object.Category.STATIC, Object.Category.SCENERY} - local scanobjects = {} + --{Object.Category.UNIT, Object.Category.STATIC, Object.Category.SCENERY} + local scanobjects={} if scanunits then - table.insert( scanobjects, Object.Category.UNIT ) + table.insert(scanobjects, Object.Category.UNIT) end if scanstatics then - table.insert( scanobjects, Object.Category.STATIC ) + table.insert(scanobjects, Object.Category.STATIC) end if scanscenery then - table.insert( scanobjects, Object.Category.SCENERY ) + table.insert(scanobjects, Object.Category.SCENERY) end -- Found stuff. local Units = {} local Statics = {} local Scenery = {} - local gotstatics = false - local gotunits = false - local gotscenery = false + local gotstatics=false + local gotunits=false + local gotscenery=false - local function EvaluateZone( ZoneObject ) + local function EvaluateZone(ZoneObject) if ZoneObject then @@ -439,20 +506,20 @@ do -- COORDINATE local ObjectCategory = ZoneObject:getCategory() -- Check for unit or static objects - if ObjectCategory == Object.Category.UNIT and ZoneObject:isExist() then + if ObjectCategory==Object.Category.UNIT and ZoneObject:isExist() then - table.insert( Units, UNIT:Find( ZoneObject ) ) - gotunits = true + table.insert(Units, UNIT:Find(ZoneObject)) + gotunits=true - elseif ObjectCategory == Object.Category.STATIC and ZoneObject:isExist() then + elseif ObjectCategory==Object.Category.STATIC and ZoneObject:isExist() then - table.insert( Statics, ZoneObject ) - gotstatics = true + table.insert(Statics, ZoneObject) + gotstatics=true - elseif ObjectCategory == Object.Category.SCENERY then + elseif ObjectCategory==Object.Category.SCENERY then - table.insert( Scenery, ZoneObject ) - gotscenery = true + table.insert(Scenery, ZoneObject) + gotscenery=true end @@ -462,18 +529,18 @@ do -- COORDINATE end -- Search the world. - world.searchObjects( scanobjects, SphereSearch, EvaluateZone ) + world.searchObjects(scanobjects, SphereSearch, EvaluateZone) - for _, unit in pairs( Units ) do - self:T( string.format( "Scan found unit %s", unit:GetName() ) ) + 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() ) + 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() ) ) - SCENERY:Register( scenery:getName(), scenery ) + for _,scenery in pairs(Scenery) do + self:T(string.format("Scan found scenery %s typename=%s", scenery:getName(), scenery:getTypeName())) + --SCENERY:Register(scenery:getName(), scenery) end return gotunits, gotstatics, gotscenery, Units, Statics, Scenery @@ -483,36 +550,81 @@ do -- COORDINATE -- @param #COORDINATE self -- @param #number radius (Optional) Scan radius in meters. Default 100 m. -- @return Core.Set#SET_UNIT Set of units. - function COORDINATE:ScanUnits( radius ) + function COORDINATE:ScanUnits(radius) - local _, _, _, units = self:ScanObjects( radius, true, false, false ) + local _,_,_,units=self:ScanObjects(radius, true, false, false) - local set = SET_UNIT:New() + local set=SET_UNIT:New() - for _, unit in pairs( units ) do - set:AddUnit( unit ) + for _,unit in pairs(units) do + set:AddUnit(unit) end return set end - --- Find the closest unit to the COORDINATE within a certain radius. + --- Find the closest unit to the COORDINATE within a certain radius. -- @param #COORDINATE self -- @param #number radius Scan radius in meters. Default 100 m. -- @return Wrapper.Unit#UNIT The closest unit or #nil if no unit is inside the given radius. - function COORDINATE:FindClosestUnit( radius ) + function COORDINATE:FindClosestUnit(radius) - local units = self:ScanUnits( radius ) + local units=self:ScanUnits(radius) - local umin = nil -- Wrapper.Unit#UNIT - local dmin = math.huge - for _, _unit in pairs( units.Set ) do - local unit = _unit -- Wrapper.Unit#UNIT - local coordinate = unit:GetCoordinate() - local d = self:Get2DDistance( coordinate ) - if d < dmin then - dmin = d - umin = unit + local umin=nil --Wrapper.Unit#UNIT + local dmin=math.huge + for _,_unit in pairs(units.Set) do + local unit=_unit --Wrapper.Unit#UNIT + local coordinate=unit:GetCoordinate() + local d=self:Get2DDistance(coordinate) + if d 180 then - direction = direction - 180 + direction = direction-180 else - direction = direction + 180 + direction = direction+180 end - local strength = math.sqrt( (wind.x) ^ 2 + (wind.z) ^ 2 ) + local strength=math.sqrt((wind.x)^2+(wind.z)^2) -- Return wind direction and strength km/h. return direction, strength end @@ -902,20 +1032,21 @@ do -- COORDINATE -- @param #COORDINATE self -- @param height (Optional) parameter specifying the height ASL. The minimum height will be always be the land height since the wind is zero below the ground. -- @return Direction the wind is blowing from in degrees. - function COORDINATE:GetWindWithTurbulenceVec3( height ) + function COORDINATE:GetWindWithTurbulenceVec3(height) -- AGL height if - local landheight = self:GetLandHeight() + 0.1 -- we at 0.1 meters to be sure to be above ground since wind is zero below ground level. + local landheight=self:GetLandHeight()+0.1 -- we at 0.1 meters to be sure to be above ground since wind is zero below ground level. -- Point at which the wind is evaluated. - local point = { x = self.x, y = math.max( height or self.y, landheight ), z = self.z } + local point={x=self.x, y=math.max(height or self.y, landheight), z=self.z} -- Get wind velocity vector including turbulences. - local vec3 = atmosphere.getWindWithTurbulence( point ) + local vec3 = atmosphere.getWindWithTurbulence(point) return vec3 end + --- Returns a text documenting the wind direction (from) and strength according the measurement system @{Settings}. -- The text will reflect the wind like this: -- @@ -956,12 +1087,13 @@ do -- COORDINATE 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 + return ( ( TargetVec3.x - SourceVec3.x ) ^ 2 + ( TargetVec3.y - SourceVec3.y ) ^ 2 + ( TargetVec3.z - SourceVec3.z ) ^ 2 ) ^ 0.5 end + --- Provides a bearing text in degrees. -- @param #COORDINATE self - -- @param #number AngleRadians The angle in radians. + -- @param #number AngleRadians The angle in randians. -- @param #number Precision The precision. -- @param Core.Settings#SETTINGS Settings -- @return #string The bearing text in degrees. @@ -989,13 +1121,13 @@ do -- COORDINATE local DistanceText if Settings:IsMetric() then - if Language == "EN" 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 + if Language == "EN" then DistanceText = " for " .. UTILS.Round( UTILS.MetersToNM( Distance ), 2 ) .. " miles" elseif Language == "RU" then DistanceText = " за " .. UTILS.Round( UTILS.MetersToNM( Distance ), 2 ) .. " миль" @@ -1015,13 +1147,13 @@ do -- COORDINATE if Altitude ~= 0 then if Settings:IsMetric() then - if Language == "EN" 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 + if Language == "EN" then return " at " .. UTILS.Round( UTILS.MetersToFeet( self.y ), -3 ) .. " feet" elseif Language == "RU" then return " в " .. UTILS.Round( self.y, -3 ) .. " ноги" @@ -1032,6 +1164,8 @@ do -- COORDINATE end end + + --- Return the velocity text of the COORDINATE. -- @param #COORDINATE self -- @return #string Velocity text. @@ -1049,6 +1183,7 @@ do -- COORDINATE end end + --- Return the heading text of the COORDINATE. -- @param #COORDINATE self -- @return #string Heading text. @@ -1061,9 +1196,10 @@ do -- COORDINATE end end + --- Provides a Bearing / Range string -- @param #COORDINATE self - -- @param #number AngleRadians The angle in radians + -- @param #number AngleRadians The angle in randians -- @param #number Distance The distance -- @param Core.Settings#SETTINGS Settings -- @return #string The BR Text @@ -1081,7 +1217,7 @@ do -- COORDINATE --- Provides a Bearing / Range / Altitude string -- @param #COORDINATE self - -- @param #number AngleRadians The angle in radians + -- @param #number AngleRadians The angle in randians -- @param #number Distance The distance -- @param Core.Settings#SETTINGS Settings -- @return #string The BRA Text @@ -1090,27 +1226,28 @@ do -- COORDINATE local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS local BearingText = self:GetBearingText( AngleRadians, 0, Settings, Language ) - local DistanceText = self:GetDistanceText( Distance, Settings, Language ) - local AltitudeText = self:GetAltitudeText( Settings, Language ) + local DistanceText = self:GetDistanceText( Distance, Settings, Language ) + local AltitudeText = self:GetAltitudeText( Settings, Language ) local BRAText = BearingText .. DistanceText .. AltitudeText -- When the POINT is a VEC2, there will be no altitude shown. return BRAText end + --- Set altitude. -- @param #COORDINATE self -- @param #number altitude New altitude in meters. -- @param #boolean asl Altitude above sea level. Default is above ground level. -- @return #COORDINATE The COORDINATE with adjusted altitude. - function COORDINATE:SetAltitude( altitude, asl ) - local alt = altitude + function COORDINATE:SetAltitude(altitude, asl) + local alt=altitude if asl then - alt = altitude + alt=altitude else - alt = self:GetLandHeight() + altitude + alt=self:GetLandHeight()+altitude end - self.y = alt + self.y=alt return self end @@ -1130,15 +1267,15 @@ do -- COORDINATE self:F2( { AltType, Type, Action, Speed, SpeedLocked } ) -- Set alttype or "RADIO" which is AGL. - AltType = AltType or "RADIO" + AltType=AltType or "RADIO" -- Speedlocked by default - if SpeedLocked == nil then - SpeedLocked = true + if SpeedLocked==nil then + SpeedLocked=true end -- Speed or default 500 km/h. - Speed = Speed or 500 + Speed=Speed or 500 -- Waypoint array. local RoutePoint = {} @@ -1156,15 +1293,15 @@ do -- COORDINATE RoutePoint.action = Action or nil -- Speed. - RoutePoint.speed = Speed / 3.6 + RoutePoint.speed = Speed/3.6 RoutePoint.speed_locked = SpeedLocked -- ETA. - RoutePoint.ETA = 0 - RoutePoint.ETA_locked = false + RoutePoint.ETA=0 + RoutePoint.ETA_locked=false -- Waypoint description. - RoutePoint.name = description + RoutePoint.name=description -- Airbase parameters for takeoff and landing points. if airbase then @@ -1176,13 +1313,13 @@ do -- COORDINATE elseif AirbaseCategory == Airbase.Category.AIRDROME then RoutePoint.airdromeId = AirbaseID else - self:E( "ERROR: Unknown airbase category in COORDINATE:WaypointAir()!" ) + self:E("ERROR: Unknown airbase category in COORDINATE:WaypointAir()!") end end - -- Time in minutes to stay at the airbase before resuming route. - if Type == COORDINATE.WaypointType.LandingReFuAr then - RoutePoint.timeReFuAr = timeReFuAr or 10 + -- Time in minutes to stay at the airbase before resuming route. + if Type==COORDINATE.WaypointType.LandingReFuAr then + RoutePoint.timeReFuAr=timeReFuAr or 10 end -- Waypoint tasks. @@ -1191,18 +1328,19 @@ do -- COORDINATE RoutePoint.task.params = {} RoutePoint.task.params.tasks = DCSTasks or {} - -- RoutePoint.properties={} - -- RoutePoint.properties.addopt={} + --RoutePoint.properties={} + --RoutePoint.properties.addopt={} - -- RoutePoint.formation_template="" + --RoutePoint.formation_template="" -- Debug. - self:T( { RoutePoint = RoutePoint } ) + self:T({RoutePoint=RoutePoint}) -- Return waypoint. return RoutePoint end + --- Build a Waypoint Air "Turning Point". -- @param #COORDINATE self -- @param #COORDINATE.WaypointAltType AltType The altitude type. @@ -1214,6 +1352,7 @@ do -- COORDINATE return self:WaypointAir( AltType, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, Speed, true, nil, DCSTasks, description ) end + --- Build a Waypoint Air "Fly Over Point". -- @param #COORDINATE self -- @param #COORDINATE.WaypointAltType AltType The altitude type. @@ -1223,6 +1362,7 @@ do -- COORDINATE return self:WaypointAir( AltType, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.FlyoverPoint, Speed ) end + --- Build a Waypoint Air "Take Off Parking Hot". -- @param #COORDINATE self -- @param #COORDINATE.WaypointAltType AltType The altitude type. @@ -1232,6 +1372,7 @@ do -- COORDINATE return self:WaypointAir( AltType, COORDINATE.WaypointType.TakeOffParkingHot, COORDINATE.WaypointAction.FromParkingAreaHot, Speed ) end + --- Build a Waypoint Air "Take Off Parking". -- @param #COORDINATE self -- @param #COORDINATE.WaypointAltType AltType The altitude type. @@ -1241,6 +1382,7 @@ do -- COORDINATE return self:WaypointAir( AltType, COORDINATE.WaypointType.TakeOffParking, COORDINATE.WaypointAction.FromParkingArea, Speed ) end + --- Build a Waypoint Air "Take Off Runway". -- @param #COORDINATE self -- @param #COORDINATE.WaypointAltType AltType The altitude type. @@ -1250,6 +1392,7 @@ do -- COORDINATE return self:WaypointAir( AltType, COORDINATE.WaypointType.TakeOff, COORDINATE.WaypointAction.FromRunway, Speed ) end + --- Build a Waypoint Air "Landing". -- @param #COORDINATE self -- @param DCS#Speed Speed Airspeed in km/h. @@ -1265,10 +1408,10 @@ do -- COORDINATE -- HeliGroup:Route( { LandWaypoint }, 1 ) -- Start landing the helicopter in one second. -- function COORDINATE:WaypointAirLanding( Speed, airbase, DCSTasks, description ) - return self:WaypointAir( nil, COORDINATE.WaypointType.Land, COORDINATE.WaypointAction.Landing, Speed, false, airbase, DCSTasks, description ) + return self:WaypointAir(nil, COORDINATE.WaypointType.Land, COORDINATE.WaypointAction.Landing, Speed, false, airbase, DCSTasks, description) end - --- Build a Waypoint Air "LandingReFuAr". Mimics the aircraft ReFueling and ReArming. + --- Build a Waypoint Air "LandingReFuAr". Mimics the aircraft ReFueling and ReArming. -- @param #COORDINATE self -- @param DCS#Speed Speed Airspeed in km/h. -- @param Wrapper.Airbase#AIRBASE airbase The airbase for takeoff and landing points. @@ -1277,9 +1420,10 @@ do -- COORDINATE -- @param #string description A text description of the waypoint, which will be shown on the F10 map. -- @return #table The route point. 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 ) + return self:WaypointAir(nil, COORDINATE.WaypointType.LandingReFuAr, COORDINATE.WaypointAction.LandingReFuAr, Speed, false, airbase, DCSTasks, description, timeReFuAr or 10) end + --- Build an ground type route point. -- @param #COORDINATE self -- @param #number Speed (Optional) Speed in km/h. The default speed is 20 km/h. @@ -1291,21 +1435,21 @@ do -- COORDINATE local RoutePoint = {} - RoutePoint.x = self.x - RoutePoint.y = self.z + RoutePoint.x = self.x + RoutePoint.y = self.z - RoutePoint.alt = self:GetLandHeight() + 1 + 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.formation_template="" - RoutePoint.ETA = 0 - RoutePoint.ETA_locked = false + RoutePoint.ETA=0 + RoutePoint.ETA_locked=false - RoutePoint.speed = (Speed or 20) / 3.6 + RoutePoint.speed = ( Speed or 20 ) / 3.6 RoutePoint.speed_locked = true RoutePoint.task = {} @@ -1327,20 +1471,20 @@ do -- COORDINATE local RoutePoint = {} - RoutePoint.x = self.x - RoutePoint.y = self.z + RoutePoint.x = self.x + RoutePoint.y = self.z - RoutePoint.alt = Depth or self.y -- Depth is for submarines only. Ships should have alt=0. + RoutePoint.alt = Depth or self.y -- Depth is for submarines only. Ships should have alt=0. RoutePoint.alt_type = "BARO" - RoutePoint.type = "Turning Point" + RoutePoint.type = "Turning Point" RoutePoint.action = "Turning Point" RoutePoint.formation_template = "" - RoutePoint.ETA = 0 - RoutePoint.ETA_locked = false + RoutePoint.ETA=0 + RoutePoint.ETA_locked=false - RoutePoint.speed = (Speed or 20) / 3.6 + RoutePoint.speed = ( Speed or 20 ) / 3.6 RoutePoint.speed_locked = true RoutePoint.task = {} @@ -1357,30 +1501,30 @@ do -- COORDINATE -- @param #number Coalition (Optional) Coalition of the airbase. -- @return Wrapper.Airbase#AIRBASE Closest Airbase to the given coordinate. -- @return #number Distance to the closest airbase in meters. - function COORDINATE:GetClosestAirbase2( Category, Coalition ) + function COORDINATE:GetClosestAirbase2(Category, Coalition) -- Get all airbases of the map. - local airbases = AIRBASE.GetAllAirbases( Coalition ) + local airbases=AIRBASE.GetAllAirbases(Coalition) - local closest = nil - local distmin = nil + local closest=nil + local distmin=nil -- Loop over all airbases. - for _, _airbase in pairs( airbases ) do - local airbase = _airbase -- Wrapper.Airbase#AIRBASE + for _,_airbase in pairs(airbases) do + local airbase=_airbase --Wrapper.Airbase#AIRBASE if airbase then - local category = airbase:GetAirbaseCategory() - if Category and Category == category or Category == nil then + local category=airbase:GetAirbaseCategory() + if Category and Category==category or Category==nil then - -- Distance to airbase. - local dist = self:Get2DDistance( airbase:GetCoordinate() ) + -- Distance to airbase. + local dist=self:Get2DDistance(airbase:GetCoordinate()) - if closest == nil then - distmin = dist - closest = airbase + if closest==nil then + distmin=dist + closest=airbase else - if dist < distmin then - distmin = dist - closest = airbase + if dist= 2 then - for i = 1, #Path - 1 do - Way = Way + Path[i + 1]:Get2DDistance( Path[i] ) + if #Path>=2 then + for i=1,#Path-1 do + Way=Way+Path[i+1]:Get2DDistance(Path[i]) end else -- There are cases where no path on road can be found. - return nil, nil, false + return nil,nil,false end return Path, Way, GotPath @@ -1607,8 +1756,8 @@ do -- COORDINATE -- @param #COORDINATE self -- @return DCS#SurfaceType Surface type. function COORDINATE:GetSurfaceType() - local vec2 = self:GetVec2() - local surface = land.getSurfaceType( vec2 ) + local vec2=self:GetVec2() + local surface=land.getSurfaceType(vec2) return surface end @@ -1616,69 +1765,77 @@ do -- COORDINATE -- @param #COORDINATE self -- @return #boolean If true, the surface type at the coordinate is land. function COORDINATE:IsSurfaceTypeLand() - return self:GetSurfaceType() == land.SurfaceType.LAND + return self:GetSurfaceType()==land.SurfaceType.LAND end - --- Checks if the surface type is road. + --- Checks if the surface type is land. -- @param #COORDINATE self -- @return #boolean If true, the surface type at the coordinate is land. function COORDINATE:IsSurfaceTypeLand() - return self:GetSurfaceType() == land.SurfaceType.LAND + return self:GetSurfaceType()==land.SurfaceType.LAND end + --- Checks if the surface type is road. -- @param #COORDINATE self -- @return #boolean If true, the surface type at the coordinate is a road. function COORDINATE:IsSurfaceTypeRoad() - return self:GetSurfaceType() == land.SurfaceType.ROAD + return self:GetSurfaceType()==land.SurfaceType.ROAD end --- Checks if the surface type is runway. -- @param #COORDINATE self -- @return #boolean If true, the surface type at the coordinate is a runway or taxi way. function COORDINATE:IsSurfaceTypeRunway() - return self:GetSurfaceType() == land.SurfaceType.RUNWAY + return self:GetSurfaceType()==land.SurfaceType.RUNWAY end --- Checks if the surface type is shallow water. -- @param #COORDINATE self -- @return #boolean If true, the surface type at the coordinate is a shallow water. function COORDINATE:IsSurfaceTypeShallowWater() - return self:GetSurfaceType() == land.SurfaceType.SHALLOW_WATER + return self:GetSurfaceType()==land.SurfaceType.SHALLOW_WATER end --- Checks if the surface type is water. -- @param #COORDINATE self -- @return #boolean If true, the surface type at the coordinate is a deep water. function COORDINATE:IsSurfaceTypeWater() - return self:GetSurfaceType() == land.SurfaceType.WATER + return self:GetSurfaceType()==land.SurfaceType.WATER end + --- Creates an explosion at the point of a certain intensity. -- @param #COORDINATE self -- @param #number ExplosionIntensity Intensity of the explosion in kg TNT. Default 100 kg. - -- @param #number Delay Delay before explosion in seconds. + -- @param #number Delay (Optional) Delay before explosion is triggered in seconds. -- @return #COORDINATE self function COORDINATE:Explosion( ExplosionIntensity, Delay ) - self:F2( { ExplosionIntensity } ) - ExplosionIntensity = ExplosionIntensity or 100 - if Delay and Delay > 0 then - SCHEDULER:New( nil, self.Explosion, { self, ExplosionIntensity }, Delay ) + ExplosionIntensity=ExplosionIntensity or 100 + if Delay and Delay>0 then + self:ScheduleOnce(Delay, self.Explosion, self, ExplosionIntensity) else - trigger.action.explosion( self:GetVec3(), ExplosionIntensity ) + trigger.action.explosion(self:GetVec3(), ExplosionIntensity) end return self end --- Creates an illumination bomb at the point. -- @param #COORDINATE self - -- @param #number power Power of illumination bomb in Candela. + -- @param #number Power Power of illumination bomb in Candela. Default 1000 cd. + -- @param #number Delay (Optional) Delay before bomb is ignited in seconds. -- @return #COORDINATE self - function COORDINATE:IlluminationBomb( power ) - self:F2() - trigger.action.illuminationBomb( self:GetVec3(), power ) + function COORDINATE:IlluminationBomb(Power, Delay) + Power=Power or 1000 + if Delay and Delay>0 then + self:ScheduleOnce(Delay, self.IlluminationBomb, self, Power) + else + trigger.action.illuminationBomb(self:GetVec3(), Power) + end + return self end + --- Smokes the point in a color. -- @param #COORDINATE self -- @param Utilities.Utils#SMOKECOLOR SmokeColor @@ -1724,90 +1881,109 @@ do -- COORDINATE --- Big smoke and fire at the coordinate. -- @param #COORDINATE self - -- @param Utilities.Utils#BIGSMOKEPRESET preset Smoke preset (0=small smoke and fire, 1=medium smoke and fire, 2=large smoke and fire, 3=huge smoke and fire, 4=small smoke, 5=medium smoke, 6=large smoke, 7=huge smoke). - -- @param #number Density (Optional) Smoke density. Number in [0,...,1]. Default 0.5. - function COORDINATE:BigSmokeAndFire( Preset, Density ) - self:F2( { Preset = Preset, Density = Density } ) - Density = Density or 0.5 - trigger.action.effectSmokeBig( self:GetVec3(), Preset, Density ) + -- @param Utilities.Utils#BIGSMOKEPRESET preset Smoke preset (1=small smoke and fire, 2=medium smoke and fire, 3=large smoke and fire, 4=huge smoke and fire, 5=small smoke, 6=medium smoke, 7=large smoke, 8=huge smoke). + -- @param #number density (Optional) Smoke density. Number in [0,...,1]. Default 0.5. + -- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number. + function COORDINATE:BigSmokeAndFire( preset, density, name ) + self:F2( { preset=preset, density=density } ) + density=density or 0.5 + self.firename = name or "Fire-"..math.random(1,10000) + trigger.action.effectSmokeBig( self:GetVec3(), preset, density, self.firename ) + end + + --- Stop big smoke and fire at the coordinate. + -- @param #COORDINATE self + -- @param #string name (Optional) Name of the fire to stop it, if not using the same COORDINATE object. + function COORDINATE:StopBigSmokeAndFire( name ) + self:F2( { name = name } ) + name = name or self.firename + trigger.action.effectSmokeStop( name ) end --- Small smoke and fire at the coordinate. -- @param #COORDINATE self - -- @number Density (Optional) Smoke density. Number between 0 and 1. Default 0.5. - function COORDINATE:BigSmokeAndFireSmall( Density ) - self:F2( { Density = Density } ) - Density = Density or 0.5 - self:BigSmokeAndFire( BIGSMOKEPRESET.SmallSmokeAndFire, Density ) + -- @param #number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + -- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number. + function COORDINATE:BigSmokeAndFireSmall( density, name ) + self:F2( { density=density } ) + density=density or 0.5 + self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmokeAndFire, density, name) end --- Medium smoke and fire at the coordinate. -- @param #COORDINATE self - -- @number Density (Optional) Smoke density. Number between 0 and 1. Default 0.5. - function COORDINATE:BigSmokeAndFireMedium( Density ) - self:F2( { Density = Density } ) - Density = Density or 0.5 - self:BigSmokeAndFire( BIGSMOKEPRESET.MediumSmokeAndFire, Density ) + -- @param #number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + -- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number. + function COORDINATE:BigSmokeAndFireMedium( density, name ) + self:F2( { density=density } ) + density=density or 0.5 + self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmokeAndFire, density, name) end --- Large smoke and fire at the coordinate. -- @param #COORDINATE self - -- @number Density (Optional) Smoke density. Number between 0 and 1. Default 0.5. - function COORDINATE:BigSmokeAndFireLarge( Density ) - self:F2( { Density = Density } ) - Density = Density or 0.5 - self:BigSmokeAndFire( BIGSMOKEPRESET.LargeSmokeAndFire, Density ) + -- @param #number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + -- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number. + function COORDINATE:BigSmokeAndFireLarge( density, name ) + self:F2( { density=density } ) + density=density or 0.5 + self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmokeAndFire, density, name) end --- Huge smoke and fire at the coordinate. -- @param #COORDINATE self - -- @number Density (Optional) Smoke density. Number between 0 and 1. Default 0.5. - function COORDINATE:BigSmokeAndFireHuge( Density ) - self:F2( { Density = Density } ) - Density = Density or 0.5 - self:BigSmokeAndFire( BIGSMOKEPRESET.HugeSmokeAndFire, Density ) + -- @param #number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + -- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number. + function COORDINATE:BigSmokeAndFireHuge( density, name ) + self:F2( { density=density } ) + density=density or 0.5 + self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmokeAndFire, density, name) end --- Small smoke at the coordinate. -- @param #COORDINATE self - -- @number Density (Optional) Smoke density. Number between 0 and 1. Default 0.5. - function COORDINATE:BigSmokeSmall( Density ) - self:F2( { Density = Density } ) - Density = Density or 0.5 - self:BigSmokeAndFire( BIGSMOKEPRESET.SmallSmoke, Density ) + -- @param #number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + -- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number. + function COORDINATE:BigSmokeSmall( density, name ) + self:F2( { density=density } ) + density=density or 0.5 + self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmoke, density, name) end --- Medium smoke at the coordinate. -- @param #COORDINATE self - -- @number Density (Optional) Smoke density. Number between 0 and 1. Default 0.5. - function COORDINATE:BigSmokeMedium( Density ) - self:F2( { Density = Density } ) - Density = Density or 0.5 - self:BigSmokeAndFire( BIGSMOKEPRESET.MediumSmoke, Density ) + -- @param number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + -- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number. + function COORDINATE:BigSmokeMedium( density, name ) + self:F2( { density=density } ) + density=density or 0.5 + self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmoke, density, name) end --- Large smoke at the coordinate. -- @param #COORDINATE self - -- @number Density (Optional) Smoke density. Number between 0 and 1. Default 0.5. - function COORDINATE:BigSmokeLarge( Density ) - self:F2( { Density = Density } ) - Density = Density or 0.5 - self:BigSmokeAndFire( BIGSMOKEPRESET.LargeSmoke, Density ) + -- @param #number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + -- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number. + function COORDINATE:BigSmokeLarge( density, name ) + self:F2( { density=density } ) + density=density or 0.5 + self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmoke, density,name) end --- Huge smoke at the coordinate. -- @param #COORDINATE self - -- @number Density (Optional) Smoke density. Number between 0 and 1. Default 0.5. - function COORDINATE:BigSmokeHuge( Density ) - self:F2( { Density = Density } ) - Density = Density or 0.5 - self:BigSmokeAndFire( BIGSMOKEPRESET.HugeSmoke, Density ) + -- @param #number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + -- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number. + function COORDINATE:BigSmokeHuge( density, name ) + self:F2( { density=density } ) + density=density or 0.5 + self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmoke, density,name) end --- Flares the point in a color. -- @param #COORDINATE self -- @param Utilities.Utils#FLARECOLOR FlareColor - -- @param DCS#Azimuth Azimuth (Optional) The azimuth of the flare direction. The default azimuth is 0. + -- @param DCS#Azimuth Azimuth (optional) The azimuth of the flare direction. The default azimuth is 0. function COORDINATE:Flare( FlareColor, Azimuth ) self:F2( { FlareColor } ) trigger.action.signalFlare( self:GetVec3(), FlareColor, Azimuth and Azimuth or 0 ) @@ -1815,7 +1991,7 @@ do -- COORDINATE --- Flare the COORDINATE White. -- @param #COORDINATE self - -- @param DCS#Azimuth Azimuth (Optional) The azimuth of the flare direction. The default azimuth is 0. + -- @param DCS#Azimuth Azimuth (optional) The azimuth of the flare direction. The default azimuth is 0. function COORDINATE:FlareWhite( Azimuth ) self:F2( Azimuth ) self:Flare( FLARECOLOR.White, Azimuth ) @@ -1823,7 +1999,7 @@ do -- COORDINATE --- Flare the COORDINATE Yellow. -- @param #COORDINATE self - -- @param DCS#Azimuth Azimuth (Optional) The azimuth of the flare direction. The default azimuth is 0. + -- @param DCS#Azimuth Azimuth (optional) The azimuth of the flare direction. The default azimuth is 0. function COORDINATE:FlareYellow( Azimuth ) self:F2( Azimuth ) self:Flare( FLARECOLOR.Yellow, Azimuth ) @@ -1831,7 +2007,7 @@ do -- COORDINATE --- Flare the COORDINATE Green. -- @param #COORDINATE self - -- @param DCS#Azimuth Azimuth (Optional) The azimuth of the flare direction. The default azimuth is 0. + -- @param DCS#Azimuth Azimuth (optional) The azimuth of the flare direction. The default azimuth is 0. function COORDINATE:FlareGreen( Azimuth ) self:F2( Azimuth ) self:Flare( FLARECOLOR.Green, Azimuth ) @@ -1857,11 +2033,11 @@ do -- COORDINATE -- local MarkID = TargetCoord:MarkToAll( "This is a target for all players" ) function COORDINATE:MarkToAll( MarkText, ReadOnly, Text ) local MarkID = UTILS.GetMarkID() - if ReadOnly == nil then - ReadOnly = false + if ReadOnly==nil then + ReadOnly=false end - local text = Text or "" - trigger.action.markToAll( MarkID, MarkText, self:GetVec3(), ReadOnly, text ) + local text=Text or "" + trigger.action.markToAll( MarkID, MarkText, self:GetVec3(), ReadOnly, text) return MarkID end @@ -1877,10 +2053,10 @@ do -- COORDINATE -- local MarkID = TargetCoord:MarkToCoalition( "This is a target for the red coalition", coalition.side.RED ) function COORDINATE:MarkToCoalition( MarkText, Coalition, ReadOnly, Text ) local MarkID = UTILS.GetMarkID() - if ReadOnly == nil then - ReadOnly = false + if ReadOnly==nil then + ReadOnly=false end - local text = Text or "" + local text=Text or "" trigger.action.markToCoalition( MarkID, MarkText, self:GetVec3(), Coalition, ReadOnly, text ) return MarkID end @@ -1924,10 +2100,10 @@ do -- COORDINATE -- local MarkID = TargetCoord:MarkToGroup( "This is a target for the attack group", AttackGroup ) function COORDINATE:MarkToGroup( MarkText, MarkGroup, ReadOnly, Text ) local MarkID = UTILS.GetMarkID() - if ReadOnly == nil then - ReadOnly = false + if ReadOnly==nil then + ReadOnly=false end - local text = Text or "" + local text=Text or "" trigger.action.markToGroup( MarkID, MarkText, self:GetVec3(), MarkGroup:GetID(), ReadOnly, text ) return MarkID end @@ -1956,17 +2132,17 @@ do -- COORDINATE -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. -- @param #string Text (Optional) Text displayed when mark is added. Default none. -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. - function COORDINATE:LineToAll( Endpoint, Coalition, Color, Alpha, LineType, ReadOnly, Text ) + function COORDINATE:LineToAll(Endpoint, Coalition, Color, Alpha, LineType, ReadOnly, Text) local MarkID = UTILS.GetMarkID() - if ReadOnly == nil then - ReadOnly = false + 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 "" ) + 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 @@ -1983,204 +2159,204 @@ do -- COORDINATE -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. -- @param #string Text (Optional) Text displayed when mark is added. Default none. -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. - function COORDINATE:CircleToAll( Radius, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text ) + function COORDINATE:CircleToAll(Radius, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text) local MarkID = UTILS.GetMarkID() - if ReadOnly == nil then - ReadOnly = false + 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 Color - FillColor[4] = FillAlpha or 0.15 - trigger.action.circleToAll( Coalition, MarkID, vec3, Radius, Color, FillColor, LineType, ReadOnly, Text or "" ) + 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 Color + FillColor[4]=FillAlpha or 0.15 + trigger.action.circleToAll(Coalition, MarkID, vec3, Radius, Color, FillColor, LineType, ReadOnly, Text or "") return MarkID end end -- Markings - --- Rectangle to all. Creates a rectangle on the map from the COORDINATE in one corner to the end COORDINATE in the opposite corner. - -- Creates a line on the F10 map from one point to another. - -- @param #COORDINATE self - -- @param #COORDINATE Endpoint COORDINATE in the opposite corner. - -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. - -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). - -- @param #number Alpha Transparency [0,1]. Default 1. - -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. - -- @param #number FillAlpha Transparency [0,1]. Default 0.15. - -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. - -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. - -- @param #string Text (Optional) Text displayed when mark is added. Default none. - -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. - function COORDINATE:RectToAll( Endpoint, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text ) - local MarkID = UTILS.GetMarkID() - if ReadOnly == nil then - ReadOnly = false - end - local vec3 = Endpoint:GetVec3() - Coalition = Coalition or -1 - Color = Color or { 1, 0, 0 } - Color[4] = Alpha or 1.0 - LineType = LineType or 1 - FillColor = FillColor or Color - FillColor[4] = FillAlpha or 0.15 - trigger.action.rectToAll( Coalition, MarkID, self:GetVec3(), vec3, Color, FillColor, LineType, ReadOnly, Text or "" ) - return MarkID - end - - --- Creates a shape defined by 4 points on the F10 map. The first point is the current COORDINATE. The remaining three points need to be specified. - -- @param #COORDINATE self - -- @param #COORDINATE Coord2 Second COORDIANTE of the quad shape. - -- @param #COORDINATE Coord3 Third COORDIANTE of the quad shape. - -- @param #COORDINATE Coord4 Fourth COORDIANTE of the quad shape. - -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. - -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). - -- @param #number Alpha Transparency [0,1]. Default 1. - -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. - -- @param #number FillAlpha Transparency [0,1]. Default 0.15. - -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. - -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. - -- @param #string Text (Optional) Text displayed when mark is added. Default none. - -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. - function COORDINATE:QuadToAll( Coord2, Coord3, Coord4, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text ) - local MarkID = UTILS.GetMarkID() - if ReadOnly == nil then - ReadOnly = false - end - local point1 = self:GetVec3() - local point2 = Coord2:GetVec3() - local point3 = Coord3:GetVec3() - local point4 = Coord4:GetVec3() - Coalition = Coalition or -1 - Color = Color or { 1, 0, 0 } - Color[4] = Alpha or 1.0 - LineType = LineType or 1 - FillColor = FillColor or Color - FillColor[4] = FillAlpha or 0.15 - trigger.action.quadToAll( Coalition, MarkID, self:GetVec3(), point2, point3, point4, Color, FillColor, LineType, ReadOnly, Text or "" ) - return MarkID - end - - --- Creates a free form shape on the F10 map. The first point is the current COORDINATE. The remaining points need to be specified. - -- **NOTE**: A free form polygon must have **at least three points** in total and currently only **up to 10 points** in total are supported. - -- @param #COORDINATE self - -- @param #table Coordinates Table of coordinates of the remaining points of the shape. - -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. - -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). - -- @param #number Alpha Transparency [0,1]. Default 1. - -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. - -- @param #number FillAlpha Transparency [0,1]. Default 0.15. - -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. - -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. - -- @param #string Text (Optional) Text displayed when mark is added. Default none. - -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. - function COORDINATE:MarkupToAllFreeForm( Coordinates, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text ) - - local MarkID = UTILS.GetMarkID() - if ReadOnly == nil then - ReadOnly = false + --- Rectangle to all. Creates a rectangle on the map from the COORDINATE in one corner to the end COORDINATE in the opposite corner. + -- Creates a line on the F10 map from one point to another. + -- @param #COORDINATE self + -- @param #COORDINATE Endpoint COORDINATE in the opposite corner. + -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. + -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). + -- @param #number Alpha Transparency [0,1]. Default 1. + -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. + -- @param #number FillAlpha Transparency [0,1]. Default 0.15. + -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. + -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. + -- @param #string Text (Optional) Text displayed when mark is added. Default none. + -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. + function COORDINATE:RectToAll(Endpoint, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text) + local MarkID = UTILS.GetMarkID() + if ReadOnly==nil then + ReadOnly=false + end + local vec3=Endpoint:GetVec3() + Coalition=Coalition or -1 + Color=Color or {1,0,0} + Color[4]=Alpha or 1.0 + LineType=LineType or 1 + FillColor=FillColor or Color + FillColor[4]=FillAlpha or 0.15 + trigger.action.rectToAll(Coalition, MarkID, self:GetVec3(), vec3, Color, FillColor, LineType, ReadOnly, Text or "") + return MarkID end - Coalition = Coalition or -1 - - Color = Color or { 1, 0, 0 } - Color[4] = Alpha or 1.0 - - LineType = LineType or 1 - - FillColor = FillColor or UTILS.DeepCopy( Color ) - FillColor[4] = FillAlpha or 0.15 - - local vecs = {} - vecs[1] = self:GetVec3() - for i, coord in ipairs( Coordinates ) do - vecs[i + 1] = coord:GetVec3() + --- Creates a shape defined by 4 points on the F10 map. The first point is the current COORDINATE. The remaining three points need to be specified. + -- @param #COORDINATE self + -- @param #COORDINATE Coord2 Second COORDINATE of the quad shape. + -- @param #COORDINATE Coord3 Third COORDINATE of the quad shape. + -- @param #COORDINATE Coord4 Fourth COORDINATE of the quad shape. + -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. + -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). + -- @param #number Alpha Transparency [0,1]. Default 1. + -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. + -- @param #number FillAlpha Transparency [0,1]. Default 0.15. + -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. + -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. + -- @param #string Text (Optional) Text displayed when mark is added. Default none. + -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. + function COORDINATE:QuadToAll(Coord2, Coord3, Coord4, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text) + local MarkID = UTILS.GetMarkID() + if ReadOnly==nil then + ReadOnly=false + end + local point1=self:GetVec3() + local point2=Coord2:GetVec3() + local point3=Coord3:GetVec3() + local point4=Coord4:GetVec3() + Coalition=Coalition or -1 + Color=Color or {1,0,0} + Color[4]=Alpha or 1.0 + LineType=LineType or 1 + FillColor=FillColor or Color + FillColor[4]=FillAlpha or 0.15 + trigger.action.quadToAll(Coalition, MarkID, self:GetVec3(), point2, point3, point4, Color, FillColor, LineType, ReadOnly, Text or "") + return MarkID end - if #vecs < 3 then - self:E( "ERROR: A free form polygon needs at least three points!" ) - elseif #vecs == 3 then - trigger.action.markupToAll( 7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], Color, FillColor, LineType, ReadOnly, Text or "" ) - elseif #vecs == 4 then - trigger.action.markupToAll( 7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], Color, FillColor, LineType, ReadOnly, Text or "" ) - elseif #vecs == 5 then - trigger.action.markupToAll( 7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], Color, FillColor, LineType, ReadOnly, Text or "" ) - elseif #vecs == 6 then - trigger.action.markupToAll( 7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], Color, FillColor, LineType, Text or "" ) - elseif #vecs == 7 then - trigger.action.markupToAll( 7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], Color, FillColor, LineType, ReadOnly, Text or "" ) - elseif #vecs == 8 then - trigger.action.markupToAll( 7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], vecs[8], Color, FillColor, LineType, ReadOnly, Text or "" ) - elseif #vecs == 9 then - trigger.action.markupToAll( 7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], vecs[8], vecs[9], Color, FillColor, LineType, ReadOnly, Text or "" ) - elseif #vecs == 10 then - trigger.action.markupToAll( 7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], vecs[8], vecs[9], vecs[10], Color, FillColor, LineType, ReadOnly, Text or "" ) - else - self:E( "ERROR: Currently a free form polygon can only have 10 points in total!" ) - -- Unfortunately, unpack(vecs) does not work! So no idea how to generalize this :( - trigger.action.markupToAll( 7, Coalition, MarkID, unpack( vecs ), Color, FillColor, LineType, ReadOnly, Text or "" ) + --- Creates a free form shape on the F10 map. The first point is the current COORDINATE. The remaining points need to be specified. + -- **NOTE**: A free form polygon must have **at least three points** in total and currently only **up to 10 points** in total are supported. + -- @param #COORDINATE self + -- @param #table Coordinates Table of coordinates of the remaining points of the shape. + -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. + -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). + -- @param #number Alpha Transparency [0,1]. Default 1. + -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. + -- @param #number FillAlpha Transparency [0,1]. Default 0.15. + -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. + -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. + -- @param #string Text (Optional) Text displayed when mark is added. Default none. + -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. + function COORDINATE:MarkupToAllFreeForm(Coordinates, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text) + + local MarkID = UTILS.GetMarkID() + if ReadOnly==nil then + ReadOnly=false + end + + Coalition=Coalition or -1 + + Color=Color or {1,0,0} + Color[4]=Alpha or 1.0 + + LineType=LineType or 1 + + FillColor=FillColor or UTILS.DeepCopy(Color) + FillColor[4]=FillAlpha or 0.15 + + local vecs={} + vecs[1]=self:GetVec3() + for i,coord in ipairs(Coordinates) do + vecs[i+1]=coord:GetVec3() + end + + if #vecs<3 then + self:E("ERROR: A free form polygon needs at least three points!") + elseif #vecs==3 then + trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], Color, FillColor, LineType, ReadOnly, Text or "") + elseif #vecs==4 then + trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], Color, FillColor, LineType, ReadOnly, Text or "") + elseif #vecs==5 then + trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], Color, FillColor, LineType, ReadOnly, Text or "") + elseif #vecs==6 then + trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], Color, FillColor, LineType, ReadOnly, Text or "") + elseif #vecs==7 then + trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], Color, FillColor, LineType, ReadOnly, Text or "") + elseif #vecs==8 then + trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], vecs[8], Color, FillColor, LineType, ReadOnly, Text or "") + elseif #vecs==9 then + trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], vecs[8], vecs[9], Color, FillColor, LineType, ReadOnly, Text or "") + elseif #vecs==10 then + trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], vecs[8], vecs[9], vecs[10], Color, FillColor, LineType, ReadOnly, Text or "") + else + self:E("ERROR: Currently a free form polygon can only have 10 points in total!") + -- Unfortunately, unpack(vecs) does not work! So no idea how to generalize this :( + trigger.action.markupToAll(7, Coalition, MarkID, unpack(vecs), Color, FillColor, LineType, ReadOnly, Text or "") + end + + return MarkID end - return MarkID - end - - --- Text to all. Creates a text imposed on the map at the COORDINATE. Text scales with the map. - -- @param #COORDINATE self - -- @param #string Text Text displayed on the F10 map. - -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. - -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). - -- @param #number Alpha Transparency [0,1]. Default 1. - -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. - -- @param #number FillAlpha Transparency [0,1]. Default 0.3. - -- @param #number FontSize Font size. Default 14. - -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. - -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. - function COORDINATE:TextToAll( Text, Coalition, Color, Alpha, FillColor, FillAlpha, FontSize, ReadOnly ) - local MarkID = UTILS.GetMarkID() - if ReadOnly == nil then - ReadOnly = false + --- Text to all. Creates a text imposed on the map at the COORDINATE. Text scales with the map. + -- @param #COORDINATE self + -- @param #string Text Text displayed on the F10 map. + -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. + -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). + -- @param #number Alpha Transparency [0,1]. Default 1. + -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. + -- @param #number FillAlpha Transparency [0,1]. Default 0.3. + -- @param #number FontSize Font size. Default 14. + -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. + -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. + function COORDINATE:TextToAll(Text, Coalition, Color, Alpha, FillColor, FillAlpha, FontSize, ReadOnly) + local MarkID = UTILS.GetMarkID() + if ReadOnly==nil then + ReadOnly=false + end + Coalition=Coalition or -1 + Color=Color or {1,0,0} + Color[4]=Alpha or 1.0 + FillColor=FillColor or Color + FillColor[4]=FillAlpha or 0.3 + FontSize=FontSize or 14 + trigger.action.textToAll(Coalition, MarkID, self:GetVec3(), Color, FillColor, FontSize, ReadOnly, Text or "Hello World") + return MarkID end - Coalition = Coalition or -1 - Color = Color or { 1, 0, 0 } - Color[4] = Alpha or 1.0 - FillColor = FillColor or Color - FillColor[4] = FillAlpha or 0.3 - FontSize = FontSize or 14 - trigger.action.textToAll( Coalition, MarkID, self:GetVec3(), Color, FillColor, FontSize, ReadOnly, Text or "Hello World" ) - return MarkID - end - --- Arrow to all. Creates an arrow from the COORDINATE to the endpoint COORDINATE on the F10 map. There is no control over other dimensions of the arrow. - -- @param #COORDINATE self - -- @param #COORDINATE Endpoint COORDINATE where the tip of the arrow is pointing at. - -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. - -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). - -- @param #number Alpha Transparency [0,1]. Default 1. - -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. - -- @param #number FillAlpha Transparency [0,1]. Default 0.15. - -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. - -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. - -- @param #string Text (Optional) Text displayed when mark is added. Default none. - -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. - function COORDINATE:ArrowToAll( Endpoint, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text ) - local MarkID = UTILS.GetMarkID() - if ReadOnly == nil then - ReadOnly = false + --- Arrow to all. Creates an arrow from the COORDINATE to the endpoint COORDINATE on the F10 map. There is no control over other dimensions of the arrow. + -- @param #COORDINATE self + -- @param #COORDINATE Endpoint COORDINATE where the tip of the arrow is pointing at. + -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. + -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). + -- @param #number Alpha Transparency [0,1]. Default 1. + -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. + -- @param #number FillAlpha Transparency [0,1]. Default 0.15. + -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. + -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. + -- @param #string Text (Optional) Text displayed when mark is added. Default none. + -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. + function COORDINATE:ArrowToAll(Endpoint, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text) + local MarkID = UTILS.GetMarkID() + if ReadOnly==nil then + ReadOnly=false + end + local vec3=Endpoint:GetVec3() + Coalition=Coalition or -1 + Color=Color or {1,0,0} + Color[4]=Alpha or 1.0 + LineType=LineType or 1 + FillColor=FillColor or Color + FillColor[4]=FillAlpha or 0.15 + --trigger.action.textToAll(Coalition, MarkID, self:GetVec3(), Color, FillColor, FontSize, ReadOnly, Text or "Hello World") + trigger.action.arrowToAll(Coalition, MarkID, vec3, self:GetVec3(), Color, FillColor, LineType, ReadOnly, Text or "") + return MarkID end - local vec3 = Endpoint:GetVec3() - Coalition = Coalition or -1 - Color = Color or { 1, 0, 0 } - Color[4] = Alpha or 1.0 - LineType = LineType or 1 - FillColor = FillColor or Color - FillColor[4] = FillAlpha or 0.15 - -- trigger.action.textToAll(Coalition, MarkID, self:GetVec3(), Color, FillColor, FontSize, ReadOnly, Text or "Hello World") - trigger.action.arrowToAll( Coalition, MarkID, vec3, self:GetVec3(), Color, FillColor, LineType, ReadOnly, Text or "" ) - return MarkID - end --- Returns if a Coordinate has Line of Sight (LOS) with the ToCoordinate. -- @param #COORDINATE self @@ -2189,7 +2365,7 @@ do -- COORDINATE -- @return #boolean true If the ToCoordinate has LOS with the Coordinate, otherwise false. function COORDINATE:IsLOS( ToCoordinate, Offset ) - Offset = Offset or 2 + Offset=Offset or 2 -- Measurement of visibility should not be from the ground, so Adding a hypotethical 2 meters to each Coordinate. local FromVec3 = self:GetVec3() @@ -2203,6 +2379,7 @@ do -- COORDINATE return IsLOS end + --- Returns if a Coordinate is in a certain Radius of this Coordinate in 2D plane using the X and Z axis. -- @param #COORDINATE self -- @param #COORDINATE Coordinate The coordinate that will be tested if it is in the radius of this coordinate. @@ -2213,11 +2390,12 @@ do -- COORDINATE local InVec2 = self:GetVec2() local Vec2 = Coordinate:GetVec2() - local InRadius = UTILS.IsInRadius( InVec2, Vec2, Radius ) + local InRadius = UTILS.IsInRadius( InVec2, Vec2, Radius) return InRadius end + --- Returns if a Coordinate is in a certain radius of this Coordinate in 3D space using the X, Y and Z axis. -- So Radius defines the radius of the a Sphere in 3D space around this coordinate. -- @param #COORDINATE self @@ -2229,7 +2407,7 @@ do -- COORDINATE local InVec3 = self:GetVec3() local Vec3 = Coordinate:GetVec3() - local InSphere = UTILS.IsInSphere( InVec3, Vec3, Radius ) + local InSphere = UTILS.IsInSphere( InVec3, Vec3, Radius) return InSphere end @@ -2241,21 +2419,21 @@ do -- COORDINATE -- @param #number Year The year. -- @param #boolean InSeconds If true, return the sun rise time in seconds. -- @return #string Sunrise time, e.g. "05:41". - function COORDINATE:GetSunriseAtDate( Day, Month, Year, InSeconds ) + function COORDINATE:GetSunriseAtDate(Day, Month, Year, InSeconds) - -- Day of the year. - local DayOfYear = UTILS.GetDayOfYear( Year, Month, Day ) + -- Day of the year. + local DayOfYear=UTILS.GetDayOfYear(Year, Month, Day) - local Latitude, Longitude = self:GetLLDDM() + local Latitude, Longitude=self:GetLLDDM() - local Tdiff = UTILS.GMTToLocalTimeDifference() + local Tdiff=UTILS.GMTToLocalTimeDifference() - local sunrise = UTILS.GetSunRiseAndSet( DayOfYear, Latitude, Longitude, true, Tdiff ) + local sunrise=UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, true, Tdiff) if InSeconds then return sunrise else - return UTILS.SecondsToClock( sunrise, true ) + return UTILS.SecondsToClock(sunrise, true) end end @@ -2265,18 +2443,18 @@ do -- COORDINATE -- @param #number DayOfYear The day of the year. -- @param #boolean InSeconds If true, return the sun rise time in seconds. -- @return #string Sunrise time, e.g. "05:41". - function COORDINATE:GetSunriseAtDayOfYear( DayOfYear, InSeconds ) + function COORDINATE:GetSunriseAtDayOfYear(DayOfYear, InSeconds) - local Latitude, Longitude = self:GetLLDDM() + local Latitude, Longitude=self:GetLLDDM() - local Tdiff = UTILS.GMTToLocalTimeDifference() + local Tdiff=UTILS.GMTToLocalTimeDifference() - local sunrise = UTILS.GetSunRiseAndSet( DayOfYear, Latitude, Longitude, true, Tdiff ) + local sunrise=UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, true, Tdiff) if InSeconds then return sunrise else - return UTILS.SecondsToClock( sunrise, true ) + return UTILS.SecondsToClock(sunrise, true) end end @@ -2285,29 +2463,29 @@ do -- COORDINATE -- @param #COORDINATE self -- @param #boolean InSeconds If true, return the sun rise time in seconds. -- @return #string Sunrise time, e.g. "05:41". - function COORDINATE:GetSunrise( InSeconds ) + function COORDINATE:GetSunrise(InSeconds) - -- Get current day of the year. - local DayOfYear = UTILS.GetMissionDayOfYear() + -- Get current day of the year. + local DayOfYear=UTILS.GetMissionDayOfYear() -- Lat and long at this point. - local Latitude, Longitude = self:GetLLDDM() + local Latitude, Longitude=self:GetLLDDM() -- GMT time diff. - local Tdiff = UTILS.GMTToLocalTimeDifference() + local Tdiff=UTILS.GMTToLocalTimeDifference() -- Sunrise in seconds of the day. - local sunrise = UTILS.GetSunRiseAndSet( DayOfYear, Latitude, Longitude, true, Tdiff ) + local sunrise=UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, true, Tdiff) - local date = UTILS.GetDCSMissionDate() + local date=UTILS.GetDCSMissionDate() -- Debug output. - -- self:I(string.format("Sun rise at lat=%.3f long=%.3f on %s (DayOfYear=%d): %s (%d sec of the day) (GMT %d)", Latitude, Longitude, date, DayOfYear, tostring(UTILS.SecondsToClock(sunrise)), sunrise, Tdiff)) + --self:I(string.format("Sun rise at lat=%.3f long=%.3f on %s (DayOfYear=%d): %s (%d sec of the day) (GMT %d)", Latitude, Longitude, date, DayOfYear, tostring(UTILS.SecondsToClock(sunrise)), sunrise, Tdiff)) if InSeconds then return sunrise else - return UTILS.SecondsToClock( sunrise, true ) + return UTILS.SecondsToClock(sunrise, true) end end @@ -2316,16 +2494,16 @@ do -- COORDINATE -- @param #COORDINATE self -- @param OnlyToday If true, only calculate the sun rise of today. If sun has already risen, the time in negative minutes since sunrise is reported. -- @return #number Minutes to the next sunrise. - function COORDINATE:GetMinutesToSunrise( OnlyToday ) + function COORDINATE:GetMinutesToSunrise(OnlyToday) -- Seconds of today - local time = UTILS.SecondsOfToday() + local time=UTILS.SecondsOfToday() -- Next Sunrise in seconds. - local sunrise = nil + local sunrise=nil -- Time to sunrise. - local delta = nil + local delta=nil if OnlyToday then @@ -2333,9 +2511,9 @@ do -- COORDINATE -- Sunrise of today --- - sunrise = self:GetSunrise( true ) + sunrise=self:GetSunrise(true) - delta = sunrise - time + delta=sunrise-time else @@ -2344,47 +2522,47 @@ do -- COORDINATE --- -- Tomorrows day of the year. - local DayOfYear = UTILS.GetMissionDayOfYear() + 1 + local DayOfYear=UTILS.GetMissionDayOfYear()+1 - local Latitude, Longitude = self:GetLLDDM() + local Latitude, Longitude=self:GetLLDDM() - local Tdiff = UTILS.GMTToLocalTimeDifference() + local Tdiff=UTILS.GMTToLocalTimeDifference() - sunrise = UTILS.GetSunRiseAndSet( DayOfYear, Latitude, Longitude, true, Tdiff ) + sunrise=UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, true, Tdiff) - delta = sunrise + UTILS.SecondsToMidnight() + delta=sunrise+UTILS.SecondsToMidnight() end - return delta / 60 + return delta/60 end --- Check if it is day, i.e. if the sun has risen about the horizon at this coordinate. -- @param #COORDINATE self -- @param #string Clock (Optional) Time in format "HH:MM:SS+D", e.g. "05:40:00+3" to check if is day at 5:40 at third day after mission start. Default is to check right now. -- @return #boolean If true, it is day. If false, it is night time. - function COORDINATE:IsDay( Clock ) + function COORDINATE:IsDay(Clock) if Clock then - local Time = UTILS.ClockToSeconds( Clock ) + local Time=UTILS.ClockToSeconds(Clock) - local clock = UTILS.Split( Clock, "+" )[1] + local clock=UTILS.Split(Clock, "+")[1] -- Tomorrows day of the year. - local DayOfYear = UTILS.GetMissionDayOfYear( Time ) + local DayOfYear=UTILS.GetMissionDayOfYear(Time) - local Latitude, Longitude = self:GetLLDDM() + local Latitude, Longitude=self:GetLLDDM() - local Tdiff = UTILS.GMTToLocalTimeDifference() + local Tdiff=UTILS.GMTToLocalTimeDifference() - local sunrise = UTILS.GetSunRiseAndSet( DayOfYear, Latitude, Longitude, true, Tdiff ) - local sunset = UTILS.GetSunRiseAndSet( DayOfYear, Latitude, Longitude, false, Tdiff ) + local sunrise=UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, true, Tdiff) + local sunset=UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, false, Tdiff) - local time = UTILS.ClockToSeconds( clock ) + local time=UTILS.ClockToSeconds(clock) -- Check if time is between sunrise and sunset. - if time > sunrise and time <= sunset then + if time>sunrise and time<=sunset then return true else return false @@ -2393,16 +2571,16 @@ do -- COORDINATE else -- Todays sun rise in sec. - local sunrise = self:GetSunrise( true ) + local sunrise=self:GetSunrise(true) -- Todays sun set in sec. - local sunset = self:GetSunset( true ) + local sunset=self:GetSunset(true) -- Seconds passed since midnight. - local time = UTILS.SecondsOfToday() + local time=UTILS.SecondsOfToday() -- Check if time is between sunrise and sunset. - if time > sunrise and time <= sunset then + if time>sunrise and time<=sunset then return true else return false @@ -2416,8 +2594,8 @@ do -- COORDINATE -- @param #COORDINATE self -- @param #string Clock (Optional) Time in format "HH:MM:SS+D", e.g. "05:40:00+3" to check if is night at 5:40 at third day after mission start. Default is to check right now. -- @return #boolean If true, it is night. If false, it is day time. - function COORDINATE:IsNight( Clock ) - return not self:IsDay( Clock ) + function COORDINATE:IsNight(Clock) + return not self:IsDay(Clock) end --- Get sun set time for a specific date at the coordinate. @@ -2427,21 +2605,21 @@ do -- COORDINATE -- @param #number Year The year. -- @param #boolean InSeconds If true, return the sun rise time in seconds. -- @return #string Sunset time, e.g. "20:41". - function COORDINATE:GetSunsetAtDate( Day, Month, Year, InSeconds ) + function COORDINATE:GetSunsetAtDate(Day, Month, Year, InSeconds) -- Day of the year. - local DayOfYear = UTILS.GetDayOfYear( Year, Month, Day ) + local DayOfYear=UTILS.GetDayOfYear(Year, Month, Day) - local Latitude, Longitude = self:GetLLDDM() + local Latitude, Longitude=self:GetLLDDM() - local Tdiff = UTILS.GMTToLocalTimeDifference() + local Tdiff=UTILS.GMTToLocalTimeDifference() - local sunset = UTILS.GetSunRiseAndSet( DayOfYear, Latitude, Longitude, false, Tdiff ) + local sunset=UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, false, Tdiff) if InSeconds then return sunset else - return UTILS.SecondsToClock( sunset, true ) + return UTILS.SecondsToClock(sunset, true) end end @@ -2450,29 +2628,29 @@ do -- COORDINATE -- @param #COORDINATE self -- @param #boolean InSeconds If true, return the sun set time in seconds. -- @return #string Sunrise time, e.g. "20:41". - function COORDINATE:GetSunset( InSeconds ) + function COORDINATE:GetSunset(InSeconds) - -- Get current day of the year. - local DayOfYear = UTILS.GetMissionDayOfYear() + -- Get current day of the year. + local DayOfYear=UTILS.GetMissionDayOfYear() -- Lat and long at this point. - local Latitude, Longitude = self:GetLLDDM() + local Latitude, Longitude=self:GetLLDDM() -- GMT time diff. - local Tdiff = UTILS.GMTToLocalTimeDifference() + local Tdiff=UTILS.GMTToLocalTimeDifference() -- Sunrise in seconds of the day. - local sunrise = UTILS.GetSunRiseAndSet( DayOfYear, Latitude, Longitude, false, Tdiff ) + local sunrise=UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, false, Tdiff) - local date = UTILS.GetDCSMissionDate() + local date=UTILS.GetDCSMissionDate() -- Debug output. - -- self:I(string.format("Sun set at lat=%.3f long=%.3f on %s (DayOfYear=%d): %s (%d sec of the day) (GMT %d)", Latitude, Longitude, date, DayOfYear, tostring(UTILS.SecondsToClock(sunrise)), sunrise, Tdiff)) + --self:I(string.format("Sun set at lat=%.3f long=%.3f on %s (DayOfYear=%d): %s (%d sec of the day) (GMT %d)", Latitude, Longitude, date, DayOfYear, tostring(UTILS.SecondsToClock(sunrise)), sunrise, Tdiff)) if InSeconds then return sunrise else - return UTILS.SecondsToClock( sunrise, true ) + return UTILS.SecondsToClock(sunrise, true) end end @@ -2481,16 +2659,16 @@ do -- COORDINATE -- @param #COORDINATE self -- @param OnlyToday If true, only calculate the sun set of today. If sun has already set, the time in negative minutes since sunset is reported. -- @return #number Minutes to the next sunrise. - function COORDINATE:GetMinutesToSunset( OnlyToday ) + function COORDINATE:GetMinutesToSunset(OnlyToday) -- Seconds of today - local time = UTILS.SecondsOfToday() + local time=UTILS.SecondsOfToday() -- Next Sunset in seconds. - local sunset = nil + local sunset=nil -- Time to sunrise. - local delta = nil + local delta=nil if OnlyToday then @@ -2498,9 +2676,9 @@ do -- COORDINATE -- Sunset of today --- - sunset = self:GetSunset( true ) + sunset=self:GetSunset(true) - delta = sunset - time + delta=sunset-time else @@ -2509,29 +2687,30 @@ do -- COORDINATE --- -- Tomorrows day of the year. - local DayOfYear = UTILS.GetMissionDayOfYear() + 1 + local DayOfYear=UTILS.GetMissionDayOfYear()+1 - local Latitude, Longitude = self:GetLLDDM() + local Latitude, Longitude=self:GetLLDDM() - local Tdiff = UTILS.GMTToLocalTimeDifference() + local Tdiff=UTILS.GMTToLocalTimeDifference() - sunset = UTILS.GetSunRiseAndSet( DayOfYear, Latitude, Longitude, false, Tdiff ) + sunset=UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, false, Tdiff) - delta = sunset + UTILS.SecondsToMidnight() + delta=sunset+UTILS.SecondsToMidnight() end - return delta / 60 + return delta/60 end + --- Return a BR string from a COORDINATE to the COORDINATE. -- @param #COORDINATE self -- @param #COORDINATE FromCoordinate The coordinate to measure the distance and the bearing from. - -- @param Core.Settings#SETTINGS Settings (Optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. + -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @return #string The BR text. function COORDINATE:ToStringBR( FromCoordinate, Settings ) local DirectionVec3 = FromCoordinate:GetDirectionVec3( self ) - local AngleRadians = self:GetAngleRadians( DirectionVec3 ) + local AngleRadians = self:GetAngleRadians( DirectionVec3 ) local Distance = self:Get2DDistance( FromCoordinate ) return "BR, " .. self:GetBRText( AngleRadians, Distance, Settings ) end @@ -2539,11 +2718,11 @@ do -- COORDINATE --- Return a BRAA string from a COORDINATE to the COORDINATE. -- @param #COORDINATE self -- @param #COORDINATE FromCoordinate The coordinate to measure the distance and the bearing from. - -- @param Core.Settings#SETTINGS Settings (Optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. + -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @return #string The BR text. function COORDINATE:ToStringBRA( FromCoordinate, Settings, Language ) local DirectionVec3 = FromCoordinate:GetDirectionVec3( self ) - local AngleRadians = self:GetAngleRadians( DirectionVec3 ) + local AngleRadians = self:GetAngleRadians( DirectionVec3 ) local Distance = FromCoordinate:Get2DDistance( self ) local Altitude = self:GetAltitudeText() return "BRA, " .. self:GetBRAText( AngleRadians, Distance, Settings, Language ) @@ -2552,12 +2731,12 @@ do -- COORDINATE --- Return a BULLS string out of the BULLS of the coalition to the COORDINATE. -- @param #COORDINATE self -- @param DCS#coalition.side Coalition The coalition. - -- @param Core.Settings#SETTINGS Settings (Optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. + -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @return #string The BR text. function COORDINATE:ToStringBULLS( Coalition, Settings ) local BullsCoordinate = COORDINATE:NewFromVec3( coalition.getMainRefPoint( Coalition ) ) local DirectionVec3 = BullsCoordinate:GetDirectionVec3( self ) - local AngleRadians = self:GetAngleRadians( DirectionVec3 ) + local AngleRadians = self:GetAngleRadians( DirectionVec3 ) local Distance = self:Get2DDistance( BullsCoordinate ) local Altitude = self:GetAltitudeText() return "BULLS, " .. self:GetBRText( AngleRadians, Distance, Settings ) @@ -2600,7 +2779,7 @@ do -- COORDINATE --- Provides a Lat Lon string in Degree Minute Second format. -- @param #COORDINATE self - -- @param Core.Settings#SETTINGS Settings (Optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. + -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @return #string The LL DMS Text function COORDINATE:ToStringLLDMS( Settings ) @@ -2611,7 +2790,7 @@ do -- COORDINATE --- Provides a Lat Lon string in Degree Decimal Minute format. -- @param #COORDINATE self - -- @param Core.Settings#SETTINGS Settings (Optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. + -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @return #string The LL DDM Text function COORDINATE:ToStringLLDDM( Settings ) @@ -2622,9 +2801,9 @@ do -- COORDINATE --- Provides a MGRS string -- @param #COORDINATE self - -- @param Core.Settings#SETTINGS Settings (Optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. + -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @return #string The MGRS Text - function COORDINATE:ToStringMGRS( Settings ) -- R2.1 Fixes issue #424. + function COORDINATE:ToStringMGRS( Settings ) --R2.1 Fixes issue #424. local MGRS_Accuracy = Settings and Settings.MGRS_Accuracy or _SETTINGS.MGRS_Accuracy local lat, lon = coord.LOtoLL( self:GetVec3() ) @@ -2639,24 +2818,24 @@ do -- COORDINATE -- @param #COORDINATE ReferenceCoord The refrence coordinate. -- @param #string ReferenceName The refrence name. -- @param Wrapper.Controllable#CONTROLLABLE Controllable - -- @param Core.Settings#SETTINGS Settings (Optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. + -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @return #string The coordinate Text in the configured coordinate system. 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 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 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 AngleRadians = self:GetAngleRadians( DirectionVec3 ) local Distance = self:Get2DDistance( ReferenceCoord ) return "Target are located " .. self:GetBRText( AngleRadians, Distance, Settings ) .. " from " .. ReferenceName end @@ -2668,15 +2847,15 @@ do -- COORDINATE --- Provides a coordinate string of the point, based on the A2G coordinate format system. -- @param #COORDINATE self -- @param Wrapper.Controllable#CONTROLLABLE Controllable - -- @param Core.Settings#SETTINGS Settings (Optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. + -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @return #string The coordinate Text in the configured coordinate system. function COORDINATE:ToStringA2G( Controllable, Settings ) self:F2( { Controllable = Controllable and Controllable:GetName() } ) - local Settings = Settings or (Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() )) or _SETTINGS + local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS - if Settings:IsA2G_BR() then + if Settings:IsA2G_BR() then -- If no Controllable is given to calculate the BR from, then MGRS will be used!!! if Controllable then local Coordinate = Controllable:GetCoordinate() @@ -2685,10 +2864,10 @@ do -- COORDINATE return self:ToStringMGRS( Settings ) end end - if Settings:IsA2G_LL_DMS() then + if Settings:IsA2G_LL_DMS() then return self:ToStringLLDMS( Settings ) end - if Settings:IsA2G_LL_DDM() then + if Settings:IsA2G_LL_DDM() then return self:ToStringLLDDM( Settings ) end if Settings:IsA2G_MGRS() then @@ -2699,18 +2878,19 @@ do -- COORDINATE end + --- Provides a coordinate string of the point, based on the A2A coordinate format system. -- @param #COORDINATE self -- @param Wrapper.Controllable#CONTROLLABLE Controllable - -- @param Core.Settings#SETTINGS Settings (Optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. + -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @return #string The coordinate Text in the configured coordinate system. function COORDINATE:ToStringA2A( Controllable, Settings, Language ) -- R2.2 self:F2( { Controllable = Controllable and Controllable:GetName() } ) - local Settings = Settings or (Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() )) or _SETTINGS + local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS - if Settings:IsA2A_BRAA() then + if Settings:IsA2A_BRAA() then if Controllable then local Coordinate = Controllable:GetCoordinate() return self:ToStringBRA( Coordinate, Settings, Language ) @@ -2722,10 +2902,10 @@ do -- COORDINATE local Coalition = Controllable:GetCoalition() return self:ToStringBULLS( Coalition, Settings, Language ) end - if Settings:IsA2A_LL_DMS() then + if Settings:IsA2A_LL_DMS() then return self:ToStringLLDMS( Settings, Language ) end - if Settings:IsA2A_LL_DDM() then + if Settings:IsA2A_LL_DDM() then return self:ToStringLLDDM( Settings, Language ) end if Settings:IsA2A_MGRS() then @@ -2741,14 +2921,14 @@ do -- COORDINATE -- * Can be overridden if for a GROUP containing x clients, a menu was selected to override the default. -- @param #COORDINATE self -- @param Wrapper.Controllable#CONTROLLABLE Controllable The controllable to retrieve the settings from, otherwise the default settings will be chosen. - -- @param Core.Settings#SETTINGS Settings (Optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. + -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @param Tasking.Task#TASK Task The task for which coordinates need to be calculated. -- @return #string The coordinate Text in the configured coordinate system. function COORDINATE:ToString( Controllable, Settings, Task ) - -- self:E( { Controllable = Controllable and Controllable:GetName() } ) +-- self:E( { Controllable = Controllable and Controllable:GetName() } ) - local Settings = Settings or (Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() )) or _SETTINGS + local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS local ModeA2A = nil @@ -2762,22 +2942,24 @@ do -- COORDINATE if Task:IsInstanceOf( TASK_CARGO ) then ModeA2A = false end - if Task:IsInstanceOf( TASK_CAPTURE_ZONE ) 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 + 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 @@ -2793,13 +2975,13 @@ do -- COORDINATE -- * Can be overridden if for a GROUP containing x clients, a menu was selected to override the default. -- @param #COORDINATE self -- @param Wrapper.Controllable#CONTROLLABLE Controllable - -- @param Core.Settings#SETTINGS Settings (Optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. + -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @return #string The pressure text in the configured measurement system. function COORDINATE:ToStringPressure( Controllable, Settings ) -- R2.3 self:F2( { Controllable = Controllable and Controllable:GetName() } ) - local Settings = Settings or (Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() )) or _SETTINGS + local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS return self:GetPressureText( nil, Settings ) end @@ -2809,13 +2991,13 @@ do -- COORDINATE -- * Can be overridden if for a GROUP containing x clients, a menu was selected to override the default. -- @param #COORDINATE self -- @param Wrapper.Controllable#CONTROLLABLE Controllable - -- @param Core.Settings#SETTINGS Settings (Optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. + -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @return #string The wind text in the configured measurement system. function COORDINATE:ToStringWind( Controllable, Settings ) self:F2( { Controllable = Controllable and Controllable:GetName() } ) - local Settings = Settings or (Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() )) or _SETTINGS + local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS return self:GetWindText( nil, Settings ) end @@ -2825,13 +3007,13 @@ do -- COORDINATE -- * Can be overridden if for a GROUP containing x clients, a menu was selected to override the default. -- @param #COORDINATE self -- @param Wrapper.Controllable#CONTROLLABLE Controllable - -- @param Core.Settings#SETTINGS + -- @param Core.Settings#SETTINGS -- @return #string The temperature text in the configured measurement system. function COORDINATE:ToStringTemperature( Controllable, Settings ) self:F2( { Controllable = Controllable and Controllable:GetName() } ) - local Settings = Settings or (Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() )) or _SETTINGS + local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS return self:GetTemperatureText( nil, Settings ) end @@ -2844,7 +3026,7 @@ do -- POINT_VEC3 -- @type POINT_VEC3 -- @field #number x The x coordinate in 3D space. -- @field #number y The y coordinate in 3D space. - -- @field #number z The z coordiante in 3D space. + -- @field #number z The z COORDINATE in 3D space. -- @field Utilities.Utils#SMOKECOLOR SmokeColor -- @field Utilities.Utils#FLARECOLOR FlareColor -- @field #POINT_VEC3.RoutePointAltType RoutePointAltType @@ -2852,6 +3034,7 @@ do -- POINT_VEC3 -- @field #POINT_VEC3.RoutePointAction RoutePointAction -- @extends #COORDINATE + --- Defines a 3D point in the simulator and with its methods, you can use or manipulate the point in 3D space. -- -- **Important Note:** Most of the functions in this section were taken from MIST, and reworked to OO concepts. @@ -2859,6 +3042,7 @@ do -- POINT_VEC3 -- I want to emphasize that the formulas embedded in the MIST framework were created by Grimes or previous authors, -- who you can find on the Eagle Dynamics Forums. -- + -- -- ## POINT_VEC3 constructor -- -- A new POINT_VEC3 object can be created with: @@ -2880,16 +3064,19 @@ do -- POINT_VEC3 -- -- local Vec3 = PointVec3:AddX( 100 ):AddZ( 150 ):GetVec3() -- + -- -- ## 3D calculation methods -- -- Various calculation methods exist to use or manipulate 3D space. Find below a short description of each method: -- + -- -- ## Point Randomization -- -- Various methods exist to calculate random locations around a given 3D point. -- -- * @{#POINT_VEC3.GetRandomPointVec3InRadius}(): Provides a random 3D point around the current 3D point, in the given inner to outer band. -- + -- -- @field #POINT_VEC3 POINT_VEC3 = { ClassName = "POINT_VEC3", @@ -2940,7 +3127,7 @@ do -- POINT_VEC3 --- Create a new POINT_VEC3 object from Vec2 coordinates. -- @param #POINT_VEC3 self -- @param DCS#Vec2 Vec2 The Vec2 point. - -- @param DCS#Distance LandHeightAdd (Optional) Add a landheight. + -- @param DCS#Distance LandHeightAdd (optional) Add a landheight. -- @return Core.Point#POINT_VEC3 self function POINT_VEC3:NewFromVec2( Vec2, LandHeightAdd ) @@ -2950,6 +3137,7 @@ do -- POINT_VEC3 return self end + --- Create a new POINT_VEC3 object from Vec3 coordinates. -- @param #POINT_VEC3 self -- @param DCS#Vec3 Vec3 The Vec3 point. @@ -2962,6 +3150,8 @@ do -- POINT_VEC3 return self end + + --- Return the x coordinate of the POINT_VEC3. -- @param #POINT_VEC3 self -- @return #number The x coodinate. @@ -3033,7 +3223,7 @@ do -- POINT_VEC3 -- @param #number z The z coordinate value to add to the current z coodinate. -- @return #POINT_VEC3 function POINT_VEC3:AddZ( z ) - self.z = self.z + z + self.z = self.z +z return self end @@ -3084,11 +3274,13 @@ do -- POINT_VEC2 ClassName = "POINT_VEC2", } + + --- POINT_VEC2 constructor. -- @param #POINT_VEC2 self -- @param DCS#Distance x The x coordinate of the Vec3 point, pointing to the North. -- @param DCS#Distance y The y coordinate of the Vec3 point, pointing to the Right. - -- @param DCS#Distance LandHeightAdd (Optional) The default height if required to be evaluated will be the land height of the x, y coordinate. You can specify an extra height to be added to the land height. + -- @param DCS#Distance LandHeightAdd (optional) The default height if required to be evaluated will be the land height of the x, y coordinate. You can specify an extra height to be added to the land height. -- @return Core.Point#POINT_VEC2 function POINT_VEC2:New( x, y, LandHeightAdd ) @@ -3235,10 +3427,11 @@ do -- POINT_VEC2 -- @param #number Altitude The Altitude to add. If nothing (nil) is given, then the current land altitude is set. -- @return #POINT_VEC2 function POINT_VEC2:AddAlt( Altitude ) - self.y = land.getHeight( { x = self.x, y = self.z } ) + (Altitude or 0) + self.y = land.getHeight( { x = self.x, y = self.z } ) + Altitude or 0 return self end + --- Return a random POINT_VEC2 within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC2. -- @param #POINT_VEC2 self -- @param DCS#Distance OuterRadius @@ -3258,11 +3451,10 @@ do -- POINT_VEC2 function POINT_VEC2:DistanceFromPointVec2( PointVec2Reference ) self:F2( PointVec2Reference ) - local Distance = ((PointVec2Reference.x - self.x) ^ 2 + (PointVec2Reference.z - self.z) ^ 2) ^ 0.5 + local Distance = ( ( PointVec2Reference.x - self.x ) ^ 2 + ( PointVec2Reference.z - self.z ) ^2 ) ^ 0.5 self:T2( Distance ) return Distance end end - From a95c49915a89393ae3f9b8e1bc0d3bccf072047b Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 1 Feb 2022 08:02:51 +0100 Subject: [PATCH 062/200] SET - correct error in intersection --- Moose Development/Moose/Core/Set.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index ea5e9f04c..e834da16c 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -317,7 +317,7 @@ do -- SET_BASE for _, Object in pairs( union.Set ) do if self:IsIncludeObject( Object ) and SetB:IsIncludeObject( Object ) then - intersection:AddObject( intersection ) + intersection:AddObject( Object ) end end From 28eb7a678c918afef5bed4353c59181d9b440a99 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 3 Feb 2022 10:00:19 +0100 Subject: [PATCH 063/200] CTLD - Added Hercules support for crates, troops & vehicles loaded with the help of the ground crew and dropped from the plane. Added weight checks for loaded crates. --- Moose Development/Moose/Ops/CTLD.lua | 777 +++++++++++++++++++++++++-- 1 file changed, 738 insertions(+), 39 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index dcf99dae1..0519fbed1 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -22,7 +22,7 @@ -- @module Ops.CTLD -- @image OPS_CTLD.jpg --- Date: Jan 2022 +-- Date: Feb 2022 do ------------------------------------------------------ @@ -299,6 +299,7 @@ CTLD_ENGINEERING = { return -1 end end + ------------------------------------------------------ --- **CTLD_CARGO** class, extends Core.Base#BASE -- @type CTLD_CARGO @@ -389,6 +390,7 @@ CTLD_CARGO = { function CTLD_CARGO:GetMass() return self.PerCrateMass end + --- Query Name. -- @param #CTLD_CARGO self -- @return #string Name @@ -568,11 +570,12 @@ do -- * Additional events to tailor your mission. -- * ANY late activated group can serve as cargo, either as troops, crates, which have to be build on-location, or static like ammo chests. -- * Option to persist (save&load) your dropped troops, crates and vehicles. +-- * Weight checks on loaded cargo. -- -- ## 0. Prerequisites -- -- You need to load an .ogg soundfile for the pilot\'s beacons into the mission, e.g. "beacon.ogg", use a once trigger, "sound to country" for that. --- Create the late-activated troops, vehicles (no statics at this point!) that will make up your deployable forces. +-- Create the late-activated troops, vehicles, that will make up your deployable forces. -- -- ## 1. Basic Setup -- @@ -606,7 +609,7 @@ do -- -- add vehicle called "Humvee" using template "Humvee", of type VEHICLE, size 2, i.e. needs two crates to be build -- -- vehicles and FOB will be spawned as crates in a LOAD zone first. Once transported to DROP zones, they can be build into the objects -- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2) --- -- if you want to add weight to your Heli, crates can have a weight in kg **per crate**. Currently no max weight checked. Fly carefully. +-- -- if you want to add weight to your Heli, crates can have a weight in kg **per crate**. Fly carefully. -- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2,2775) -- -- if you want to limit your stock, add a number (here: 10) as parameter after weight. No parameter / nil means unlimited stock. -- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2,2775,10) @@ -614,7 +617,8 @@ do -- -- add infantry unit called "Forward Ops Base" using template "FOB", of type FOB, size 4, i.e. needs four crates to be build: -- my_ctld:AddCratesCargo("Forward Ops Base",{"FOB"},CTLD_CARGO.Enum.FOB,4) -- --- -- add crates to repair FOB or VEHICLE type units - the 2nd parameter needs to match the template you want to repair +-- -- add crates to repair FOB or VEHICLE type units - the 2nd parameter needs to match the template you want to repair, +-- -- e.g. the "Humvee" here refers back to the "Humvee" crates cargo added above (same template!) -- my_ctld:AddCratesRepair("Humvee Repair","Humvee",CTLD_CARGO.Enum.REPAIR,1) -- my_ctld.repairtime = 300 -- takes 300 seconds to repair something -- @@ -624,9 +628,9 @@ do -- -- ## 1.3 Add logistics zones -- --- Add zones for loading troops and crates and dropping, building crates +-- Add (normal, round!) zones for loading troops and crates and dropping, building crates -- --- -- Add a zone of type LOAD to our setup. Players can load troops and crates. +-- -- Add a zone of type LOAD to our setup. Players can load any troops and crates here as defined in 1.2 above. -- -- "Loadzone" is the name of the zone from the ME. Players can load, if they are inside the zone. -- -- Smoke and Flare color for this zone is blue, it is active (can be used) and has a radio beacon. -- my_ctld:AddCTLDZone("Loadzone",CTLD.CargoZoneType.LOAD,SMOKECOLOR.Blue,true,true) @@ -646,7 +650,7 @@ do -- -- "Tarawa" is the unitname (callsign) of the ship from the ME. Players can load, if they are inside the zone. -- -- The ship is 240 meters long and 20 meters wide. -- -- Note that you need to adjust the max hover height to deck height plus 5 meters or so for loading to work. --- -- When the ship is moving, forcing hoverload might not be a good idea. +-- -- When the ship is moving, avoid forcing hoverload. -- my_ctld:AddCTLDZone("Tarawa",CTLD.CargoZoneType.SHIP,SMOKECOLOR.Blue,true,true,240,20) -- -- ## 2. Options @@ -682,22 +686,21 @@ do -- Use this function to adjust what a heli type can or cannot do: -- -- -- E.g. update unit capabilities for testing. Please stay realistic in your mission design. --- -- Make a Gazelle into a heavy truck, this type can load both crates and troops and eight of each type: --- my_ctld:UnitCapabilities("SA342L", true, true, 8, 8, 12) +-- -- Make a Gazelle into a heavy truck, this type can load both crates and troops and eight of each type, up to 4000 kgs: +-- my_ctld:UnitCapabilities("SA342L", true, true, 8, 8, 12, 4000) -- --- -- Default unit type capabilities are: --- --- ["SA342Mistral"] = {type="SA342Mistral", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12}, --- ["SA342L"] = {type="SA342L", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12}, --- ["SA342M"] = {type="SA342M", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12}, --- ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12}, --- ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8, length = 15}, --- ["Mi-8MT"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15}, --- ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 15}, --- ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18}, --- ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18}, --- ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25}, --- ["UH-60L"] = {type="UH-60L", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16}, +-- -- Default unit type capabilities are: +-- ["SA342Mistral"] = {type="SA342Mistral", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12, cargoweightlimit = 400}, +-- ["SA342L"] = {type="SA342L", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12, cargoweightlimit = 400}, +-- ["SA342M"] = {type="SA342M", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12, cargoweightlimit = 400}, +-- ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12, cargoweightlimit = 400}, +-- ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8, length = 15, cargoweightlimit = 700}, +-- ["Mi-8MT"] = {type="Mi-8MT", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15, cargoweightlimit = 3000}, +-- ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 15, cargoweightlimit = 0}, +-- ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18, cargoweightlimit = 700}, +-- ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18, cargoweightlimit = 700}, +-- ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25, cargoweightlimit = 19000}, +-- ["UH-60L"] = {type="UH-60L", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- -- ### 2.1.2 Activate and deactivate zones -- @@ -977,25 +980,26 @@ CTLD.CargoZoneType = { -- @field #boolean troops Can transport troops. -- @field #number cratelimit Number of crates transportable. -- @field #number trooplimit Number of troop units transportable. +-- @field #number cargoweightlimit Max loadable kgs of cargo. CTLD.UnitTypes = { - ["SA342Mistral"] = {type="SA342Mistral", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12}, - ["SA342L"] = {type="SA342L", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12}, - ["SA342M"] = {type="SA342M", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12}, - ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12}, - ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8, length = 15}, - ["Mi-8MTV2"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15}, - ["Mi-8MT"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15}, - ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 15}, - ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18}, - ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18}, - ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25}, -- 19t cargo, 64 paratroopers. + ["SA342Mistral"] = {type="SA342Mistral", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12, cargoweightlimit = 400}, + ["SA342L"] = {type="SA342L", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12, cargoweightlimit = 400}, + ["SA342M"] = {type="SA342M", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12, cargoweightlimit = 400}, + ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12, cargoweightlimit = 400}, + ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8, length = 15, cargoweightlimit = 700}, + ["Mi-8MTV2"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15, cargoweightlimit = 3000}, + ["Mi-8MT"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15, cargoweightlimit = 3000}, + ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 15, cargoweightlimit = 0}, + ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18, cargoweightlimit = 700}, + ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18, cargoweightlimit = 700}, + ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25, cargoweightlimit = 19000}, -- 19t cargo, 64 paratroopers. --Actually it's longer, but the center coord is off-center of the model. - ["UH-60L"] = {type="UH-60L", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16}, -- 4t cargo, 20 (unsec) seats + ["UH-60L"] = {type="UH-60L", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats } --- CTLD class version. -- @field #string version -CTLD.version="1.0.4" +CTLD.version="1.0.5" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1354,6 +1358,7 @@ function CTLD:_GetUnitCapabilities(Unit) capabilities.trooplimit = 0 capabilities.type = "generic" capabilities.length = 20 + capabilities.cargoweightlimit = 0 end return capabilities end @@ -1409,6 +1414,7 @@ function CTLD:_EventHandler(EventData) -- Herc support --self:T_unit:GetTypeName()) if _unit:GetTypeName() == "Hercules" and self.enableHercules then + local unitname = event.IniUnitName or "none" self.Loaded_Cargo[unitname] = nil self:_RefreshF10Menus() end @@ -2049,16 +2055,25 @@ function CTLD:_FindCratesNearby( _group, _unit, _dist) -- cycle local index = 0 local found = {} + local loadedmass = self:_GetUnitCargoMass(_unit) + local unittype = _unit:GetTypeName() + local capabilities = self:_GetUnitCapabilities(_unit) -- #CTLD.UnitCapabilities + local maxmass = capabilities.cargoweightlimit + local maxloadable = maxmass - loadedmass + self:T(self.lid .. " Max loadable mass: " .. maxloadable) for _,_cargoobject in pairs (existingcrates) do local cargo = _cargoobject -- #CTLD_CARGO local static = cargo:GetPositionable() -- Wrapper.Static#STATIC -- crates local staticid = cargo:GetID() + local weight = cargo:GetMass() -- weight in kgs of this cargo + self:T(self.lid .. " Found cargo mass: " .. weight) if static and static:IsAlive() then local staticpos = static:GetCoordinate() local distance = self:_GetDistance(location,staticpos) - if distance <= finddist and static then + if distance <= finddist and static and weight <= maxloadable then index = index + 1 table.insert(found, staticid, cargo) + maxloadable = maxloadable - weight end end end @@ -2104,6 +2119,7 @@ function CTLD:_LoadCratesNearby(Group, Unit) if self.Loaded_Cargo[unitname] then loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo numberonboard = loaded.Cratesloaded or 0 + massonboard = self:_GetUnitCargoMass(Unit) else loaded = {} -- #CTLD.LoadedCargo loaded.Troopsloaded = 0 @@ -2113,10 +2129,11 @@ function CTLD:_LoadCratesNearby(Group, Unit) -- get nearby crates local finddist = self.CrateDistance or 35 local nearcrates,number = self:_FindCratesNearby(Group,Unit,finddist) -- #table + self:T(self.lid .. " Crates found: " .. number) if number == 0 and self.hoverautoloading then return self -- exit elseif number == 0 then - self:_SendMessage("Sorry no loadable crates nearby!", 10, false, Group) + self:_SendMessage("Sorry no loadable crates nearby or max cargo weight reached!", 10, false, Group) return self -- exit elseif numberonboard == cratelimit then self:_SendMessage("Sorry no fully loaded!", 10, false, Group) @@ -2985,6 +3002,25 @@ function CTLD:_RefreshF10Menus() return self end +--- [Internal] Function to check if a template exists in the mission. +-- @param #CTLD self +-- @param #table temptable Table of string names +-- @return #boolen outcome +function CTLD:_CheckTemplates(temptable) + self:T(self.lid .. " _CheckTemplates") + local outcome = true + if type(temptable) ~= "table" then + temptable = {temptable} + end + for _,_name in pairs(temptable) do + if not _DATABASE.Templates.Groups[_name] then + outcome = false + self:E(self.lid .. "ERROR: Template name " .. _name .. " is missing!") + end + end + return outcome +end + --- User function - Add *generic* troop type loadable as cargo. This type will load directly into the heli without crates. -- @param #CTLD self -- @param #string Name Unique name of this type of troop. E.g. "Anti-Air Small". @@ -2996,6 +3032,10 @@ function CTLD:_RefreshF10Menus() function CTLD:AddTroopsCargo(Name,Templates,Type,NoTroops,PerTroopMass,Stock) self:T(self.lid .. " AddTroopsCargo") self:T({Name,Templates,Type,NoTroops,PerTroopMass,Stock}) + if not self:_CheckTemplates(Templates) then + self:E(self.lid .. "Troops Cargo for " .. Name .. " has missing template(s)!" ) + return self + end self.CargoCounter = self.CargoCounter + 1 -- Troops are directly loadable local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,true,NoTroops,nil,nil,PerTroopMass,Stock) @@ -3013,6 +3053,10 @@ end -- @param #number Stock Number of groups in stock. Nil for unlimited. function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock) self:T(self.lid .. " AddCratesCargo") + if not self:_CheckTemplates(Templates) then + self:E(self.lid .. "Crates Cargo for " .. Name .. " has missing template(s)!" ) + return self + end self.CargoCounter = self.CargoCounter + 1 -- Crates are not directly loadable local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,false,NoCrates,nil,nil,PerCrateMass,Stock) @@ -3055,13 +3099,17 @@ end --- User function - Add *generic* repair crates loadable as cargo. This type will create crates that need to be loaded, moved, dropped and built. -- @param #CTLD self -- @param #string Name Unique name of this type of cargo. E.g. "Humvee". --- @param #string Template Template of VEHICLE or FOB cargo that this can repair. +-- @param #string Template Template of VEHICLE or FOB cargo that this can repair. MUST be the same as given in `AddCratesCargo(..)`! -- @param #CTLD_CARGO.Enum Type Type of cargo, here REPAIR. -- @param #number NoCrates Number of crates needed to build this cargo. -- @param #number PerCrateMass Mass in kg of each crate -- @param #number Stock Number of groups in stock. Nil for unlimited. function CTLD:AddCratesRepair(Name,Template,Type,NoCrates, PerCrateMass,Stock) self:T(self.lid .. " AddCratesRepair") + if not self:_CheckTemplates(Template) then + self:E(self.lid .. "Repair Cargo for " .. Name .. " has a missing template!" ) + return self + end self.CargoCounter = self.CargoCounter + 1 -- Crates are not directly loadable local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Template,Type,false,false,NoCrates,nil,nil,PerCrateMass,Stock) @@ -3558,7 +3606,8 @@ end -- @param #number Cratelimit Unit can carry number of crates. Default 0. -- @param #number Trooplimit Unit can carry number of troops. Default 0. -- @param #number Length Unit lenght (in mteres) for the load radius. Default 20. - function CTLD:UnitCapabilities(Unittype, Cancrates, Cantroops, Cratelimit, Trooplimit, Length) + -- @param #number Maxcargoweight Maxmimum weight in kgs this helo can carry. Default 0. + function CTLD:UnitCapabilities(Unittype, Cancrates, Cantroops, Cratelimit, Trooplimit, Length, Maxcargoweight) self:T(self.lid .. " UnitCapabilities") local unittype = nil local unit = nil @@ -3578,6 +3627,7 @@ end capabilities.cratelimit = Cratelimit or 0 capabilities.trooplimit = Trooplimit or 0 capabilities.length = Length or 20 + capabilities.cargoweightlimit = Maxcargoweight or 0 self.UnitTypes[unittype] = capabilities return self end @@ -4594,6 +4644,655 @@ end return self end end -- end do + +do +--- Hercules Cargo Drop Events by Anubis Yinepu +-- Moose CTLD OO refactoring by Applevangelist +-- +-- This script will only work for the Herculus mod by Anubis +-- Payloads carried by pylons 11, 12 and 13 need to be declared in the Herculus_Loadout.lua file +-- Except for Ammo pallets, this script will spawn whatever payload gets launched from pylons 11, 12 and 13 +-- Pylons 11, 12 and 13 are moveable within the Herculus cargobay area +-- Ammo pallets can only be jettisoned from these pylons with no benefit to DCS world +-- To benefit DCS world, Ammo pallets need to be off/on loaded using DCS arming and refueling window +-- Cargo_Container_Enclosed = true: Cargo enclosed in container with parachute, need to be dropped from 100m (300ft) or more, except when parked on ground +-- Cargo_Container_Enclosed = false: Open cargo with no parachute, need to be dropped from 10m (30ft) or less + +------------------------------------------------------ +--- **CTLD_HERCULES** class, extends Core.Base#BASE +-- @type CTLD_HERCULES +-- @field #string ClassName +-- @field #string lid +-- @field #string Name +-- @field #string Version +-- @extends Core.Base#BASE +CTLD_HERCULES = { + ClassName = "CTLD_HERCULES", + lid = "", + Name = "", + Version = "0.0.1", +} + +--- Define cargo types. +-- @type CTLD_HERCULES.Types +-- @field #table Type Name of cargo type, container (boolean) in container or not. +CTLD_HERCULES.Types = { + ["ATGM M1045 HMMWV TOW Air [7183lb]"] = {['name'] = "M1045 HMMWV TOW", ['container'] = true}, + ["ATGM M1045 HMMWV TOW Skid [7073lb]"] = {['name'] = "M1045 HMMWV TOW", ['container'] = false}, + ["APC M1043 HMMWV Armament Air [7023lb]"] = {['name'] = "M1043 HMMWV Armament", ['container'] = true}, + ["APC M1043 HMMWV Armament Skid [6912lb]"] = {['name'] = "M1043 HMMWV Armament", ['container'] = false}, + ["SAM Avenger M1097 Air [7200lb]"] = {['name'] = "M1097 Avenger", ['container'] = true}, + ["SAM Avenger M1097 Skid [7090lb]"] = {['name'] = "M1097 Avenger", ['container'] = false}, + ["APC Cobra Air [10912lb]"] = {['name'] = "Cobra", ['container'] = true}, + ["APC Cobra Skid [10802lb]"] = {['name'] = "Cobra", ['container'] = false}, + ["APC M113 Air [21624lb]"] = {['name'] = "M-113", ['container'] = true}, + ["APC M113 Skid [21494lb]"] = {['name'] = "M-113", ['container'] = false}, + ["Tanker M978 HEMTT [34000lb]"] = {['name'] = "M978 HEMTT Tanker", ['container'] = false}, + ["HEMTT TFFT [34400lb]"] = {['name'] = "HEMTT TFFT", ['container'] = false}, + ["SPG M1128 Stryker MGS [33036lb]"] = {['name'] = "M1128 Stryker MGS", ['container'] = false}, + ["AAA Vulcan M163 Air [21666lb]"] = {['name'] = "Vulcan", ['container'] = true}, + ["AAA Vulcan M163 Skid [21577lb]"] = {['name'] = "Vulcan", ['container'] = false}, + ["APC M1126 Stryker ICV [29542lb]"] = {['name'] = "M1126 Stryker ICV", ['container'] = false}, + ["ATGM M1134 Stryker [30337lb]"] = {['name'] = "M1134 Stryker ATGM", ['container'] = false}, + ["APC LAV-25 Air [22520lb]"] = {['name'] = "LAV-25", ['container'] = true}, + ["APC LAV-25 Skid [22514lb]"] = {['name'] = "LAV-25", ['container'] = false}, + ["M1025 HMMWV Air [6160lb]"] = {['name'] = "Hummer", ['container'] = true}, + ["M1025 HMMWV Skid [6050lb]"] = {['name'] = "Hummer", ['container'] = false}, + ["IFV M2A2 Bradley [34720lb]"] = {['name'] = "M-2 Bradley", ['container'] = false}, + ["IFV MCV-80 [34720lb]"] = {['name'] = "MCV-80", ['container'] = false}, + ["IFV BMP-1 [23232lb]"] = {['name'] = "BMP-1", ['container'] = false}, + ["IFV BMP-2 [25168lb]"] = {['name'] = "BMP-2", ['container'] = false}, + ["IFV BMP-3 [32912lb]"] = {['name'] = "BMP-3", ['container'] = false}, + ["ARV BRDM-2 Air [12320lb]"] = {['name'] = "BRDM-2", ['container'] = true}, + ["ARV BRDM-2 Skid [12210lb]"] = {['name'] = "BRDM-2", ['container'] = false}, + ["APC BTR-80 Air [23936lb]"] = {['name'] = "BTR-80", ['container'] = true}, + ["APC BTR-80 Skid [23826lb]"] = {['name'] = "BTR-80", ['container'] = false}, + ["APC BTR-82A Air [24998lb]"] = {['name'] = "BTR-82A", ['container'] = true}, + ["APC BTR-82A Skid [24888lb]"] = {['name'] = "BTR-82A", ['container'] = false}, + ["SAM ROLAND ADS [34720lb]"] = {['name'] = "Roland Radar", ['container'] = false}, + ["SAM ROLAND LN [34720b]"] = {['name'] = "Roland ADS", ['container'] = false}, + ["SAM SA-13 STRELA [21624lb]"] = {['name'] = "Strela-10M3", ['container'] = false}, + ["AAA ZSU-23-4 Shilka [32912lb]"] = {['name'] = "ZSU-23-4 Shilka", ['container'] = false}, + ["SAM SA-19 Tunguska 2S6 [34720lb]"] = {['name'] = "2S6 Tunguska", ['container'] = false}, + ["Transport UAZ-469 Air [3747lb]"] = {['name'] = "UAZ-469", ['container'] = true}, + ["Transport UAZ-469 Skid [3630lb]"] = {['name'] = "UAZ-469", ['container'] = false}, + ["AAA GEPARD [34720lb]"] = {['name'] = "Gepard", ['container'] = false}, + ["SAM CHAPARRAL Air [21624lb]"] = {['name'] = "M48 Chaparral", ['container'] = true}, + ["SAM CHAPARRAL Skid [21516lb]"] = {['name'] = "M48 Chaparral", ['container'] = false}, + ["SAM LINEBACKER [34720lb]"] = {['name'] = "M6 Linebacker", ['container'] = false}, + ["Transport URAL-375 [14815lb]"] = {['name'] = "Ural-375", ['container'] = false}, + ["Transport M818 [16000lb]"] = {['name'] = "M 818", ['container'] = false}, + ["IFV MARDER [34720lb]"] = {['name'] = "Marder", ['container'] = false}, + ["Transport Tigr Air [15900lb]"] = {['name'] = "Tigr_233036", ['container'] = true}, + ["Transport Tigr Skid [15730lb]"] = {['name'] = "Tigr_233036", ['container'] = false}, + ["IFV TPZ FUCH [33440lb]"] = {['name'] = "TPZ", ['container'] = false}, + ["IFV BMD-1 Air [18040lb]"] = {['name'] = "BMD-1", ['container'] = true}, + ["IFV BMD-1 Skid [17930lb]"] = {['name'] = "BMD-1", ['container'] = false}, + ["IFV BTR-D Air [18040lb]"] = {['name'] = "BTR_D", ['container'] = true}, + ["IFV BTR-D Skid [17930lb]"] = {['name'] = "BTR_D", ['container'] = false}, + ["EWR SBORKA Air [21624lb]"] = {['name'] = "Dog Ear radar", ['container'] = true}, + ["EWR SBORKA Skid [21624lb]"] = {['name'] = "Dog Ear radar", ['container'] = false}, + ["ART 2S9 NONA Air [19140lb]"] = {['name'] = "SAU 2-C9", ['container'] = true}, + ["ART 2S9 NONA Skid [19030lb]"] = {['name'] = "SAU 2-C9", ['container'] = false}, + ["ART GVOZDIKA [34720lb]"] = {['name'] = "SAU Gvozdika", ['container'] = false}, + ["APC MTLB Air [26400lb]"] = {['name'] = "MTLB", ['container'] = true}, + ["APC MTLB Skid [26290lb]"] = {['name'] = "MTLB", ['container'] = false}, + ["Generic Crate [20000lb]"] = {['name'] = "Hercules_Container_Parachute", ['container'] = true} --nothing generic in Moose CTLD +} + +--- Cargo Object +-- @type CTLD_HERCULES.CargoObject +-- @field #number Cargo_Drop_Direction +-- @field #table Cargo_Contents +-- @field #string Cargo_Type_name +-- @field #boolean Container_Enclosed +-- @field #boolean ParatrooperGroupSpawn +-- @field #number Cargo_Country +-- @field #boolean offload_cargo +-- @field #boolean all_cargo_survive_to_the_ground +-- @field #boolean all_cargo_gets_destroyed +-- @field #boolean destroy_cargo_dropped_without_parachute +-- @field Core.Timer#TIMER scheduleFunctionID + +--- [User] Instantiate a new object +-- @param #CTLD_HERCULES self +-- @param #string Coalition Coalition side, "red", "blue" or "neutral" +-- @param #string Alias Name of this instance +-- @param Ops.CTLD#CTLD CtldObject CTLD instance to link into +-- @return #CTLD_HERCULES self +-- @usage +-- Integrate to your CTLD instance like so, where `my_ctld` is a previously created CTLD instance: +-- +-- local herccargo = CTLD_HERCULES:New("blue", "Hercules Test", my_ctld) +-- +-- You also need: +-- * A template called "Infantry" for 10 Paratroopers (as set via herccargo.infantrytemplate). +-- * Depending on what you are loading with the help of the ground crew, there are 42 more templates for the various vehicles that are loadable. +-- There's a **quick check output in the `dcs.log`** which tells you what's there and what not. +-- E.g.: +-- ...Checking template for APC BTR-82A Air [24998lb] (BTR-82A) ... MISSING) +-- ...Checking template for ART 2S9 NONA Skid [19030lb] (SAU 2-C9) ... MISSING) +-- ...Checking template for EWR SBORKA Air [21624lb] (Dog Ear radar) ... MISSING) +-- ...Checking template for Transport Tigr Air [15900lb] (Tigr_233036) ... OK) +-- +-- Expected template names are the ones in the rounded brackets. +function CTLD_HERCULES:New(Coalition, Alias, CtldObject) + -- Inherit everything from FSM class. + local self=BASE:Inherit(self, FSM:New()) -- #CTLD_HERCULES + + --set Coalition + if Coalition and type(Coalition)=="string" then + if Coalition=="blue" then + self.coalition=coalition.side.BLUE + self.coalitiontxt = Coalition + elseif Coalition=="red" then + self.coalition=coalition.side.RED + self.coalitiontxt = Coalition + elseif Coalition=="neutral" then + self.coalition=coalition.side.NEUTRAL + self.coalitiontxt = Coalition + else + self:E("ERROR: Unknown coalition in CTLD!") + end + else + self.coalition = Coalition + self.coalitiontxt = string.lower(UTILS.GetCoalitionName(self.coalition)) + end + + -- Set alias. + if Alias then + self.alias=tostring(Alias) + else + self.alias="UNHCR" + if self.coalition then + if self.coalition==coalition.side.RED then + self.alias="Red CTLD Hercules" + elseif self.coalition==coalition.side.BLUE then + self.alias="Blue CTLD Hercules" + end + end + end + + -- Set some string id for output to DCS.log file. + self.lid=string.format("%s (%s) | ", self.alias, self.coalitiontxt) + + self.infantrytemplate = "Infantry" -- template for a group of 10 paratroopers + self.CTLD = CtldObject -- Ops.CTLD#CTLD + + self.verbose = true + + self.j = 0 + self.carrierGroups = {} + self.Cargo = {} + self.ParatrooperCount = {} + + self.ObjectTracker = {} + + -- Set some string id for output to DCS.log file. + self.lid=string.format("%s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown") + + --self:HandleEvent(EVENTS.Birth,self._HandleBirth) + self:HandleEvent(EVENTS.Shot, self._HandleShot) + + self:I(self.lid .. "Started") + + self:CheckTemplates() + + return self +end + +--- [Internal] Function to check availability of templates +-- @param #CTLD_HERCULES self +-- @return #CTLD_HERCULES self +function CTLD_HERCULES:CheckTemplates() + self:T(self.lid .. 'CheckTemplates') + -- inject Paratroopers + self.Types["Paratroopers 10"] = { + name = self.infantrytemplate, + container = false, + available = false, + } + local missing = {} + local nomissing = 0 + local found = {} + local nofound = 0 + + -- list of groundcrew loadables + for _index,_tab in pairs (self.Types) do + local outcometxt = "MISSING" + if _DATABASE.Templates.Groups[_tab.name] then + outcometxt = "OK" + self.Types[_index].available= true + found[_tab.name] = true + else + self.Types[_index].available = false + missing[_tab.name] = true + end + if self.verbose then + self:I(string.format(self.lid .. "Checking template for %s (%s) ... %s", _index,_tab.name,outcometxt)) + end + end + for _,_name in pairs(found) do + nofound = nofound + 1 + end + for _,_name in pairs(missing) do + nomissing = nomissing + 1 + end + self:I(string.format(self.lid .. "Template Check Summary: Found %d, Missing %d, Total %d",nofound,nomissing,nofound+nomissing)) + return self +end + +--- [Internal] Function to spawn a soldier group of 10 units +-- @param #CTLD_HERCULES self +-- @param Wrapper.Group#GROUP Cargo_Drop_initiator +-- @param Core.Point#POINT_VEC3 Cargo_Drop_Position +-- @param #string Cargo_Type_name +-- @param #number CargoHeading +-- @param #number Cargo_Country +-- @param #number GroupSpacing +-- @return #CTLD_HERCULES self +function CTLD_HERCULES:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Drop_Position, Cargo_Type_name, CargoHeading, Cargo_Country, GroupSpacing) + --- TODO: Rework into Moose Spawns + self:T(self.lid .. 'Soldier_SpawnGroup') + self:T(Cargo_Drop_Position) + -- create a matching #CTLD_CARGO type + local InjectTroopsType = CTLD_CARGO:New(nil,self.infantrytemplate,{self.infantrytemplate},CTLD_CARGO.Enum.TROOPS,true,true,10,nil,false,80) + -- get a #ZONE object + local position = Cargo_Drop_Position:GetVec2() + local dropzone = ZONE_RADIUS:New("Infantry " .. math.random(1,10000),position,100) + -- and go: + self.CTLD:InjectTroops(dropzone,InjectTroopsType) + return self +end + +--- [Internal] Function to spawn a group +-- @param #CTLD_HERCULES self +-- @param Wrapper.Group#GROUP Cargo_Drop_initiator +-- @param Core.Point#POINT_VEC3 Cargo_Drop_Position +-- @param #string Cargo_Type_name +-- @param #number CargoHeading +-- @param #number Cargo_Country +-- @return #CTLD_HERCULES self +function CTLD_HERCULES:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Drop_Position, Cargo_Type_name, CargoHeading, Cargo_Country) + --- TODO: Rework into Moose Spawns + self:T(self.lid .. "Cargo_SpawnGroup") + self:T(Cargo_Type_name) + if Cargo_Type_name ~= 'Container red 1' then + -- create a matching #CTLD_CARGO type + local InjectVehicleType = CTLD_CARGO:New(nil,Cargo_Type_name,{Cargo_Type_name},CTLD_CARGO.Enum.VEHICLE,true,true,1,nil,false,1000) + -- get a #ZONE object + local position = Cargo_Drop_Position:GetVec2() + local dropzone = ZONE_RADIUS:New("Vehicle " .. math.random(1,10000),position,100) + -- and go: + self.CTLD:InjectVehicles(dropzone,InjectVehicleType) + end + return self +end + +--- [Internal] Function to spawn static cargo +-- @param #CTLD_HERCULES self +-- @param Wrapper.Group#GROUP Cargo_Drop_initiator +-- @param Core.Point#POINT_VEC3 Cargo_Drop_Position +-- @param #string Cargo_Type_name +-- @param #number CargoHeading +-- @param #boolean dead +-- @param #number Cargo_Country +-- @return #CTLD_HERCULES self +function CTLD_HERCULES:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Drop_Position, Cargo_Type_name, CargoHeading, dead, Cargo_Country) + --- TODO: Rework into Moose Static Spawns + self:T(self.lid .. "Cargo_SpawnStatic") + self:T("Static " .. Cargo_Type_name .. " Dead " .. tostring(dead)) + local position = Cargo_Drop_Position:GetVec2() + local Zone = ZONE_RADIUS:New("Cargo Static " .. math.random(1,10000),position,100) + if not dead then + -- CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass, Stock) + local injectstatic = CTLD_CARGO:New(nil,"Cargo Static Group "..math.random(1,10000),"iso_container",CTLD_CARGO.Enum.STATIC,true,false,1,nil,true,4500,1) + self.CTLD:InjectStatics(Zone,injectstatic,true) + else + --local static = SPAWNSTATIC:NewFromType("iso_container","Cargos",Cargo_Country) + --static.InitDead = true + --static:SpawnFromZone(Zone,CargoHeading) + end + return self +end + +--- [Internal] Spawn cargo objects +-- @param #CTLD_HERCULES self +-- @param Wrapper.Group#GROUP Cargo_Drop_initiator +-- @param #number Cargo_Drop_Direction +-- @param Core.Point#COORDINATE Cargo_Content_position +-- @param #string Cargo_Type_name +-- @param #boolean Cargo_over_water +-- @param #boolean Container_Enclosed +-- @param #boolean ParatrooperGroupSpawn +-- @param #boolean offload_cargo +-- @param #boolean all_cargo_survive_to_the_ground +-- @param #boolean all_cargo_gets_destroyed +-- @param #boolean destroy_cargo_dropped_without_parachute +-- @param #number Cargo_Country +-- @return #CTLD_HERCULES self +function CTLD_HERCULES:Cargo_SpawnObjects(Cargo_Drop_initiator,Cargo_Drop_Direction, Cargo_Content_position, Cargo_Type_name, Cargo_over_water, Container_Enclosed, ParatrooperGroupSpawn, offload_cargo, all_cargo_survive_to_the_ground, all_cargo_gets_destroyed, destroy_cargo_dropped_without_parachute, Cargo_Country) + self:T(self.lid .. 'Cargo_SpawnObjects') + + local CargoHeading = self.CargoHeading + --local Cargo_Drop_Position = {} + + if offload_cargo == true or ParatrooperGroupSpawn == true then + if ParatrooperGroupSpawn == true then + self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 0) + self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 5) + self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 10) + else + self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 0) + end + else + if all_cargo_gets_destroyed == true or Cargo_over_water == true then + if Container_Enclosed == true then + --self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, true, Cargo_Country) + if ParatrooperGroupSpawn == false then + --self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position, "Hercules_Container_Parachute_Static", CargoHeading, true, Cargo_Country) + end + else + --self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, true, Cargo_Country) + end + else + if all_cargo_survive_to_the_ground == true then + if ParatrooperGroupSpawn == true then + self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, true, Cargo_Country) + else + self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country) + end + if Container_Enclosed == true then + if ParatrooperGroupSpawn == false then + self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position, "Hercules_Container_Parachute_Static", CargoHeading, false, Cargo_Country) + end + end + end + if destroy_cargo_dropped_without_parachute == true then + if Container_Enclosed == true then + if ParatrooperGroupSpawn == true then + self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 0) + else + self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country) + self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position, "Hercules_Container_Parachute_Static", CargoHeading, false, Cargo_Country) + end + else + self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, true, Cargo_Country) + end + end + end + end + return self +end + +--- [Internal] Function to calculate object height +-- @param #CTLD_HERCULES self +-- @param Wrapper.Group#GROUP group The group for which to calculate the height +-- @return #number height over ground +function CTLD_HERCULES:Calculate_Object_Height_AGL(group) + self:T(self.lid .. "Calculate_Object_Height_AGL") + if group.ClassName and group.ClassName == "GROUP" then + local gcoord = group:GetCoordinate() + local height = group:GetHeight() + local lheight = gcoord:GetLandHeight() + self:T(self.lid .. "Height " .. height - lheight) + return height - lheight + else + -- DCS object + --self:T({group}) + if group:isExist() then + local dcsposition = group:getPosition().p + local dcsvec2 = {x = dcsposition.x, y = dcsposition.z} -- Vec2 + local height = math.floor(group:getPosition().p.y - land.getHeight(dcsvec2)) + self.ObjectTracker[group.id_] = dcsposition -- Vec3 + self:T(self.lid .. "Height " .. height) + --self:T({group.id_,self.ObjectTracker[group.id_]}) + return height + else + return 0 + end + end +end + +--- [Internal] Function to check surface type +-- @param #CTLD_HERCULES self +-- @param Wrapper.Group#GROUP group The group for which to calculate the height +-- @return #number height over ground +function CTLD_HERCULES:Check_SurfaceType(object) + self:T(self.lid .. "Check_SurfaceType") + -- LAND,--1 SHALLOW_WATER,--2 WATER,--3 ROAD,--4 RUNWAY--5 + if object:isExist() then + return land.getSurfaceType({x = object:getPosition().p.x, y = object:getPosition().p.z}) + else + return 1 + end +end + +--- [Internal] Function to track cargo objects +-- @param #CTLD_HERCULES self +-- @param #CTLD_HERCULES.CargoObject cargo +-- @param Wrapper.Group#GROUP initiator +-- @return #number height over ground +function CTLD_HERCULES:Cargo_Track(cargo, initiator) + self:T(self.lid .. "Cargo_Track") + local Cargo_Drop_initiator = initiator + if cargo.Cargo_Contents ~= nil then + if self:Calculate_Object_Height_AGL(cargo.Cargo_Contents) < 10 then --pallet less than 5m above ground before spawning + if self:Check_SurfaceType(cargo.Cargo_Contents) == 2 or self:Check_SurfaceType(cargo.Cargo_Contents) == 3 then + cargo.Cargo_over_water = true--pallets gets destroyed in water + end + local dcsvec3 = self.ObjectTracker[cargo.Cargo_Contents.id_] -- last known position + self:T("SPAWNPOSITION: ") + self:T({dcsvec3}) + local Vec2 = { + x=dcsvec3.x, + y=dcsvec3.z, + } + local vec3 = COORDINATE:NewFromVec2(Vec2) + self.ObjectTracker[cargo.Cargo_Contents.id_] = nil + self:Cargo_SpawnObjects(Cargo_Drop_initiator,cargo.Cargo_Drop_Direction, vec3, cargo.Cargo_Type_name, cargo.Cargo_over_water, cargo.Container_Enclosed, cargo.ParatrooperGroupSpawn, cargo.offload_cargo, cargo.all_cargo_survive_to_the_ground, cargo.all_cargo_gets_destroyed, cargo.destroy_cargo_dropped_without_parachute, cargo.Cargo_Country) + if cargo.Cargo_Contents:isExist() then + cargo.Cargo_Contents:destroy()--remove pallet+parachute before hitting ground and replace with Cargo_SpawnContents + end + --timer.removeFunction(cargo.scheduleFunctionID) + cargo.scheduleFunctionID:Stop() + cargo = {} + end + end + return self +end + +--- [Internal] Function to calc north correction +-- @param #CTLD_HERCULES self +-- @param Core.Point#POINT_Vec3 point Position Vec3 +-- @return #number north correction +function CTLD_HERCULES:Calculate_Cargo_Drop_initiator_NorthCorrection(point) + self:T(self.lid .. "Calculate_Cargo_Drop_initiator_NorthCorrection") + if not point.z then --Vec2; convert to Vec3 + 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 + +--- [Internal] Function to calc initiator heading +-- @param #CTLD_HERCULES self +-- @param Wrapper.Group#GROUP Cargo_Drop_initiator +-- @return #number north corrected heading +function CTLD_HERCULES:Calculate_Cargo_Drop_initiator_Heading(Cargo_Drop_initiator) + self:T(self.lid .. "Calculate_Cargo_Drop_initiator_Heading") + local Heading = Cargo_Drop_initiator:GetHeading() + Heading = Heading + self:Calculate_Cargo_Drop_initiator_NorthCorrection(Cargo_Drop_initiator:GetVec3()) + if Heading < 0 then + Heading = Heading + (2 * math.pi)-- put heading in range of 0 to 2*pi + end + return Heading + 0.06 -- rad +end + +--- [Internal] Function to initialize dropped cargo +-- @param #CTLD_HERCULES self +-- @param Wrapper.Group#GROUP Initiator +-- @param #table Cargo_Contents Table 'weapon' from event data +-- @param #string Cargo_Type_name Name of this cargo +-- @param #boolean Container_Enclosed Is container? +-- @param #boolean SoldierGroup Is soldier group? +-- @param #boolean ParatrooperGroupSpawnInit Is paratroopers? +-- @return #CTLD_HERCULES self +function CTLD_HERCULES:Cargo_Initialize(Initiator, Cargo_Contents, Cargo_Type_name, Container_Enclosed, SoldierGroup, ParatrooperGroupSpawnInit) + self:T(self.lid .. "Cargo_Initialize") + local Cargo_Drop_initiator = Initiator:GetName() + if Cargo_Drop_initiator ~= nil then + if ParatrooperGroupSpawnInit == true then + self:T("Paratrooper Drop") + -- Paratroopers + if not self.ParatrooperCount[Cargo_Drop_initiator] then + self.ParatrooperCount[Cargo_Drop_initiator] = 1 + else + self.ParatrooperCount[Cargo_Drop_initiator] = self.ParatrooperCount[Cargo_Drop_initiator] + 1 + end + + local Paratroopers = self.ParatrooperCount[Cargo_Drop_initiator] + + self:T("Paratrooper Drop Number " .. self.ParatrooperCount[Cargo_Drop_initiator]) + + local SpawnParas = false + + if math.fmod(Paratroopers,10) == 0 then + SpawnParas = true + end + + self.j = self.j + 1 + self.Cargo[self.j] = {} + self.Cargo[self.j].Cargo_Drop_Direction = self:Calculate_Cargo_Drop_initiator_Heading(Initiator) + self.Cargo[self.j].Cargo_Contents = Cargo_Contents + self.Cargo[self.j].Cargo_Type_name = Cargo_Type_name + self.Cargo[self.j].Container_Enclosed = Container_Enclosed + self.Cargo[self.j].ParatrooperGroupSpawn = SpawnParas + self.Cargo[self.j].Cargo_Country = Initiator:GetCountry() + + if self:Calculate_Object_Height_AGL(Initiator) < 5.0 then --aircraft on ground + self.Cargo[self.j].offload_cargo = true + elseif self:Calculate_Object_Height_AGL(Initiator) < 10.0 then --aircraft less than 10m above ground + self.Cargo[self.j].all_cargo_survive_to_the_ground = true + elseif self:Calculate_Object_Height_AGL(Initiator) < 100.0 then --aircraft more than 10m but less than 100m above ground + self.Cargo[self.j].all_cargo_gets_destroyed = true + else + self.Cargo[self.j].all_cargo_gets_destroyed = false + end + + local timer = TIMER:New(self.Cargo_Track,self,self.Cargo[self.j],Initiator) + self.Cargo[self.j].scheduleFunctionID = timer + timer:Start(5,2,600) + + else + -- no paras + self.j = self.j + 1 + self.Cargo[self.j] = {} + self.Cargo[self.j].Cargo_Drop_Direction = self:Calculate_Cargo_Drop_initiator_Heading(Initiator) + self.Cargo[self.j].Cargo_Contents = Cargo_Contents + self.Cargo[self.j].Cargo_Type_name = Cargo_Type_name + self.Cargo[self.j].Container_Enclosed = Container_Enclosed + self.Cargo[self.j].ParatrooperGroupSpawn = false + self.Cargo[self.j].Cargo_Country = Initiator:GetCountry() + + if self:Calculate_Object_Height_AGL(Initiator) < 5.0 then--aircraft on ground + self.Cargo[self.j].offload_cargo = true + elseif self:Calculate_Object_Height_AGL(Initiator) < 10.0 then--aircraft less than 10m above ground + self.Cargo[self.j].all_cargo_survive_to_the_ground = true + elseif self:Calculate_Object_Height_AGL(Initiator) < 100.0 then--aircraft more than 10m but less than 100m above ground + self.Cargo[self.j].all_cargo_gets_destroyed = true + else + self.Cargo[self.j].destroy_cargo_dropped_without_parachute = true --aircraft more than 100m above ground + end + + local timer = TIMER:New(self.Cargo_Track,self,self.Cargo[self.j],Initiator) + self.Cargo[self.j].scheduleFunctionID = timer + timer:Start(5,2,600) + end + end + return self +end + +--- [Internal] Function to change cargotype per group (Wrench) +-- @param #CTLD_HERCULES self +-- @param #number key Carrier key id +-- @param #string cargoType Type of cargo +-- @param #number cargoNum Number of cargo objects +-- @return #CTLD_HERCULES self +function CTLD_HERCULES:SetType(key,cargoType,cargoNum) + self:T(self.lid .. "SetType") + self.carrierGroups[key]['cargoType'] = cargoType + self.carrierGroups[key]['cargoNum'] = cargoNum + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +-- EventHandlers +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + +--- [Internal] Function to capture SHOT event +-- @param #CTLD_HERCULES self +-- @param Core.Event#EVENTDATA Cargo_Drop_Event The event data +-- @return #CTLD_HERCULES self +function CTLD_HERCULES:_HandleShot(Cargo_Drop_Event) + self:T(self.lid .. "Shot Event ID:" .. Cargo_Drop_Event.id) + if Cargo_Drop_Event.id == EVENTS.Shot then + + local GT_Name = "" + local SoldierGroup = false + local ParatrooperGroupSpawnInit = false + + local GT_DisplayName = Weapon.getDesc(Cargo_Drop_Event.weapon).typeName:sub(15, -1)--Remove "weapons.bombs." from string + self:T(string.format("%sCargo_Drop_Event: %s", self.lid, Weapon.getDesc(Cargo_Drop_Event.weapon).typeName)) + + if (GT_DisplayName == "Squad 30 x Soldier [7950lb]") then + self:Cargo_Initialize(Cargo_Drop_Event.IniGroup, Cargo_Drop_Event.weapon, "Soldier M4 GRG", false, true, true) + end + + if self.Types[GT_DisplayName] then + local GT_Name = self.Types[GT_DisplayName]['name'] + local Cargo_Container_Enclosed = self.Types[GT_DisplayName]['container'] + self:Cargo_Initialize(Cargo_Drop_Event.IniGroup, Cargo_Drop_Event.weapon, GT_Name, Cargo_Container_Enclosed) + end + end + return self +end + +--- [Internal] Function to capture BIRTH event +-- @param #CTLD_HERCULES self +-- @param Core.Event#EVENTDATA event The event data +-- @return #CTLD_HERCULES self +function CTLD_HERCULES:_HandleBirth(event) + -- not sure what this is needed for? I think this for setting generic crates "content" setting. + self:T(self.lid .. "Birth Event ID:" .. event.id) + --[[ + if event.id == EVENTS.Birth then + local desc = event.initiator:getDesc() + if desc["displayName"] == "Hercules" then + local grpTab = {} + grpTab['object'] = event.IniGroup + grpTab['name'] = event.IniGroupName + grpTab['cargoType'] = 'Container red 1' + grpTab['cargoNum'] = 1 + grpTab['key'] = #self.carrierGroups + 1 + + table.insert(self.carrierGroups,grpTab) + + local hercCargoMenu = MENU_GROUP:New(event.IniGroup,"CargoTypes",nil) + local mlrs = MENU_GROUP_COMMAND:New(event.IniGroup,"MLRS",hercCargoMenu,self.SetType,self,grpTab['key'],'MLRS',1) + local mlrs = MENU_GROUP_COMMAND:New(event.IniGroup,"Mortar",hercCargoMenu,self.SetType,self,grpTab['key'],'2B11 mortar',8) + local mlrs = MENU_GROUP_COMMAND:New(event.IniGroup,"M-109",hercCargoMenu,self.SetType,self,grpTab['key'],'M-109',1) + local mlrs = MENU_GROUP_COMMAND:New(event.IniGroup,"FOB Crate",hercCargoMenu,self.SetType,self,grpTab['key'],'Container red 1',1) + end + end + --]] + return self +end + +end + ------------------------------------------------------------------- -- End Ops.CTLD.lua ------------------------------------------------------------------- From 63cbc0c55bcd385b0fcf60a5119a19ac418a9126 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 3 Feb 2022 10:01:48 +0100 Subject: [PATCH 064/200] RANGE - added option to save target sheet --- Moose Development/Moose/Functional/Range.lua | 2718 ++++++++++-------- 1 file changed, 1462 insertions(+), 1256 deletions(-) diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index 845f062bf..9da579d6d 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -19,17 +19,17 @@ -- * Bomb, rocket and missile impact points can be marked by smoke. -- * Direct hits on targets can trigger flares. -- * Smoke and flare colors can be adjusted for each player via radio menu. --- * Range information and weather at the range can be obtained via radio menu. +-- * Range information and weather report at the range can be reported via radio menu. -- * Persistence: Bombing range results can be saved to disk and loaded the next time the mission is started. -- * Range control voice overs (>40) for hit assessment. -- -- === -- -- ## Youtube Videos: --- + -- -- * [MOOSE YouTube Channel](https://www.youtube.com/channel/UCjrA9j5LQoWsG4SpS8i79Qg) -- * [MOOSE - On the Range - Demonstration Video](https://www.youtube.com/watch?v=kIXcxNB9_3M) --- +-- -- === -- -- ## Missions: @@ -50,10 +50,11 @@ -- @module Functional.Range -- @image Range.JPG +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- RANGE class -- @type RANGE -- @field #string ClassName Name of the Class. --- @field #boolean Debug If true, debug info is sent as messages on the screen. +-- @field #boolean Debug If true, debug info is send as messages on the screen. -- @field #boolean verbose Verbosity level. Higher means more output to DCS log file. -- @field #string id String id of range for output in DCS log. -- @field #string rangename Name of the range. @@ -76,13 +77,13 @@ -- @field #number Tmsg Time [sec] messages to players are displayed. Default 30 sec. -- @field #string examinergroupname Name of the examiner group which should get all messages. -- @field #boolean examinerexclusive If true, only the examiner gets messages. If false, clients and examiner get messages. --- @field #number strafemaxalt Maximum altitude in meters AGL for registering for a strafe run. Default is 914 m = 3000 ft. +-- @field #number strafemaxalt Maximum altitude above ground for registering for a strafe run. Default is 914 m = 3000 ft. -- @field #number ndisplayresult Number of (player) results that a displayed. Default is 10. -- @field Utilities.Utils#SMOKECOLOR BombSmokeColor Color id used for smoking bomb targets. -- @field Utilities.Utils#SMOKECOLOR StrafeSmokeColor Color id used to smoke strafe targets. -- @field Utilities.Utils#SMOKECOLOR StrafePitSmokeColor Color id used to smoke strafe pit approach boxes. --- @field #number illuminationminalt Minimum altitude in meters AGL at which illumination bombs are fired. Default is 500 m. --- @field #number illuminationmaxalt Maximum altitude in meters AGL at which illumination bombs are fired. Default is 1000 m. +-- @field #number illuminationminalt Minimum altitude AGL in meters at which illumination bombs are fired. Default is 500 m. +-- @field #number illuminationmaxalt Maximum altitude AGL in meters at which illumination bombs are fired. Default is 1000 m. -- @field #number scorebombdistance Distance from closest target up to which bomb hits are counted. Default 1000 m. -- @field #number TdelaySmoke Time delay in seconds between impact of bomb and starting the smoke. Default 3 seconds. -- @field #boolean eventmoose If true, events are handled by MOOSE. If false, events are handled directly by DCS eventhandler. Default true. @@ -91,17 +92,20 @@ -- @field #boolean trackmissiles If true (default), all missile types are tracked and impact point to closest bombing target is evaluated. -- @field #boolean defaultsmokebomb If true, initialize player settings to smoke bomb. -- @field #boolean autosave If true, automatically save results every X seconds. --- @field #number instructorfreq Frequency on which the range control transmits. +-- @field #number instructorfreq Frequency on which the range control transmitts. -- @field Sound.RadioQueue#RADIOQUEUE instructor Instructor radio queue. --- @field #number rangecontrolfreq Frequency on which the range control transmits. +-- @field #number rangecontrolfreq Frequency on which the range control transmitts. -- @field Sound.RadioQueue#RADIOQUEUE rangecontrol Range control radio queue. -- @field #string rangecontrolrelayname Name of relay unit. -- @field #string instructorrelayname Name of relay unit. -- @field #string soundpath Path inside miz file where the sound files are located. Default is "Range Soundfiles/". +-- @field #boolean targetsheet If true, players can save their target sheets. Rangeboss will not work if targetsheets do not save. +-- @field #string targetpath Path where to save the target sheets. +-- @field #string targetprefix File prefix for target sheet files. -- @extends Core.Fsm#FSM --- *Don't only practice your art, but force your way into its secrets; art deserves that, for it and knowledge can raise man to the Divine.* - Ludwig van Beethoven --- +-- -- === -- -- ![Banner Image](..\Presentations\RANGE\RANGE_Main.png) @@ -121,21 +125,21 @@ -- -- Due to a DCS bug, it is not possible to directly monitor when a player enters a plane. So in a mission with client slots, it is vital that -- a player first enters as spectator or hits ESC twice and **after that** jumps into the slot of his aircraft! --- If that is not done, the script is not started correctly. This can be checked by looking at the radio menus. If the mission was entered correctly, +-- If that is not done, the script is not started correctly. This can be checked by looking at the radio menues. If the mission was entered correctly, -- there should be an "On the Range" menu items in the "F10. Other..." menu. -- -- # Strafe Pits --- +-- -- Each strafe pit can consist of multiple targets. Often one finds two or three strafe targets next to each other. -- -- A strafe pit can be added to the range by the @{#RANGE.AddStrafePit}(*targetnames, boxlength, boxwidth, heading, inverseheading, goodpass, foulline*) function. -- --- * The first parameter *targetnames* defines the target or targets. This can be a single item or a Table with the name(s) of @{Wrapper.Unit} or @{Static} objects defined in the mission editor. +-- * The first parameter *targetnames* defines the target or targets. This has to be given as a lua table which contains the names of @{Wrapper.Unit} or @{Static} objects defined in the mission editor. -- * In order to perform a valid pass on the strafe pit, the pilot has to begin his run from the correct direction. Therefore, an "approach box" is defined in front --- of the strafe targets. The parameters *boxlength* and *boxwidth* define the size of the box in meters, while the *heading* parameter defines the heading of the box FROM the target. --- For example, if heading 120 is set, the approach box will start FROM the target and extend outwards on heading 120. A strafe run approach must then be flown apx. heading 300 TOWARDS the target. --- If the parameter *heading* is passed as **nil**, the heading is automatically taken from the heading set in the ME for the first target unit. --- * The parameter *inverseheading* turns the heading around by 180 degrees. This is useful when the default heading of strafe target units point in the wrong/opposite direction. +-- of the strafe targets. The parameters *boxlength* and *boxwidth* define the size of the box while the parameter *heading* defines its direction. +-- If the parameter *heading* is passed as **nil**, the heading is automatically taken from the heading of the first target unit as defined in the ME. +-- The parameter *inverseheading* turns the heading around by 180 degrees. This is sometimes useful, since the default heading of strafe target units point in the +-- wrong/opposite direction. -- * The parameter *goodpass* defines the number of hits a pilot has to achieve during a run to be judged as a "good" pass. -- * The last parameter *foulline* sets the distance from the pit targets to the foul line. Hit from closer than this line are not counted! -- @@ -146,26 +150,27 @@ -- strafing pits of the range and can be adjusted by the @{#RANGE.SetMaxStrafeAlt}(maxalt) function. -- -- # Bombing targets --- +-- -- One ore multiple bombing targets can be added to the range by the @{#RANGE.AddBombingTargets}(targetnames, goodhitrange, randommove) function. -- --- * The first parameter *targetnames* defines the target or targets. This can be a single item or a Table with the name(s) of @{Wrapper.Unit} or @{Static} objects defined in the mission editor. --- * The (optional) parameter *goodhitrange* specifies the radius in metres around the target within which a bomb/rocket hit is considered to be "good". --- * If final (optional) parameter *randommove* can be enabled to create moving targets. If this parameter is set to true, the units of this bombing target will randomly move within the range zone. +-- * The first parameter *targetnames* has to be a lua table, which contains the names of @{Wrapper.Unit} and/or @{Static} objects defined in the mission editor. +-- Note that the @{Range} logic **automatically** determines, if a name belongs to a @{Wrapper.Unit} or @{Static} object now. +-- * The (optional) parameter *goodhitrange* specifies the radius around the target. If a bomb or rocket falls at a distance smaller than this number, the hit is considered to be "good". +-- * If final (optional) parameter "*randommove*" can be enabled to create moving targets. If this parameter is set to true, the units of this bombing target will randomly move within the range zone. -- Note that there might be quirks since DCS units can get stuck in buildings etc. So it might be safer to manually define a route for the units in the mission editor if moving targets are desired. -- -- ## Adding Groups --- +-- -- Another possibility to add bombing targets is the @{#RANGE.AddBombingTargetGroup}(*group, goodhitrange, randommove*) function. Here the parameter *group* is a MOOSE @{Wrapper.Group} object -- and **all** units in this group are defined as bombing targets. --- +-- -- ## Specifying Coordinates --- +-- -- It is also possible to specify coordinates rather than unit or static objects as bombing target locations. This has the advantage, that even when the unit/static object is dead, the specified -- coordinate will still be a valid impact point. This can be done via the @{#RANGE.AddBombingTargetCoordinate}(*coord*, *name*, *goodhitrange*) function. -- -- # Fine Tuning --- +-- -- Many range parameters have good default values. However, the mission designer can change these settings easily with the supplied user functions: -- -- * @{#RANGE.SetMaxStrafeAlt}() sets the max altitude for valid strafing runs. @@ -181,60 +186,60 @@ -- * @{#RANGE.TrackMissilesON}() or @{#RANGE.TrackMissilesOFF}() can be used to enable/disable tracking and evaluating of all missile types a player fires. -- -- # Radio Menu --- +-- -- Each range gets a radio menu with various submenus where each player can adjust his individual settings or request information about the range or his scores. -- -- The main range menu can be found at "F10. Other..." --> "F*X*. On the Range..." --> "F1. ...". -- --- The range menu contains the following submenus: --- +-- The range menu contains the following submenues: +-- -- ![Banner Image](..\Presentations\RANGE\Menu_Main.png) -- -- * "F1. Statistics...": Range results of all players and personal stats. -- * "F2. Mark Targets": Mark range targets by smoke or flares. -- * "F3. My Settings" Personal settings. -- * "F4. Range Info": Information about the range, such as bearing and range. --- +-- -- ## F1 Statistics --- +-- -- ![Banner Image](..\Presentations\RANGE\Menu_Stats.png) --- +-- -- ## F2 Mark Targets --- +-- -- ![Banner Image](..\Presentations\RANGE\Menu_Stats.png) --- +-- -- ## F3 My Settings --- +-- -- ![Banner Image](..\Presentations\RANGE\Menu_MySettings.png) --- +-- -- ## F4 Range Info --- +-- -- ![Banner Image](..\Presentations\RANGE\Menu_RangeInfo.png) --- +-- -- # Voice Overs --- +-- -- Voice over sound files can be downloaded from the Moose Discord. Check the pinned messages in the *#func-range* channel. --- +-- -- Instructor radio will inform players when they enter or exit the range zone and provide the radio frequency of the range control for hit assessment. -- This can be enabled via the @{#RANGE.SetInstructorRadio}(*frequency*) functions, where *frequency* is the AM frequency in MHz. --- +-- -- The range control can be enabled via the @{#RANGE.SetRangeControl}(*frequency*) functions, where *frequency* is the AM frequency in MHz. --- +-- -- By default, the sound files are placed in the "Range Soundfiles/" folder inside the mission (.miz) file. Another folder can be specified via the @{#RANGE.SetSoundfilesPath}(*path*) function. --- +-- -- # Persistence --- +-- -- To automatically save bombing results to disk, use the @{#RANGE.SetAutosave}() function. Bombing results will be saved as csv file in your "Saved Games\DCS.openbeta\Logs" directory. -- Each range has a separate file, which is named "RANGE-<*RangeName*>_BombingResults.csv". --- +-- -- The next time you start the mission, these results are also automatically loaded. --- +-- -- Strafing results are currently **not** saved. -- -- # Examples -- -- ## Goldwater Range --- +-- -- This example shows hot to set up the [Barry M. Goldwater range](https://en.wikipedia.org/wiki/Barry_M._Goldwater_Air_Force_Range). -- It consists of two strafe pits each has two targets plus three bombing targets. -- @@ -253,18 +258,19 @@ -- -- Note that this could also be done manually by simply measuring the distance between the target and the foul line in the ME. -- GoldwaterRange:GetFoullineDistance("GWR Strafe Pit Left 1", "GWR Foul Line Left") -- --- -- Add strafe pits. Each pit (left and right) consists of two targets. Where "nil" is used as input, the default value is used. --- GoldwaterRange:AddStrafePit(strafepit_left, 3000, 300, nil, true, 30, 500) --- GoldwaterRange:AddStrafePit(strafepit_right, nil, nil, nil, true, nil, 500) +-- -- Add strafe pits. Each pit (left and right) consists of two targets. +-- GoldwaterRange:AddStrafePit(strafepit_left, 3000, 300, nil, true, 20, fouldist) +-- GoldwaterRange:AddStrafePit(strafepit_right, nil, nil, nil, true, nil, fouldist) -- -- -- Add bombing targets. A good hit is if the bomb falls less then 50 m from the target. -- GoldwaterRange:AddBombingTargets(bombtargets, 50) -- --- -- Start Range. +-- -- Start range. -- GoldwaterRange:Start() -- -- The [476th - Air Weapons Range Objects mod](http://www.476vfightergroup.com/downloads.php?do=file&id=287) is (implicitly) used in this example. -- +-- -- # Debugging -- -- In case you have problems, it is always a good idea to have a look at your DCS log file. You find it in your "Saved Games" folder, so for example in @@ -284,11 +290,13 @@ -- Note that it can happen that the RANGE radio menu is not shown. Check that the range object is defined as a **global** variable rather than a local one. -- The could avoid the lua garbage collection to accidentally/falsely deallocate the RANGE objects. -- +-- +-- -- @field #RANGE -RANGE = { +RANGE={ ClassName = "RANGE", Debug = false, - verbose = 0, + verbose = 0, id = nil, rangename = nil, location = nil, @@ -330,22 +338,26 @@ RANGE = { rangecontrolfreq = nil, rangecontrol = nil, soundpath = "Range Soundfiles/", + targetsheet = nil, + targetpath = nil, + targetprefix = nil, } --- Default range parameters. -- @list Defaults -RANGE.Defaults = { - goodhitrange = 25, -- meters - strafemaxalt = 914, -- meters AGL - dtBombtrack = 0.005, -- seconds - Tmsg = 30, -- seconds - ndisplayresult = 10, - rangeradius = 5000, -- meters - TdelaySmoke = 3.0, -- seconds - boxlength = 3000, -- meters - boxwidth = 300, -- meters - goodpass = 20, -- targethits per pass - foulline = 610, -- meters +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, } --- Target type, i.e. unit, static, or coordinate. @@ -353,12 +365,19 @@ RANGE.Defaults = { -- @field #string UNIT Target is a unit. -- @field #string STATIC Target is a static. -- @field #string COORD Target is a coordinate. -RANGE.TargetType = { - UNIT = "Unit", - STATIC = "Static", - COORD = "Coordinate", +RANGE.TargetType={ + UNIT="Unit", + STATIC="Static", + COORD="Coordinate", } +--- Default range variables for RangeBoss/Hypeman tie in. +hypemanStrafeRollIn = "nil" +StrafeAircraftType = "strafeAircraftTypeNotSet" +Straferesult={} +clientRollingIn = false +clientStrafed = false +invalidStrafe = false --- Player settings. -- @type RANGE.PlayerData -- @field #boolean smokebombimpact Smoke bomb impact points. @@ -457,81 +476,81 @@ RANGE.TargetType = { -- @field #RANGE.Soundfile IREnterRange -- @field #RANGE.Soundfile IRExitRange 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 }, + 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}, } --- Global list of all defined range names. -- @field #table Names -RANGE.Names = {} +RANGE.Names={} --- Main radio menu on group level. -- @field #table MenuF10 Root menu table on group level. -RANGE.MenuF10 = {} +RANGE.MenuF10={} --- Main radio menu on mission level. -- @field #table MenuF10Root Root menu on mission level. -RANGE.MenuF10Root = nil +RANGE.MenuF10Root=nil --- Range script version. -- @field #string version -RANGE.version = "2.3.0" +RANGE.version="2.3.0" --- TODO list: --- TODO: Verbosity level for messages. --- TODO: Add option for default settings such as smoke off. --- TODO: Add custom weapons, which can be specified by the user. --- TODO: Check if units are still alive. --- DONE: Add statics for strafe pits. --- DONE: Add missiles. --- DONE: Convert env.info() to self:T() --- DONE: Add user functions. --- DONE: Rename private functions, i.e. start with _functionname. --- DONE: number of displayed results variable. --- DONE: Add tire option for strafe pits. ==> No really feasible since tires are very small and cannot be seen. --- DONE: Check that menu texts are short enough to be correctly displayed in VR. +--TODO list: +--TODO: Verbosity level for messages. +--TODO: Add option for default settings such as smoke off. +--TODO: Add custom weapons, which can be specified by the user. +--TODO: Check if units are still alive. +--DONE: Add statics for strafe pits. +--DONE: Add missiles. +--DONE: Convert env.info() to self:T() +--DONE: Add user functions. +--DONE: Rename private functions, i.e. start with _functionname. +--DONE: number of displayed results variable. +--DONE: Add tire option for strafe pits. ==> No really feasible since tires are very small and cannot be seen. +--DONE: Check that menu texts are short enough to be correctly displayed in VR. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -539,28 +558,28 @@ RANGE.version = "2.3.0" -- @param #RANGE self -- @param #string rangename Name of the range. Has to be unique. Will we used to create F10 menu items etc. -- @return #RANGE RANGE object. -function RANGE:New( rangename ) - BASE:F( { rangename = rangename } ) +function RANGE:New(rangename) + BASE:F({rangename=rangename}) -- Inherit BASE. - local self = BASE:Inherit( self, FSM:New() ) -- #RANGE + local self=BASE:Inherit(self, FSM:New()) -- #RANGE -- Get range name. - -- TODO: make sure that the range name is not given twice. This would lead to problems in the F10 radio menu. - self.rangename = rangename or "Practice Range" + --TODO: make sure that the range name is not given twice. This would lead to problems in the F10 radio menu. + self.rangename=rangename or "Practice Range" -- Log id. - self.id = string.format( "RANGE %s | ", self.rangename ) + self.id=string.format("RANGE %s | ", self.rangename) -- Debug info. - local text = string.format( "Script version %s - creating new RANGE object %s.", RANGE.version, self.rangename ) - self:I( self.id .. text ) + local text=string.format("Script version %s - creating new RANGE object %s.", RANGE.version, self.rangename) + self:I(self.id..text) -- Defaults self:SetDefaultPlayerSmokeBomb() -- Start State. - self:SetStartState( "Stopped" ) + self:SetStartState("Stopped") --- -- Add FSM transitions. @@ -675,136 +694,136 @@ end function RANGE:onafterStart() -- Location/coordinate of range. - local _location = nil + local _location=nil -- Count bomb targets. - local _count = 0 - for _, _target in pairs( self.bombingTargets ) do - _count = _count + 1 + local _count=0 + for _,_target in pairs(self.bombingTargets) do + _count=_count+1 -- Get range location. - if _location == nil then - _location = self:_GetBombTargetCoordinate( _target ) + if _location==nil then + _location=self:_GetBombTargetCoordinate(_target) end end - self.nbombtargets = _count + self.nbombtargets=_count -- Count strafing targets. - _count = 0 - for _, _target in pairs( self.strafeTargets ) do - _count = _count + 1 + _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() + for _,_unit in pairs(_target.targets) do + if _location==nil then + _location=_unit:GetCoordinate() end end end - self.nstrafetargets = _count + self.nstrafetargets=_count -- Location of the range. We simply take the first unit/target we find if it was not explicitly specified by the user. - if self.location == nil then - self.location = _location + 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 ) + 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 -- Define a MOOSE zone of the range. - if self.rangezone == nil then - self.rangezone = ZONE_RADIUS:New( self.rangename, { x = self.location.x, y = self.location.z }, self.rangeradius ) + if self.rangezone==nil then + self.rangezone=ZONE_RADIUS:New(self.rangename, {x=self.location.x, y=self.location.z}, self.rangeradius) end -- Starting range. - 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 ) + 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) -- Event handling. if self.eventmoose then -- Events are handled my MOOSE. - self:T( self.id .. "Events are handled by MOOSE." ) - self:HandleEvent( EVENTS.Birth ) - self:HandleEvent( EVENTS.Hit ) - self:HandleEvent( EVENTS.Shot ) + self:T(self.id.."Events are handled by MOOSE.") + self:HandleEvent(EVENTS.Birth) + self:HandleEvent(EVENTS.Hit) + self:HandleEvent(EVENTS.Shot) else -- Events are handled directly by DCS. - self:T( self.id .. "Events are handled directly by DCS." ) - world.addEventHandler( self ) + self:T(self.id.."Events are handled directly by DCS.") + world.addEventHandler(self) end -- Make bomb target move randomly within the range zone. - for _, _target in pairs( self.bombingTargets ) do + for _,_target in pairs(self.bombingTargets) do -- Check if it is a static object. - -- local _static=self:_CheckStatic(_target.target:GetName()) - local _static = _target.type == RANGE.TargetType.STATIC + --local _static=self:_CheckStatic(_target.target:GetName()) + local _static=_target.type==RANGE.TargetType.STATIC - if _target.move and _static == false and _target.speed > 1 then - local unit = _target.target -- Wrapper.Unit#UNIT - _target.target:PatrolZones( { self.rangezone }, _target.speed * 0.75, "Off road" ) + if _target.move and _static==false and _target.speed>1 then + local unit=_target.target --Wrapper.Unit#UNIT + _target.target:PatrolZones({self.rangezone}, _target.speed*0.75, "Off road") end end - + -- Init range control. if self.rangecontrolfreq then - + -- Radio queue. - self.rangecontrol = RADIOQUEUE:New( self.rangecontrolfreq, nil, self.rangename ) - self.rangecontrol.schedonce = true - + self.rangecontrol=RADIOQUEUE:New(self.rangecontrolfreq, nil, self.rangename) + self.rangecontrol.schedonce=true + -- Init numbers. - self.rangecontrol:SetDigit( 0, RANGE.Sound.RC0.filename, RANGE.Sound.RC0.duration, self.soundpath ) - self.rangecontrol:SetDigit( 1, RANGE.Sound.RC1.filename, RANGE.Sound.RC1.duration, self.soundpath ) - self.rangecontrol:SetDigit( 2, RANGE.Sound.RC2.filename, RANGE.Sound.RC2.duration, self.soundpath ) - self.rangecontrol:SetDigit( 3, RANGE.Sound.RC3.filename, RANGE.Sound.RC3.duration, self.soundpath ) - self.rangecontrol:SetDigit( 4, RANGE.Sound.RC4.filename, RANGE.Sound.RC4.duration, self.soundpath ) - self.rangecontrol:SetDigit( 5, RANGE.Sound.RC5.filename, RANGE.Sound.RC5.duration, self.soundpath ) - self.rangecontrol:SetDigit( 6, RANGE.Sound.RC6.filename, RANGE.Sound.RC6.duration, self.soundpath ) - self.rangecontrol:SetDigit( 7, RANGE.Sound.RC7.filename, RANGE.Sound.RC7.duration, self.soundpath ) - self.rangecontrol:SetDigit( 8, RANGE.Sound.RC8.filename, RANGE.Sound.RC8.duration, self.soundpath ) - self.rangecontrol:SetDigit( 9, RANGE.Sound.RC9.filename, RANGE.Sound.RC9.duration, self.soundpath ) - + self.rangecontrol:SetDigit(0, 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) + -- Set location where the messages are transmitted from. - self.rangecontrol:SetSenderCoordinate( self.location ) - self.rangecontrol:SetSenderUnitName( self.rangecontrolrelayname ) - + self.rangecontrol:SetSenderCoordinate(self.location) + self.rangecontrol:SetSenderUnitName(self.rangecontrolrelayname) + -- Start range control radio queue. - self.rangecontrol:Start( 1, 0.1 ) + self.rangecontrol:Start(1, 0.1) -- Init range control. if self.instructorfreq then - + -- Radio queue. - self.instructor = RADIOQUEUE:New( self.instructorfreq, nil, self.rangename ) - self.instructor.schedonce = true - + self.instructor=RADIOQUEUE:New(self.instructorfreq, nil, self.rangename) + self.instructor.schedonce=true + -- Init numbers. - self.instructor:SetDigit( 0, RANGE.Sound.IR0.filename, RANGE.Sound.IR0.duration, self.soundpath ) - self.instructor:SetDigit( 1, RANGE.Sound.IR1.filename, RANGE.Sound.IR1.duration, self.soundpath ) - self.instructor:SetDigit( 2, RANGE.Sound.IR2.filename, RANGE.Sound.IR2.duration, self.soundpath ) - self.instructor:SetDigit( 3, RANGE.Sound.IR3.filename, RANGE.Sound.IR3.duration, self.soundpath ) - self.instructor:SetDigit( 4, RANGE.Sound.IR4.filename, RANGE.Sound.IR4.duration, self.soundpath ) - self.instructor:SetDigit( 5, RANGE.Sound.IR5.filename, RANGE.Sound.IR5.duration, self.soundpath ) - self.instructor:SetDigit( 6, RANGE.Sound.IR6.filename, RANGE.Sound.IR6.duration, self.soundpath ) - self.instructor:SetDigit( 7, RANGE.Sound.IR7.filename, RANGE.Sound.IR7.duration, self.soundpath ) - self.instructor:SetDigit( 8, RANGE.Sound.IR8.filename, RANGE.Sound.IR8.duration, self.soundpath ) - self.instructor:SetDigit( 9, RANGE.Sound.IR9.filename, RANGE.Sound.IR9.duration, self.soundpath ) - + self.instructor:SetDigit(0, 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) + -- Set location where the messages are transmitted from. - self.instructor:SetSenderCoordinate( self.location ) - self.instructor:SetSenderUnitName( self.instructorrelayname ) - + self.instructor:SetSenderCoordinate(self.location) + self.instructor:SetSenderUnitName(self.instructorrelayname) + -- Start instructor radio queue. - self.instructor:Start( 1, 0.1 ) - + self.instructor:Start(1, 0.1) + end - + end - + -- Load prev results. if self.autosave then self:Load() @@ -816,10 +835,10 @@ function RANGE:onafterStart() self:_SmokeBombTargets() self:_SmokeStrafeTargets() self:_SmokeStrafeTargetBoxes() - self.rangezone:SmokeZone( SMOKECOLOR.White ) + self.rangezone:SmokeZone(SMOKECOLOR.White) end - self:__Status( -60 ) + self:__Status(-60) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -828,10 +847,10 @@ end --- Set maximal strafing altitude. Player entering a strafe pit above that altitude are not registered for a valid pass. -- @param #RANGE self --- @param #number maxalt Maximum altitude in meters AGL. Default is 914 m = 3000 ft. +-- @param #number maxalt Maximum altitude AGL in meters. Default is 914 m= 3000 ft. -- @return #RANGE self -function RANGE:SetMaxStrafeAlt( maxalt ) - self.strafemaxalt = maxalt or RANGE.Defaults.strafemaxalt +function RANGE:SetMaxStrafeAlt(maxalt) + self.strafemaxalt=maxalt or RANGE.Defaults.strafemaxalt return self end @@ -839,8 +858,8 @@ end -- @param #RANGE self -- @param #number dt Time interval in seconds. Default is 0.005 s. -- @return #RANGE self -function RANGE:SetBombtrackTimestep( dt ) - self.dtBombtrack = dt or RANGE.Defaults.dtBombtrack +function RANGE:SetBombtrackTimestep(dt) + self.dtBombtrack=dt or RANGE.Defaults.dtBombtrack return self end @@ -848,8 +867,8 @@ end -- @param #RANGE self -- @param #number time Time in seconds. Default is 30 s. -- @return #RANGE self -function RANGE:SetMessageTimeDuration( time ) - self.Tmsg = time or RANGE.Defaults.Tmsg +function RANGE:SetMessageTimeDuration(time) + self.Tmsg=time or RANGE.Defaults.Tmsg return self end @@ -857,7 +876,7 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:SetAutosaveOn() - self.autosave = true + self.autosave=true return self end @@ -865,7 +884,23 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:SetAutosaveOff() - self.autosave = false + self.autosave=false + return self +end + +--- Enable saving of player's target sheets and specify an optional directory path. +-- @param #RANGE self +-- @param #string path (Optional) Path where to save the target sheets. +-- @param #string prefix (Optional) Prefix for target sheet files. File name will be saved as *prefix_aircrafttype-0001.csv*, *prefix_aircrafttype-0002.csv*, etc. +-- @return #RANGE self +function RANGE:SetTargetSheet(path, prefix) + if io then + self.targetsheet=true + self.targetpath=path + self.targetprefix=prefix + else + self:E(self.lid.."ERROR: io is not desanitized. Cannot save target sheet.") + end return self end @@ -874,9 +909,9 @@ end -- @param #string examinergroupname Name of the group of the examiner. -- @param #boolean exclusively If true, messages are send exclusively to the examiner, i.e. not to the clients. -- @return #RANGE self -function RANGE:SetMessageToExaminer( examinergroupname, exclusively ) - self.examinergroupname = examinergroupname - self.examinerexclusive = exclusively +function RANGE:SetMessageToExaminer(examinergroupname, exclusively) + self.examinergroupname=examinergroupname + self.examinerexclusive=exclusively return self end @@ -884,8 +919,8 @@ end -- @param #RANGE self -- @param #number nmax Number of results. Default is 10. -- @return #RANGE self -function RANGE:SetDisplayedMaxPlayerResults( nmax ) - self.ndisplayresult = nmax or RANGE.Defaults.ndisplayresult +function RANGE:SetDisplayedMaxPlayerResults(nmax) + self.ndisplayresult=nmax or RANGE.Defaults.ndisplayresult return self end @@ -893,20 +928,20 @@ end -- @param #RANGE self -- @param #number radius Radius in km. Default 5 km. -- @return #RANGE self -function RANGE:SetRangeRadius( radius ) - self.rangeradius = radius * 1000 or RANGE.Defaults.rangeradius +function RANGE:SetRangeRadius(radius) + self.rangeradius=radius*1000 or RANGE.Defaults.rangeradius return self end --- Set player setting whether bomb impact points are smoked or not. -- @param #RANGE self --- @param #boolean switch (Optional) If true, impact points of bombs will be smoked. Default is true. +-- @param #boolean switch If true nor nil default is to smoke impact points of bombs. -- @return #RANGE self -function RANGE:SetDefaultPlayerSmokeBomb( switch ) - if switch == nil or switch == true then - self.defaultsmokebomb = true +function RANGE:SetDefaultPlayerSmokeBomb(switch) + if switch==true or switch==nil then + self.defaultsmokebomb=true else - self.defaultsmokebomb = false + self.defaultsmokebomb=false end return self end @@ -915,8 +950,8 @@ end -- @param #RANGE self -- @param #number distance Threshold distance in km. Default 25 km. -- @return #RANGE self -function RANGE:SetBombtrackThreshold( distance ) - self.BombtrackThreshold = (distance or 25) * 1000 +function RANGE:SetBombtrackThreshold(distance) + self.BombtrackThreshold=(distance or 25)*1000 return self end @@ -925,8 +960,8 @@ end -- @param #RANGE self -- @param Core.Point#COORDINATE coordinate Coordinate of the range. -- @return #RANGE self -function RANGE:SetRangeLocation( coordinate ) - self.location = coordinate +function RANGE:SetRangeLocation(coordinate) + self.location=coordinate return self end @@ -935,8 +970,8 @@ end -- @param #RANGE self -- @param Core.Zone#ZONE zone MOOSE zone defining the range perimeters. -- @return #RANGE self -function RANGE:SetRangeZone( zone ) - self.rangezone = zone +function RANGE:SetRangeZone(zone) + self.rangezone=zone return self end @@ -944,8 +979,8 @@ end -- @param #RANGE self -- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default SMOKECOLOR.Red. -- @return #RANGE self -function RANGE:SetBombTargetSmokeColor( colorid ) - self.BombSmokeColor = colorid or SMOKECOLOR.Red +function RANGE:SetBombTargetSmokeColor(colorid) + self.BombSmokeColor=colorid or SMOKECOLOR.Red return self end @@ -953,8 +988,8 @@ end -- @param #RANGE self -- @param #number distance Distance in meters. Default 1000 m. -- @return #RANGE self -function RANGE:SetScoreBombDistance( distance ) - self.scorebombdistance = distance or 1000 +function RANGE:SetScoreBombDistance(distance) + self.scorebombdistance=distance or 1000 return self end @@ -962,8 +997,8 @@ end -- @param #RANGE self -- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default SMOKECOLOR.Green. -- @return #RANGE self -function RANGE:SetStrafeTargetSmokeColor( colorid ) - self.StrafeSmokeColor = colorid or SMOKECOLOR.Green +function RANGE:SetStrafeTargetSmokeColor(colorid) + self.StrafeSmokeColor=colorid or SMOKECOLOR.Green return self end @@ -971,8 +1006,8 @@ end -- @param #RANGE self -- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default SMOKECOLOR.White. -- @return #RANGE self -function RANGE:SetStrafePitSmokeColor( colorid ) - self.StrafePitSmokeColor = colorid or SMOKECOLOR.White +function RANGE:SetStrafePitSmokeColor(colorid) + self.StrafePitSmokeColor=colorid or SMOKECOLOR.White return self end @@ -980,8 +1015,8 @@ end -- @param #RANGE self -- @param #number delay Time delay in seconds. Default is 3 seconds. -- @return #RANGE self -function RANGE:SetSmokeTimeDelay( delay ) - self.TdelaySmoke = delay or RANGE.Defaults.TdelaySmoke +function RANGE:SetSmokeTimeDelay(delay) + self.TdelaySmoke=delay or RANGE.Defaults.TdelaySmoke return self end @@ -989,7 +1024,7 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:DebugON() - self.Debug = true + self.Debug=true return self end @@ -997,7 +1032,7 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:DebugOFF() - self.Debug = false + self.Debug=false return self end @@ -1005,7 +1040,7 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:SetMessagesOFF() - self.messages = false + self.messages=false return self end @@ -1013,15 +1048,16 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:SetMessagesON() - self.messages = true + self.messages=true return self end + --- Enables tracking of all bomb types. Note that this is the default setting. -- @param #RANGE self -- @return #RANGE self function RANGE:TrackBombsON() - self.trackbombs = true + self.trackbombs=true return self end @@ -1029,7 +1065,7 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:TrackBombsOFF() - self.trackbombs = false + self.trackbombs=false return self end @@ -1037,7 +1073,7 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:TrackRocketsON() - self.trackrockets = true + self.trackrockets=true return self end @@ -1045,7 +1081,7 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:TrackRocketsOFF() - self.trackrockets = false + self.trackrockets=false return self end @@ -1053,7 +1089,7 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:TrackMissilesON() - self.trackmissiles = true + self.trackmissiles=true return self end @@ -1061,18 +1097,19 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:TrackMissilesOFF() - self.trackmissiles = false + self.trackmissiles=false return self end + --- Enable range control and set frequency. -- @param #RANGE self -- @param #number frequency Frequency in MHz. Default 256 MHz. -- @param #string relayunitname Name of the unit used for transmission. -- @return #RANGE self -function RANGE:SetRangeControl( frequency, relayunitname ) - self.rangecontrolfreq = frequency or 256 - self.rangecontrolrelayname = relayunitname +function RANGE:SetRangeControl(frequency, relayunitname) + self.rangecontrolfreq=frequency or 256 + self.rangecontrolrelayname=relayunitname return self end @@ -1081,162 +1118,163 @@ end -- @param #number frequency Frequency in MHz. Default 305 MHz. -- @param #string relayunitname Name of the unit used for transmission. -- @return #RANGE self -function RANGE:SetInstructorRadio( frequency, relayunitname ) - self.instructorfreq = frequency or 305 - self.instructorrelayname = relayunitname +function RANGE:SetInstructorRadio(frequency, relayunitname) + self.instructorfreq=frequency or 305 + self.instructorrelayname=relayunitname return self end --- Set sound files folder within miz file. -- @param #RANGE self --- @param #string path Path for sound files. Default "Range Soundfiles/". Mind the slash "/" at the end! +-- @param #string path Path for sound files. Default "ATIS Soundfiles/". Mind the slash "/" at the end! -- @return #RANGE self -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 ) ) +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 --- Add new strafe pit. For a strafe pit, hits from guns are counted. One pit can consist of several units. --- A strafe run approach is only valid if the player enters via a zone in front of the pit, which is defined by boxlength, boxwidth, and heading. +-- Note, an approach is only valid, if the player enters via a zone in front of the pit, which defined by boxlength and boxheading. -- Furthermore, the player must not be too high and fly in the direction of the pit to make a valid target apporoach. -- @param #RANGE self --- @param #table targetnames Single or multiple (Table) unit or static names defining the strafe targets. The first target in the list determines the approach box origin (heading and box). +-- @param #table targetnames Table of unit or static names defining the strafe targets. The first target in the list determines the approach zone (heading and box). -- @param #number boxlength (Optional) Length of the approach box in meters. Default is 3000 m. -- @param #number boxwidth (Optional) Width of the approach box in meters. Default is 300 m. --- @param #number heading (Optional) Approach box heading in degrees (originating FROM the target). Default is the heading set in the ME for the first target unit --- @param #boolean inverseheading (Optional) Use inverse heading (heading --> heading - 180 Degrees). Default is false. +-- @param #number heading (Optional) Approach heading in Degrees. Default is heading of the unit as defined in the mission editor. +-- @param #boolean inverseheading (Optional) Take inverse heading (heading --> heading - 180 Degrees). Default is false. -- @param #number goodpass (Optional) Number of hits for a "good" strafing pass. Default is 20. --- @param #number foulline (Optional) Foul line distance. Hits from closer than this distance are not counted. Default is 610 m = 2000 ft. Set to 0 for no foul line. +-- @param #number foulline (Optional) Foul line distance. Hits from closer than this distance are not counted. Default 610 m = 2000 ft. Set to 0 for no foul line. -- @return #RANGE self -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 } ) +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}) -- Create table if necessary. - if type( targetnames ) ~= "table" then - targetnames = { targetnames } + if type(targetnames) ~= "table" then + targetnames={targetnames} end -- Make targets - local _targets = {} - local center = nil -- Wrapper.Unit#UNIT - local ntargets = 0 + local _targets={} + local center=nil --Wrapper.Unit#UNIT + local ntargets=0 - for _i, _name in ipairs( targetnames ) do + for _i,_name in ipairs(targetnames) do -- Check if we have a static or unit object. - local _isstatic = self:_CheckStatic( _name ) + local _isstatic=self:_CheckStatic(_name) - local unit = nil - if _isstatic == true then + local unit=nil + if _isstatic==true then -- Add static object. - self:T( self.id .. string.format( "Adding STATIC object %s as strafe target #%d.", _name, _i ) ) - unit = STATIC:FindByName( _name, false ) + 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 + elseif _isstatic==false then -- Add unit object. - self:T( self.id .. string.format( "Adding UNIT object %s as strafe target #%d.", _name, _i ) ) - unit = UNIT:FindByName( _name ) + self:T(self.id..string.format("Adding UNIT object %s as strafe target #%d.", _name, _i)) + unit=UNIT:FindByName(_name) else -- Neither unit nor static object with this name could be found. - local text = string.format( "ERROR! Could not find ANY strafe target object with name %s.", _name ) - self:E( self.id .. text ) + local text=string.format("ERROR! Could not find ANY strafe target object with name %s.", _name) + self:E(self.id..text) end -- Add object to targets. if unit then - table.insert( _targets, unit ) + table.insert(_targets, unit) -- Define center as the first unit we find - if center == nil then - center = unit + if center==nil then + center=unit end - ntargets = ntargets + 1 + ntargets=ntargets+1 end end -- Check if at least one target could be found. - 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 ) + 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 -- Approach box dimensions. - local l = boxlength or RANGE.Defaults.boxlength - local w = (boxwidth or RANGE.Defaults.boxwidth) / 2 + local l=boxlength or RANGE.Defaults.boxlength + local w=(boxwidth or RANGE.Defaults.boxwidth)/2 -- Heading: either manually entered or automatically taken from unit heading. - local heading = heading or center:GetHeading() + local heading=heading or center:GetHeading() -- Invert the heading since some units point in the "wrong" direction. In particular the strafe pit from 476th range objects. if inverseheading ~= nil then if inverseheading then - heading = heading - 180 + heading=heading-180 end end - if heading < 0 then - heading = heading + 360 + if heading<0 then + heading=heading+360 end - if heading >= 360 then - heading = heading - 360 + if heading>360 then + heading=heading-360 end -- Number of hits called a "good" pass. - goodpass = goodpass or RANGE.Defaults.goodpass + goodpass=goodpass or RANGE.Defaults.goodpass -- Foule line distance. - foulline = foulline or RANGE.Defaults.foulline + foulline=foulline or RANGE.Defaults.foulline -- Coordinate of the range. - local Ccenter = center:GetCoordinate() + local Ccenter=center:GetCoordinate() -- Name of the target defined as its unit name. - local _name = center:GetName() + local _name=center:GetName() -- Points defining the approach area. - 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 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 } + local pv2={} + for i,p in ipairs(p) do + pv2[i]={x=p.x, y=p.z} end -- Create polygon zone. - local _polygon = ZONE_POLYGON_BASE:New( _name, pv2 ) + local _polygon=ZONE_POLYGON_BASE:New(_name, pv2) -- Create tires - -- _polygon:BoundZone() + --_polygon:BoundZone() - local st = {} -- #RANGE.StrafeTarget - st.name = _name - st.polygon = _polygon - st.coordinate = Ccenter - st.goodPass = goodpass - st.targets = _targets - st.foulline = foulline - st.smokepoints = p - st.heading = heading + local st={} --#RANGE.StrafeTarget + st.name=_name + st.polygon=_polygon + st.coordinate=Ccenter + st.goodPass=goodpass + st.targets=_targets + st.foulline=foulline + st.smokepoints=p + st.heading=heading -- Add zone to table. - table.insert( self.strafeTargets, st ) + table.insert(self.strafeTargets, st) -- Debug info - 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 ) + 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 + --- Add all units of a group as one new strafe target pit. -- For a strafe pit, hits from guns are counted. One pit can consist of several units. -- Note, an approach is only valid, if the player enters via a zone in front of the pit, which defined by boxlength and boxheading. @@ -1246,33 +1284,33 @@ end -- @param #number boxlength (Optional) Length of the approach box in meters. Default is 3000 m. -- @param #number boxwidth (Optional) Width of the approach box in meters. Default is 300 m. -- @param #number heading (Optional) Approach heading in Degrees. Default is heading of the unit as defined in the mission editor. --- @param #boolean inverseheading (Optional) Use inverse heading (heading --> heading - 180 Degrees). Default is false. +-- @param #boolean inverseheading (Optional) Take inverse heading (heading --> heading - 180 Degrees). Default is false. -- @param #number goodpass (Optional) Number of hits for a "good" strafing pass. Default is 20. -- @param #number foulline (Optional) Foul line distance. Hits from closer than this distance are not counted. Default 610 m = 2000 ft. Set to 0 for no foul line. -- @return #RANGE self -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 } ) +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 -- Get units of group. - local _units = group:GetUnits() + local _units=group:GetUnits() -- Make table of unit names. - local _names = {} - for _, _unit in ipairs( _units ) do + local _names={} + for _,_unit in ipairs(_units) do - local _unit = _unit -- Wrapper.Unit#UNIT + local _unit=_unit --Wrapper.Unit#UNIT if _unit and _unit:IsAlive() then - local _name = _unit:GetName() - table.insert( _names, _name ) + local _name=_unit:GetName() + table.insert(_names,_name) end end -- Add strafe pit. - self:AddStrafePit( _names, boxlength, boxwidth, heading, inverseheading, goodpass, foulline ) + self:AddStrafePit(_names, boxlength, boxwidth, heading, inverseheading, goodpass, foulline) end return self @@ -1280,36 +1318,36 @@ end --- Add bombing target(s) to range. -- @param #RANGE self --- @param #table targetnames Single or multiple (Table) names of unit or static objects serving as bomb targets. --- @param #number goodhitrange (Optional) Max hit distance from target unit in meters which is considered as a good hit. Default is 25 m. --- @param #boolean randommove (Optional) If true, unit will move randomly within the range. Default is false. +-- @param #table targetnames Table containing names of unit or static objects serving as bomb targets. +-- @param #number goodhitrange (Optional) Max distance from target unit (in meters) which is considered as a good hit. Default is 25 m. +-- @param #boolean randommove If true, unit will move randomly within the range. Default is false. -- @return #RANGE self -function RANGE:AddBombingTargets( targetnames, goodhitrange, randommove ) - self:F( { targetnames = targetnames, goodhitrange = goodhitrange, randommove = randommove } ) +function RANGE:AddBombingTargets(targetnames, goodhitrange, randommove) + self:F({targetnames=targetnames, goodhitrange=goodhitrange, randommove=randommove}) -- Create a table if necessary. - if type( targetnames ) ~= "table" then - targetnames = { targetnames } + if type(targetnames) ~= "table" then + targetnames={targetnames} end -- Default range is 25 m. - goodhitrange = goodhitrange or RANGE.Defaults.goodhitrange + goodhitrange=goodhitrange or RANGE.Defaults.goodhitrange - for _, name in pairs( targetnames ) do + for _,name in pairs(targetnames) do -- Check if we have a static or unit object. - local _isstatic = self:_CheckStatic( name ) + 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 ) + 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 ) ) + self:E(self.id..string.format("ERROR! Could not find bombing target %s.", name)) end end @@ -1320,92 +1358,80 @@ end --- Add a unit or static object as bombing target. -- @param #RANGE self -- @param Wrapper.Positionable#POSITIONABLE unit Positionable (unit or static) of the strafe target. --- @param #number goodhitrange (Optional) Max hit distance from target unit in meters which is considered as a good hit. Default is 25 m. --- @param #boolean randommove (Optional) If true, unit will move randomly within the range. Default is false. +-- @param #number goodhitrange Max distance from unit which is considered as a good hit. +-- @param #boolean randommove If true, unit will move randomly within the range. Default is false. -- @return #RANGE self -function RANGE:AddBombingTargetUnit( unit, goodhitrange, randommove ) - self:F( { unit = unit, goodhitrange = goodhitrange, randommove = randommove } ) +function RANGE:AddBombingTargetUnit(unit, goodhitrange, randommove) + self:F({unit=unit, goodhitrange=goodhitrange, randommove=randommove}) -- Get name of positionable. - local name = unit:GetName() + local name=unit:GetName() -- Check if we have a static or unit object. - local _isstatic = self:_CheckStatic( name ) + local _isstatic=self:_CheckStatic(name) -- Default range is 25 m. - goodhitrange = goodhitrange or RANGE.Defaults.goodhitrange + goodhitrange=goodhitrange or RANGE.Defaults.goodhitrange -- Set randommove to false if it was not specified. - if randommove == nil or _isstatic == true then - randommove = false + if randommove==nil or _isstatic==true then + randommove=false end -- Debug or error output. - 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 ) ) ) + 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 ) ) + 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 -- Get max speed of unit in km/h. - local speed = 0 - if _isstatic == false then - speed = self:_GetSpeed( unit ) + local speed=0 + if _isstatic==false then + speed=self:_GetSpeed(unit) end - local target = {} -- #RANGE.BombTarget - target.name = name - target.target = unit - target.goodhitrange = goodhitrange - target.move = randommove - target.speed = speed - target.coordinate = unit:GetCoordinate() + local target={} --#RANGE.BombTarget + 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 + target.type=RANGE.TargetType.STATIC else - target.type = RANGE.TargetType.UNIT + target.type=RANGE.TargetType.UNIT end -- Insert target to table. - table.insert( self.bombingTargets, target ) + table.insert(self.bombingTargets, target) return self end ---- Add a coordinate of a bombing target. + +--- Add a coordinate of a bombing target. This -- @param #RANGE self -- @param Core.Point#COORDINATE coord The coordinate. --- @param #string name (Optional) Name of target. Default is "Bomb Target". --- @param #number goodhitrange (Optional) Max hit distance from target unit in meters which is considered as a good hit. Default is 25 m. +-- @param #string name Name of target. +-- @param #number goodhitrange Max distance from unit which is considered as a good hit. -- @return #RANGE self --- @usage --- --- -- Setup a Range --- RangeOne = RANGE:New( "Range One" ) --- -- Find the STATIC target object as setup in the ME. --- RangeOneBombTarget = STATIC:FindByName( "RangeOneBombTarget" ) --- -- Add the coordinate of the STATIC target object as a bomb target (thus keeping the bomb function active, even if the STATIC target is destroyed). --- RangeOne:AddBombingTargetCoordinate( RangeOneBombTarget:GetCoordinate(), "RangeOneBombTarget", 50) --- -- Or, add the coordinate of the STATIC target object as a bomb target using default values (name will be "Bomb Target", goodhitrange will be 25 m). --- RangeOne:AddBombingTargetCoordinate( RangeOneBombTarget:GetCoordinate() ) --- -- Start Range. --- RangeOne:Start() --- -function RANGE:AddBombingTargetCoordinate( coord, name, goodhitrange ) +function RANGE:AddBombingTargetCoordinate(coord, name, goodhitrange) - local target = {} -- #RANGE.BombTarget - 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 + local target={} --#RANGE.BombTarget + 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 -- Insert target to table. - table.insert( self.bombingTargets, target ) + table.insert(self.bombingTargets, target) return self end @@ -1413,19 +1439,19 @@ end --- Add all units of a group as bombing targets. -- @param #RANGE self -- @param Wrapper.Group#GROUP group Group of bombing targets. --- @param #number goodhitrange (Optional) Max hit distance from target unit in meters which is considered as a good hit. Default is 25 m. --- @param #boolean randommove (Optional) If true, unit will move randomly within the range. Default is false. +-- @param #number goodhitrange Max distance from unit which is considered as a good hit. +-- @param #boolean randommove If true, unit will move randomly within the range. Default is false. -- @return #RANGE self -function RANGE:AddBombingTargetGroup( group, goodhitrange, randommove ) - self:F( { group = group, goodhitrange = goodhitrange, randommove = randommove } ) +function RANGE:AddBombingTargetGroup(group, goodhitrange, randommove) + self:F({group=group, goodhitrange=goodhitrange, randommove=randommove}) if group then - local _units = group:GetUnits() + local _units=group:GetUnits() - for _, _unit in pairs( _units ) do + for _,_unit in pairs(_units) do if _unit and _unit:IsAlive() then - self:AddBombingTargetUnit( _unit, goodhitrange, randommove ) + self:AddBombingTargetUnit(_unit, goodhitrange, randommove) end end end @@ -1433,58 +1459,47 @@ function RANGE:AddBombingTargetGroup( group, goodhitrange, randommove ) return self end ---- Returns the foul line distance between strafe pit target and a foul line distance marker object. +--- Measures the foule line distance between two unit or static objects. -- @param #RANGE self -- @param #string namepit Name of the strafe pit target object. --- @param #string namefoulline Name of the foul line distance marker object. +-- @param #string namefoulline Name of the fould line distance marker object. -- @return #number Foul line distance in meters. --- @usage --- --- -- Setup a Range --- RangeOne = RANGE:New( "Range One" ) --- -- Get distance between strafe target objext and foul line distance marker object. --- RangeOneFoulDistance = RangeOne:GetFoullineDistance( "RangeOneStrafeTarget" , "RangeOneFoulLineObject" ) --- -- Add a strafe pit using the measured foul line distance. Where nil is used, strafe pit default values will be used - adjust as required. --- RangeOne:AddStrafePit( "RangeOneStrafeTarget", nil, nil, nil, nil, nil, RangeOneFoulDistance ) --- -- Start Range. --- RangeOne:Start() --- -function RANGE:GetFoullineDistance( namepit, namefoulline ) - self:F( { namepit = namepit, namefoulline = namefoulline } ) +function RANGE:GetFoullineDistance(namepit, namefoulline) + self:F({namepit=namepit, namefoulline=namefoulline}) -- Check if we have units or statics. - local _staticpit = self:_CheckStatic( namepit ) - local _staticfoul = self:_CheckStatic( namefoulline ) + local _staticpit=self:_CheckStatic(namepit) + local _staticfoul=self:_CheckStatic(namefoulline) -- Get the unit or static pit object. - local pit = nil - if _staticpit == true then - pit = STATIC:FindByName( namepit, false ) - elseif _staticpit == false then - pit = UNIT:FindByName( namepit ) + 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 ) ) + 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 -- Get the unit or static foul line object. - local foul = nil - if _staticfoul == true then - foul = STATIC:FindByName( namefoulline, false ) - elseif _staticfoul == false then - foul = UNIT:FindByName( namefoulline ) + 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 ) ) + 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 -- Get the distance between the two objects. - local fouldist = 0 - if pit ~= nil and foul ~= nil then - fouldist = pit:GetCoordinate():Get2DDistance( foul:GetCoordinate() ) + 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 ) ) + 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 ) ) + self:T(self.id..string.format("Foul line distance = %.1f m.", fouldist)) return fouldist end @@ -1495,139 +1510,141 @@ end --- General event handler. -- @param #RANGE self -- @param #table Event DCS event table. -function RANGE:onEvent( Event ) - self:F3( Event ) +function RANGE:onEvent(Event) + self:F3(Event) if Event == nil or Event.initiator == nil then - self:T3( "Skipping onEvent. Event or Event.initiator unknown." ) + 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." ) + 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 DCSweapon = Event.weapon - local EventData = {} - local _playerunit = nil - local _playername = nil + local EventData={} + local _playerunit=nil + local _playername=nil if Event.initiator then - EventData.IniUnitName = Event.initiator:getName() - EventData.IniDCSGroup = Event.initiator:getGroup() + EventData.IniUnitName = Event.initiator:getName() + EventData.IniDCSGroup = Event.initiator:getGroup() EventData.IniGroupName = Event.initiator:getGroup():getName() -- Get player unit and name. This returns nil,nil if the event was not fired by a player unit. And these are the only events we are interested in. - _playerunit, _playername = self:_GetPlayerUnitAndName( EventData.IniUnitName ) + _playerunit, _playername = self:_GetPlayerUnitAndName(EventData.IniUnitName) end if Event.target then - EventData.TgtUnitName = Event.target:getName() - EventData.TgtUnit = UNIT:FindByName( EventData.TgtUnitName ) + 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.Weapon = Event.weapon + EventData.weapon = Event.weapon EventData.WeaponTypeName = Event.weapon:getTypeName() end -- Event info. - 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 ) ) ) + 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))) -- Call event Birth function. - if Event.id == world.event.S_EVENT_BIRTH and _playername then - self:OnEventBirth( EventData ) + if Event.id==world.event.S_EVENT_BIRTH and _playername then + self:OnEventBirth(EventData) end -- Call event Shot function. - if Event.id == world.event.S_EVENT_SHOT and _playername and Event.weapon then - self:OnEventShot( EventData ) + if Event.id==world.event.S_EVENT_SHOT and _playername and Event.weapon then + self:OnEventShot(EventData) end -- Call event Hit function. - if Event.id == world.event.S_EVENT_HIT and _playername and DCStgtunit then - self:OnEventHit( EventData ) + if Event.id==world.event.S_EVENT_HIT and _playername and DCStgtunit then + self:OnEventHit(EventData) end end + --- Range event handler for event birth. -- @param #RANGE self -- @param Core.Event#EVENTDATA EventData -function RANGE:OnEventBirth( EventData ) - self:F( { eventbirth = EventData } ) +function RANGE:OnEventBirth(EventData) + self:F({eventbirth = EventData}) - local _unitName = EventData.IniUnitName - local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) + 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 ) ) + 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 _uid=_unit:GetID() + local _group=_unit:GetGroup() + local _gid=_group:GetID() + local _callsign=_unit:GetCallsign() -- Debug output. - 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 ) + 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) -- Reset current strafe status. self.strafeStatus[_uid] = nil -- Add Menu commands after a delay of 0.1 seconds. - -- SCHEDULER:New(nil, self._AddF10Commands, {self,_unitName}, 0.1) - self:ScheduleOnce( 0.1, self._AddF10Commands, self, _unitName ) + --SCHEDULER:New(nil, self._AddF10Commands, {self,_unitName}, 0.1) + self:ScheduleOnce(0.1, self._AddF10Commands, self, _unitName) -- By default, some bomb impact points and do not flare each hit on target. - self.PlayerSettings[_playername] = {} -- #RANGE.PlayerData - 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 + self.PlayerSettings[_playername]={} --#RANGE.PlayerData + 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 -- Start check in zone timer. if self.planes[_uid] ~= true then - -- SCHEDULER:New(nil, self._CheckInZone, {self, EventData.IniUnitName}, 1, 1) - self.timerCheckZone = TIMER:New( self._CheckInZone, self, EventData.IniUnitName ):Start( 1, 1 ) + --SCHEDULER:New(nil, self._CheckInZone, {self, EventData.IniUnitName}, 1, 1) + self.timerCheckZone=TIMER:New(self._CheckInZone, self, EventData.IniUnitName):Start(1, 1) self.planes[_uid] = true end - end + end end --- Range event handler for event hit. -- @param #RANGE self -- @param Core.Event#EVENTDATA EventData -function RANGE:OnEventHit( EventData ) - self:F( { eventhit = EventData } ) +function RANGE:OnEventHit(EventData) + self:F({eventhit = EventData}) -- Debug info. - 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 ) ) + 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)) -- Player info local _unitName = EventData.IniUnitName - local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) - if _unit == nil or _playername == nil then + local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + if _unit==nil or _playername==nil then return end @@ -1635,7 +1652,7 @@ function RANGE:OnEventHit( EventData ) local _unitID = _unit:GetID() -- Target - local target = EventData.TgtUnit + local target = EventData.TgtUnit local targetname = EventData.TgtUnitName -- Current strafe target of player. @@ -1648,30 +1665,31 @@ function RANGE:OnEventHit( EventData ) local targetPos = target:GetCoordinate() -- Loop over valid targets for this run. - for _, _target in pairs( _currentTarget.zone.targets ) do + for _,_target in pairs(_currentTarget.zone.targets) do -- Check the the target is the same that was actually hit. - if _target and _target:IsAlive() and _target:GetName() == targetname then + if _target and _target:IsAlive() and _target:GetName() == targetname then -- Get distance between player and target. - local dist = playerPos:Get2DDistance( targetPos ) + local dist=playerPos:Get2DDistance(targetPos) if dist > _currentTarget.zone.foulline then -- Increase hit counter of this run. - _currentTarget.hits = _currentTarget.hits + 1 + _currentTarget.hits = _currentTarget.hits + 1 -- Flare target. if _unit and _playername and self.PlayerSettings[_playername].flaredirecthits then - targetPos:Flare( self.PlayerSettings[_playername].flarecolor ) + targetPos:Flare(self.PlayerSettings[_playername].flarecolor) end else -- Too close to the target. - 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 + 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 + invalidStrafe = true --Rangeboss Edit end end @@ -1680,9 +1698,9 @@ function RANGE:OnEventHit( EventData ) end -- Bombing Targets - for _, _bombtarget in pairs( self.bombingTargets ) do + for _,_bombtarget in pairs(self.bombingTargets) do - local _target = _bombtarget.target -- Wrapper.Positionable#POSITIONABLE + local _target=_bombtarget.target --Wrapper.Positionable#POSITIONABLE -- Check if one of the bomb targets was hit. if _target and _target:IsAlive() and _bombtarget.name == targetname then @@ -1691,56 +1709,55 @@ function RANGE:OnEventHit( EventData ) -- Flare target. if self.PlayerSettings[_playername].flaredirecthits then - + -- Position of target. local targetPos = _target:GetCoordinate() - - targetPos:Flare( self.PlayerSettings[_playername].flarecolor ) + + targetPos:Flare(self.PlayerSettings[_playername].flarecolor) end end end end - end --- Range event handler for event shot (when a unit releases a rocket or bomb (but not a fast firing gun). -- @param #RANGE self -- @param Core.Event#EVENTDATA EventData -function RANGE:OnEventShot( EventData ) - self:F( { eventshot = EventData } ) +function RANGE:OnEventShot(EventData) + self:F({eventshot = EventData}) -- Nil checks. - if EventData.Weapon == nil then + if EventData.Weapon==nil then return end - if EventData.IniDCSUnit == nil then + if EventData.IniDCSUnit==nil then return end -- Weapon data. - local _weapon = EventData.Weapon:getTypeName() -- should be the same as Event.WeaponTypeName - local _weaponStrArray = UTILS.Split( _weapon, "%." ) + local _weapon = EventData.Weapon:getTypeName() -- should be the same as Event.WeaponTypeName + local _weaponStrArray = UTILS.Split(_weapon,"%.") local _weaponName = _weaponStrArray[#_weaponStrArray] -- Weapon descriptor. - local desc = EventData.Weapon:getDesc() + local desc=EventData.Weapon:getDesc() -- Weapon category: 0=SHELL, 1=MISSILE, 2=ROCKET, 3=BOMB (Weapon.Category.X) - local weaponcategory = desc.category + local weaponcategory=desc.category -- Debug info. - 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 ) + 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) -- Tracking conditions for bombs, rockets and missiles. - local _bombs = weaponcategory == Weapon.Category.BOMB -- string.match(_weapon, "weapons.bombs") - local _rockets = weaponcategory == Weapon.Category.ROCKET -- string.match(_weapon, "weapons.nurs") - local _missiles = weaponcategory == Weapon.Category.MISSILE -- string.match(_weapon, "weapons.missiles") or _viggen + local _bombs = weaponcategory==Weapon.Category.BOMB --string.match(_weapon, "weapons.bombs") + local _rockets = weaponcategory==Weapon.Category.ROCKET --string.match(_weapon, "weapons.nurs") + local _missiles = weaponcategory==Weapon.Category.MISSILE --string.match(_weapon, "weapons.missiles") or _viggen -- Check if any condition applies here. local _track = (_bombs and self.trackbombs) or (_rockets and self.trackrockets) or (_missiles and self.trackmissiles) @@ -1749,37 +1766,39 @@ function RANGE:OnEventShot( EventData ) local _unitName = EventData.IniUnitName -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) + local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) -- Set this to larger value than the threshold. - local dPR = self.BombtrackThreshold * 2 + local dPR=self.BombtrackThreshold*2 -- Distance player to range. 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 ) ) + 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 -- Only track if distance player to range is < 25 km. Also check that a player shot. No need to track AI weapons. - if _track and dPR <= self.BombtrackThreshold and _unit and _playername then + if _track and dPR<=self.BombtrackThreshold and _unit and _playername then + -- Player data. - local playerData = self.PlayerSettings[_playername] -- #RANGE.PlayerData + local playerData=self.PlayerSettings[_playername] --#RANGE.PlayerData -- Tracking info and init of last bomb position. - self:T( self.id .. string.format( "RANGE %s: Tracking %s - %s.", self.rangename, _weapon, EventData.weapon:getName() ) ) + self:T(self.id..string.format("RANGE %s: Tracking %s - %s.", self.rangename, _weapon, EventData.weapon:getName())) -- Init bomb position. - local _lastBombPos = { x = 0, y = 0, z = 0 } -- DCS#Vec3 + local _lastBombPos = {x=0,y=0,z=0} --DCS#Vec3 -- Function monitoring the position of a bomb until impact. - local function trackBomb( _ordnance ) + local function trackBomb(_ordnance) -- When the pcall returns a failure the weapon has hit. - local _status, _bombPos = pcall( function() + local _status,_bombPos = pcall( + function() return _ordnance:getPoint() - end ) + end) - self:T2( self.id .. string.format( "Range %s: Bomb still in air: %s", self.rangename, tostring( _status ) ) ) + self:T2(self.id..string.format("Range %s: Bomb still in air: %s", self.rangename, tostring(_status))) if _status then ---------------------------- @@ -1787,7 +1806,7 @@ function RANGE:OnEventShot( EventData ) ---------------------------- -- Remember this position. - _lastBombPos = { x = _bombPos.x, y = _bombPos.y, z = _bombPos.z } + _lastBombPos = {x = _bombPos.x, y = _bombPos.y, z= _bombPos.z } -- Check again in ~0.005 seconds ==> 200 checks per second. return timer.getTime() + self.dtBombtrack @@ -1799,55 +1818,57 @@ function RANGE:OnEventShot( EventData ) ----------------------------- -- Get closet target to last position. - local _closetTarget = nil -- #RANGE.BombTarget - local _distance = nil - local _closeCoord = nil - local _hitquality = "POOR" + local _closetTarget=nil --#RANGE.BombTarget + local _distance=nil + local _closeCoord=nil + local _hitquality="POOR" -- Get callsign. - local _callsign = self:_myname( _unitName ) + local _callsign=self:_myname(_unitName) -- Coordinate of impact point. - local impactcoord = COORDINATE:NewFromVec3( _lastBombPos ) + local impactcoord=COORDINATE:NewFromVec3(_lastBombPos) -- Check if impact happened in range zone. - local insidezone = self.rangezone:IsCoordinateInZone( impactcoord ) + local insidezone=self.rangezone:IsCoordinateInZone(impactcoord) -- Impact point of bomb. if self.Debug then - impactcoord:MarkToAll( "Bomb impact point" ) + impactcoord:MarkToAll("Bomb impact point") end -- Smoke impact point of bomb. if playerData.smokebombimpact and insidezone then if playerData.delaysmoke then - timer.scheduleFunction( self._DelayedSmoke, { coord = impactcoord, color = playerData.smokecolor }, timer.getTime() + self.TdelaySmoke ) + timer.scheduleFunction(self._DelayedSmoke, {coord=impactcoord, color=playerData.smokecolor}, timer.getTime() + self.TdelaySmoke) else - impactcoord:Smoke( playerData.smokecolor ) + impactcoord:Smoke(playerData.smokecolor) end end -- Loop over defined bombing targets. - for _, _bombtarget in pairs( self.bombingTargets ) do + for _,_bombtarget in pairs(self.bombingTargets) do -- Get target coordinate. - local targetcoord = self:_GetBombTargetCoordinate( _bombtarget ) + local targetcoord=self:_GetBombTargetCoordinate(_bombtarget) if targetcoord then -- Distance between bomb and target. - local _temp = impactcoord:Get2DDistance( targetcoord ) + local _temp = impactcoord:Get2DDistance(targetcoord) -- Find closest target to last known position of the bomb. if _distance == nil or _temp < _distance then _distance = _temp _closetTarget = _bombtarget - _closeCoord = targetcoord - if _distance <= 0.5 * _bombtarget.goodhitrange then + _closeCoord=targetcoord + if _distance <= 1.53 then -- Rangeboss Edit + _hitquality = "SHACK" -- Rangeboss Edit + elseif _distance <= 0.5*_bombtarget.goodhitrange then --Rangeboss Edit _hitquality = "EXCELLENT" elseif _distance <= _bombtarget.goodhitrange then _hitquality = "GOOD" - elseif _distance <= 2 * _bombtarget.goodhitrange then + elseif _distance <= 2*_bombtarget.goodhitrange then _hitquality = "INEFFECTIVE" else _hitquality = "POOR" @@ -1861,44 +1882,48 @@ function RANGE:OnEventShot( EventData ) if _distance and _distance <= self.scorebombdistance then -- Init bomb player results. if not self.bombPlayerResults[_playername] then - self.bombPlayerResults[_playername] = {} + self.bombPlayerResults[_playername]={} end -- Local results. - local _results = self.bombPlayerResults[_playername] + local _results=self.bombPlayerResults[_playername] - local result = {} -- #RANGE.BombResult - 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 + local result={} --#RANGE.BombResult + 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 + result.roundsFired=0 --Rangeboss Edit + result.roundsHit=0 --Rangeboss Edit + result.roundsQuality="N/A" --Rangeboss Edit + result.rangename = self.rangename -- Add to table. - table.insert( _results, result ) + table.insert(_results, result) -- Call impact. - self:Impact( result, playerData ) + self:Impact(result, playerData) elseif insidezone then -- Send message. - 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 ) - + 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 ) + 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." ) + self:T(self.id.."Weapon impacted outside range zone.") end - -- Terminate the timer - self:T( self.id .. string.format( "Range %s, player %s: Terminating bomb track timer.", self.rangename, _playername ) ) + --Terminate the timer + self:T(self.id..string.format("Range %s, player %s: Terminating bomb track timer.", self.rangename, _playername)) return nil end -- _status check @@ -1906,62 +1931,133 @@ function RANGE:OnEventShot( EventData ) end -- end function trackBomb -- Weapon is not yet "alife" just yet. Start timer in one second. - 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 ) + 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 -- if _track (string.match) and player-range distance < threshold. + end --if _track (string.match) and player-range distance < threshold. end ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM Functions ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- +function RANGE:_SaveTargetSheet(_playername, result) --RangeBoss Specific Function + + --- Function that saves data to file + local function _savefile(filename, data) + local f = io.open(filename, "wb") + if f then + f:write(data) + f:close() + else + env.info("RANGEBOSS EDIT - could not save target sheet to file") + --self:E(self.lid..string.format("ERROR: could not save target sheet to file %s.\nFile may contain invalid characters.", tostring(filename))) + end + end + + + -- Set path or default. + local path=self.targetpath + if lfs then + path=path or lfs.writedir()..[[Logs\]] + end + + -- Create unused file name. + local filename=nil + for i=1,9999 do + + -- Create file name + if self.targetprefix then + filename=string.format("%s_%s-%04d.csv", self.targetprefix, playerData.actype, i) + else + local name=UTILS.ReplaceIllegalCharacters(_playername, "_") + filename=string.format("RANGERESULTS-%s_Targetsheet-%s-%04d.csv",self.rangename,name, i) + end + + -- Set path. + if path~=nil then + filename=path.."\\"..filename + end + + -- Check if file exists. + local _exists=UTILS.FileExists(filename) + if not _exists then + break + end + end + + -- Header line + local data="Name,Target,Distance,Radial,Quality,Rounds Fired,Rounds Hit,Rounds Quality,Attack Heading,Weapon,Airframe,Mission Time,OS Time\n" + + --local result=_result --#RANGE.BombResult + 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" + local roundsFired=result.roundsFired + local roundsHit=result.roundsHit + local strafeResult=result.roundsQuality + local attackHeading=result.heading + if os then + date=os.date() + end + data=data..string.format("%s,%s,%.2f,%03d,%s,%03d,%03d,%s,%03d,%s,%s,%s,%s", _playername, target, distance, radial, quality, roundsFired, roundsHit, strafeResult, attackHeading, weapon, airframe, time, date) + + + -- Save file. + _savefile(filename, data) +end --- Check spawn queue and spawn aircraft if necessary. -- @param #RANGE self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function RANGE:onafterStatus( From, Event, To ) +function RANGE:onafterStatus(From, Event, To) - if self.verbose > 0 then + 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" + 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 ) + local relay=UNIT:FindByName(self.instructorrelayname) if relay then - alive = tostring( relay:IsAlive() ) + alive=tostring(relay:IsAlive()) end end - text = text .. string.format( ", Instructor %.3f MHz (Relay=%s alive=%s)", self.instructorfreq, tostring( self.instructorrelayname ), alive ) + 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.rangecontrol then + local alive="N/A" if self.rangecontrolrelayname then - local relay = UNIT:FindByName( self.rangecontrolrelayname ) + local relay=UNIT:FindByName(self.rangecontrolrelayname) if relay then - alive = tostring( relay:IsAlive() ) + alive=tostring(relay:IsAlive()) end end - text = text .. string.format( ", Control %.3f MHz (Relay=%s alive=%s)", self.rangecontrolfreq, tostring( self.rangecontrolrelayname ), alive ) + text=text..string.format(", Control %.3f MHz (Relay=%s alive=%s)", self.rangecontrolfreq, tostring(self.rangecontrolrelayname), alive) end - + + -- Check range status. - self:I( self.id .. text ) - + self:I(self.id..text) + end -- Check player status. self:_CheckPlayers() -- Check back in ~10 seconds. - self:__Status( -10 ) + self:__Status(-10) end --- Function called after player enters the range zone. @@ -1970,23 +2066,23 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param #RANGE.PlayerData player Player data. -function RANGE:onafterEnterRange( From, Event, To, player ) - +function RANGE:onafterEnterRange(From, Event, To, player) + if self.instructor and self.rangecontrol then - + -- Range control radio frequency split. - local RF = UTILS.Split( string.format( "%.3f", self.rangecontrolfreq ), "." ) - + local RF=UTILS.Split(string.format("%.3f", self.rangecontrolfreq), ".") + -- Radio message that player entered the range - 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] ) + 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 ) + self.instructor:NewTransmission(RANGE.Sound.IRMegaHertz.filename, RANGE.Sound.IRMegaHertz.duration, self.soundpath) end - + end --- Function called after player leaves the range zone. @@ -1995,14 +2091,15 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param #RANGE.PlayerData player Player data. -function RANGE:onafterExitRange( From, Event, To, player ) +function RANGE:onafterExitRange(From, Event, To, player) if self.instructor then - self.instructor:NewTransmission( RANGE.Sound.IRExitRange.filename, RANGE.Sound.IRExitRange.duration, self.soundpath ) + self.instructor:NewTransmission(RANGE.Sound.IRExitRange.filename, RANGE.Sound.IRExitRange.duration, self.soundpath) end end + --- Function called after bomb impact on range. -- @param #RANGE self -- @param #string From From state. @@ -2010,44 +2107,49 @@ end -- @param #string To To state. -- @param #RANGE.BombResult result Result of bomb impact. -- @param #RANGE.PlayerData player Player data table. -function RANGE:onafterImpact( From, Event, To, result, player ) +function RANGE:onafterImpact(From, Event, To, result, player) + + -- Only display target name if there is more than one bomb target. + local targetname=nil + if #self.bombingTargets>1 then + local targetname=result.name + end -- Send message to player. - local text = string.format( "%s, impact %03d° for %d m (%d ft)", player.playername, result.radial, result.distance, UTILS.MetersToFeet( result.distance ) ) - -- Only display target name if there is more than one bomb target. - if #self.bombingTargets > 1 then - text = text .. string.format( " from bulls of target %s.", result.name ) + 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 .. "." + text=text.."." end - text = text .. string.format( " %s hit.", result.quality ) - + 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 ) + self.rangecontrol:NewTransmission(RANGE.Sound.RCImpact.filename, RANGE.Sound.RCImpact.duration, self.soundpath, nil, nil, text, self.subduration) + self.rangecontrol:Number2Transmission(string.format("%03d", result.radial), nil, 0.1) + self.rangecontrol:NewTransmission(RANGE.Sound.RCDegrees.filename, RANGE.Sound.RCDegrees.duration, self.soundpath) + self.rangecontrol:NewTransmission(RANGE.Sound.RCFor.filename, RANGE.Sound.RCFor.duration, self.soundpath) + self.rangecontrol:Number2Transmission(string.format("%d", UTILS.MetersToFeet(result.distance))) + self.rangecontrol:NewTransmission(RANGE.Sound.RCFeet.filename, RANGE.Sound.RCFeet.duration, self.soundpath) + if result.quality=="POOR" then + self.rangecontrol:NewTransmission(RANGE.Sound.RCPoorHit.filename, RANGE.Sound.RCPoorHit.duration, self.soundpath, nil, 0.5) + elseif result.quality=="INEFFECTIVE" then + self.rangecontrol:NewTransmission(RANGE.Sound.RCIneffectiveHit.filename, RANGE.Sound.RCIneffectiveHit.duration, self.soundpath, nil, 0.5) + elseif result.quality=="GOOD" then + self.rangecontrol:NewTransmission(RANGE.Sound.RCGoodHit.filename, RANGE.Sound.RCGoodHit.duration, self.soundpath, nil, 0.5) + elseif result.quality=="EXCELLENT" then + self.rangecontrol:NewTransmission(RANGE.Sound.RCExcellentHit.filename, RANGE.Sound.RCExcellentHit.duration, self.soundpath, nil, 0.5) end - - end + + end -- Unit. - local unit = UNIT:FindByName( player.unitname ) + local unit=UNIT:FindByName(player.unitname) -- Send message. - self:_DisplayMessageToGroup( unit, text, nil, true ) - self:T( self.id .. text ) - + self:_DisplayMessageToGroup(unit, text, nil, true) + self:T(self.id..text) + -- Save results. if self.autosave then self:Save() @@ -2060,11 +2162,11 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function RANGE:onbeforeSave( From, Event, To ) +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." ) ) + self:E(self.id..string.format("WARNING: io and/or lfs not desanitized. Cannot save player results.")) return false end end @@ -2074,62 +2176,62 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function RANGE:onafterSave( From, Event, To ) +function RANGE:onafterSave(From, Event, To) - local function _savefile( filename, data ) - local f = io.open( filename, "wb" ) + local function _savefile(filename, data) + local f=io.open(filename, "wb") if f then - f:write( data ) + f:write(data) f:close() - self:I( self.id .. string.format( "Saving player results to file %s", tostring( filename ) ) ) + 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 ) ) ) + self:E(self.id..string.format("ERROR: Could not save results to file %s", tostring(filename))) end end -- Path. - local path = lfs.writedir() .. [[Logs\]] + local path=lfs.writedir()..[[Logs\]] -- Set file name. - local filename = path .. string.format( "RANGE-%s_BombingResults.csv", self.rangename ) + local filename=path..string.format("RANGE-%s_BombingResults.csv", self.rangename) -- Header line. - local scores = "Name,Pass,Target,Distance,Radial,Quality,Weapon,Airframe,Mission Time" + local scores="Name,Pass,Target,Distance,Radial,Quality,Weapon,Airframe,Mission Time" -- Loop over all players. - for playername, results in pairs( self.bombPlayerResults ) do + for playername,results in pairs(self.bombPlayerResults) do -- Loop over player grades table. - for i, _result in pairs( results ) do - local result = _result -- #RANGE.BombResult - 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" + for i,_result in pairs(results) do + local result=_result --#RANGE.BombResult + 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() + 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 ) + 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 ) + _savefile(filename, scores) end ---- Function called before load event. Checks that io and lfs are desanitized. +--- Function called before save event. Checks that io and lfs are desanitized. -- @param #RANGE self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function RANGE:onbeforeLoad( From, Event, To ) +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." ) ) + self:E(self.id..string.format("WARNING: io and/or lfs not desanitized. Cannot load player results.")) return false end end @@ -2139,74 +2241,74 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function RANGE:onafterLoad( From, Event, To ) +function RANGE:onafterLoad(From, Event, To) --- Function that load data from a file. - local function _loadfile( filename ) - local f = io.open( filename, "rb" ) + local function _loadfile(filename) + local f=io.open(filename, "rb") if f then - -- self:I(self.id..string.format("Loading player results from file %s", tostring(filename))) - local data = f:read( "*all" ) + --self:I(self.id..string.format("Loading player results from file %s", tostring(filename))) + 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 ) ) ) + 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 -- Path in DCS log file. - local path = lfs.writedir() .. [[Logs\]] + local path=lfs.writedir()..[[Logs\]] -- Set file name. - local filename = path .. string.format( "RANGE-%s_BombingResults.csv", self.rangename ) + local filename=path..string.format("RANGE-%s_BombingResults.csv", self.rangename) -- Info message. - local text = string.format( "Loading player bomb results from file %s", filename ) - self:I( self.id .. text ) + local text=string.format("Loading player bomb results from file %s", filename) + self:I(self.id..text) -- Load asset data from file. - local data = _loadfile( filename ) + local data=_loadfile(filename) if data then -- Split by line break. - local results = UTILS.Split( data, "\n" ) + local results=UTILS.Split(data,"\n") -- Remove first header line. - table.remove( results, 1 ) + table.remove(results, 1) -- Init player scores table. - self.bombPlayerResults = {} + self.bombPlayerResults={} -- Loop over all lines. - for _, _result in pairs( results ) do + for _,_result in pairs(results) do -- Parameters are separated by commata. - local resultdata = UTILS.Split( _result, "," ) + local resultdata=UTILS.Split(_result, ",") -- Grade table - local result = {} -- #RANGE.BombResult + local result={} --#RANGE.BombResult -- Player name. - local playername = resultdata[1] - result.player = playername + local playername=resultdata[1] + result.player=playername -- Results data. - 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" + 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" -- Create player array if necessary. - self.bombPlayerResults[playername] = self.bombPlayerResults[playername] or {} + self.bombPlayerResults[playername]=self.bombPlayerResults[playername] or {} -- Add result to table. - table.insert( self.bombPlayerResults[playername], result ) + table.insert(self.bombPlayerResults[playername], result) end end end @@ -2217,52 +2319,50 @@ end --- Start smoking a coordinate with a delay. -- @param #table _args Argements passed. -function RANGE._DelayedSmoke( _args ) - trigger.action.smoke( _args.coord:GetVec3(), _args.color ) +function RANGE._DelayedSmoke(_args) + trigger.action.smoke(_args.coord:GetVec3(), _args.color) end --- Display top 10 stafing results of a specific player. -- @param #RANGE self -- @param #string _unitName Name of the player unit. -function RANGE:_DisplayMyStrafePitResults( _unitName ) - self:F( _unitName ) +function RANGE:_DisplayMyStrafePitResults(_unitName) + self:F(_unitName) -- Get player unit and name - local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) + local _unit,_playername = self:_GetPlayerUnitAndName(_unitName) if _unit and _playername then -- Message header. - local _message = string.format( "My Top %d Strafe Pit Results:\n", self.ndisplayresult ) + local _message = string.format("My Top %d Strafe Pit Results:\n", self.ndisplayresult) -- Get player results. local _results = self.strafePlayerResults[_playername] -- Create message. if _results == nil then - -- No score yet. - _message = string.format( "%s: No Score yet.", _playername ) + -- No score yet. + _message = string.format("%s: No Score yet.", _playername) else -- Sort results table wrt number of hits. - local _sort = function( a, b ) - return a.hits > b.hits - end - table.sort( _results, _sort ) + local _sort = function( a,b ) return a.hits > b.hits end + table.sort(_results,_sort) -- Prepare message of best results. local _bestMsg = "" local _count = 1 -- Loop over results - for _, _result in pairs( _results ) do + for _,_result in pairs(_results) do -- Message text. - _message = _message .. string.format( "\n[%d] Hits %d - %s - %s", _count, _result.hits, _result.zone.name, _result.text ) + _message = _message..string.format("\n[%d] Hits %d - %s - %s", _count, _result.hits, _result.zone.name, _result.text) -- Best result. if _bestMsg == "" then - _bestMsg = string.format( "Hits %d - %s - %s", _result.hits, _result.zone.name, _result.text ) + _bestMsg = string.format("Hits %d - %s - %s", _result.hits, _result.zone.name, _result.text) end -- 10 runs @@ -2271,26 +2371,26 @@ function RANGE:_DisplayMyStrafePitResults( _unitName ) end -- Increase counter - _count = _count + 1 + _count = _count+1 end -- Message text. - _message = _message .. "\n\nBEST: " .. _bestMsg + _message = _message .."\n\nBEST: ".._bestMsg end -- Send message to group. - self:_DisplayMessageToGroup( _unit, _message, nil, true, true ) + self:_DisplayMessageToGroup(_unit, _message, nil, true, true) end end --- Display top 10 strafing results of all players. -- @param #RANGE self -- @param #string _unitName Name fo the player unit. -function RANGE:_DisplayStrafePitResults( _unitName ) - self:F( _unitName ) +function RANGE:_DisplayStrafePitResults(_unitName) + self:F(_unitName) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) + local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) -- Check if we have a unit which is a player. if _unit and _playername then @@ -2299,14 +2399,14 @@ function RANGE:_DisplayStrafePitResults( _unitName ) local _playerResults = {} -- Message text. - local _message = string.format( "Strafe Pit Results - Top %d Players:\n", self.ndisplayresult ) + local _message = string.format("Strafe Pit Results - Top %d Players:\n", self.ndisplayresult) -- Loop over player results. - for _playerName, _results in pairs( self.strafePlayerResults ) do + for _playerName,_results in pairs(self.strafePlayerResults) do -- Get the best result of the player. local _best = nil - for _, _result in pairs( _results ) do + for _,_result in pairs(_results) do if _best == nil or _result.hits > _best.hits then _best = _result end @@ -2314,232 +2414,226 @@ function RANGE:_DisplayStrafePitResults( _unitName ) -- Add best result to table. 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 } ) + 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 - -- Sort list! - local _sort = function( a, b ) - return a.hits > b.hits - end - table.sort( _playerResults, _sort ) + --Sort list! + local _sort = function( a,b ) return a.hits > b.hits end + table.sort(_playerResults,_sort) -- Add top 10 results. - for _i = 1, math.min( #_playerResults, self.ndisplayresult ) do - _message = _message .. string.format( "\n[%d] %s", _i, _playerResults[_i].msg ) + for _i = 1, math.min(#_playerResults, self.ndisplayresult) do + _message = _message..string.format("\n[%d] %s", _i, _playerResults[_i].msg) end -- In case there are no scores yet. - if #_playerResults < 1 then - _message = _message .. "No player scored yet." + if #_playerResults<1 then + _message = _message.."No player scored yet." end -- Send message. - self:_DisplayMessageToGroup( _unit, _message, nil, true, true ) + self:_DisplayMessageToGroup(_unit, _message, nil, true, true) end end --- Display top 10 bombing run results of specific player. -- @param #RANGE self -- @param #string _unitName Name of the player unit. -function RANGE:_DisplayMyBombingResults( _unitName ) - self:F( _unitName ) +function RANGE:_DisplayMyBombingResults(_unitName) + self:F(_unitName) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) + local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) if _unit and _playername then -- Init message. - local _message = string.format( "My Top %d Bombing Results:\n", self.ndisplayresult ) + local _message = string.format("My Top %d Bombing Results:\n", self.ndisplayresult) -- Results from player. local _results = self.bombPlayerResults[_playername] -- No score so far. if _results == nil then - _message = _playername .. ": No Score yet." + _message = _playername..": No Score yet." else -- Sort results wrt to distance. - local _sort = function( a, b ) - return a.distance < b.distance - end - table.sort( _results, _sort ) + local _sort = function( a,b ) return a.distance < b.distance end + table.sort(_results,_sort) -- Loop over results. local _bestMsg = "" - for i, _result in pairs( _results ) do - local result = _result -- #RANGE.BombResult + for i,_result in pairs(_results) do + local result=_result --#RANGE.BombResult -- Message with name, weapon and distance. - _message = _message .. "\n" .. string.format( "[%d] %d m %03d° - %s - %s - %s hit", i, result.distance, result.radial, result.name, result.weapon, result.quality ) + _message = _message.."\n"..string.format("[%d] %d m %03d° - %s - %s - %s hit", i, result.distance, result.radial, result.name, result.weapon, result.quality) -- Store best/first result. if _bestMsg == "" then - _bestMsg = string.format( "%d m %03d° - %s - %s - %s hit", result.distance, result.radial, result.name, result.weapon, result.quality ) + _bestMsg = string.format("%d m %03d° - %s - %s - %s hit", result.distance, result.radial, result.name, result.weapon, result.quality) end -- Best 10 runs only. - if i == self.ndisplayresult then + if i==self.ndisplayresult then break end end -- Message. - _message = _message .. "\n\nBEST: " .. _bestMsg + _message = _message .."\n\nBEST: ".._bestMsg end -- Send message. - self:_DisplayMessageToGroup( _unit, _message, nil, true, true ) + self:_DisplayMessageToGroup(_unit, _message, nil, true, true) end end --- Display best bombing results of top 10 players. -- @param #RANGE self -- @param #string _unitName Name of player unit. -function RANGE:_DisplayBombingResults( _unitName ) - self:F( _unitName ) +function RANGE:_DisplayBombingResults(_unitName) + self:F(_unitName) -- Results table. local _playerResults = {} -- Get player unit and name. - local _unit, _player = self:_GetPlayerUnitAndName( _unitName ) + local _unit, _player = self:_GetPlayerUnitAndName(_unitName) -- Check if we have a unit with a player. if _unit and _player then -- Message header. - local _message = string.format( "Bombing Results - Top %d Players:\n", self.ndisplayresult ) + local _message = string.format("Bombing Results - Top %d Players:\n", self.ndisplayresult) -- Loop over players. - for _playerName, _results in pairs( self.bombPlayerResults ) do + for _playerName,_results in pairs(self.bombPlayerResults) do -- Find best result of player. local _best = nil - for _, _result in pairs( _results ) do + for _,_result in pairs(_results) do if _best == nil or _result.distance < _best.distance then - _best = _result + _best = _result end end -- Put best result of player into table. if _best ~= nil then - local bestres = string.format( "%s: %d m - %s - %s - %s hit", _playerName, _best.distance, _best.name, _best.weapon, _best.quality ) - table.insert( _playerResults, { msg = bestres, distance = _best.distance } ) + local bestres=string.format("%s: %d m - %s - %s - %s hit", _playerName, _best.distance, _best.name, _best.weapon, _best.quality) + table.insert(_playerResults, {msg = bestres, distance = _best.distance}) end end -- Sort list of player results. - local _sort = function( a, b ) - return a.distance < b.distance - end - table.sort( _playerResults, _sort ) + local _sort = function( a,b ) return a.distance < b.distance end + table.sort(_playerResults,_sort) -- Loop over player results. - for _i = 1, math.min( #_playerResults, self.ndisplayresult ) do - _message = _message .. string.format( "\n[%d] %s", _i, _playerResults[_i].msg ) + for _i = 1, math.min(#_playerResults, self.ndisplayresult) do + _message = _message..string.format("\n[%d] %s", _i, _playerResults[_i].msg) end -- In case there are no scores yet. - if #_playerResults < 1 then - _message = _message .. "No player scored yet." + if #_playerResults<1 then + _message = _message.."No player scored yet." end -- Send message. - self:_DisplayMessageToGroup( _unit, _message, nil, true, true ) + self:_DisplayMessageToGroup(_unit, _message, nil, true, true) end end --- Report information like bearing and range from player unit to range. -- @param #RANGE self -- @param #string _unitname Name of the player unit. -function RANGE:_DisplayRangeInfo( _unitname ) - self:F( _unitname ) +function RANGE:_DisplayRangeInfo(_unitname) + self:F(_unitname) -- Get player unit and player name. - local unit, playername = self:_GetPlayerUnitAndName( _unitname ) + local unit, playername = self:_GetPlayerUnitAndName(_unitname) -- Check if we have a player. if unit and playername then -- Message text. - local text = "" + local text="" -- Current coordinates. - local coord = unit:GetCoordinate() + local coord=unit:GetCoordinate() if self.location then - local settings = _DATABASE:GetPlayerSettings( playername ) or _SETTINGS -- Core.Settings#SETTINGS + local settings=_DATABASE:GetPlayerSettings(playername) or _SETTINGS --Core.Settings#SETTINGS -- Direction vector from current position (coord) to target (position). - local position = self.location -- Core.Point#COORDINATE - local bulls = position:ToStringBULLS( unit:GetCoalition(), settings ) - local lldms = position:ToStringLLDMS( settings ) - local llddm = position:ToStringLLDDM( settings ) - local rangealt = position:GetLandHeight() - local vec3 = coord:GetDirectionVec3( position ) - local angle = coord:GetAngleDegrees( vec3 ) - local range = coord:Get2DDistance( position ) + local position=self.location --Core.Point#COORDINATE + local bulls=position:ToStringBULLS(unit:GetCoalition(), settings) + local lldms=position:ToStringLLDMS(settings) + local llddm=position:ToStringLLDDM(settings) + local rangealt=position:GetLandHeight() + local vec3=coord:GetDirectionVec3(position) + local angle=coord:GetAngleDegrees(vec3) + local range=coord:Get2DDistance(position) -- Bearing string. - local Bs = string.format( "%03d°", angle ) + local Bs=string.format('%03d°', angle) local texthit if self.PlayerSettings[playername].flaredirecthits then - texthit = string.format( "Flare direct hits: ON (flare color %s)\n", self:_flarecolor2text( self.PlayerSettings[playername].flarecolor ) ) + texthit=string.format("Flare direct hits: ON (flare color %s)\n", self:_flarecolor2text(self.PlayerSettings[playername].flarecolor)) else - texthit = string.format( "Flare direct hits: OFF\n" ) + texthit=string.format("Flare direct hits: OFF\n") end local textbomb if self.PlayerSettings[playername].smokebombimpact then - textbomb = string.format( "Smoke bomb impact points: ON (smoke color %s)\n", self:_smokecolor2text( self.PlayerSettings[playername].smokecolor ) ) + textbomb=string.format("Smoke bomb impact points: ON (smoke color %s)\n", self:_smokecolor2text(self.PlayerSettings[playername].smokecolor)) else - textbomb = string.format( "Smoke bomb impact points: OFF\n" ) + textbomb=string.format("Smoke bomb impact points: OFF\n") end local textdelay if self.PlayerSettings[playername].delaysmoke then - textdelay = string.format( "Smoke bomb delay: ON (delay %.1f seconds)", self.TdelaySmoke ) + textdelay=string.format("Smoke bomb delay: ON (delay %.1f seconds)", self.TdelaySmoke) else - textdelay = string.format( "Smoke bomb delay: OFF" ) + textdelay=string.format("Smoke bomb delay: OFF") end -- Player unit settings. - local trange = string.format( "%.1f km", range / 1000 ) - local trangealt = string.format( "%d m", rangealt ) - local tstrafemaxalt = string.format( "%d m", self.strafemaxalt ) + local trange=string.format("%.1f km", range/1000) + local trangealt=string.format("%d m", rangealt) + local tstrafemaxalt=string.format("%d m", self.strafemaxalt) if settings:IsImperial() then - trange = string.format( "%.1f NM", UTILS.MetersToNM( range ) ) - trangealt = string.format( "%d feet", UTILS.MetersToFeet( rangealt ) ) - tstrafemaxalt = string.format( "%d feet", UTILS.MetersToFeet( self.strafemaxalt ) ) + trange=string.format("%.1f NM", UTILS.MetersToNM(range)) + trangealt=string.format("%d feet", UTILS.MetersToFeet(rangealt)) + tstrafemaxalt=string.format("%d feet", UTILS.MetersToFeet(self.strafemaxalt)) end -- Message. - text = text .. string.format( "Information on %s:\n", self.rangename ) - text = text .. string.format( "-------------------------------------------------------\n" ) - text = text .. string.format( "Bearing %s, Range %s\n", Bs, trange ) - text = text .. string.format( "%s\n", bulls ) - text = text .. string.format( "%s\n", lldms ) - text = text .. string.format( "%s\n", llddm ) - text = text .. string.format( "Altitude ASL: %s\n", trangealt ) - text = text .. string.format( "Max strafing alt AGL: %s\n", tstrafemaxalt ) - text = text .. string.format( "# of strafe targets: %d\n", self.nstrafetargets ) - text = text .. string.format( "# of bomb targets: %d\n", self.nbombtargets ) - text = text .. texthit - text = text .. textbomb - text = text .. textdelay + text=text..string.format("Information on %s:\n", self.rangename) + text=text..string.format("-------------------------------------------------------\n") + text=text..string.format("Bearing %s, Range %s\n", Bs, trange) + text=text..string.format("%s\n", bulls) + text=text..string.format("%s\n", lldms) + text=text..string.format("%s\n", llddm) + text=text..string.format("Altitude ASL: %s\n", trangealt) + text=text..string.format("Max strafing alt AGL: %s\n", tstrafemaxalt) + text=text..string.format("# of strafe targets: %d\n", self.nstrafetargets) + text=text..string.format("# of bomb targets: %d\n", self.nbombtargets) + text=text..texthit + text=text..textbomb + text=text..textdelay -- Send message to player group. - self:_DisplayMessageToGroup( unit, text, nil, true, true ) + self:_DisplayMessageToGroup(unit, text, nil, true, true) -- Debug output. - self:T2( self.id .. text ) + self:T2(self.id..text) end end end @@ -2547,148 +2641,150 @@ end --- Display bombing target locations to player. -- @param #RANGE self -- @param #string _unitname Name of the player unit. -function RANGE:_DisplayBombTargets( _unitname ) - self:F( _unitname ) +function RANGE:_DisplayBombTargets(_unitname) + self:F(_unitname) -- Get player unit and player name. - local _unit, _playername = self:_GetPlayerUnitAndName( _unitname ) + local _unit, _playername = self:_GetPlayerUnitAndName(_unitname) -- Check if we have a player. if _unit and _playername then -- Player settings. - local _settings = _DATABASE:GetPlayerSettings( _playername ) or _SETTINGS -- Core.Settings#SETTINGS + local _settings=_DATABASE:GetPlayerSettings(_playername) or _SETTINGS --Core.Settings#SETTINGS -- Message text. - local _text = "Bomb Target Locations:" + local _text="Bomb Target Locations:" - for _, _bombtarget in pairs( self.bombingTargets ) do - local bombtarget = _bombtarget -- #RANGE.BombTarget + for _,_bombtarget in pairs(self.bombingTargets) do + local bombtarget=_bombtarget --#RANGE.BombTarget -- Coordinate of bombtarget. - local coord = self:_GetBombTargetCoordinate( bombtarget ) + local coord=self:_GetBombTargetCoordinate(bombtarget) if coord then - + -- Get elevation - local elevation = coord:GetLandHeight() - local eltxt = string.format( "%d m", elevation ) + local elevation=coord:GetLandHeight() + local eltxt=string.format("%d m", elevation) if not _settings:IsMetric() then - elevation = UTILS.MetersToFeet( elevation ) - eltxt = string.format( "%d ft", elevation ) + elevation=UTILS.MetersToFeet(elevation) + eltxt=string.format("%d ft", elevation) end - local ca2g = coord:ToStringA2G( _unit, _settings ) - _text = _text .. string.format( "\n- %s:\n%s @ %s", bombtarget.name or "unknown", ca2g, eltxt ) + local ca2g=coord:ToStringA2G(_unit,_settings) + _text=_text..string.format("\n- %s:\n%s @ %s", bombtarget.name or "unknown", ca2g, eltxt) end end - self:_DisplayMessageToGroup( _unit, _text, 60, true, true ) + self:_DisplayMessageToGroup(_unit,_text, 120, true, true) end end --- Display pit location and heading to player. -- @param #RANGE self -- @param #string _unitname Name of the player unit. -function RANGE:_DisplayStrafePits( _unitname ) - self:F( _unitname ) +function RANGE:_DisplayStrafePits(_unitname) + self:F(_unitname) -- Get player unit and player name. - local _unit, _playername = self:_GetPlayerUnitAndName( _unitname ) + local _unit, _playername = self:_GetPlayerUnitAndName(_unitname) -- Check if we have a player. if _unit and _playername then -- Player settings. - local _settings = _DATABASE:GetPlayerSettings( _playername ) or _SETTINGS -- Core.Settings#SETTINGS + local _settings=_DATABASE:GetPlayerSettings(_playername) or _SETTINGS --Core.Settings#SETTINGS -- Message text. - local _text = "Strafe Target Locations:" + local _text="Strafe Target Locations:" - for _, _strafepit in pairs( self.strafeTargets ) do - local _target = _strafepit -- Wrapper.Positionable#POSITIONABLE + for _,_strafepit in pairs(self.strafeTargets) do + local _target=_strafepit --Wrapper.Positionable#POSITIONABLE -- Pit parameters. - local coord = _strafepit.coordinate -- Core.Point#COORDINATE - local heading = _strafepit.heading + local coord=_strafepit.coordinate --Core.Point#COORDINATE + local heading=_strafepit.heading -- Turn heading around ==> approach heading. - if heading > 180 then - heading = heading - 180 + if heading>180 then + heading=heading-180 else - heading = heading + 180 + heading=heading+180 end - local mycoord = coord:ToStringA2G( _unit, _settings ) - _text = _text .. string.format( "\n- %s: heading %03d°\n%s", _strafepit.name, heading, mycoord ) + 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 ) + self:_DisplayMessageToGroup(_unit,_text, nil, true, true) end end + --- Report weather conditions at range. Temperature, QFE pressure and wind data. -- @param #RANGE self -- @param #string _unitname Name of the player unit. -function RANGE:_DisplayRangeWeather( _unitname ) - self:F( _unitname ) +function RANGE:_DisplayRangeWeather(_unitname) + self:F(_unitname) -- Get player unit and player name. - local unit, playername = self:_GetPlayerUnitAndName( _unitname ) + local unit, playername = self:_GetPlayerUnitAndName(_unitname) -- Check if we have a player. if unit and playername then -- Message text. - local text = "" + local text="" -- Current coordinates. - local coord = unit:GetCoordinate() + local coord=unit:GetCoordinate() if self.location then -- Get atmospheric data at range location. - local position = self.location -- Core.Point#COORDINATE - local T = position:GetTemperature() - local P = position:GetPressure() - local Wd, Ws = position:GetWind() + local position=self.location --Core.Point#COORDINATE + local T=position:GetTemperature() + local P=position:GetPressure() + local Wd,Ws=position:GetWind() -- Get Beaufort wind scale. - local Bn, Bd = UTILS.BeaufortScale( Ws ) + local Bn,Bd=UTILS.BeaufortScale(Ws) - local WD = string.format( "%03d°", Wd ) - local Ts = string.format( "%d°C", T ) + local WD=string.format('%03d°', Wd) + local Ts=string.format("%d°C",T) - local hPa2inHg = 0.0295299830714 - local hPa2mmHg = 0.7500615613030 + local hPa2inHg=0.0295299830714 + local hPa2mmHg=0.7500615613030 - local settings = _DATABASE:GetPlayerSettings( playername ) or _SETTINGS -- Core.Settings#SETTINGS - local tT = string.format( "%d°C", T ) - local tW = string.format( "%.1f m/s", Ws ) - local tP = string.format( "%.1f mmHg", P * hPa2mmHg ) + local settings=_DATABASE:GetPlayerSettings(playername) or _SETTINGS --Core.Settings#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 - -- tT=string.format("%d°F", UTILS.CelsiusToFahrenheit(T)) - tW = string.format( "%.1f knots", UTILS.MpsToKnots( Ws ) ) - tP = string.format( "%.2f inHg", P * hPa2inHg ) + --tT=string.format("%d°F", UTILS.CelciusToFarenheit(T)) + tW=string.format("%.1f knots", UTILS.MpsToKnots(Ws)) + tP=string.format("%.2f inHg", P*hPa2inHg) end + -- Message text. - 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 ) + 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 ) + text=string.format("No range location defined for range %s.", self.rangename) end -- Send message to player group. - self:_DisplayMessageToGroup( unit, text, nil, true, true ) + self:_DisplayMessageToGroup(unit, text, nil, true, true) -- Debug output. - self:T2( self.id .. text ) + self:T2(self.id..text) else - self:T( self.id .. string.format( "ERROR! Could not find player unit in RangeInfo! Name = %s", _unitname ) ) + self:T(self.id..string.format("ERROR! Could not find player unit in RangeInfo! Name = %s", _unitname)) end end @@ -2700,23 +2796,23 @@ end -- @param #string _unitName Name of player unit. function RANGE:_CheckPlayers() - for playername, _playersettings in pairs( self.PlayerSettings ) do - local playersettings = _playersettings -- #RANGE.PlayerData + for playername,_playersettings in pairs(self.PlayerSettings) do + local playersettings=_playersettings --#RANGE.PlayerData - local unitname = playersettings.unitname - local unit = UNIT:FindByName( unitname ) + local unitname=playersettings.unitname + local unit=UNIT:FindByName(unitname) if unit and unit:IsAlive() then - if unit:IsInZone( self.rangezone ) then + if unit:IsInZone(self.rangezone) then ------------------------------ -- Player INSIDE Range Zone -- ------------------------------ if not playersettings.inzone then - playersettings.inzone = true - self:EnterRange( playersettings ) + playersettings.inzone=true + self:EnterRange(playersettings) end else @@ -2725,9 +2821,9 @@ function RANGE:_CheckPlayers() -- Player OUTSIDE Range Zone -- ------------------------------- - if playersettings.inzone == true then - playersettings.inzone = false - self:ExitRange( playersettings ) + if playersettings.inzone==true then + playersettings.inzone=false + self:ExitRange(playersettings) end end @@ -2739,64 +2835,66 @@ end --- Check if player is inside a strafing zone. If he is, we start looking for hits. If he was and left the zone again, the result is stored. -- @param #RANGE self -- @param #string _unitName Name of player unit. -function RANGE:_CheckInZone( _unitName ) - self:F2( _unitName ) +function RANGE:_CheckInZone(_unitName) + self:F2(_unitName) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) + local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + local unitheading = 0 --RangeBoss if _unit and _playername then --- Function to check if unit is in zone and facing in the right direction and is below the max alt. - local function checkme( targetheading, _zone ) - local zone = _zone -- Core.Zone#ZONE + local function checkme(targetheading, _zone) + local zone=_zone --Core.Zone#ZONE -- Heading check. - local unitheading = _unit:GetHeading() - local pitheading = targetheading - 180 - local deltaheading = unitheading - pitheading - local towardspit = math.abs( deltaheading ) <= 90 or math.abs( deltaheading - 360 ) <= 90 - + local unitheading = _unit:GetHeading() + unitheadingStrafe = _unit:GetHeading() --RangeBoss + 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 } -- DCS#Vec2 - local landheight = land.getHeight( vec2 ) - local unitalt = vec3.y - landheight - - if unitalt <= self.strafemaxalt then - local unitinzone = zone:IsVec2InZone( vec2 ) + + local vec3=_unit:GetVec3() + local vec2={x=vec3.x, y=vec3.z} --DCS#Vec2 + 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 -- Current position of player unit. - local _unitID = _unit:GetID() + local _unitID = _unit:GetID() -- Currently strafing? (strafeStatus is nil if not) local _currentStrafeRun = self.strafeStatus[_unitID] - if _currentStrafeRun then -- player has already registered for a strafing run. + if _currentStrafeRun then -- player has already registered for a strafing run. -- Get the current approach zone and check if player is inside. - local zone = _currentStrafeRun.zone.polygon -- Core.Zone#ZONE_POLYGON_BASE + local zone=_currentStrafeRun.zone.polygon --Core.Zone#ZONE_POLYGON_BASE -- Check if unit in zone and facing the right direction. - local unitinzone = checkme( _currentStrafeRun.zone.heading, zone ) + local unitinzone=checkme(_currentStrafeRun.zone.heading, zone) -- Check if player is in strafe zone and below max alt. if unitinzone then - + StrafeAircraftType = _unit:GetTypeName() --RangeBoss -- Still in zone, keep counting hits. Increase counter. - _currentStrafeRun.time = _currentStrafeRun.time + 1 + _currentStrafeRun.time = _currentStrafeRun.time+1 else -- Increase counter - _currentStrafeRun.time = _currentStrafeRun.time + 1 + _currentStrafeRun.time = _currentStrafeRun.time+1 if _currentStrafeRun.time <= 3 then @@ -2804,71 +2902,131 @@ function RANGE:_CheckInZone( _unitName ) self.strafeStatus[_unitID] = nil -- Message text. - local _msg = string.format( "%s left strafing zone %s too quickly. No Score.", _playername, _currentStrafeRun.zone.name ) + local _msg = string.format("%s left strafing zone %s too quickly. No Score.", _playername, _currentStrafeRun.zone.name) -- Send message. - self:_DisplayMessageToGroup( _unit, _msg, nil, true ) - + self:_DisplayMessageToGroup(_unit, _msg, nil, true) + if self.rangecontrol then - self.rangecontrol:NewTransmission( RANGE.Sound.RCLeftStrafePitTooQuickly.filename, RANGE.Sound.RCLeftStrafePitTooQuickly.duration, self.soundpath ) + self.rangecontrol:NewTransmission(RANGE.Sound.RCLeftStrafePitTooQuickly.filename, RANGE.Sound.RCLeftStrafePitTooQuickly.duration, self.soundpath) end else -- Get current ammo. - local _ammo = self:_GetAmmo( _unitName ) + local _ammo=self:_GetAmmo(_unitName) -- Result. local _result = self.strafeStatus[_unitID] - local _sound = nil -- #RANGE.Soundfile - + local _sound = nil --#RANGE.Soundfile + --[[ --RangeBoss commented out in order to implement strafe quality based on accuracy percentage, not the number of rounds on target -- Judge this pass. Text is displayed on summary. - if _result.hits >= _result.zone.goodPass * 2 then + if _result.hits >= _result.zone.goodPass*2 then _result.text = "EXCELLENT PASS" - _sound = RANGE.Sound.RCExcellentPass + _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 + _sound=RANGE.Sound.RCGoodPass + elseif _result.hits >= _result.zone.goodPass/2 then _result.text = "INEFFECTIVE PASS" - _sound = RANGE.Sound.RCIneffectivePass + _sound=RANGE.Sound.RCIneffectivePass else _result.text = "POOR PASS" - _sound = RANGE.Sound.RCPoorPass + _sound=RANGE.Sound.RCPoorPass + end + ]] + -- Calculate accuracy of run. Number of hits wrt number of rounds fired. + local shots=_result.ammo-_ammo + local accur=0 + if shots>0 then + accur=_result.hits/shots*100 + if accur > 100 then accur = 100 end end - -- Calculate accuracy of run. Number of hits wrt number of rounds fired. - local shots = _result.ammo - _ammo - local accur = 0 - if shots > 0 then - accur = _result.hits / shots * 100 - if accur > 100 then - accur = 100 + if invalidStrafe == true then-- + _result.text = "* INVALID - PASSED FOUL LINE *" + _sound=RANGE.Sound.RCPoorPass -- + else + if accur >= 90 then + _result.text = "DEADEYE PASS" + _sound=RANGE.Sound.RCExcellentPass + elseif accur >= 75 then + _result.text = "EXCELLENT PASS" + _sound=RANGE.Sound.RCExcellentPass + elseif accur >= 50 then + _result.text = "GOOD PASS" + _sound=RANGE.Sound.RCGoodPass + elseif accur >= 25 then + _result.text = "INEFFECTIVE PASS" + _sound=RANGE.Sound.RCIneffectivePass + else + _result.text = "POOR PASS" + _sound=RANGE.Sound.RCPoorPass end end + clientStrafed = true --RANGEBOSS -- Message text. - local _text = string.format( "%s, hits on target %s: %d", self:_myname( _unitName ), _result.zone.name, _result.hits ) + 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 ) + _text=_text..string.format("\nTotal rounds fired %d. Accuracy %.1f %%.", shots, accur) end - _text = _text .. string.format( "\n%s", _result.text ) + _text=_text..string.format("\n%s", _result.text) -- Send message. - self:_DisplayMessageToGroup( _unit, _text ) + self:_DisplayMessageToGroup(_unit, _text) + + --RangeBoss Edit for strafe table insert + + -- Local results. + + local result={} --#RANGE.BombResult + result.name=_result.zone.name or "unknown" + result.distance=0 + result.radial=0 + result.weapon="N/A" + result.quality="N/A" + result.player=_playernamee + result.time=timer.getAbsTime() + result.airframe=StrafeAircraftType + result.roundsFired=shots --RANGEBOSS + result.roundsHit=_result.hits --RANGEBOSS + result.roundsQuality=_result.text --RANGEBOSS + result.strafeAccuracy=accur + result.heading =unitheadingStrafe --RANGEBOSS + + Straferesult.name= _result.zone.name or "unknown" + Straferesult.distance=0 + Straferesult.radial=0 + Straferesult.weapon="N/A" + Straferesult.quality="N/A" + Straferesult.player=_playername + Straferesult.time=timer.getAbsTime() + Straferesult.airframe=StrafeAircraftType + Straferesult.roundsFired=shots + Straferesult.roundsHit= _result.hits + Straferesult.roundsQuality=_result.text + Straferesult.strafeAccuracy=accur + Straferesult.rangename=self.rangename + + -- Save trap sheet. + if playerData.targeton and self.targetsheet then + self:_SaveTargetSheet(_playername, result) + end + --RangeBoss edit for strafe data saved to file -- Voice over. - 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 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 ) + 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 ) + self.rangecontrol:NewTransmission(_sound.filename, _sound.duration, self.soundpath, nil, 0.5) end -- Set strafe status to nil. @@ -2876,7 +3034,7 @@ function RANGE:_CheckInZone( _unitName ) -- Save stats so the player can retrieve them. local _stats = self.strafePlayerResults[_playername] or {} - table.insert( _stats, _result ) + table.insert(_stats, _result) self.strafePlayerResults[_playername] = _stats end @@ -2885,32 +3043,34 @@ function RANGE:_CheckInZone( _unitName ) else -- Check to see if we're in any of the strafing zones (first time). - for _, _targetZone in pairs( self.strafeTargets ) do + for _,_targetZone in pairs(self.strafeTargets) do -- Get the current approach zone and check if player is inside. - local zone = _targetZone.polygon -- Core.Zone#ZONE_POLYGON_BASE + local zone=_targetZone.polygon --Core.Zone#ZONE_POLYGON_BASE -- Check if unit in zone and facing the right direction. - local unitinzone = checkme( _targetZone.heading, zone ) + local unitinzone=checkme(_targetZone.heading, zone) -- Player is inside zone. if unitinzone then -- Get ammo at the beginning of the run. - local _ammo = self:_GetAmmo( _unitName ) + local _ammo=self:_GetAmmo(_unitName) -- Init strafe status for this player. - self.strafeStatus[_unitID] = { hits = 0, zone = _targetZone, time = 1, ammo = _ammo, pastfoulline = false } + self.strafeStatus[_unitID] = {hits = 0, zone = _targetZone, time = 1, ammo=_ammo, pastfoulline=false} -- Rolling in! - local _msg = string.format( "%s, rolling in on strafe pit %s.", self:_myname( _unitName ), _targetZone.name ) - + 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.rangecontrol:NewTransmission(RANGE.Sound.RCRollingInOnStrafeTarget.filename, RANGE.Sound.RCRollingInOnStrafeTarget.duration, self.soundpath) + end + clientRollingIn = true --RANGEBOSS -- Send message. - self:_DisplayMessageToGroup( _unit, _msg, 10, true ) + self:_DisplayMessageToGroup(_unit, _msg, 10, true) + hypemanStrafeRollIn = _msg --RANGEBOSS -- We found our player. Skip remaining checks. break @@ -2930,18 +3090,18 @@ end --- Add menu commands for player. -- @param #RANGE self -- @param #string _unitName Name of player unit. -function RANGE:_AddF10Commands( _unitName ) - self:F( _unitName ) +function RANGE:_AddF10Commands(_unitName) + self:F(_unitName) -- Get player unit and name. - local _unit, playername = self:_GetPlayerUnitAndName( _unitName ) + local _unit, playername = self:_GetPlayerUnitAndName(_unitName) -- Check for player unit. if _unit and playername then -- Get group and ID. - local group = _unit:GetGroup() - local _gid = group:GetID() + local group=_unit:GetGroup() + local _gid=group:GetID() if group and _gid then @@ -2951,7 +3111,7 @@ function RANGE:_AddF10Commands( _unitName ) self.MenuAddedTo[_gid] = true -- Range root menu path. - local _rangePath = nil + local _rangePath=nil if RANGE.MenuF10Root then @@ -2959,7 +3119,8 @@ function RANGE:_AddF10Commands( _unitName ) -- MISSION LEVEL -- ------------------- - _rangePath = missionCommands.addSubMenuForGroup( _gid, self.rangename, RANGE.MenuF10Root ) + --_rangePath = missionCommands.addSubMenuForGroup(_gid, self.rangename, RANGE.MenuF10Root) + _rangePath = MENU_GROUP:New(group,"On the Range") else @@ -2969,60 +3130,63 @@ function RANGE:_AddF10Commands( _unitName ) -- Main F10 menu: F10/On the Range// if RANGE.MenuF10[_gid] == nil then - RANGE.MenuF10[_gid] = missionCommands.addSubMenuForGroup( _gid, "On the Range" ) + --RANGE.MenuF10[_gid]=missionCommands.addSubMenuForGroup(_gid, "On the Range") + RANGE.MenuF10[_gid]=MENU_GROUP:New(group,"On the Range") end - _rangePath = missionCommands.addSubMenuForGroup( _gid, self.rangename, RANGE.MenuF10[_gid] ) - + --_rangePath = missionCommands.addSubMenuForGroup(_gid, self.rangename, RANGE.MenuF10[_gid]) + _rangePath = MENU_GROUP:New(group,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 _statsPath = MENU_GROUP:New(group,"Statistics",_rangePath) + local _markPath = MENU_GROUP:New(group,"Mark Targets",_rangePath) + local _settingsPath = MENU_GROUP:New(group,"My Settings",_rangePath) + local _infoPath = MENU_GROUP:New(group,"Range Info",_rangePath) + -- F10/On the Range//My Settings/ - local _mysmokePath = missionCommands.addSubMenuForGroup( _gid, "Smoke Color", _settingsPath ) - local _myflarePath = missionCommands.addSubMenuForGroup( _gid, "Flare Color", _settingsPath ) + local _mysmokePath = MENU_GROUP:New(group,"Smoke Color",_settingsPath) + local _myflarePath = MENU_GROUP:New(group,"Flare Color",_settingsPath) - -- F10/On the Range//Mark Targets/ - 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 ) + --F10/On the Range//Mark Targets/ + local _MoMap = MENU_GROUP_COMMAND:New(group,"Mark On Map",_markPath,self._MarkTargetsOnMap, self, _unitName) + local _IllRng = MENU_GROUP_COMMAND:New(group, "Illuminate Range", _markPath, self._IlluminateBombTargets, self, _unitName) + local _SSpit = MENU_GROUP_COMMAND:New(group, "Smoke Strafe Pits", _markPath, self._SmokeStrafeTargetBoxes, self, _unitName) + local _SStgts = MENU_GROUP_COMMAND:New(group, "Smoke Strafe Tgts", _markPath, self._SmokeStrafeTargets, self, _unitName) + local _SBtgts = MENU_GROUP_COMMAND:New(group, "Smoke Bomb Tgts", _markPath, self._SmokeBombTargets, self, _unitName) -- F10/On the Range//Stats/ - 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 ) + local _AllSR = MENU_GROUP_COMMAND:New(group, "All Strafe Results", _statsPath, self._DisplayStrafePitResults, self, _unitName) + local _AllBR = MENU_GROUP_COMMAND:New(group, "All Bombing Results", _statsPath, self._DisplayBombingResults, self, _unitName) + local _MySR = MENU_GROUP_COMMAND:New(group, "My Strafe Results", _statsPath, self._DisplayMyStrafePitResults, self, _unitName) + local _MyBR = MENU_GROUP_COMMAND:New(group, "My Bomb Results", _statsPath, self._DisplayMyBombingResults, self, _unitName) + local _ResetST = MENU_GROUP_COMMAND:New(group, "Reset All Stats", _statsPath, self._ResetRangeStats, self, _unitName) -- F10/On the Range//My Settings/Smoke Color/ - 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 ) + local _BlueSM = MENU_GROUP_COMMAND:New(group, "Blue Smoke", _mysmokePath, self._playersmokecolor, self, _unitName, SMOKECOLOR.Blue) + local _GrSM = MENU_GROUP_COMMAND:New(group, "Green Smoke", _mysmokePath, self._playersmokecolor, self, _unitName, SMOKECOLOR.Green) + local _OrSM = MENU_GROUP_COMMAND:New(group, "Orange Smoke", _mysmokePath, self._playersmokecolor, self, _unitName, SMOKECOLOR.Orange) + local _ReSM = MENU_GROUP_COMMAND:New(group, "Red Smoke", _mysmokePath, self._playersmokecolor, self, _unitName, SMOKECOLOR.Red) + local _WhSm = MENU_GROUP_COMMAND:New(group, "White Smoke", _mysmokePath, self._playersmokecolor, self, _unitName, SMOKECOLOR.White) -- F10/On the Range//My Settings/Flare Color/ - 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 ) + local _GrFl = MENU_GROUP_COMMAND:New(group, "Green Flares", _myflarePath, self._playerflarecolor, self, _unitName, FLARECOLOR.Green) + local _ReFl = MENU_GROUP_COMMAND:New(group, "Red Flares", _myflarePath, self._playerflarecolor, self, _unitName, FLARECOLOR.Red) + local _WhFl = MENU_GROUP_COMMAND:New(group, "White Flares", _myflarePath, self._playerflarecolor, self, _unitName, FLARECOLOR.White) + local _YeFl = MENU_GROUP_COMMAND:New(group, "Yellow Flares", _myflarePath, self._playerflarecolor, self, _unitName, FLARECOLOR.Yellow) -- F10/On the Range//My Settings/ - 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 ) + local _SmDe = MENU_GROUP_COMMAND:New(group, "Smoke Delay On/Off", _settingsPath, self._SmokeBombDelayOnOff, self, _unitName) + local _SmIm = MENU_GROUP_COMMAND:New(group, "Smoke Impact On/Off", _settingsPath, self._SmokeBombImpactOnOff, self, _unitName) + local _FlHi = MENU_GROUP_COMMAND:New(group, "Flare Hits On/Off", _settingsPath, self._FlareDirectHitsOnOff, self, _unitName) + local _AlMeA = MENU_GROUP_COMMAND:New(group, "All Messages On/Off", _settingsPath, self._MessagesToPlayerOnOff, self, _unitName) + local _TrpSh = MENU_GROUP_COMMAND:New(group, "Targetsheet On/Off", _settingsPath, self._TargetsheetOnOff, self, _unitName) -- F10/On the Range//Range Information - 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 ) + local _WeIn = MENU_GROUP_COMMAND:New(group, "General Info", _infoPath, self._DisplayRangeInfo, self, _unitName) + local _WeRe = MENU_GROUP_COMMAND:New(group, "Weather Report", _infoPath, self._DisplayRangeWeather, self, _unitName) + local _BoTgtgs = MENU_GROUP_COMMAND:New(group, "Bombing Targets", _infoPath, self._DisplayBombTargets, self, _unitName) + local _StrPits = MENU_GROUP_COMMAND:New(group, "Strafe Pits", _infoPath, self._DisplayStrafePits, self, _unitName):Refresh() end else - self:E( self.id .. "Could not find group or group ID in AddF10Menu() function. Unit name: " .. _unitName ) + 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 ) + self:E(self.id.."Player unit does not exist in AddF10Menu() function. Unit name: ".._unitName) end end @@ -3031,83 +3195,84 @@ end -- Helper Functions ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Get the coordinate of a Bomb target. +--- Get the number of shells a unit currently has. -- @param #RANGE self -- @param #RANGE.BombTarget target Bomb target data. -- @return Core.Point#COORDINATE Target coordinate. -function RANGE:_GetBombTargetCoordinate( target ) +function RANGE:_GetBombTargetCoordinate(target) - local coord = nil -- Core.Point#COORDINATE + local coord=nil --Core.Point#COORDINATE - if target.type == RANGE.TargetType.UNIT then + if target.type==RANGE.TargetType.UNIT then if not target.move then -- Target should not move. - coord = target.coordinate + coord=target.coordinate else -- Moving target. Check if alive and get current position if target.target and target.target:IsAlive() then - coord = target.target:GetCoordinate() + coord=target.target:GetCoordinate() end end - elseif target.type == RANGE.TargetType.STATIC then + elseif target.type==RANGE.TargetType.STATIC then -- Static targets dont move. - coord = target.coordinate + coord=target.coordinate - elseif target.type == RANGE.TargetType.COORD then + elseif target.type==RANGE.TargetType.COORD then -- Coordinates dont move. - coord = target.coordinate + coord=target.coordinate else - self:E( self.id .. "ERROR: Unknown target type." ) + self:E(self.id.."ERROR: Unknown target type.") end return coord end + --- Get the number of shells a unit currently has. -- @param #RANGE self -- @param #string unitname Name of the player unit. -- @return Number of shells left -function RANGE:_GetAmmo( unitname ) - self:F2( unitname ) +function RANGE:_GetAmmo(unitname) + self:F2(unitname) -- Init counter. - local ammo = 0 + local ammo=0 - local unit, playername = self:_GetPlayerUnitAndName( unitname ) + local unit, playername = self:_GetPlayerUnitAndName(unitname) if unit and playername then - local has_ammo = false + local has_ammo=false - local ammotable = unit:GetAmmo() - self:T2( { ammotable = ammotable } ) + 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 ) ) + local weapons=#ammotable + self:T2(self.id..string.format("Number of weapons %d.", weapons)) - for w = 1, weapons do + for w=1,weapons do - local Nammo = ammotable[w]["count"] - local Tammo = ammotable[w]["desc"]["typeName"] + local Nammo=ammotable[w]["count"] + local Tammo=ammotable[w]["desc"]["typeName"] -- We are specifically looking for shells here. - if string.match( Tammo, "shell" ) then + if string.match(Tammo, "shell") then -- Add up all shells - ammo = ammo + Nammo + ammo=ammo+Nammo - local text = string.format( "Player %s has %d rounds ammo of type %s", playername, Nammo, Tammo ) - self:T( self.id .. text ) + 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 ) + local text=string.format("Player %s has %d ammo of type %s", playername, Nammo, Tammo) + self:T(self.id..text) end end end @@ -3119,46 +3284,46 @@ end --- Mark targets on F10 map. -- @param #RANGE self -- @param #string _unitName Name of the player unit. -function RANGE:_MarkTargetsOnMap( _unitName ) - self:F( _unitName ) +function RANGE:_MarkTargetsOnMap(_unitName) + self:F(_unitName) -- Get group. - local group = nil -- Wrapper.Group#GROUP + local group=nil --Wrapper.Group#GROUP if _unitName then - group = UNIT:FindByName( _unitName ):GetGroup() + group=UNIT:FindByName(_unitName):GetGroup() end -- Mark bomb targets. - for _, _bombtarget in pairs( self.bombingTargets ) do - local bombtarget = _bombtarget -- #RANGE.BombTarget - local coord = self:_GetBombTargetCoordinate( _bombtarget ) + for _,_bombtarget in pairs(self.bombingTargets) do + local bombtarget=_bombtarget --#RANGE.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 ) + 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 ) ) + coord:MarkToAll(string.format("Bomb target %s", bombtarget.name)) end end -- Mark strafe targets. - for _, _strafepit in pairs( self.strafeTargets ) do - for _, _target in pairs( _strafepit.targets ) do - local _target = _target -- Wrapper.Positionable#POSITIONABLE + for _,_strafepit in pairs(self.strafeTargets) do + for _,_target in pairs(_strafepit.targets) do + local _target=_target --Wrapper.Positionable#POSITIONABLE if _target and _target:IsAlive() then - local coord = _target:GetCoordinate() -- Core.Point#COORDINATE + local coord=_target:GetCoordinate() --Core.Point#COORDINATE if group then - -- coord:MarkToGroup("Strafe target ".._target:GetName(), group) - coord:MarkToGroup( string.format( "Strafe target %s:\n%s\n%s", _target:GetName(), coord:ToStringLLDMS(), coord:ToStringBULLS( group:GetCoalition() ) ), group ) + --coord:MarkToGroup("Strafe target ".._target:GetName(), group) + 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() ) + 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 ) + 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 @@ -3166,67 +3331,67 @@ end --- Illuminate targets. Fires illumination bombs at one random bomb and one random strafe target at a random altitude between 400 and 800 m. -- @param #RANGE self -- @param #string _unitName (Optional) Name of the player unit. -function RANGE:_IlluminateBombTargets( _unitName ) - self:F( _unitName ) +function RANGE:_IlluminateBombTargets(_unitName) + self:F(_unitName) -- All bombing target coordinates. - local bomb = {} + local bomb={} - for _, _bombtarget in pairs( self.bombingTargets ) do - local _target = _bombtarget.target -- Wrapper.Positionable#POSITIONABLE - local coord = self:_GetBombTargetCoordinate( _bombtarget ) + for _,_bombtarget in pairs(self.bombingTargets) do + local _target=_bombtarget.target --Wrapper.Positionable#POSITIONABLE + local coord=self:_GetBombTargetCoordinate(_bombtarget) if coord then - table.insert( bomb, coord ) + table.insert(bomb, coord) end end - if #bomb > 0 then - local coord = bomb[math.random( #bomb )] -- Core.Point#COORDINATE - local c = COORDINATE:New( coord.x, coord.y + math.random( self.illuminationminalt, self.illuminationmaxalt ), coord.z ) + if #bomb>0 then + local coord=bomb[math.random(#bomb)] --Core.Point#COORDINATE + local c=COORDINATE:New(coord.x,coord.y+math.random(self.illuminationminalt,self.illuminationmaxalt),coord.z) c:IlluminationBomb() end -- All strafe target coordinates. - local strafe = {} + local strafe={} - for _, _strafepit in pairs( self.strafeTargets ) do - for _, _target in pairs( _strafepit.targets ) do - local _target = _target -- Wrapper.Positionable#POSITIONABLE + for _,_strafepit in pairs(self.strafeTargets) do + for _,_target in pairs(_strafepit.targets) do + local _target=_target --Wrapper.Positionable#POSITIONABLE if _target and _target:IsAlive() then - local coord = _target:GetCoordinate() -- Core.Point#COORDINATE - table.insert( strafe, coord ) + local coord=_target:GetCoordinate() --Core.Point#COORDINATE + table.insert(strafe, coord) end end end -- Pick a random strafe target. - if #strafe > 0 then - local coord = strafe[math.random( #strafe )] -- Core.Point#COORDINATE - local c = COORDINATE:New( coord.x, coord.y + math.random( self.illuminationminalt, self.illuminationmaxalt ), coord.z ) + if #strafe>0 then + local coord=strafe[math.random(#strafe)] --Core.Point#COORDINATE + 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 ) + 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 --- Reset player statistics. -- @param #RANGE self -- @param #string _unitName Name of the player unit. -function RANGE:_ResetRangeStats( _unitName ) - self:F( _unitName ) +function RANGE:_ResetRangeStats(_unitName) + self:F(_unitName) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName( _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 ) + local text=string.format("%s, %s, your range stats were cleared.", self.rangename, _playername) + self:DisplayMessageToGroup(_unit, text, 5, false, true) end end @@ -3237,19 +3402,19 @@ end -- @param #number _time Duration how long the message is displayed. -- @param #boolean _clear Clear up old messages. -- @param #boolean display If true, display message regardless of player setting "Messages Off". -function RANGE:_DisplayMessageToGroup( _unit, _text, _time, _clear, display ) - self:F( { unit = _unit, text = _text, time = _time, clear = _clear } ) +function RANGE:_DisplayMessageToGroup(_unit, _text, _time, _clear, display) + self:F({unit=_unit, text=_text, time=_time, clear=_clear}) -- Defaults - _time = _time or self.Tmsg - if _clear == nil or _clear == false then - _clear = false + _time=_time or self.Tmsg + if _clear==nil or _clear==false then + _clear=false else - _clear = true + _clear=true end -- Messages globally disabled. - if self.messages == false then + if self.messages==false then return end @@ -3257,22 +3422,22 @@ function RANGE:_DisplayMessageToGroup( _unit, _text, _time, _clear, display ) if _unit and _unit:IsAlive() then -- Group ID. - local _gid = _unit:GetGroup():GetID() + local _gid=_unit:GetGroup():GetID() -- Get playername and player settings - local _, playername = self:_GetPlayerUnitAndName( _unit:GetName() ) - local playermessage = self.PlayerSettings[playername].messages + local _, playername=self:_GetPlayerUnitAndName(_unit:GetName()) + local playermessage=self.PlayerSettings[playername].messages -- Send message to player if messages enabled and not only for the examiner. - if _gid and (playermessage == true or display) and (not self.examinerexclusive) then - trigger.action.outTextForGroup( _gid, _text, _time, _clear ) + if _gid and (playermessage==true or display) and (not self.examinerexclusive) then + trigger.action.outTextForGroup(_gid, _text, _time, _clear) end -- Send message to examiner. - if self.examinergroupname ~= nil then - local _examinerid = GROUP:FindByName( self.examinergroupname ):GetID() + if self.examinergroupname~=nil then + local _examinerid=GROUP:FindByName(self.examinergroupname):GetID() if _examinerid then - trigger.action.outTextForGroup( _examinerid, _text, _time, _clear ) + trigger.action.outTextForGroup(_examinerid, _text, _time, _clear) end end end @@ -3282,20 +3447,20 @@ end --- Toggle status of smoking bomb impact points. -- @param #RANGE self -- @param #string unitname Name of the player unit. -function RANGE:_SmokeBombImpactOnOff( unitname ) - self:F( unitname ) +function RANGE:_SmokeBombImpactOnOff(unitname) + self:F(unitname) - local unit, playername = self:_GetPlayerUnitAndName( 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 ) + 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 ) + 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 ) + self:_DisplayMessageToGroup(unit, text, 5, false, true) end end @@ -3303,20 +3468,20 @@ end --- Toggle status of time delay for smoking bomb impact points -- @param #RANGE self -- @param #string unitname Name of the player unit. -function RANGE:_SmokeBombDelayOnOff( unitname ) - self:F( unitname ) +function RANGE:_SmokeBombDelayOnOff(unitname) + self:F(unitname) - local unit, playername = self:_GetPlayerUnitAndName( 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 ) + 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 ) + 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 ) + self:_DisplayMessageToGroup(unit, text, 5, false, true) end end @@ -3324,19 +3489,62 @@ end --- Toggle display messages to player. -- @param #RANGE self -- @param #string unitname Name of the player unit. -function RANGE:_MessagesToPlayerOnOff( unitname ) - self:F( unitname ) +function RANGE:_MessagesToPlayerOnOff(unitname) + self:F(unitname) - local unit, playername = self:_GetPlayerUnitAndName( 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 ) + 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 ) + 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 + +--- Targetsheet saves if player on or off. +-- @param #RANGE self +-- @param #string _unitname Name of the player unit. +function RANGE:_TargetsheetOnOff(_unitname) + self:F2(_unitname) + + -- Get player unit and player name. + local unit, playername = self:_GetPlayerUnitAndName(_unitname) + + -- Check if we have a player. + if unit and playername then + + -- Player data. + local playerData=self.PlayerSettings[playername] --#RANGE.PlayerData + + if playerData then + + -- Check if option is enabled at all. + local text="" + if self.targetsheet then + + -- Invert current setting. + playerData.targeton=not playerData.targeton + + -- Inform player. + if playerData.targeton==true then + text=string.format("roger, your targetsheets are now SAVED.") + else + text=string.format("affirm, your targetsheets are NOT SAVED.") + end + + else + text="negative, target sheet data recorder is broken on this range." + end + + -- Message to player. + --self:MessageToPlayer(playerData, text, nil, playerData.name, 5) + self:_DisplayMessageToGroup(unit,text,5,false,false) end - self:_DisplayMessageToGroup( unit, text, 5, false, true ) - self.PlayerSettings[playername].messages = not self.PlayerSettings[playername].messages end end @@ -3344,20 +3552,20 @@ end --- Toggle status of flaring direct hits of range targets. -- @param #RANGE self -- @param #string unitname Name of the player unit. -function RANGE:_FlareDirectHitsOnOff( unitname ) - self:F( unitname ) +function RANGE:_FlareDirectHitsOnOff(unitname) + self:F(unitname) - local unit, playername = self:_GetPlayerUnitAndName( 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 ) + 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 ) + 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 ) + self:_DisplayMessageToGroup(unit, text, 5, false, true) end end @@ -3365,21 +3573,21 @@ end --- Mark bombing targets with smoke. -- @param #RANGE self -- @param #string unitname Name of the player unit. -function RANGE:_SmokeBombTargets( unitname ) - self:F( unitname ) +function RANGE:_SmokeBombTargets(unitname) + self:F(unitname) - for _, _bombtarget in pairs( self.bombingTargets ) do - local _target = _bombtarget.target -- Wrapper.Positionable#POSITIONABLE - local coord = self:_GetBombTargetCoordinate( _bombtarget ) + for _,_bombtarget in pairs(self.bombingTargets) do + local _target=_bombtarget.target --Wrapper.Positionable#POSITIONABLE + local coord=self:_GetBombTargetCoordinate(_bombtarget) if coord then - coord:Smoke( self.BombSmokeColor ) + 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 ) + 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 @@ -3387,17 +3595,17 @@ end --- Mark strafing targets with smoke. -- @param #RANGE self -- @param #string unitname Name of the player unit. -function RANGE:_SmokeStrafeTargets( unitname ) - self:F( unitname ) +function RANGE:_SmokeStrafeTargets(unitname) + self:F(unitname) - for _, _target in pairs( self.strafeTargets ) do - _target.coordinate:Smoke( self.StrafeSmokeColor ) + 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 ) + 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 @@ -3405,21 +3613,21 @@ end --- Mark approach boxes of strafe targets with smoke. -- @param #RANGE self -- @param #string unitname Name of the player unit. -function RANGE:_SmokeStrafeTargetBoxes( unitname ) - self:F( unitname ) +function RANGE:_SmokeStrafeTargetBoxes(unitname) + self:F(unitname) - for _, _target in pairs( self.strafeTargets ) do - local zone = _target.polygon -- Core.Zone#ZONE - zone:SmokeZone( self.StrafePitSmokeColor, 4 ) - for _, _point in pairs( _target.smokepoints ) do - _point:SmokeOrange() -- Corners are smoked orange. + for _,_target in pairs(self.strafeTargets) do + local zone=_target.polygon --Core.Zone#ZONE + zone:SmokeZone(self.StrafePitSmokeColor, 4) + for _,_point in pairs(_target.smokepoints) do + _point:SmokeOrange() --Corners are smoked orange. 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 ) + 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 @@ -3428,14 +3636,14 @@ end -- @param #RANGE self -- @param #string _unitName Name of the player unit. -- @param Utilities.Utils#SMOKECOLOR color ID of the smoke color. -function RANGE:_playersmokecolor( _unitName, color ) - self:F( { unitname = _unitName, color = color } ) +function RANGE:_playersmokecolor(_unitName, color) + self:F({unitname=_unitName, color=color}) - local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) + 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 ) + 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 @@ -3444,14 +3652,14 @@ end -- @param #RANGE self -- @param #string _unitName Name of the player unit. -- @param Utilities.Utils#FLARECOLOR color ID of flare color. -function RANGE:_playerflarecolor( _unitName, color ) - self:F( { unitname = _unitName, color = color } ) +function RANGE:_playerflarecolor(_unitName, color) + self:F({unitname=_unitName, color=color}) - local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) + 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 ) + 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 @@ -3460,22 +3668,22 @@ end -- @param #RANGE self -- @param Utilities.Utils#SMOKECOLOR color Color Id. -- @return #string Color text. -function RANGE:_smokecolor2text( color ) - self:F( color ) +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" + 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 ) ) + txt=string.format("unknown color (%s)", tostring(color)) end return txt @@ -3485,20 +3693,20 @@ end -- @param #RANGE self -- @param Utilities.Utils#FLARECOLOR color Color Id. -- @return #string Color text. -function RANGE:_flarecolor2text( color ) - self:F( color ) +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" + 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 ) ) + txt=string.format("unknown color (%s)", tostring(color)) end return txt @@ -3507,34 +3715,34 @@ end --- Checks if a static object with a certain name exists. It also added it to the MOOSE data base, if it is not already in there. -- @param #RANGE self -- @param #string name Name of the potential static object. --- @return #boolean Returns true if a static with this name exists. Returns false if a unit with this name exists. Returns nil if neither unit or static exist. -function RANGE:_CheckStatic( name ) - self:F2( name ) +-- @return #boolean Returns true if a static with this name exists. Retruns false if a unit with this name exists. Returns nil if neither unit or static exist. +function RANGE:_CheckStatic(name) + self:F2(name) -- Get DCS static object. - local _DCSstatic = StaticObject.getByName( name ) + local _DCSstatic=StaticObject.getByName(name) if _DCSstatic and _DCSstatic:isExist() then - -- Static does exist at least in DCS. Check if it also in the MOOSE DB. - local _MOOSEstatic = STATIC:FindByName( name, false ) + --Static does exist at least in DCS. Check if it also in the MOOSE DB. + local _MOOSEstatic=STATIC:FindByName(name, false) -- If static is not yet in MOOSE DB, we add it. Can happen for cargo statics! if not _MOOSEstatic then - self:T( self.id .. string.format( "Adding DCS static to MOOSE database. Name = %s.", name ) ) - _DATABASE:AddStatic( name ) + 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 ) ) + self:T3(self.id..string.format("No static object with name %s exists.", name)) end -- Check if a unit has this name. - if UNIT:FindByName( name ) then + if UNIT:FindByName(name) then return false else - self:T3( self.id .. string.format( "No unit object with name %s exists.", name ) ) + self:T3(self.id..string.format("No unit object with name %s exists.", name)) end -- If not unit or static exist, we return nil. @@ -3545,17 +3753,17 @@ end -- @param #RANGE self -- @param Wrapper.Controllable#CONTROLLABLE controllable -- @return Maximum speed in km/h. -function RANGE:_GetSpeed( controllable ) - self:F2( controllable ) +function RANGE:_GetSpeed(controllable) + self:F2(controllable) -- Get DCS descriptors - local desc = controllable:GetDesc() + local desc=controllable:GetDesc() -- Get speed - local speed = 0 + local speed=0 if desc then - speed = desc.speedMax * 3.6 - self:T( { speed = speed } ) + speed=desc.speedMax*3.6 + self:T({speed=speed}) end return speed @@ -3567,20 +3775,20 @@ end -- @return Wrapper.Unit#UNIT Unit of player. -- @return #string Name of the player. -- @return nil If player does not exist. -function RANGE:_GetPlayerUnitAndName( _unitName ) - self:F2( _unitName ) +function RANGE:_GetPlayerUnitAndName(_unitName) + self:F2(_unitName) if _unitName ~= nil then -- Get DCS unit from its name. - local DCSunit = Unit.getByName( _unitName ) + local DCSunit=Unit.getByName(_unitName) if DCSunit then - local playername = DCSunit:getPlayerName() - local unit = UNIT:Find( DCSunit ) + local playername=DCSunit:getPlayerName() + local unit=UNIT:Find(DCSunit) - self:T2( { DCSunit = DCSunit, unit = unit, playername = playername } ) + self:T2({DCSunit=DCSunit, unit=unit, playername=playername}) if DCSunit and unit and playername then return unit, playername end @@ -3590,23 +3798,21 @@ function RANGE:_GetPlayerUnitAndName( _unitName ) end -- Return nil if we could not find a player. - return nil, nil + return nil,nil end ---- Returns a string which consists of the player name. +--- Returns a string which consits of this callsign and the player name. -- @param #RANGE self -- @param #string unitname Name of the player unit. -function RANGE:_myname( unitname ) - self:F2( unitname ) +function RANGE:_myname(unitname) + self:F2(unitname) - local unit = UNIT:FindByName( unitname ) - local pname = unit:GetPlayerName() + local unit=UNIT:FindByName(unitname) + local pname=unit:GetPlayerName() + local csign=unit:GetCallsign() - -- TODO: Either remove these leftovers, or implement them. - -- local csign=unit:GetCallsign() - -- return string.format("%s (%s)", csign, pname) - - return string.format( "%s", pname ) + --return string.format("%s (%s)", csign, pname) + return string.format("%s", pname) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 9f7588b245aaff0b400a5bb25a5b28ddc087fce3 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 4 Feb 2022 08:54:02 +0100 Subject: [PATCH 065/200] DETECTION - added 3 missing functions --- .../Moose/Functional/Detection.lua | 53 +++++++++++++++++-- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Functional/Detection.lua b/Moose Development/Moose/Functional/Detection.lua index 642748128..80465118a 100644 --- a/Moose Development/Moose/Functional/Detection.lua +++ b/Moose Development/Moose/Functional/Detection.lua @@ -2441,13 +2441,13 @@ do -- DETECTION_AREAS -- ## 4.1) Retrieve the Detected Unit Sets and Detected Zones -- -- The methods to manage the DetectedItems[].Set(s) are implemented in @{Functional.Detection#DETECTION_BASE} and - -- the methods to manage the DetectedItems[].Zone(s) is implemented in @{Functional.Detection#DETECTION_AREAS}. + -- the methods to manage the DetectedItems[].Zone(s) are implemented in @{Functional.Detection#DETECTION_AREAS}. -- -- Retrieve the DetectedItems[].Set with the method @{Functional.Detection#DETECTION_BASE.GetDetectedSet}(). A @{Core.Set#SET_UNIT} object will be returned. -- - -- Retrieve the formed @{Zone@ZONE_UNIT}s as a result of the grouping the detected units within the DetectionZoneRange, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZones}(). - -- To understand the amount of zones created, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZoneCount}(). - -- If you want to obtain a specific zone from the DetectedZones, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZone}() with a given index. + -- Retrieve the formed @{Zone@ZONE_UNIT}s as a result of the grouping the detected units within the DetectionZoneRange, use the method @{Functional.Detection#DETECTION_AREAS.GetDetectionZones}(). + -- To understand the amount of zones created, use the method @{Functional.Detection#DETECTION_AREAS.GetDetectionZoneCount}(). + -- If you want to obtain a specific zone from the DetectedZones, use the method @{Functional.Detection#DETECTION_AREAS.GetDetectionZoneByID}() with a given index. -- -- ## 4.4) Flare or Smoke detected units -- @@ -2489,7 +2489,50 @@ do -- DETECTION_AREAS return self end - + + --- Retrieve set of detected zones. + -- @param #DETECTION_AREAS self + -- @return Core.Set#SET_ZONE The @{Set} of ZONE_UNIT objects detected. + function DETECTION_AREAS:GetDetectionZones() + local zoneset = SET_ZONE:New() + for _ID,_Item in pairs (self.DetectedItems) do + local item = _Item -- #DETECTION_BASE.DetectedItem + if item.Zone then + zoneset:AddZone(item.Zone) + end + end + return zoneset + end + + --- Retrieve a specific zone by its ID (number) + -- @param #DETECTION_AREAS self + -- @param #number ID + -- @return Core.Zone#ZONE_UNIT The zone or nil if it does not exist + function DETECTION_AREAS:GetDetectionZoneByID(ID) + local zone = nil + for _ID,_Item in pairs (self.DetectedItems) do + local item = _Item -- #DETECTION_BASE.DetectedItem + if item.ID == ID then + zone = item.Zone + break + end + end + return zone + end + + --- Retrieve number of detected zones. + -- @param #DETECTION_AREAS self + -- @return #number The number of zones. + function DETECTION_AREAS:GetDetectionZoneCount() + local zoneset = 0 + for _ID,_Item in pairs (self.DetectedItems) do + if _Item.Zone then + zoneset = zoneset + 1 + end + end + return zoneset + end + --- Report summary of a detected item using a given numeric index. -- @param #DETECTION_AREAS self -- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem. From 7f4a5c48ec9f0d76125fbc0c54faa71c93b93716 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 8 Feb 2022 07:47:48 +0100 Subject: [PATCH 066/200] CTLD - add subcategory option, added `CTLD:AddCTLDZoneFromAirbase(AirbaseName, Type, Color, Active, HasBeacon)` --- Moose Development/Moose/Ops/CTLD.lua | 129 ++++++++++++++++++++++----- 1 file changed, 107 insertions(+), 22 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 0519fbed1..22de445b8 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -25,9 +25,10 @@ -- Date: Feb 2022 do + ------------------------------------------------------ --- **CTLD_ENGINEERING** class, extends Core.Base#BASE --- @type CTLD_ENGINEERING +--- @type CTLD_ENGINEERING -- @field #string ClassName -- @field #string lid -- @field #string Name @@ -37,6 +38,9 @@ do -- @field Wrapper.Unit#UNIT HeliUnit -- @field #string State -- @extends Core.Base#BASE + +--- +-- @field #CTLD_ENGINEERING CTLD_ENGINEERING = { ClassName = "CTLD_ENGINEERING", lid = "", @@ -299,10 +303,14 @@ CTLD_ENGINEERING = { return -1 end end - + +end + +do ------------------------------------------------------ --- **CTLD_CARGO** class, extends Core.Base#BASE -- @type CTLD_CARGO +-- @field #string ClassName Class name. -- @field #number ID ID of this cargo. -- @field #string Name Name for menu. -- @field #table Templates Table of #POSITIONABLE objects. @@ -312,9 +320,13 @@ CTLD_ENGINEERING = { -- @field #number CratesNeeded Crates needed to build. -- @field Wrapper.Positionable#POSITIONABLE Positionable Representation of cargo in the mission. -- @field #boolean HasBeenDropped True if dropped from heli. --- @field #number PerCrateMass Mass in kg --- @field #number Stock Number of builds available, -1 for unlimited +-- @field #number PerCrateMass Mass in kg. +-- @field #number Stock Number of builds available, -1 for unlimited. +-- @field #string Subcategory Sub-category name. -- @extends Core.Base#BASE + +--- +-- @field #CTLD_CARGO CTLD_CARGO = { ClassName = "CTLD_CARGO", ID = 0, @@ -331,9 +343,9 @@ CTLD_CARGO = { Mark = nil, } + --- --- Define cargo types. - -- @type CTLD_CARGO.Enum - -- @field #string Type Type of Cargo. + -- @field Enum CTLD_CARGO.Enum = { VEHICLE = "Vehicle", -- #string vehicles TROOPS = "Troops", -- #string troops @@ -357,8 +369,9 @@ CTLD_CARGO = { -- @param #boolean Dropped Cargo/Troops have been unloaded from a chopper. -- @param #number PerCrateMass Mass in kg -- @param #number Stock Number of builds available, nil for unlimited + -- @param #string Subcategory Name of subcategory, handy if using > 10 types to load. -- @return #CTLD_CARGO self - function CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass, Stock) + function CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass, Stock, Subcategory) -- Inherit everything from BASE class. local self=BASE:Inherit(self, BASE:New()) -- #CTLD self:T({ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped}) @@ -374,6 +387,7 @@ CTLD_CARGO = { self.PerCrateMass = PerCrateMass or 0 -- #number self.Stock = Stock or nil --#number self.Mark = nil + self.Subcategory = Subcategory or "Other" return self end @@ -384,6 +398,13 @@ CTLD_CARGO = { return self.ID end + --- Query Subcategory + -- @param #CTLD_CARGO self + -- @return #string SubCategory + function CTLD_CARGO:GetSubCat() + return self.Subcategory + end + --- Query Mass. -- @param #CTLD_CARGO self -- @return #number Mass in kg @@ -678,6 +699,7 @@ do -- my_ctld.FlareColor = FLARECOLOR.Red -- color to use when flaring from heli -- my_ctld.basetype = "container_cargo" -- default shape of the cargo container -- my_ctld.droppedbeacontimeout = 600 -- dropped beacon lasts 10 minutes +-- my_ctld.usesubcats = false -- use sub-category names for crates, adds an extra menu layer in "Get Crates", useful if you have > 10 crate types. -- -- ## 2.1 User functions -- @@ -999,7 +1021,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="1.0.5" +CTLD.version="1.0.6" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1159,6 +1181,10 @@ function CTLD:New(Coalition, Prefixes, Alias) self.saveinterval = 600 self.eventoninject = true + -- sub categories + self.usesubcats = false + self.subcats = {} + local AliaS = string.gsub(self.alias," ","_") self.filename = string.format("CTLD_%s_Persist.csv",AliaS) @@ -1900,13 +1926,14 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) end local templ = cargotype:GetTemplates() local sorte = cargotype:GetType() + local subcat = cargotype.Subcategory self.CargoCounter = self.CargoCounter + 1 local realcargo = nil if drop then - realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,true,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass) + realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,true,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass,subcat) table.insert(droppedcargo,realcargo) else - realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,false,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass) + realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,false,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass,subcat) Cargo:RemoveStock() end table.insert(self.Spawned_Cargo, realcargo) @@ -2922,6 +2949,16 @@ function CTLD:_RefreshF10Menus() end -- end for self.CtldUnits = _UnitList + -- subcats? + if self.usesubcats then + for _id,_cargo in pairs(self.Cargo_Crates) do + local entry = _cargo -- #CTLD_CARGO + if not self.subcats[entry.Subcategory] then + self.subcats[entry.Subcategory] = entry.Subcategory + end + end + end + -- build unit menus local menucount = 0 local menus = {} @@ -2970,11 +3007,26 @@ function CTLD:_RefreshF10Menus() if cancrates then local loadmenu = MENU_GROUP_COMMAND:New(_group,"Load crates",topcrates, self._LoadCratesNearby, self, _group, _unit) local cratesmenu = MENU_GROUP:New(_group,"Get Crates",topcrates) - for _,_entry in pairs(self.Cargo_Crates) do - local entry = _entry -- #CTLD_CARGO - menucount = menucount + 1 - local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) - menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) + + if self.usesubcats then + local subcatmenus = {} + for _name,_entry in pairs(self.subcats) do + subcatmenus[_name] = MENU_GROUP:New(_group,_name,cratesmenu) + end + for _,_entry in pairs(self.Cargo_Crates) do + local entry = _entry -- #CTLD_CARGO + local subcat = entry.Subcategory + menucount = menucount + 1 + local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) + menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates, self, _group, _unit, entry) + end + else + for _,_entry in pairs(self.Cargo_Crates) do + local entry = _entry -- #CTLD_CARGO + menucount = menucount + 1 + local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) + menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) + end end for _,_entry in pairs(self.Cargo_Statics) do local entry = _entry -- #CTLD_CARGO @@ -3051,7 +3103,8 @@ end -- @param #number NoCrates Number of crates needed to build this cargo. -- @param #number PerCrateMass Mass in kg of each crate -- @param #number Stock Number of groups in stock. Nil for unlimited. -function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock) +-- @param #string SubCategory Name of sub-category (optional). +function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory) self:T(self.lid .. " AddCratesCargo") if not self:_CheckTemplates(Templates) then self:E(self.lid .. "Crates Cargo for " .. Name .. " has missing template(s)!" ) @@ -3059,7 +3112,7 @@ function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock) end self.CargoCounter = self.CargoCounter + 1 -- Crates are not directly loadable - local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,false,NoCrates,nil,nil,PerCrateMass,Stock) + local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,false,NoCrates,nil,nil,PerCrateMass,Stock,SubCategory) table.insert(self.Cargo_Crates,cargo) return self end @@ -3104,7 +3157,8 @@ end -- @param #number NoCrates Number of crates needed to build this cargo. -- @param #number PerCrateMass Mass in kg of each crate -- @param #number Stock Number of groups in stock. Nil for unlimited. -function CTLD:AddCratesRepair(Name,Template,Type,NoCrates, PerCrateMass,Stock) +-- @param #string SubCategory Name of the sub-category (optional). +function CTLD:AddCratesRepair(Name,Template,Type,NoCrates, PerCrateMass,Stock,SubCategory) self:T(self.lid .. " AddCratesRepair") if not self:_CheckTemplates(Template) then self:E(self.lid .. "Repair Cargo for " .. Name .. " has a missing template!" ) @@ -3112,7 +3166,7 @@ function CTLD:AddCratesRepair(Name,Template,Type,NoCrates, PerCrateMass,Stock) end self.CargoCounter = self.CargoCounter + 1 -- Crates are not directly loadable - local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Template,Type,false,false,NoCrates,nil,nil,PerCrateMass,Stock) + local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Template,Type,false,false,NoCrates,nil,nil,PerCrateMass,Stock,SubCategory) table.insert(self.Cargo_Crates,cargo) return self end @@ -3245,7 +3299,7 @@ function CTLD:_GetVHFBeacon(Name) end ---- User function - Crates and adds a #CTLD.CargoZone zone for this CTLD instance. +--- User function - Creates and adds a #CTLD.CargoZone zone for this CTLD instance. -- Zones of type LOAD: Players load crates and troops here. -- Zones of type DROP: Players can drop crates here. Note that troops can be unloaded anywhere. -- Zone of type MOVE: Dropped troops and vehicles will start moving to the nearest zone of this type (also see options). @@ -3287,6 +3341,25 @@ function CTLD:AddCTLDZone(Name, Type, Color, Active, HasBeacon, Shiplength, Ship return self end +--- User function - Creates and adds a #CTLD.CargoZone zone for this CTLD instance from an Airbase or FARP name. +-- Zones of type LOAD: Players load crates and troops here. +-- Zones of type DROP: Players can drop crates here. Note that troops can be unloaded anywhere. +-- Zone of type MOVE: Dropped troops and vehicles will start moving to the nearest zone of this type (also see options). +-- @param #CTLD self +-- @param #string AirbaseName Name of the Airbase, can be e.g. AIRBASE.Caucasus.Beslan or "Beslan". For FARPs, this will be the UNIT name. +-- @param #string Type Type of this zone, #CTLD.CargoZoneType +-- @param #number Color Smoke/Flare color e.g. #SMOKECOLOR.Red +-- @param #string Active Is this zone currently active? +-- @param #string HasBeacon Does this zone have a beacon if it is active? +-- @return #CTLD self +function CTLD:AddCTLDZoneFromAirbase(AirbaseName, Type, Color, Active, HasBeacon) + self:T(self.lid .. " AddCTLDZoneFromAirbase") + local AFB = AIRBASE:FindByName(AirbaseName) + local name = AFB:GetZone():GetName() + self:T(self.lid .. "AFB " .. AirbaseName .. " ZoneName " .. name) + self:AddCTLDZone(name, Type, Color, Active, HasBeacon) + return self +end --- (Internal) Function to create a dropped beacon -- @param #CTLD self @@ -3403,6 +3476,9 @@ function CTLD:_AddRadioBeacon(Name, Sound, Mhz, Modulation, IsShip, IsDropped) Zone = self.droppedbeaconref[Name] else Zone = ZONE:FindByName(Name) + if not Zone then + Zone = AIRBASE:FindByName(Name):GetZone() + end end local Sound = Sound or "beacon.ogg" if IsDropped and Zone then @@ -3499,12 +3575,18 @@ function CTLD:IsUnitInZone(Unit,Zonetype) zonecoord = zone:GetCoordinate() zoneradius = czone.shiplength zonewidth = czone.shipwidth - else + elseif ZONE:FindByName(zonename) then zone = ZONE:FindByName(zonename) self:T("Checking Zone: "..zonename) zonecoord = zone:GetCoordinate() zoneradius = zone:GetRadius() zonewidth = zoneradius + elseif AIRBASE:FindByName(zonename) then + zone = AIRBASE:FindByName(zonename):GetZone() + self:T("Checking Zone: "..zonename) + zonecoord = zone:GetCoordinate() + zoneradius = zone:GetRadius() + zonewidth = zoneradius end local distance = self:_GetDistance(zonecoord,unitcoord) if distance <= zoneradius and active then @@ -3571,6 +3653,9 @@ function CTLD:SmokeZoneNearBy(Unit, Flare) zone = UNIT:FindByName(zonename) else zone = ZONE:FindByName(zonename) + if not zone then + zone = AIRBASE:FindByName(zonename):GetZone() + end end local zonecoord = zone:GetCoordinate() local active = CZone.active @@ -4595,7 +4680,7 @@ end for _id,_entry in pairs (loadeddata) do local dataset = UTILS.Split(_entry,",") - -- 1=Group,2=x,3=y,4=z,5=CargoName,6=CargoTemplates,7=CargoType,8=CratesNeeded,9=CrateMass + -- 1=Group,2=x,3=y,4=z,5=CargoName,6=CargoTemplates,7=CargoType,8=CratesNeeded,9=CrateMass,10=SubCategory local groupname = dataset[1] local vec2 = {} vec2.x = tonumber(dataset[2]) From a4163017d5290f476a18c51bd50ab60a60a47fa6 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 8 Feb 2022 07:49:04 +0100 Subject: [PATCH 067/200] CSAR - CSAR:SpawnCSARAtZone(Zone ...) - Zone can now be a ZONE object as well as a string --- Moose Development/Moose/Ops/CSAR.lua | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 859c2e3c2..4a3253a42 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -22,7 +22,7 @@ -- @module Ops.CSAR -- @image OPS_CSAR.jpg --- Date: Dec 2021 +-- Date: Feb 2022 ------------------------------------------------------------------------- --- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM @@ -248,7 +248,7 @@ CSAR.AircraftType["UH-60L"] = 10 --- CSAR class version. -- @field #string version -CSAR.version="1.0.2" +CSAR.version="1.0.3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -697,7 +697,7 @@ end --- (Internal) Function to add a CSAR object into the scene at a zone coordinate. For mission designers wanting to add e.g. PoWs to the scene. -- @param #CSAR self --- @param #string _zone Name of the zone. +-- @param #string _zone Name of the zone. Can also be passed as a (normal, round) ZONE object. -- @param #number _coalition Coalition. -- @param #string _description (optional) Description. -- @param #boolean _randomPoint (optional) Random yes or no. @@ -708,7 +708,16 @@ end function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _nomessage, unitname, typename, forcedesc) self:T(self.lid .. " _SpawnCsarAtZone") local freq = self:_GenerateADFFrequency() - local _triggerZone = ZONE:New(_zone) -- trigger to use as reference position + + local _triggerZone = nil + if type(_zone) == "string" then + _triggerZone = ZONE:New(_zone) -- trigger to use as reference position + elseif type(_zone) == "table" and _zone.ClassName then + if string.find(_zone.ClassName, "ZONE",1) then + _triggerZone = _zone -- is already a zone + end + end + if _triggerZone == nil then self:E(self.lid.."ERROR: Can\'t find zone called " .. _zone, 10) return @@ -742,7 +751,7 @@ end --- Function to add a CSAR object into the scene at a zone coordinate. For mission designers wanting to add e.g. PoWs to the scene. -- @param #CSAR self --- @param #string Zone Name of the zone. +-- @param #string Zone Name of the zone. Can also be passed as a (normal, round) ZONE object. -- @param #number Coalition Coalition. -- @param #string Description (optional) Description. -- @param #boolean RandomPoint (optional) Random yes or no. From ba5ccc1021674213a3366110a69838c8e5b46b4c Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 13 Feb 2022 12:08:23 +0100 Subject: [PATCH 068/200] CTLD:SetTroopDropZoneRadius(Radius) --- Moose Development/Moose/Ops/CTLD.lua | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 22de445b8..5742a6fc8 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -914,8 +914,8 @@ do -- @field #CTLD CTLD = { ClassName = "CTLD", - verbose = 0, - lid = "", + verbose = 0, + lid = "", coalition = 1, coalitiontxt = "blue", PilotGroups = {}, -- #GROUP_SET of heli pilots @@ -1021,7 +1021,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="1.0.6" +CTLD.version="1.0.7" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1149,6 +1149,7 @@ function CTLD:New(Coalition, Prefixes, Alias) self.smokedistance = 2000 self.movetroopstowpzone = true self.movetroopsdistance = 5000 + self.troopdropzoneradius = 100 -- added support Hercules Mod self.enableHercules = false @@ -1418,6 +1419,17 @@ function CTLD:_GenerateVHFrequencies() return self end +--- (User) Set drop zone radius for troop drops in meters. Minimum distance is 25m for security reasons. +-- @param #CTLD self +-- @param #number Radius The radius to use. +function CTLD:SetTroopDropZoneRadius(Radius) + self:T(self.lid .. " SetTroopDropZoneRadius") + local tradius = Radius or 100 + if tradius < 25 then tradius = 25 end + self.troopdropzoneradius = tradius + return self +end + --- (Internal) Event handler function -- @param #CTLD self -- @param Core.Event#EVENTDATA EventData @@ -2482,7 +2494,7 @@ function CTLD:_UnloadTroops(Group, Unit) local name = cargo:GetName() or "none" local temptable = cargo:GetTemplates() or {} local position = Group:GetCoordinate() - local zoneradius = 100 -- drop zone radius + local zoneradius = self.troopdropzoneradius or 100 -- drop zone radius local factor = 1 if IsHerc then factor = cargo:GetCratesNeeded() or 1 -- spread a bit more if airdropping From a0d492cd2de47715d046284e2db7b79c4fb7bebf Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 15 Feb 2022 14:41:31 +0100 Subject: [PATCH 069/200] added back GROUP:GetHighestThreat() --- Moose Development/Moose/Wrapper/Group.lua | 34 +++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 2708b8ca3..7e39503f8 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -2609,6 +2609,40 @@ function GROUP:GetSkill() return skill end +--- Get the unit in the group with the highest threat level, which is still alive. +-- @param #GROUP self +-- @return Wrapper.Unit#UNIT The most dangerous unit in the group. +-- @return #number Threat level of the unit. +function GROUP:GetHighestThreat() + + -- Get units of the group. + local units=self:GetUnits() + + if units then + + local threat=nil ; local maxtl=0 + for _,_unit in pairs(units or {}) do + local unit=_unit --Wrapper.Unit#UNIT + + if unit and unit:IsAlive() then + + -- Threat level of group. + local tl=unit:GetThreatLevel() + + -- Check if greater the current threat. + if tl>maxtl then + maxtl=tl + threat=unit + end + end + end + + return threat, maxtl + end + + return nil, nil +end + --do -- Smoke -- ----- Signal a flare at the position of the GROUP. From 00c8690e61ec067f622557c928f889a06993dc4d Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 15 Feb 2022 18:07:26 +0100 Subject: [PATCH 070/200] CTLD - corrected default weight limits when using CTLD:UnitCapabilities() - was setting loadable weight to zero --- Moose Development/Moose/Ops/CTLD.lua | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 5742a6fc8..baa437a5a 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1021,7 +1021,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="1.0.7" +CTLD.version="1.0.8" --- Instantiate a new CTLD. -- @param #CTLD self @@ -3702,8 +3702,8 @@ end -- @param #boolean Cantroops Unit can load troops. Default false. -- @param #number Cratelimit Unit can carry number of crates. Default 0. -- @param #number Trooplimit Unit can carry number of troops. Default 0. - -- @param #number Length Unit lenght (in mteres) for the load radius. Default 20. - -- @param #number Maxcargoweight Maxmimum weight in kgs this helo can carry. Default 0. + -- @param #number Length Unit lenght (in metres) for the load radius. Default 20. + -- @param #number Maxcargoweight Maxmimum weight in kgs this helo can carry. Default 500. function CTLD:UnitCapabilities(Unittype, Cancrates, Cantroops, Cratelimit, Trooplimit, Length, Maxcargoweight) self:T(self.lid .. " UnitCapabilities") local unittype = nil @@ -3716,6 +3716,13 @@ end else return self end + local length = 20 + local maxcargo = 500 + local existingcaps = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + if existingcaps then + length = existingcaps.length or 20 + maxcargo = existingcaps.cargoweightlimit or 500 + end -- set capabilities local capabilities = {} -- #CTLD.UnitCapabilities capabilities.type = unittype @@ -3723,8 +3730,8 @@ end capabilities.troops = Cantroops or false capabilities.cratelimit = Cratelimit or 0 capabilities.trooplimit = Trooplimit or 0 - capabilities.length = Length or 20 - capabilities.cargoweightlimit = Maxcargoweight or 0 + capabilities.length = Length or length + capabilities.cargoweightlimit = Maxcargoweight or maxcargo self.UnitTypes[unittype] = capabilities return self end From 6c6cdcf7630e85c390b1204ec76467e2389ef256 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 16 Feb 2022 10:06:04 +0100 Subject: [PATCH 071/200] CTLD - fix list/build side effect from adding weight limits to helos --- Moose Development/Moose/Ops/CTLD.lua | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index baa437a5a..251eb952f 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1021,7 +1021,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="1.0.8" +CTLD.version="1.0.9" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1844,7 +1844,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities local canloadcratesno = capabilities.cratelimit local loaddist = self.CrateDistance or 35 - local nearcrates, numbernearby = self:_FindCratesNearby(Group,Unit,loaddist) + local nearcrates, numbernearby = self:_FindCratesNearby(Group,Unit,loaddist,true) if numbernearby >= canloadcratesno and not drop then self:_SendMessage("There are enough crates nearby already! Take care of those first!", 10, false, Group) return self @@ -2027,7 +2027,7 @@ end function CTLD:_ListCratesNearby( _group, _unit) self:T(self.lid .. " _ListCratesNearby") local finddist = self.CrateDistance or 35 - local crates,number = self:_FindCratesNearby(_group,_unit, finddist) -- #table + local crates,number = self:_FindCratesNearby(_group,_unit, finddist,true) -- #table if number > 0 then local text = REPORT:New("Crates Found Nearby:") text:Add("------------------------------------------------------------") @@ -2084,9 +2084,10 @@ end -- @param Wrapper.Group#GROUP _group Group -- @param Wrapper.Unit#UNIT _unit Unit -- @param #number _dist Distance +-- @param #boolean _ignoreweight Find everything in range, ignore loadable weight -- @return #table Table of crates -- @return #number Number Number of crates found -function CTLD:_FindCratesNearby( _group, _unit, _dist) +function CTLD:_FindCratesNearby( _group, _unit, _dist, _ignoreweight) self:T(self.lid .. " _FindCratesNearby") local finddist = _dist local location = _group:GetCoordinate() @@ -2109,7 +2110,7 @@ function CTLD:_FindCratesNearby( _group, _unit, _dist) if static and static:IsAlive() then local staticpos = static:GetCoordinate() local distance = self:_GetDistance(location,staticpos) - if distance <= finddist and static and weight <= maxloadable then + if distance <= finddist and static and (weight <= maxloadable or _ignoreweight) then index = index + 1 table.insert(found, staticid, cargo) maxloadable = maxloadable - weight @@ -2167,7 +2168,7 @@ function CTLD:_LoadCratesNearby(Group, Unit) end -- get nearby crates local finddist = self.CrateDistance or 35 - local nearcrates,number = self:_FindCratesNearby(Group,Unit,finddist) -- #table + local nearcrates,number = self:_FindCratesNearby(Group,Unit,finddist,false) -- #table self:T(self.lid .. " Crates found: " .. number) if number == 0 and self.hoverautoloading then return self -- exit @@ -2663,7 +2664,7 @@ function CTLD:_BuildCrates(Group, Unit,Engineering) end -- get nearby crates local finddist = self.CrateDistance or 35 - local crates,number = self:_FindCratesNearby(Group,Unit, finddist) -- #table + local crates,number = self:_FindCratesNearby(Group,Unit, finddist,true) -- #table local buildables = {} local foundbuilds = false local canbuild = false @@ -2747,7 +2748,7 @@ function CTLD:_RepairCrates(Group, Unit, Engineering) self:T(self.lid .. " _RepairCrates") -- get nearby crates local finddist = self.CrateDistance or 35 - local crates,number = self:_FindCratesNearby(Group,Unit,finddist) -- #table + local crates,number = self:_FindCratesNearby(Group,Unit,finddist,true) -- #table local buildables = {} local foundbuilds = false local canbuild = false @@ -4023,7 +4024,7 @@ end self:T(_engineers.lid .. _engineers:GetStatus()) if wrenches and wrenches:IsAlive() then if engineers:IsStatus("Running") or engineers:IsStatus("Searching") then - local crates,number = self:_FindCratesNearby(wrenches,nil, self.EngineerSearch) -- #table + local crates,number = self:_FindCratesNearby(wrenches,nil, self.EngineerSearch,true) -- #table engineers:Search(crates,number) elseif engineers:IsStatus("Moving") then engineers:Move() From 3d9bb14713d31b601011890e1cb1eaaf2b03a400 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 17 Feb 2022 17:41:32 +0100 Subject: [PATCH 072/200] CSAR - added "wet feet" option for a 2nd template to be used over water --- Moose Development/Moose/Ops/CSAR.lua | 43 +++++++++++++++++++++------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 4a3253a42..ccb8a472f 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -118,7 +118,8 @@ -- self.SRSModulation = radio.modulation.AM -- modulation -- -- -- self.csarUsePara = false -- If set to true, will use the LandingAfterEjection Event instead of Ejection --shagrat --- +-- self.wetfeettemplate = "man in floating thingy" -- if you use a mod to have a pilot in a rescue float, put the template name in here for wet feet spawns +-- -- ## 3. Results -- -- Number of successful landings with save pilots and aggregated number of saved pilots is stored in these variables in the object: @@ -227,8 +228,9 @@ CSAR = { -- @field #number frequency Frequency of the NDB. -- @field #string player Player name if applicable. -- @field Wrapper.Group#GROUP group Spawned group object. --- @field #number timestamp Timestamp for approach process --- @field #boolean alive Group is alive or dead/rescued +-- @field #number timestamp Timestamp for approach process. +-- @field #boolean alive Group is alive or dead/rescued. +-- @field #boolean wetfeet Group is spawned over (deep) water. --- All slot / Limit settings -- @type CSAR.AircraftType @@ -248,7 +250,7 @@ CSAR.AircraftType["UH-60L"] = 10 --- CSAR class version. -- @field #string version -CSAR.version="1.0.3" +CSAR.version="1.0.4" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -390,6 +392,10 @@ function CSAR:New(Coalition, Template, Alias) -- added 0.1.3 self.csarUsePara = false -- shagrat set to true, will use the LandingAfterEjection Event instead of Ejection + + -- added 0.1.4 + self.wetfeettemplate = nil + self.usewetfeet = false -- WARNING - here\'ll be dragons -- for this to work you need to de-sanitize your mission environment in \Scripts\MissionScripting.lua @@ -502,8 +508,9 @@ end -- @param #string Typename Typename of unit. -- @param #number Frequency Frequency of the NDB in Hz -- @param #string Playername Name of Player (if applicable) +-- @param #boolean Wetfeet Ejected over water -- @return #CSAR self. -function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername) +function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername,Wetfeet) self:T({"_CreateDownedPilotTrack",Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername}) -- create new entry @@ -519,6 +526,7 @@ function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Descript DownedPilot.group = Group DownedPilot.timestamp = 0 DownedPilot.alive = true + DownedPilot.wetfeet = Wetfeet or false -- Add Pilot local PilotTable = self.downedPilots @@ -568,17 +576,23 @@ end -- @param #number country Country for template. -- @param Core.Point#COORDINATE point Coordinate to spawn at. -- @param #number frequency Frequency of the pilot's beacon +-- @param #boolean wetfeet Spawn is over water -- @return Wrapper.Group#GROUP group The #GROUP object. -- @return #string alias The alias name. -function CSAR:_SpawnPilotInField(country,point,frequency) - self:T({country,point,frequency}) +function CSAR:_SpawnPilotInField(country,point,frequency,wetfeet) + self:T({country,point,frequency,tostring(wetfeet)}) local freq = frequency or 1000 local freq = freq / 1000 -- kHz for i=1,10 do math.random(i,10000) end - if point:IsSurfaceTypeWater() then point.y = 0 end + if point:IsSurfaceTypeWater() or wetfeet then + point.y = 0 + end local template = self.template + if self.usewetfeet and wetfeet then + template = self.wetfeettemplate + end local alias = string.format("Pilot %.2fkHz-%d", freq, math.random(1,99)) local coalition = self.coalition local pilotcacontrol = self.allowDownedPilotCAcontrol -- Switch AI on/oof - is this really correct for CA? @@ -644,13 +658,19 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla self:T({_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description}) local template = self.template + local wetfeet = false + + local surface = _point:GetSurfaceType() + if surface == land.SurfaceType.WATER then + wetfeet = true + end if not _freq then _freq = self:_GenerateADFFrequency() if not _freq then _freq = 333000 end --noob catch end - local _spawnedGroup, _alias = self:_SpawnPilotInField(_country,_point,_freq) + local _spawnedGroup, _alias = self:_SpawnPilotInField(_country,_point,_freq,wetfeet) local _typeName = _typeName or "Pilot" @@ -688,7 +708,7 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla local _GroupName = _spawnedGroup:GetName() or _alias - self:_CreateDownedPilotTrack(_spawnedGroup,_GroupName,_coalition,_unitName,_text,_typeName,_freq,_playerName) + self:_CreateDownedPilotTrack(_spawnedGroup,_GroupName,_coalition,_unitName,_text,_typeName,_freq,_playerName,wetfeet) self:_InitSARForPilot(_spawnedGroup, _unitName, _freq, noMessage) --shagrat use unitName to have the aircraft callsign / descriptive "name" etc. @@ -1999,6 +2019,9 @@ function CSAR:onafterStart(From, Event, To) self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart() end self.mash = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart() -- currently only GROUP objects, maybe support STATICs also? + if self.wetfeettemplate then + self.usewetfeet = true + end self:__Status(-10) return self end From 84f231ea08decd2b50ed3ea661e574f1996c951d Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 18 Feb 2022 08:22:47 +0100 Subject: [PATCH 073/200] CSAR - added wet feet check if also using csarUsePara (no landing event triggered) --- Moose Development/Moose/Ops/CSAR.lua | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index ccb8a472f..96b4069e7 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -118,8 +118,8 @@ -- self.SRSModulation = radio.modulation.AM -- modulation -- -- -- self.csarUsePara = false -- If set to true, will use the LandingAfterEjection Event instead of Ejection --shagrat --- self.wetfeettemplate = "man in floating thingy" -- if you use a mod to have a pilot in a rescue float, put the template name in here for wet feet spawns --- +-- self.wetfeettemplate = "man in floating thingy" -- if you use a mod to have a pilot in a rescue float, put the template name in here for wet feet spawns. Note: in conjunction with csarUsePara this might create dual ejected pilots in edge cases. +-- -- ## 3. Results -- -- Number of successful landings with save pilots and aggregated number of saved pilots is stored in these variables in the object: @@ -250,7 +250,7 @@ CSAR.AircraftType["UH-60L"] = 10 --- CSAR class version. -- @field #string version -CSAR.version="1.0.4" +CSAR.version="1.0.4a" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -955,9 +955,19 @@ function CSAR:_EventHandler(EventData) if self.limitmaxdownedpilots and self:_ReachedPilotLimit() then return end - + + + -- TODO: Over water check --- EVENTS.LandingAfterEjection NOT triggered by DCS, so handle csarUsePara = true case + -- might create dual pilots in edge cases + + local wetfeet = false + + local surface = _unit:GetCoordinate():GetSurfaceType() + if surface == land.SurfaceType.WATER then + wetfeet = true + end -- all checks passed, get going. - if self.csarUsePara == false then --shagrat check parameter LandingAfterEjection, if true don't spawn a Pilot from EJECTION event, wait for the Chute to land + if self.csarUsePara == false or (self.csarUsePara and wetfeet ) then --shagrat check parameter LandingAfterEjection, if true don't spawn a Pilot from EJECTION event, wait for the Chute to land local _freq = self:_GenerateADFFrequency() self:_AddCsar(_coalition, _unit:GetCountry(), _unit:GetCoordinate() , _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, false, "none") return true From 94f093826b2ae580a7559573b0b4f77347aed2ee Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 21 Feb 2022 08:36:37 +0100 Subject: [PATCH 074/200] SEAD - adding workaround for AGM_154 which lost target data --- Moose Development/Moose/Functional/Sead.lua | 346 ++++++++++++++------ 1 file changed, 240 insertions(+), 106 deletions(-) diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index 8c65f0ba7..2982f478e 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -19,7 +19,7 @@ -- -- ### Authors: **FlightControl**, **applevangelist** -- --- Last Update: Nov 2021 +-- Last Update: Feb 2022 -- -- === -- @@ -59,6 +59,7 @@ SEAD = { Padding = 10, CallBack = nil, UseCallBack = false, + debug = false, } --- Missile enumerators @@ -76,6 +77,8 @@ SEAD = { ["X_25"] = "X_25", ["X_31"] = "X_31", ["Kh25"] = "Kh25", + ["BGM_109"] = "BGM_109", + ["AGM_154"] = "AGM_154", } --- Missile enumerators - from DCS ME and Wikipedia @@ -85,7 +88,7 @@ SEAD = { ["AGM_88"] = { 150, 3}, ["AGM_45"] = { 12, 2}, ["AGM_122"] = { 16.5, 2.3}, - ["AGM_84"] = { 280, 0.85}, + ["AGM_84"] = { 280, 0.8}, ["ALARM"] = { 45, 2}, ["LD-10"] = { 60, 4}, ["X_58"] = { 70, 4}, @@ -93,6 +96,8 @@ SEAD = { ["X_25"] = { 25, 0.76}, ["X_31"] = {150, 3}, ["Kh25"] = {25, 0.8}, + ["BGM_109"] = {460, 0.705}, --in-game ~465kn + ["AGM_154"] = {130, 0.61}, } --- Creates the main object which is handling defensive actions for SA sites or moving SA vehicles. @@ -108,8 +113,8 @@ SEAD = { -- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } ) function SEAD:New( SEADGroupPrefixes, Padding ) - local self = BASE:Inherit( self, BASE:New() ) - self:F( SEADGroupPrefixes ) + local self = BASE:Inherit( self, FSM:New() ) + self:T( SEADGroupPrefixes ) if type( SEADGroupPrefixes ) == 'table' then for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do @@ -122,14 +127,21 @@ function SEAD:New( SEADGroupPrefixes, Padding ) local padding = Padding or 10 if padding < 10 then padding = 10 end self.Padding = padding - self.UseEmissionsOnOff = false + self.UseEmissionsOnOff = true + + self.debug = false self.CallBack = nil self.UseCallBack = false - + self:HandleEvent( EVENTS.Shot, self.HandleEventShot ) - - self:I("*** SEAD - Started Version 0.3.3") + + -- Start State. + self:SetStartState("Running") + self:AddTransition("*", "ManageEvasion", "*") + self:AddTransition("*", "CalculateHitZone", "*") + + self:I("*** SEAD - Started Version 0.4.3") return self end @@ -213,7 +225,7 @@ function SEAD:_CheckHarms(WeaponName) local hit = false local name = "" for _,_name in pairs (SEAD.Harms) do - if string.find(WeaponName,_name,1) then + if string.find(WeaponName,_name,1,true) then hit = true name = _name break @@ -249,6 +261,186 @@ function SEAD:_GetDistance(_point1, _point2) end end +--- (Internal) Calculate hit zone of an AGM-88 +-- @param #SEAD self +-- @param #table SEADWeapon DCS.Weapon object +-- @param Core.Point#COORDINATE pos0 Position of the plane when it fired +-- @param #number height Height when the missile was fired +-- @param Wrapper.Group#GROUP SEADGroup Attacker group +-- @param #string SEADWeaponName Weapon Name +-- @return #SEAD self +function SEAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADGroup,SEADWeaponName) + self:T("**** Calculating hit zone for " .. (SEADWeaponName or "None")) + if SEADWeapon and SEADWeapon:isExist() then + --local pos = SEADWeapon:getPoint() + + -- postion and height + local position = SEADWeapon:getPosition() + local mheight = height + -- heading + local wph = math.atan2(position.x.z, position.x.x) + if wph < 0 then + wph=wph+2*math.pi + end + wph=math.deg(wph) + + -- velocity + local wpndata = SEAD.HarmData["AGM_88"] + if string.find(SEADWeaponName,"154",1) then + wpndata = SEAD.HarmData["AGM_154"] + end + local mveloc = math.floor(wpndata[2] * 340.29) + local c1 = (2*mheight*9.81)/(mveloc^2) + local c2 = (mveloc^2) / 9.81 + local Ropt = c2 * math.sqrt(c1+1) + if height <= 5000 then + Ropt = Ropt * 0.72 + elseif height <= 7500 then + Ropt = Ropt * 0.82 + elseif height <= 10000 then + Ropt = Ropt * 0.87 + elseif height <= 12500 then + Ropt = Ropt * 0.98 + end + + -- look at a couple of zones across the trajectory + for n=1,3 do + local dist = Ropt - ((n-1)*20000) + local predpos= pos0:Translate(dist,wph) + if predpos then + + local targetzone = ZONE_RADIUS:New("Target Zone",predpos:GetVec2(),20000) + + if self.debug then + predpos:MarkToAll(string.format("height=%dm | heading=%d | velocity=%ddeg | Ropt=%dm",mheight,wph,mveloc,Ropt),false) + targetzone:DrawZone(coalition.side.BLUE,{0,0,1},0.2,nil,nil,3,true) + end + + local seadset = SET_GROUP:New():FilterPrefixes(self.SEADGroupPrefixes):FilterZones({targetzone}):FilterOnce() + local tgtcoord = targetzone:GetRandomPointVec2() + --if tgtcoord and tgtcoord.ClassName == "COORDINATE" then + --local tgtgrp = seadset:FindNearestGroupFromPointVec2(tgtcoord) + local tgtgrp = seadset:GetRandom() + local _targetgroup = nil + local _targetgroupname = "none" + local _targetskill = "Random" + if tgtgrp and tgtgrp:IsAlive() then + _targetgroup = tgtgrp + _targetgroupname = tgtgrp:GetName() -- group name + _targetskill = tgtgrp:GetUnit(1):GetSkill() + self:T("*** Found Target = ".. _targetgroupname) + self:ManageEvasion(_targetskill,_targetgroup,pos0,"AGM_88",SEADGroup, 20) + end + --end + end + end + end + return self +end + +--- (Internal) Handle Evasion +-- @param #SEAD self +-- @param #string _targetskill +-- @param Wrapper.Group#GROUP _targetgroup +-- @param Core.Point#COORDINATE SEADPlanePos +-- @param #string SEADWeaponName +-- @param Wrapper.Group#GROUP SEADGroup Attacker Group +-- @param #number timeoffset Offset for tti calc +-- @return #SEAD self +function SEAD:onafterManageEvasion(From,Event,To,_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup,timeoffset) + local timeoffset = timeoffset or 0 + if _targetskill == "Random" then -- when skill is random, choose a skill + local Skills = { "Average", "Good", "High", "Excellent" } + _targetskill = Skills[ math.random(1,4) ] + end + --self:T( _targetskill ) + if self.TargetSkill[_targetskill] then + local _evade = math.random (1,100) -- random number for chance of evading action + if (_evade > self.TargetSkill[_targetskill].Evade) then + self:T("*** SEAD - Evading") + -- calculate distance of attacker + local _targetpos = _targetgroup:GetCoordinate() + local _distance = self:_GetDistance(SEADPlanePos, _targetpos) + -- weapon speed + local hit, data = self:_CheckHarms(SEADWeaponName) + local wpnspeed = 666 -- ;) + local reach = 10 + if hit then + local wpndata = SEAD.HarmData[data] + reach = wpndata[1] * 1,1 + local mach = wpndata[2] + wpnspeed = math.floor(mach * 340.29) + end + -- time to impact + local _tti = math.floor(_distance / wpnspeed) - timeoffset -- estimated impact time + if _distance > 0 then + _distance = math.floor(_distance / 1000) -- km + else + _distance = 0 + end + + self:T( string.format("*** SEAD - target skill %s, distance %dkm, reach %dkm, tti %dsec", _targetskill, _distance,reach,_tti )) + + if reach >= _distance then + self:T("*** SEAD - Shot in Reach") + + local function SuppressionStart(args) + self:T(string.format("*** SEAD - %s Radar Off & Relocating",args[2])) + local grp = args[1] -- Wrapper.Group#GROUP + local name = args[2] -- #string Group Name + local attacker = args[3] -- Wrapper.Group#GROUP + if self.UseEmissionsOnOff then + grp:EnableEmission(false) + end + grp:OptionAlarmStateGreen() -- needed else we cannot move around + grp:RelocateGroundRandomInRadius(20,300,false,false,"Diamond") + if self.UseCallBack then + local object = self.CallBack + object:SeadSuppressionStart(grp,name,attacker) + end + end + + local function SuppressionStop(args) + self:T(string.format("*** SEAD - %s Radar On",args[2])) + local grp = args[1] -- Wrapper.Group#GROUP + local name = args[2] -- #string Group Nam + if self.UseEmissionsOnOff then + grp:EnableEmission(true) + end + grp:OptionAlarmStateRed() + grp:OptionEngageRange(self.EngagementRange) + self.SuppressedGroups[name] = false + if self.UseCallBack then + local object = self.CallBack + object:SeadSuppressionEnd(grp,name) + end + end + + -- randomize switch-on time + local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2]) + if delay > _tti then delay = delay / 2 end -- speed up + if _tti > 600 then delay = _tti - 90 end -- shot from afar, 600 is default shorad ontime + + local SuppressionStartTime = timer.getTime() + delay + local SuppressionEndTime = timer.getTime() + _tti + self.Padding + local _targetgroupname = _targetgroup:GetName() + if not self.SuppressedGroups[_targetgroupname] then + self:T(string.format("*** SEAD - %s | Parameters TTI %ds | Switch-Off in %ds",_targetgroupname,_tti,delay)) + timer.scheduleFunction(SuppressionStart,{_targetgroup,_targetgroupname, SEADGroup},SuppressionStartTime) + timer.scheduleFunction(SuppressionStop,{_targetgroup,_targetgroupname},SuppressionEndTime) + self.SuppressedGroups[_targetgroupname] = true + if self.UseCallBack then + local object = self.CallBack + object:SeadSuppressionPlanned(_targetgroup,_targetgroupname,SuppressionStartTime,SuppressionEndTime, SEADGroup) + end + end + + end + end + end + return self +end + --- (Internal) Detects if an SAM site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME. -- @param #SEAD self -- @param Core.Event#EVENTDATA EventData @@ -256,6 +448,7 @@ end function SEAD:HandleEventShot( EventData ) self:T( { EventData.id } ) local SEADPlane = EventData.IniUnit -- Wrapper.Unit#UNIT + local SEADGroup = EventData.IniGroup -- Wrapper.Group#GROUP local SEADPlanePos = SEADPlane:GetCoordinate() -- Core.Point#COORDINATE local SEADUnit = EventData.IniDCSUnit local SEADUnitName = EventData.IniDCSUnitName @@ -270,114 +463,55 @@ function SEAD:HandleEventShot( EventData ) local _targetskill = "Random" local _targetgroupname = "none" local _target = EventData.Weapon:getTarget() -- Identify target - local _targetUnit = UNIT:Find(_target) -- Wrapper.Unit#UNIT + if not _target or self.debug then -- AGM-88 or 154 w/o target data + self:E("***** SEAD - No target data for " .. (SEADWeaponName or "None")) + if string.find(SEADWeaponName,"AGM_88",1,true) or string.find(SEADWeaponName,"AGM_154",1,true) then + self:I("**** Tracking AGM-88/154 with no target data.") + local pos0 = SEADPlane:GetCoordinate() + local fheight = SEADPlane:GetHeight() + self:__CalculateHitZone(20,SEADWeapon,pos0,fheight,SEADGroup,SEADWeaponName) + end + return self + end + local targetcat = _target:getCategory() -- Identify category + local _targetUnit = nil -- Wrapper.Unit#UNIT local _targetgroup = nil -- Wrapper.Group#GROUP - if _targetUnit and _targetUnit:IsAlive() then - _targetgroup = _targetUnit:GetGroup() - _targetgroupname = _targetgroup:GetName() -- group name - local _targetUnitName = _targetUnit:GetName() - _targetUnit:GetSkill() - _targetskill = _targetUnit:GetSkill() + self:T(string.format("*** Targetcat = %d",targetcat)) + if targetcat == Object.Category.UNIT then -- UNIT + self:T("*** Target Category UNIT") + _targetUnit = UNIT:Find(_target) -- Wrapper.Unit#UNIT + if _targetUnit and _targetUnit:IsAlive() then + _targetgroup = _targetUnit:GetGroup() + _targetgroupname = _targetgroup:GetName() -- group name + local _targetUnitName = _targetUnit:GetName() + _targetUnit:GetSkill() + _targetskill = _targetUnit:GetSkill() + end + elseif targetcat == Object.Category.STATIC then + self:T("*** Target Category STATIC") + local seadset = SET_GROUP:New():FilterPrefixes(self.SEADGroupPrefixes):FilterOnce() + local targetpoint = _target:getPoint() or {x=0,y=0,z=0} + local tgtcoord = COORDINATE:NewFromVec3(targetpoint) + local tgtgrp = seadset:FindNearestGroupFromPointVec2(tgtcoord) + if tgtgrp and tgtgrp:IsAlive() then + _targetgroup = tgtgrp + _targetgroupname = tgtgrp:GetName() -- group name + _targetskill = tgtgrp:GetUnit(1):GetSkill() + self:T("*** Found Target = ".. _targetgroupname) + end end -- see if we are shot at local SEADGroupFound = false for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do - self:T( _targetgroupname, SEADGroupPrefix ) - if string.find( _targetgroupname, SEADGroupPrefix, 1, true ) then + self:T("Target = ".. _targetgroupname .. " | Prefix = " .. SEADGroupPrefix ) + if string.find( _targetgroupname, SEADGroupPrefix,1,true ) then SEADGroupFound = true self:T( '*** SEAD - Group Match Found' ) break end end if SEADGroupFound == true then -- yes we are being attacked - if _targetskill == "Random" then -- when skill is random, choose a skill - local Skills = { "Average", "Good", "High", "Excellent" } - _targetskill = Skills[ math.random(1,4) ] - end - --self:T( _targetskill ) - if self.TargetSkill[_targetskill] then - local _evade = math.random (1,100) -- random number for chance of evading action - if (_evade > self.TargetSkill[_targetskill].Evade) then - self:T("*** SEAD - Evading") - -- calculate distance of attacker - local _targetpos = _targetgroup:GetCoordinate() - local _distance = self:_GetDistance(SEADPlanePos, _targetpos) - -- weapon speed - local hit, data = self:_CheckHarms(SEADWeaponName) - local wpnspeed = 666 -- ;) - local reach = 10 - if hit then - local wpndata = SEAD.HarmData[data] - reach = wpndata[1] * 1,1 - local mach = wpndata[2] - wpnspeed = math.floor(mach * 340.29) - end - -- time to impact - local _tti = math.floor(_distance / wpnspeed) -- estimated impact time - if _distance > 0 then - _distance = math.floor(_distance / 1000) -- km - else - _distance = 0 - end - - self:T( string.format("*** SEAD - target skill %s, distance %dkm, reach %dkm, tti %dsec", _targetskill, _distance,reach,_tti )) - - if reach >= _distance then - self:T("*** SEAD - Shot in Reach") - - local function SuppressionStart(args) - self:T(string.format("*** SEAD - %s Radar Off & Relocating",args[2])) - local grp = args[1] -- Wrapper.Group#GROUP - local name = args[2] -- #string Group Name - if self.UseEmissionsOnOff then - grp:EnableEmission(false) - end - grp:OptionAlarmStateGreen() -- needed else we cannot move around - grp:RelocateGroundRandomInRadius(20,300,false,false,"Diamond") - if self.UseCallBack then - local object = self.CallBack - object:SeadSuppressionStart(grp,name) - end - end - - local function SuppressionStop(args) - self:T(string.format("*** SEAD - %s Radar On",args[2])) - local grp = args[1] -- Wrapper.Group#GROUP - local name = args[2] -- #string Group Nam - if self.UseEmissionsOnOff then - grp:EnableEmission(true) - end - grp:OptionAlarmStateAuto() - grp:OptionEngageRange(self.EngagementRange) - self.SuppressedGroups[name] = false - if self.UseCallBack then - local object = self.CallBack - object:SeadSuppressionEnd(grp,name) - end - end - - -- randomize switch-on time - local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2]) - if delay > _tti then delay = delay / 2 end -- speed up - if _tti > (3*delay) then delay = (_tti / 2) * 0.9 end -- shot from afar - - local SuppressionStartTime = timer.getTime() + delay - local SuppressionEndTime = timer.getTime() + _tti + self.Padding - - if not self.SuppressedGroups[_targetgroupname] then - self:T(string.format("*** SEAD - %s | Parameters TTI %ds | Switch-Off in %ds",_targetgroupname,_tti,delay)) - timer.scheduleFunction(SuppressionStart,{_targetgroup,_targetgroupname},SuppressionStartTime) - timer.scheduleFunction(SuppressionStop,{_targetgroup,_targetgroupname},SuppressionEndTime) - self.SuppressedGroups[_targetgroupname] = true - if self.UseCallBack then - local object = self.CallBack - object:SeadSuppressionPlanned(_targetgroup,_targetgroupname,SuppressionStartTime,SuppressionEndTime) - end - end - - end - end - end + self:ManageEvasion(_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup) end end return self From cde0d09f0ac0cf33e96dac731699dde29144cf8d Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 21 Feb 2022 19:36:22 +0100 Subject: [PATCH 075/200] CSAR - remove double class --- Moose Development/Moose/Ops/CSAR.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 96b4069e7..f8998b328 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -6,6 +6,14 @@ -- -- === -- +-- ## Missions:--- **Ops** -- Combat Search and Rescue. +-- +-- === +-- +-- **CSAR** - MOOSE based Helicopter CSAR Operations. +-- +-- === +-- -- ## Missions: -- -- ### [CSAR - Combat Search & Rescue](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20CSAR) From 473362af459790a3d6b609e4a45182ada186ca38 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 3 Mar 2022 11:02:25 +0100 Subject: [PATCH 076/200] CTLD - small fix for finding crates when using engineers --- Moose Development/Moose/Ops/CTLD.lua | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 251eb952f..f04515322 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1021,7 +1021,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="1.0.9" +CTLD.version="1.0.10" --- Instantiate a new CTLD. -- @param #CTLD self @@ -2095,11 +2095,18 @@ function CTLD:_FindCratesNearby( _group, _unit, _dist, _ignoreweight) -- cycle local index = 0 local found = {} - local loadedmass = self:_GetUnitCargoMass(_unit) - local unittype = _unit:GetTypeName() - local capabilities = self:_GetUnitCapabilities(_unit) -- #CTLD.UnitCapabilities - local maxmass = capabilities.cargoweightlimit - local maxloadable = maxmass - loadedmass + local loadedmass = 0 + local unittype = "none" + local capabilities = {} + local maxmass = 2000 + local maxloadable = 2000 + if not _ignoreweight then + loadedmass = self:_GetUnitCargoMass(_unit) + unittype = _unit:GetTypeName() + capabilities = self:_GetUnitCapabilities(_unit) -- #CTLD.UnitCapabilities + maxmass = capabilities.cargoweightlimit + maxloadable = maxmass - loadedmass + end self:T(self.lid .. " Max loadable mass: " .. maxloadable) for _,_cargoobject in pairs (existingcrates) do local cargo = _cargoobject -- #CTLD_CARGO From ae7a3630123bed276c666d744dbe655767b00ef6 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 3 Mar 2022 12:34:43 +0100 Subject: [PATCH 077/200] CTLD - small extra nil check in _GetUnitCargoMass(Unit) --- Moose Development/Moose/Ops/CTLD.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index f04515322..c04e3e26f 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -2263,6 +2263,7 @@ end -- @return #number mass in kgs function CTLD:_GetUnitCargoMass(Unit) self:T(self.lid .. " _GetUnitCargoMass") + if not Unit then return 0 end local unitname = Unit:GetName() local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo local loadedmass = 0 -- #number From 26b1fd34875cfbc393e97b60698cc2efb8dc14dc Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 9 Mar 2022 10:26:16 +0100 Subject: [PATCH 078/200] Update CTLD.lua (#1692) minor nil check --- Moose Development/Moose/Ops/CTLD.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index c04e3e26f..02f334590 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -2104,7 +2104,7 @@ function CTLD:_FindCratesNearby( _group, _unit, _dist, _ignoreweight) loadedmass = self:_GetUnitCargoMass(_unit) unittype = _unit:GetTypeName() capabilities = self:_GetUnitCapabilities(_unit) -- #CTLD.UnitCapabilities - maxmass = capabilities.cargoweightlimit + maxmass = capabilities.cargoweightlimit or 2000 maxloadable = maxmass - loadedmass end self:T(self.lid .. " Max loadable mass: " .. maxloadable) From 85a7e18fae670e91485e0a39d0a62ef2086c6ec7 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 11 Mar 2022 10:18:59 +0100 Subject: [PATCH 079/200] SCORING: Corrected calc error in summary scoring functions --- Moose Development/Moose/Functional/Scoring.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Functional/Scoring.lua b/Moose Development/Moose/Functional/Scoring.lua index 75bc18704..c2c777df9 100644 --- a/Moose Development/Moose/Functional/Scoring.lua +++ b/Moose Development/Moose/Functional/Scoring.lua @@ -1650,7 +1650,7 @@ function SCORING:ReportScoreGroupDetailed( PlayerGroup ) self:F( { ReportMissions, ScoreMissions, PenaltyMissions } ) local PlayerScore = ScoreHits + ScoreDestroys + ScoreCoalitionChanges + ScoreGoals + ScoreMissions - local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + ScoreGoals + PenaltyMissions + local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + PenaltyGoals + PenaltyMissions PlayerMessage = string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )%s%s%s%s%s", PlayerName, @@ -1705,7 +1705,7 @@ function SCORING:ReportScoreAllSummary( PlayerGroup ) self:F( { ReportMissions, ScoreMissions, PenaltyMissions } ) local PlayerScore = ScoreHits + ScoreDestroys + ScoreCoalitionChanges + ScoreGoals + ScoreMissions - local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + ScoreGoals + PenaltyMissions + local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + PenaltyGoals + PenaltyMissions PlayerMessage = string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )", PlayerName, From 802a77238a715675b6d96387c559a46927fffabd Mon Sep 17 00:00:00 2001 From: TommyC81 Date: Sat, 12 Mar 2022 12:47:14 +0400 Subject: [PATCH 080/200] Range re-formatting and documentation re-fixing (#1691) * Update Range.lua Code formatting. * RANGE - Documentation fixes. --- Moose Development/Moose/Functional/Range.lua | 2735 +++++++++--------- 1 file changed, 1364 insertions(+), 1371 deletions(-) diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index 9da579d6d..7949c22ac 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -19,17 +19,17 @@ -- * Bomb, rocket and missile impact points can be marked by smoke. -- * Direct hits on targets can trigger flares. -- * Smoke and flare colors can be adjusted for each player via radio menu. --- * Range information and weather report at the range can be reported via radio menu. +-- * Range information and weather at the range can be obtained via radio menu. -- * Persistence: Bombing range results can be saved to disk and loaded the next time the mission is started. -- * Range control voice overs (>40) for hit assessment. -- -- === -- -- ## Youtube Videos: - -- +-- -- * [MOOSE YouTube Channel](https://www.youtube.com/channel/UCjrA9j5LQoWsG4SpS8i79Qg) -- * [MOOSE - On the Range - Demonstration Video](https://www.youtube.com/watch?v=kIXcxNB9_3M) --- +-- -- === -- -- ## Missions: @@ -50,11 +50,10 @@ -- @module Functional.Range -- @image Range.JPG -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- RANGE class -- @type RANGE -- @field #string ClassName Name of the Class. --- @field #boolean Debug If true, debug info is send as messages on the screen. +-- @field #boolean Debug If true, debug info is sent as messages on the screen. -- @field #boolean verbose Verbosity level. Higher means more output to DCS log file. -- @field #string id String id of range for output in DCS log. -- @field #string rangename Name of the range. @@ -77,13 +76,13 @@ -- @field #number Tmsg Time [sec] messages to players are displayed. Default 30 sec. -- @field #string examinergroupname Name of the examiner group which should get all messages. -- @field #boolean examinerexclusive If true, only the examiner gets messages. If false, clients and examiner get messages. --- @field #number strafemaxalt Maximum altitude above ground for registering for a strafe run. Default is 914 m = 3000 ft. +-- @field #number strafemaxalt Maximum altitude in meters AGL for registering for a strafe run. Default is 914 m = 3000 ft. -- @field #number ndisplayresult Number of (player) results that a displayed. Default is 10. -- @field Utilities.Utils#SMOKECOLOR BombSmokeColor Color id used for smoking bomb targets. -- @field Utilities.Utils#SMOKECOLOR StrafeSmokeColor Color id used to smoke strafe targets. -- @field Utilities.Utils#SMOKECOLOR StrafePitSmokeColor Color id used to smoke strafe pit approach boxes. --- @field #number illuminationminalt Minimum altitude AGL in meters at which illumination bombs are fired. Default is 500 m. --- @field #number illuminationmaxalt Maximum altitude AGL in meters at which illumination bombs are fired. Default is 1000 m. +-- @field #number illuminationminalt Minimum altitude in meters AGL at which illumination bombs are fired. Default is 500 m. +-- @field #number illuminationmaxalt Maximum altitude in meters AGL at which illumination bombs are fired. Default is 1000 m. -- @field #number scorebombdistance Distance from closest target up to which bomb hits are counted. Default 1000 m. -- @field #number TdelaySmoke Time delay in seconds between impact of bomb and starting the smoke. Default 3 seconds. -- @field #boolean eventmoose If true, events are handled by MOOSE. If false, events are handled directly by DCS eventhandler. Default true. @@ -105,7 +104,7 @@ -- @extends Core.Fsm#FSM --- *Don't only practice your art, but force your way into its secrets; art deserves that, for it and knowledge can raise man to the Divine.* - Ludwig van Beethoven --- +-- -- === -- -- ![Banner Image](..\Presentations\RANGE\RANGE_Main.png) @@ -129,17 +128,17 @@ -- there should be an "On the Range" menu items in the "F10. Other..." menu. -- -- # Strafe Pits --- +-- -- Each strafe pit can consist of multiple targets. Often one finds two or three strafe targets next to each other. -- -- A strafe pit can be added to the range by the @{#RANGE.AddStrafePit}(*targetnames, boxlength, boxwidth, heading, inverseheading, goodpass, foulline*) function. -- --- * The first parameter *targetnames* defines the target or targets. This has to be given as a lua table which contains the names of @{Wrapper.Unit} or @{Static} objects defined in the mission editor. +-- * The first parameter *targetnames* defines the target or targets. This can be a single item or a Table with the name(s) of @{Wrapper.Unit} or @{Static} objects defined in the mission editor. -- * In order to perform a valid pass on the strafe pit, the pilot has to begin his run from the correct direction. Therefore, an "approach box" is defined in front --- of the strafe targets. The parameters *boxlength* and *boxwidth* define the size of the box while the parameter *heading* defines its direction. --- If the parameter *heading* is passed as **nil**, the heading is automatically taken from the heading of the first target unit as defined in the ME. --- The parameter *inverseheading* turns the heading around by 180 degrees. This is sometimes useful, since the default heading of strafe target units point in the --- wrong/opposite direction. +-- of the strafe targets. The parameters *boxlength* and *boxwidth* define the size of the box in meters, while the *heading* parameter defines the heading of the box FROM the target. +-- For example, if heading 120 is set, the approach box will start FROM the target and extend outwards on heading 120. A strafe run approach must then be flown apx. heading 300 TOWARDS the target. +-- If the parameter *heading* is passed as **nil**, the heading is automatically taken from the heading set in the ME for the first target unit. +-- * The parameter *inverseheading* turns the heading around by 180 degrees. This is useful when the default heading of strafe target units point in the wrong/opposite direction. -- * The parameter *goodpass* defines the number of hits a pilot has to achieve during a run to be judged as a "good" pass. -- * The last parameter *foulline* sets the distance from the pit targets to the foul line. Hit from closer than this line are not counted! -- @@ -150,27 +149,26 @@ -- strafing pits of the range and can be adjusted by the @{#RANGE.SetMaxStrafeAlt}(maxalt) function. -- -- # Bombing targets --- +-- -- One ore multiple bombing targets can be added to the range by the @{#RANGE.AddBombingTargets}(targetnames, goodhitrange, randommove) function. -- --- * The first parameter *targetnames* has to be a lua table, which contains the names of @{Wrapper.Unit} and/or @{Static} objects defined in the mission editor. --- Note that the @{Range} logic **automatically** determines, if a name belongs to a @{Wrapper.Unit} or @{Static} object now. --- * The (optional) parameter *goodhitrange* specifies the radius around the target. If a bomb or rocket falls at a distance smaller than this number, the hit is considered to be "good". +-- * The first parameter *targetnames* defines the target or targets. This can be a single item or a Table with the name(s) of @{Wrapper.Unit} or @{Static} objects defined in the mission editor. +-- * The (optional) parameter *goodhitrange* specifies the radius in metres around the target within which a bomb/rocket hit is considered to be "good". -- * If final (optional) parameter "*randommove*" can be enabled to create moving targets. If this parameter is set to true, the units of this bombing target will randomly move within the range zone. -- Note that there might be quirks since DCS units can get stuck in buildings etc. So it might be safer to manually define a route for the units in the mission editor if moving targets are desired. -- -- ## Adding Groups --- +-- -- Another possibility to add bombing targets is the @{#RANGE.AddBombingTargetGroup}(*group, goodhitrange, randommove*) function. Here the parameter *group* is a MOOSE @{Wrapper.Group} object -- and **all** units in this group are defined as bombing targets. --- +-- -- ## Specifying Coordinates --- +-- -- It is also possible to specify coordinates rather than unit or static objects as bombing target locations. This has the advantage, that even when the unit/static object is dead, the specified -- coordinate will still be a valid impact point. This can be done via the @{#RANGE.AddBombingTargetCoordinate}(*coord*, *name*, *goodhitrange*) function. -- -- # Fine Tuning --- +-- -- Many range parameters have good default values. However, the mission designer can change these settings easily with the supplied user functions: -- -- * @{#RANGE.SetMaxStrafeAlt}() sets the max altitude for valid strafing runs. @@ -186,60 +184,60 @@ -- * @{#RANGE.TrackMissilesON}() or @{#RANGE.TrackMissilesOFF}() can be used to enable/disable tracking and evaluating of all missile types a player fires. -- -- # Radio Menu --- +-- -- Each range gets a radio menu with various submenus where each player can adjust his individual settings or request information about the range or his scores. -- -- The main range menu can be found at "F10. Other..." --> "F*X*. On the Range..." --> "F1. ...". -- --- The range menu contains the following submenues: --- +-- The range menu contains the following submenus: +-- -- ![Banner Image](..\Presentations\RANGE\Menu_Main.png) -- -- * "F1. Statistics...": Range results of all players and personal stats. -- * "F2. Mark Targets": Mark range targets by smoke or flares. -- * "F3. My Settings" Personal settings. -- * "F4. Range Info": Information about the range, such as bearing and range. --- +-- -- ## F1 Statistics --- +-- -- ![Banner Image](..\Presentations\RANGE\Menu_Stats.png) --- +-- -- ## F2 Mark Targets --- +-- -- ![Banner Image](..\Presentations\RANGE\Menu_Stats.png) --- +-- -- ## F3 My Settings --- +-- -- ![Banner Image](..\Presentations\RANGE\Menu_MySettings.png) --- +-- -- ## F4 Range Info --- +-- -- ![Banner Image](..\Presentations\RANGE\Menu_RangeInfo.png) --- +-- -- # Voice Overs --- +-- -- Voice over sound files can be downloaded from the Moose Discord. Check the pinned messages in the *#func-range* channel. --- +-- -- Instructor radio will inform players when they enter or exit the range zone and provide the radio frequency of the range control for hit assessment. -- This can be enabled via the @{#RANGE.SetInstructorRadio}(*frequency*) functions, where *frequency* is the AM frequency in MHz. --- +-- -- The range control can be enabled via the @{#RANGE.SetRangeControl}(*frequency*) functions, where *frequency* is the AM frequency in MHz. --- +-- -- By default, the sound files are placed in the "Range Soundfiles/" folder inside the mission (.miz) file. Another folder can be specified via the @{#RANGE.SetSoundfilesPath}(*path*) function. --- +-- -- # Persistence --- +-- -- To automatically save bombing results to disk, use the @{#RANGE.SetAutosave}() function. Bombing results will be saved as csv file in your "Saved Games\DCS.openbeta\Logs" directory. -- Each range has a separate file, which is named "RANGE-<*RangeName*>_BombingResults.csv". --- +-- -- The next time you start the mission, these results are also automatically loaded. --- +-- -- Strafing results are currently **not** saved. -- -- # Examples -- -- ## Goldwater Range --- +-- -- This example shows hot to set up the [Barry M. Goldwater range](https://en.wikipedia.org/wiki/Barry_M._Goldwater_Air_Force_Range). -- It consists of two strafe pits each has two targets plus three bombing targets. -- @@ -258,9 +256,9 @@ -- -- Note that this could also be done manually by simply measuring the distance between the target and the foul line in the ME. -- GoldwaterRange:GetFoullineDistance("GWR Strafe Pit Left 1", "GWR Foul Line Left") -- --- -- Add strafe pits. Each pit (left and right) consists of two targets. --- GoldwaterRange:AddStrafePit(strafepit_left, 3000, 300, nil, true, 20, fouldist) --- GoldwaterRange:AddStrafePit(strafepit_right, nil, nil, nil, true, nil, fouldist) +-- -- Add strafe pits. Each pit (left and right) consists of two targets. Where "nil" is used as input, the default value is used. +-- GoldwaterRange:AddStrafePit(strafepit_left, 3000, 300, nil, true, 30, 500) +-- GoldwaterRange:AddStrafePit(strafepit_right, nil, nil, nil, true, nil, 500) -- -- -- Add bombing targets. A good hit is if the bomb falls less then 50 m from the target. -- GoldwaterRange:AddBombingTargets(bombtargets, 50) @@ -290,74 +288,71 @@ -- Note that it can happen that the RANGE radio menu is not shown. Check that the range object is defined as a **global** variable rather than a local one. -- The could avoid the lua garbage collection to accidentally/falsely deallocate the RANGE objects. -- --- --- -- @field #RANGE -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 = {}, +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, + 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/", - targetsheet = nil, - targetpath = nil, - targetprefix = nil, + 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/", + targetsheet = nil, + targetpath = nil, + targetprefix = nil, } --- Default range parameters. -- @list Defaults -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.Defaults = { + goodhitrange = 25, + strafemaxalt = 914, + dtBombtrack = 0.005, + Tmsg = 30, + ndisplayresult = 10, + rangeradius = 5000, + TdelaySmoke = 3.0, + boxlength = 3000, + boxwidth = 300, + goodpass = 20, + foulline = 610 } --- Target type, i.e. unit, static, or coordinate. @@ -365,19 +360,20 @@ RANGE.Defaults={ -- @field #string UNIT Target is a unit. -- @field #string STATIC Target is a static. -- @field #string COORD Target is a coordinate. -RANGE.TargetType={ - UNIT="Unit", - STATIC="Static", - COORD="Coordinate", +RANGE.TargetType = { + UNIT = "Unit", + STATIC = "Static", + COORD = "Coordinate" } --- Default range variables for RangeBoss/Hypeman tie in. hypemanStrafeRollIn = "nil" StrafeAircraftType = "strafeAircraftTypeNotSet" -Straferesult={} +Straferesult = {} clientRollingIn = false clientStrafed = false invalidStrafe = false + --- Player settings. -- @type RANGE.PlayerData -- @field #boolean smokebombimpact Smoke bomb impact points. @@ -476,81 +472,81 @@ invalidStrafe = false -- @field #RANGE.Soundfile IREnterRange -- @field #RANGE.Soundfile IRExitRange 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}, + 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 }, } --- Global list of all defined range names. -- @field #table Names -RANGE.Names={} +RANGE.Names = {} --- Main radio menu on group level. -- @field #table MenuF10 Root menu table on group level. -RANGE.MenuF10={} +RANGE.MenuF10 = {} --- Main radio menu on mission level. -- @field #table MenuF10Root Root menu on mission level. -RANGE.MenuF10Root=nil +RANGE.MenuF10Root = nil --- Range script version. -- @field #string version -RANGE.version="2.3.0" +RANGE.version = "2.3.0" ---TODO list: ---TODO: Verbosity level for messages. ---TODO: Add option for default settings such as smoke off. ---TODO: Add custom weapons, which can be specified by the user. ---TODO: Check if units are still alive. ---DONE: Add statics for strafe pits. ---DONE: Add missiles. ---DONE: Convert env.info() to self:T() ---DONE: Add user functions. ---DONE: Rename private functions, i.e. start with _functionname. ---DONE: number of displayed results variable. ---DONE: Add tire option for strafe pits. ==> No really feasible since tires are very small and cannot be seen. ---DONE: Check that menu texts are short enough to be correctly displayed in VR. +-- TODO list: +-- TODO: Verbosity level for messages. +-- TODO: Add option for default settings such as smoke off. +-- TODO: Add custom weapons, which can be specified by the user. +-- TODO: Check if units are still alive. +-- DONE: Add statics for strafe pits. +-- DONE: Add missiles. +-- DONE: Convert env.info() to self:T() +-- DONE: Add user functions. +-- DONE: Rename private functions, i.e. start with _functionname. +-- DONE: number of displayed results variable. +-- DONE: Add tire option for strafe pits. ==> No really feasible since tires are very small and cannot be seen. +-- DONE: Check that menu texts are short enough to be correctly displayed in VR. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -558,28 +554,28 @@ RANGE.version="2.3.0" -- @param #RANGE self -- @param #string rangename Name of the range. Has to be unique. Will we used to create F10 menu items etc. -- @return #RANGE RANGE object. -function RANGE:New(rangename) - BASE:F({rangename=rangename}) +function RANGE:New( rangename ) + BASE:F( { rangename = rangename } ) -- Inherit BASE. - local self=BASE:Inherit(self, FSM:New()) -- #RANGE + local self = BASE:Inherit( self, FSM:New() ) -- #RANGE -- Get range name. - --TODO: make sure that the range name is not given twice. This would lead to problems in the F10 radio menu. - self.rangename=rangename or "Practice Range" + -- TODO: make sure that the range name is not given twice. This would lead to problems in the F10 radio menu. + self.rangename = rangename or "Practice Range" -- Log id. - self.id=string.format("RANGE %s | ", self.rangename) + self.id = string.format( "RANGE %s | ", self.rangename ) -- Debug info. - local text=string.format("Script version %s - creating new RANGE object %s.", RANGE.version, self.rangename) - self:I(self.id..text) + local text = string.format( "Script version %s - creating new RANGE object %s.", RANGE.version, self.rangename ) + self:I( self.id .. text ) -- Defaults self:SetDefaultPlayerSmokeBomb() -- Start State. - self:SetStartState("Stopped") + self:SetStartState( "Stopped" ) --- -- Add FSM transitions. @@ -694,136 +690,136 @@ end function RANGE:onafterStart() -- Location/coordinate of range. - local _location=nil + local _location = nil -- Count bomb targets. - local _count=0 - for _,_target in pairs(self.bombingTargets) do - _count=_count+1 + local _count = 0 + for _, _target in pairs( self.bombingTargets ) do + _count = _count + 1 -- Get range location. - if _location==nil then - _location=self:_GetBombTargetCoordinate(_target) + if _location == nil then + _location = self:_GetBombTargetCoordinate( _target ) end end - self.nbombtargets=_count + self.nbombtargets = _count -- Count strafing targets. - _count=0 - for _,_target in pairs(self.strafeTargets) do - _count=_count+1 + _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() + for _, _unit in pairs( _target.targets ) do + if _location == nil then + _location = _unit:GetCoordinate() end end end - self.nstrafetargets=_count + self.nstrafetargets = _count -- Location of the range. We simply take the first unit/target we find if it was not explicitly specified by the user. - if self.location==nil then - self.location=_location + 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) + 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 -- Define a MOOSE zone of the range. - if self.rangezone==nil then - self.rangezone=ZONE_RADIUS:New(self.rangename, {x=self.location.x, y=self.location.z}, self.rangeradius) + if self.rangezone == nil then + self.rangezone = ZONE_RADIUS:New( self.rangename, { x = self.location.x, y = self.location.z }, self.rangeradius ) end -- Starting range. - 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) + 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 ) -- Event handling. if self.eventmoose then -- Events are handled my MOOSE. - self:T(self.id.."Events are handled by MOOSE.") - self:HandleEvent(EVENTS.Birth) - self:HandleEvent(EVENTS.Hit) - self:HandleEvent(EVENTS.Shot) + self:T( self.id .. "Events are handled by MOOSE." ) + self:HandleEvent( EVENTS.Birth ) + self:HandleEvent( EVENTS.Hit ) + self:HandleEvent( EVENTS.Shot ) else -- Events are handled directly by DCS. - self:T(self.id.."Events are handled directly by DCS.") - world.addEventHandler(self) + self:T( self.id .. "Events are handled directly by DCS." ) + world.addEventHandler( self ) end -- Make bomb target move randomly within the range zone. - for _,_target in pairs(self.bombingTargets) do + for _, _target in pairs( self.bombingTargets ) do -- Check if it is a static object. - --local _static=self:_CheckStatic(_target.target:GetName()) - local _static=_target.type==RANGE.TargetType.STATIC + -- local _static=self:_CheckStatic(_target.target:GetName()) + local _static = _target.type == RANGE.TargetType.STATIC - if _target.move and _static==false and _target.speed>1 then - local unit=_target.target --Wrapper.Unit#UNIT - _target.target:PatrolZones({self.rangezone}, _target.speed*0.75, "Off road") + if _target.move and _static == false and _target.speed > 1 then + local unit = _target.target -- Wrapper.Unit#UNIT + _target.target:PatrolZones( { self.rangezone }, _target.speed * 0.75, "Off road" ) end end - + -- Init range control. if self.rangecontrolfreq then - + -- Radio queue. - self.rangecontrol=RADIOQUEUE:New(self.rangecontrolfreq, nil, self.rangename) - self.rangecontrol.schedonce=true - + self.rangecontrol = RADIOQUEUE:New( self.rangecontrolfreq, nil, self.rangename ) + self.rangecontrol.schedonce = true + -- Init numbers. - self.rangecontrol:SetDigit(0, RANGE.Sound.RC0.filename, RANGE.Sound.RC0.duration, self.soundpath) - self.rangecontrol:SetDigit(1, RANGE.Sound.RC1.filename, RANGE.Sound.RC1.duration, self.soundpath) - self.rangecontrol:SetDigit(2, RANGE.Sound.RC2.filename, RANGE.Sound.RC2.duration, self.soundpath) - self.rangecontrol:SetDigit(3, RANGE.Sound.RC3.filename, RANGE.Sound.RC3.duration, self.soundpath) - self.rangecontrol:SetDigit(4, RANGE.Sound.RC4.filename, RANGE.Sound.RC4.duration, self.soundpath) - self.rangecontrol:SetDigit(5, RANGE.Sound.RC5.filename, RANGE.Sound.RC5.duration, self.soundpath) - self.rangecontrol:SetDigit(6, RANGE.Sound.RC6.filename, RANGE.Sound.RC6.duration, self.soundpath) - self.rangecontrol:SetDigit(7, RANGE.Sound.RC7.filename, RANGE.Sound.RC7.duration, self.soundpath) - self.rangecontrol:SetDigit(8, RANGE.Sound.RC8.filename, RANGE.Sound.RC8.duration, self.soundpath) - self.rangecontrol:SetDigit(9, RANGE.Sound.RC9.filename, RANGE.Sound.RC9.duration, self.soundpath) - + self.rangecontrol:SetDigit( 0, 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 ) + -- Set location where the messages are transmitted from. - self.rangecontrol:SetSenderCoordinate(self.location) - self.rangecontrol:SetSenderUnitName(self.rangecontrolrelayname) - + self.rangecontrol:SetSenderCoordinate( self.location ) + self.rangecontrol:SetSenderUnitName( self.rangecontrolrelayname ) + -- Start range control radio queue. - self.rangecontrol:Start(1, 0.1) + self.rangecontrol:Start( 1, 0.1 ) -- Init range control. if self.instructorfreq then - + -- Radio queue. - self.instructor=RADIOQUEUE:New(self.instructorfreq, nil, self.rangename) - self.instructor.schedonce=true - + self.instructor = RADIOQUEUE:New( self.instructorfreq, nil, self.rangename ) + self.instructor.schedonce = true + -- Init numbers. - self.instructor:SetDigit(0, RANGE.Sound.IR0.filename, RANGE.Sound.IR0.duration, self.soundpath) - self.instructor:SetDigit(1, RANGE.Sound.IR1.filename, RANGE.Sound.IR1.duration, self.soundpath) - self.instructor:SetDigit(2, RANGE.Sound.IR2.filename, RANGE.Sound.IR2.duration, self.soundpath) - self.instructor:SetDigit(3, RANGE.Sound.IR3.filename, RANGE.Sound.IR3.duration, self.soundpath) - self.instructor:SetDigit(4, RANGE.Sound.IR4.filename, RANGE.Sound.IR4.duration, self.soundpath) - self.instructor:SetDigit(5, RANGE.Sound.IR5.filename, RANGE.Sound.IR5.duration, self.soundpath) - self.instructor:SetDigit(6, RANGE.Sound.IR6.filename, RANGE.Sound.IR6.duration, self.soundpath) - self.instructor:SetDigit(7, RANGE.Sound.IR7.filename, RANGE.Sound.IR7.duration, self.soundpath) - self.instructor:SetDigit(8, RANGE.Sound.IR8.filename, RANGE.Sound.IR8.duration, self.soundpath) - self.instructor:SetDigit(9, RANGE.Sound.IR9.filename, RANGE.Sound.IR9.duration, self.soundpath) - + self.instructor:SetDigit( 0, 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 ) + -- Set location where the messages are transmitted from. - self.instructor:SetSenderCoordinate(self.location) - self.instructor:SetSenderUnitName(self.instructorrelayname) - + self.instructor:SetSenderCoordinate( self.location ) + self.instructor:SetSenderUnitName( self.instructorrelayname ) + -- Start instructor radio queue. - self.instructor:Start(1, 0.1) - + self.instructor:Start( 1, 0.1 ) + end - + end - + -- Load prev results. if self.autosave then self:Load() @@ -835,10 +831,10 @@ function RANGE:onafterStart() self:_SmokeBombTargets() self:_SmokeStrafeTargets() self:_SmokeStrafeTargetBoxes() - self.rangezone:SmokeZone(SMOKECOLOR.White) + self.rangezone:SmokeZone( SMOKECOLOR.White ) end - self:__Status(-60) + self:__Status( -60 ) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -847,10 +843,10 @@ end --- Set maximal strafing altitude. Player entering a strafe pit above that altitude are not registered for a valid pass. -- @param #RANGE self --- @param #number maxalt Maximum altitude AGL in meters. Default is 914 m= 3000 ft. +-- @param #number maxalt Maximum altitude in meters AGL. Default is 914 m = 3000 ft. -- @return #RANGE self -function RANGE:SetMaxStrafeAlt(maxalt) - self.strafemaxalt=maxalt or RANGE.Defaults.strafemaxalt +function RANGE:SetMaxStrafeAlt( maxalt ) + self.strafemaxalt = maxalt or RANGE.Defaults.strafemaxalt return self end @@ -858,8 +854,8 @@ end -- @param #RANGE self -- @param #number dt Time interval in seconds. Default is 0.005 s. -- @return #RANGE self -function RANGE:SetBombtrackTimestep(dt) - self.dtBombtrack=dt or RANGE.Defaults.dtBombtrack +function RANGE:SetBombtrackTimestep( dt ) + self.dtBombtrack = dt or RANGE.Defaults.dtBombtrack return self end @@ -867,8 +863,8 @@ end -- @param #RANGE self -- @param #number time Time in seconds. Default is 30 s. -- @return #RANGE self -function RANGE:SetMessageTimeDuration(time) - self.Tmsg=time or RANGE.Defaults.Tmsg +function RANGE:SetMessageTimeDuration( time ) + self.Tmsg = time or RANGE.Defaults.Tmsg return self end @@ -876,7 +872,7 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:SetAutosaveOn() - self.autosave=true + self.autosave = true return self end @@ -884,7 +880,7 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:SetAutosaveOff() - self.autosave=false + self.autosave = false return self end @@ -893,13 +889,13 @@ end -- @param #string path (Optional) Path where to save the target sheets. -- @param #string prefix (Optional) Prefix for target sheet files. File name will be saved as *prefix_aircrafttype-0001.csv*, *prefix_aircrafttype-0002.csv*, etc. -- @return #RANGE self -function RANGE:SetTargetSheet(path, prefix) +function RANGE:SetTargetSheet( path, prefix ) if io then - self.targetsheet=true - self.targetpath=path - self.targetprefix=prefix + self.targetsheet = true + self.targetpath = path + self.targetprefix = prefix else - self:E(self.lid.."ERROR: io is not desanitized. Cannot save target sheet.") + self:E( self.lid .. "ERROR: io is not desanitized. Cannot save target sheet." ) end return self end @@ -909,9 +905,9 @@ end -- @param #string examinergroupname Name of the group of the examiner. -- @param #boolean exclusively If true, messages are send exclusively to the examiner, i.e. not to the clients. -- @return #RANGE self -function RANGE:SetMessageToExaminer(examinergroupname, exclusively) - self.examinergroupname=examinergroupname - self.examinerexclusive=exclusively +function RANGE:SetMessageToExaminer( examinergroupname, exclusively ) + self.examinergroupname = examinergroupname + self.examinerexclusive = exclusively return self end @@ -919,8 +915,8 @@ end -- @param #RANGE self -- @param #number nmax Number of results. Default is 10. -- @return #RANGE self -function RANGE:SetDisplayedMaxPlayerResults(nmax) - self.ndisplayresult=nmax or RANGE.Defaults.ndisplayresult +function RANGE:SetDisplayedMaxPlayerResults( nmax ) + self.ndisplayresult = nmax or RANGE.Defaults.ndisplayresult return self end @@ -928,8 +924,8 @@ end -- @param #RANGE self -- @param #number radius Radius in km. Default 5 km. -- @return #RANGE self -function RANGE:SetRangeRadius(radius) - self.rangeradius=radius*1000 or RANGE.Defaults.rangeradius +function RANGE:SetRangeRadius( radius ) + self.rangeradius = radius * 1000 or RANGE.Defaults.rangeradius return self end @@ -937,11 +933,11 @@ end -- @param #RANGE self -- @param #boolean switch If true nor nil default is to smoke impact points of bombs. -- @return #RANGE self -function RANGE:SetDefaultPlayerSmokeBomb(switch) - if switch==true or switch==nil then - self.defaultsmokebomb=true +function RANGE:SetDefaultPlayerSmokeBomb( switch ) + if switch == true or switch == nil then + self.defaultsmokebomb = true else - self.defaultsmokebomb=false + self.defaultsmokebomb = false end return self end @@ -950,8 +946,8 @@ end -- @param #RANGE self -- @param #number distance Threshold distance in km. Default 25 km. -- @return #RANGE self -function RANGE:SetBombtrackThreshold(distance) - self.BombtrackThreshold=(distance or 25)*1000 +function RANGE:SetBombtrackThreshold( distance ) + self.BombtrackThreshold = (distance or 25) * 1000 return self end @@ -960,8 +956,8 @@ end -- @param #RANGE self -- @param Core.Point#COORDINATE coordinate Coordinate of the range. -- @return #RANGE self -function RANGE:SetRangeLocation(coordinate) - self.location=coordinate +function RANGE:SetRangeLocation( coordinate ) + self.location = coordinate return self end @@ -970,8 +966,8 @@ end -- @param #RANGE self -- @param Core.Zone#ZONE zone MOOSE zone defining the range perimeters. -- @return #RANGE self -function RANGE:SetRangeZone(zone) - self.rangezone=zone +function RANGE:SetRangeZone( zone ) + self.rangezone = zone return self end @@ -979,8 +975,8 @@ end -- @param #RANGE self -- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default SMOKECOLOR.Red. -- @return #RANGE self -function RANGE:SetBombTargetSmokeColor(colorid) - self.BombSmokeColor=colorid or SMOKECOLOR.Red +function RANGE:SetBombTargetSmokeColor( colorid ) + self.BombSmokeColor = colorid or SMOKECOLOR.Red return self end @@ -988,8 +984,8 @@ end -- @param #RANGE self -- @param #number distance Distance in meters. Default 1000 m. -- @return #RANGE self -function RANGE:SetScoreBombDistance(distance) - self.scorebombdistance=distance or 1000 +function RANGE:SetScoreBombDistance( distance ) + self.scorebombdistance = distance or 1000 return self end @@ -997,8 +993,8 @@ end -- @param #RANGE self -- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default SMOKECOLOR.Green. -- @return #RANGE self -function RANGE:SetStrafeTargetSmokeColor(colorid) - self.StrafeSmokeColor=colorid or SMOKECOLOR.Green +function RANGE:SetStrafeTargetSmokeColor( colorid ) + self.StrafeSmokeColor = colorid or SMOKECOLOR.Green return self end @@ -1006,8 +1002,8 @@ end -- @param #RANGE self -- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default SMOKECOLOR.White. -- @return #RANGE self -function RANGE:SetStrafePitSmokeColor(colorid) - self.StrafePitSmokeColor=colorid or SMOKECOLOR.White +function RANGE:SetStrafePitSmokeColor( colorid ) + self.StrafePitSmokeColor = colorid or SMOKECOLOR.White return self end @@ -1015,8 +1011,8 @@ end -- @param #RANGE self -- @param #number delay Time delay in seconds. Default is 3 seconds. -- @return #RANGE self -function RANGE:SetSmokeTimeDelay(delay) - self.TdelaySmoke=delay or RANGE.Defaults.TdelaySmoke +function RANGE:SetSmokeTimeDelay( delay ) + self.TdelaySmoke = delay or RANGE.Defaults.TdelaySmoke return self end @@ -1024,7 +1020,7 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:DebugON() - self.Debug=true + self.Debug = true return self end @@ -1032,7 +1028,7 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:DebugOFF() - self.Debug=false + self.Debug = false return self end @@ -1040,7 +1036,7 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:SetMessagesOFF() - self.messages=false + self.messages = false return self end @@ -1048,16 +1044,15 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:SetMessagesON() - self.messages=true + self.messages = true return self end - --- Enables tracking of all bomb types. Note that this is the default setting. -- @param #RANGE self -- @return #RANGE self function RANGE:TrackBombsON() - self.trackbombs=true + self.trackbombs = true return self end @@ -1065,7 +1060,7 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:TrackBombsOFF() - self.trackbombs=false + self.trackbombs = false return self end @@ -1073,7 +1068,7 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:TrackRocketsON() - self.trackrockets=true + self.trackrockets = true return self end @@ -1081,7 +1076,7 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:TrackRocketsOFF() - self.trackrockets=false + self.trackrockets = false return self end @@ -1089,7 +1084,7 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:TrackMissilesON() - self.trackmissiles=true + self.trackmissiles = true return self end @@ -1097,19 +1092,18 @@ end -- @param #RANGE self -- @return #RANGE self function RANGE:TrackMissilesOFF() - self.trackmissiles=false + self.trackmissiles = false return self end - --- Enable range control and set frequency. -- @param #RANGE self -- @param #number frequency Frequency in MHz. Default 256 MHz. -- @param #string relayunitname Name of the unit used for transmission. -- @return #RANGE self -function RANGE:SetRangeControl(frequency, relayunitname) - self.rangecontrolfreq=frequency or 256 - self.rangecontrolrelayname=relayunitname +function RANGE:SetRangeControl( frequency, relayunitname ) + self.rangecontrolfreq = frequency or 256 + self.rangecontrolrelayname = relayunitname return self end @@ -1118,163 +1112,162 @@ end -- @param #number frequency Frequency in MHz. Default 305 MHz. -- @param #string relayunitname Name of the unit used for transmission. -- @return #RANGE self -function RANGE:SetInstructorRadio(frequency, relayunitname) - self.instructorfreq=frequency or 305 - self.instructorrelayname=relayunitname +function RANGE:SetInstructorRadio( frequency, relayunitname ) + self.instructorfreq = frequency or 305 + self.instructorrelayname = relayunitname return self end --- Set sound files folder within miz file. -- @param #RANGE self --- @param #string path Path for sound files. Default "ATIS Soundfiles/". Mind the slash "/" at the end! +-- @param #string path Path for sound files. Default "Range Soundfiles/". Mind the slash "/" at the end! -- @return #RANGE self -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)) +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 --- Add new strafe pit. For a strafe pit, hits from guns are counted. One pit can consist of several units. --- Note, an approach is only valid, if the player enters via a zone in front of the pit, which defined by boxlength and boxheading. +-- A strafe run approach is only valid if the player enters via a zone in front of the pit, which is defined by boxlength, boxwidth, and heading. -- Furthermore, the player must not be too high and fly in the direction of the pit to make a valid target apporoach. -- @param #RANGE self --- @param #table targetnames Table of unit or static names defining the strafe targets. The first target in the list determines the approach zone (heading and box). +-- @param #table targetnames Single or multiple (Table) unit or static names defining the strafe targets. The first target in the list determines the approach box origin (heading and box). -- @param #number boxlength (Optional) Length of the approach box in meters. Default is 3000 m. -- @param #number boxwidth (Optional) Width of the approach box in meters. Default is 300 m. --- @param #number heading (Optional) Approach heading in Degrees. Default is heading of the unit as defined in the mission editor. --- @param #boolean inverseheading (Optional) Take inverse heading (heading --> heading - 180 Degrees). Default is false. +-- @param #number heading (Optional) Approach box heading in degrees (originating FROM the target). Default is the heading set in the ME for the first target unit +-- @param #boolean inverseheading (Optional) Use inverse heading (heading --> heading - 180 Degrees). Default is false. -- @param #number goodpass (Optional) Number of hits for a "good" strafing pass. Default is 20. --- @param #number foulline (Optional) Foul line distance. Hits from closer than this distance are not counted. Default 610 m = 2000 ft. Set to 0 for no foul line. +-- @param #number foulline (Optional) Foul line distance. Hits from closer than this distance are not counted. Default is 610 m = 2000 ft. Set to 0 for no foul line. -- @return #RANGE self -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}) +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 } ) -- Create table if necessary. - if type(targetnames) ~= "table" then - targetnames={targetnames} + if type( targetnames ) ~= "table" then + targetnames = { targetnames } end -- Make targets - local _targets={} - local center=nil --Wrapper.Unit#UNIT - local ntargets=0 + local _targets = {} + local center = nil -- Wrapper.Unit#UNIT + local ntargets = 0 - for _i,_name in ipairs(targetnames) do + for _i, _name in ipairs( targetnames ) do -- Check if we have a static or unit object. - local _isstatic=self:_CheckStatic(_name) + local _isstatic = self:_CheckStatic( _name ) - local unit=nil - if _isstatic==true then + local unit = nil + if _isstatic == true then -- Add static object. - self:T(self.id..string.format("Adding STATIC object %s as strafe target #%d.", _name, _i)) - unit=STATIC:FindByName(_name, false) + 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 + elseif _isstatic == false then -- Add unit object. - self:T(self.id..string.format("Adding UNIT object %s as strafe target #%d.", _name, _i)) - unit=UNIT:FindByName(_name) + self:T( self.id .. string.format( "Adding UNIT object %s as strafe target #%d.", _name, _i ) ) + unit = UNIT:FindByName( _name ) else -- Neither unit nor static object with this name could be found. - local text=string.format("ERROR! Could not find ANY strafe target object with name %s.", _name) - self:E(self.id..text) + local text = string.format( "ERROR! Could not find ANY strafe target object with name %s.", _name ) + self:E( self.id .. text ) end -- Add object to targets. if unit then - table.insert(_targets, unit) + table.insert( _targets, unit ) -- Define center as the first unit we find - if center==nil then - center=unit + if center == nil then + center = unit end - ntargets=ntargets+1 + ntargets = ntargets + 1 end end -- Check if at least one target could be found. - 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) + 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 -- Approach box dimensions. - local l=boxlength or RANGE.Defaults.boxlength - local w=(boxwidth or RANGE.Defaults.boxwidth)/2 + local l = boxlength or RANGE.Defaults.boxlength + local w = (boxwidth or RANGE.Defaults.boxwidth) / 2 -- Heading: either manually entered or automatically taken from unit heading. - local heading=heading or center:GetHeading() + local heading = heading or center:GetHeading() -- Invert the heading since some units point in the "wrong" direction. In particular the strafe pit from 476th range objects. if inverseheading ~= nil then if inverseheading then - heading=heading-180 + heading = heading - 180 end end - if heading<0 then - heading=heading+360 + if heading < 0 then + heading = heading + 360 end - if heading>360 then - heading=heading-360 + if heading > 360 then + heading = heading - 360 end -- Number of hits called a "good" pass. - goodpass=goodpass or RANGE.Defaults.goodpass + goodpass = goodpass or RANGE.Defaults.goodpass -- Foule line distance. - foulline=foulline or RANGE.Defaults.foulline + foulline = foulline or RANGE.Defaults.foulline -- Coordinate of the range. - local Ccenter=center:GetCoordinate() + local Ccenter = center:GetCoordinate() -- Name of the target defined as its unit name. - local _name=center:GetName() + local _name = center:GetName() -- Points defining the approach area. - 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 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} + local pv2 = {} + for i, p in ipairs( p ) do + pv2[i] = { x = p.x, y = p.z } end -- Create polygon zone. - local _polygon=ZONE_POLYGON_BASE:New(_name, pv2) + local _polygon = ZONE_POLYGON_BASE:New( _name, pv2 ) -- Create tires - --_polygon:BoundZone() + -- _polygon:BoundZone() - local st={} --#RANGE.StrafeTarget - st.name=_name - st.polygon=_polygon - st.coordinate=Ccenter - st.goodPass=goodpass - st.targets=_targets - st.foulline=foulline - st.smokepoints=p - st.heading=heading + local st = {} -- #RANGE.StrafeTarget + st.name = _name + st.polygon = _polygon + st.coordinate = Ccenter + st.goodPass = goodpass + st.targets = _targets + st.foulline = foulline + st.smokepoints = p + st.heading = heading -- Add zone to table. - table.insert(self.strafeTargets, st) + table.insert( self.strafeTargets, st ) -- Debug info - 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) + 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 - --- Add all units of a group as one new strafe target pit. -- For a strafe pit, hits from guns are counted. One pit can consist of several units. -- Note, an approach is only valid, if the player enters via a zone in front of the pit, which defined by boxlength and boxheading. @@ -1288,29 +1281,29 @@ end -- @param #number goodpass (Optional) Number of hits for a "good" strafing pass. Default is 20. -- @param #number foulline (Optional) Foul line distance. Hits from closer than this distance are not counted. Default 610 m = 2000 ft. Set to 0 for no foul line. -- @return #RANGE self -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}) +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 -- Get units of group. - local _units=group:GetUnits() + local _units = group:GetUnits() -- Make table of unit names. - local _names={} - for _,_unit in ipairs(_units) do + local _names = {} + for _, _unit in ipairs( _units ) do - local _unit=_unit --Wrapper.Unit#UNIT + local _unit = _unit -- Wrapper.Unit#UNIT if _unit and _unit:IsAlive() then - local _name=_unit:GetName() - table.insert(_names,_name) + local _name = _unit:GetName() + table.insert( _names, _name ) end end -- Add strafe pit. - self:AddStrafePit(_names, boxlength, boxwidth, heading, inverseheading, goodpass, foulline) + self:AddStrafePit( _names, boxlength, boxwidth, heading, inverseheading, goodpass, foulline ) end return self @@ -1318,36 +1311,36 @@ end --- Add bombing target(s) to range. -- @param #RANGE self --- @param #table targetnames Table containing names of unit or static objects serving as bomb targets. +-- @param #table targetnames Single or multiple (Table) names of unit or static objects serving as bomb targets. -- @param #number goodhitrange (Optional) Max distance from target unit (in meters) which is considered as a good hit. Default is 25 m. -- @param #boolean randommove If true, unit will move randomly within the range. Default is false. -- @return #RANGE self -function RANGE:AddBombingTargets(targetnames, goodhitrange, randommove) - self:F({targetnames=targetnames, goodhitrange=goodhitrange, randommove=randommove}) +function RANGE:AddBombingTargets( targetnames, goodhitrange, randommove ) + self:F( { targetnames = targetnames, goodhitrange = goodhitrange, randommove = randommove } ) -- Create a table if necessary. - if type(targetnames) ~= "table" then - targetnames={targetnames} + if type( targetnames ) ~= "table" then + targetnames = { targetnames } end -- Default range is 25 m. - goodhitrange=goodhitrange or RANGE.Defaults.goodhitrange + goodhitrange = goodhitrange or RANGE.Defaults.goodhitrange - for _,name in pairs(targetnames) do + for _, name in pairs( targetnames ) do -- Check if we have a static or unit object. - local _isstatic=self:_CheckStatic(name) + 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) + 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)) + self:E( self.id .. string.format( "ERROR! Could not find bombing target %s.", name ) ) end end @@ -1361,77 +1354,76 @@ end -- @param #number goodhitrange Max distance from unit which is considered as a good hit. -- @param #boolean randommove If true, unit will move randomly within the range. Default is false. -- @return #RANGE self -function RANGE:AddBombingTargetUnit(unit, goodhitrange, randommove) - self:F({unit=unit, goodhitrange=goodhitrange, randommove=randommove}) +function RANGE:AddBombingTargetUnit( unit, goodhitrange, randommove ) + self:F( { unit = unit, goodhitrange = goodhitrange, randommove = randommove } ) -- Get name of positionable. - local name=unit:GetName() + local name = unit:GetName() -- Check if we have a static or unit object. - local _isstatic=self:_CheckStatic(name) + local _isstatic = self:_CheckStatic( name ) -- Default range is 25 m. - goodhitrange=goodhitrange or RANGE.Defaults.goodhitrange + goodhitrange = goodhitrange or RANGE.Defaults.goodhitrange -- Set randommove to false if it was not specified. - if randommove==nil or _isstatic==true then - randommove=false + if randommove == nil or _isstatic == true then + randommove = false end -- Debug or error output. - 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))) + 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)) + 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 -- Get max speed of unit in km/h. - local speed=0 - if _isstatic==false then - speed=self:_GetSpeed(unit) + local speed = 0 + if _isstatic == false then + speed = self:_GetSpeed( unit ) end - local target={} --#RANGE.BombTarget - target.name=name - target.target=unit - target.goodhitrange=goodhitrange - target.move=randommove - target.speed=speed - target.coordinate=unit:GetCoordinate() + local target = {} -- #RANGE.BombTarget + 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 + target.type = RANGE.TargetType.STATIC else - target.type=RANGE.TargetType.UNIT + target.type = RANGE.TargetType.UNIT end -- Insert target to table. - table.insert(self.bombingTargets, target) + table.insert( self.bombingTargets, target ) return self end - --- Add a coordinate of a bombing target. This -- @param #RANGE self -- @param Core.Point#COORDINATE coord The coordinate. -- @param #string name Name of target. -- @param #number goodhitrange Max distance from unit which is considered as a good hit. -- @return #RANGE self -function RANGE:AddBombingTargetCoordinate(coord, name, goodhitrange) +function RANGE:AddBombingTargetCoordinate( coord, name, goodhitrange ) - local target={} --#RANGE.BombTarget - 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 + local target = {} -- #RANGE.BombTarget + 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 -- Insert target to table. - table.insert(self.bombingTargets, target) + table.insert( self.bombingTargets, target ) return self end @@ -1442,16 +1434,16 @@ end -- @param #number goodhitrange Max distance from unit which is considered as a good hit. -- @param #boolean randommove If true, unit will move randomly within the range. Default is false. -- @return #RANGE self -function RANGE:AddBombingTargetGroup(group, goodhitrange, randommove) - self:F({group=group, goodhitrange=goodhitrange, randommove=randommove}) +function RANGE:AddBombingTargetGroup( group, goodhitrange, randommove ) + self:F( { group = group, goodhitrange = goodhitrange, randommove = randommove } ) if group then - local _units=group:GetUnits() + local _units = group:GetUnits() - for _,_unit in pairs(_units) do + for _, _unit in pairs( _units ) do if _unit and _unit:IsAlive() then - self:AddBombingTargetUnit(_unit, goodhitrange, randommove) + self:AddBombingTargetUnit( _unit, goodhitrange, randommove ) end end end @@ -1464,42 +1456,42 @@ end -- @param #string namepit Name of the strafe pit target object. -- @param #string namefoulline Name of the fould line distance marker object. -- @return #number Foul line distance in meters. -function RANGE:GetFoullineDistance(namepit, namefoulline) - self:F({namepit=namepit, namefoulline=namefoulline}) +function RANGE:GetFoullineDistance( namepit, namefoulline ) + self:F( { namepit = namepit, namefoulline = namefoulline } ) -- Check if we have units or statics. - local _staticpit=self:_CheckStatic(namepit) - local _staticfoul=self:_CheckStatic(namefoulline) + local _staticpit = self:_CheckStatic( namepit ) + local _staticfoul = self:_CheckStatic( namefoulline ) -- Get the unit or static pit object. - local pit=nil - if _staticpit==true then - pit=STATIC:FindByName(namepit, false) - elseif _staticpit==false then - pit=UNIT:FindByName(namepit) + 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)) + 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 -- Get the unit or static foul line object. - local foul=nil - if _staticfoul==true then - foul=STATIC:FindByName(namefoulline, false) - elseif _staticfoul==false then - foul=UNIT:FindByName(namefoulline) + 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)) + 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 -- Get the distance between the two objects. - local fouldist=0 - if pit~=nil and foul~=nil then - fouldist=pit:GetCoordinate():Get2DDistance(foul:GetCoordinate()) + 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)) + 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)) + self:T( self.id .. string.format( "Foul line distance = %.1f m.", fouldist ) ) return fouldist end @@ -1510,120 +1502,119 @@ end --- General event handler. -- @param #RANGE self -- @param #table Event DCS event table. -function RANGE:onEvent(Event) - self:F3(Event) +function RANGE:onEvent( Event ) + self:F3( Event ) if Event == nil or Event.initiator == nil then - self:T3("Skipping onEvent. Event or Event.initiator unknown.") + 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.") + 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 DCSweapon = Event.weapon - local EventData={} - local _playerunit=nil - local _playername=nil + local EventData = {} + local _playerunit = nil + local _playername = nil if Event.initiator then - EventData.IniUnitName = Event.initiator:getName() - EventData.IniDCSGroup = Event.initiator:getGroup() + EventData.IniUnitName = Event.initiator:getName() + EventData.IniDCSGroup = Event.initiator:getGroup() EventData.IniGroupName = Event.initiator:getGroup():getName() -- Get player unit and name. This returns nil,nil if the event was not fired by a player unit. And these are the only events we are interested in. - _playerunit, _playername = self:_GetPlayerUnitAndName(EventData.IniUnitName) + _playerunit, _playername = self:_GetPlayerUnitAndName( EventData.IniUnitName ) end if Event.target then - EventData.TgtUnitName = Event.target:getName() - EventData.TgtUnit = UNIT:FindByName(EventData.TgtUnitName) + 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.Weapon = Event.weapon + EventData.weapon = Event.weapon EventData.WeaponTypeName = Event.weapon:getTypeName() end -- Event info. - 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))) + 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 ) ) ) -- Call event Birth function. - if Event.id==world.event.S_EVENT_BIRTH and _playername then - self:OnEventBirth(EventData) + if Event.id == world.event.S_EVENT_BIRTH and _playername then + self:OnEventBirth( EventData ) end -- Call event Shot function. - if Event.id==world.event.S_EVENT_SHOT and _playername and Event.weapon then - self:OnEventShot(EventData) + if Event.id == world.event.S_EVENT_SHOT and _playername and Event.weapon then + self:OnEventShot( EventData ) end -- Call event Hit function. - if Event.id==world.event.S_EVENT_HIT and _playername and DCStgtunit then - self:OnEventHit(EventData) + if Event.id == world.event.S_EVENT_HIT and _playername and DCStgtunit then + self:OnEventHit( EventData ) end end - --- Range event handler for event birth. -- @param #RANGE self -- @param Core.Event#EVENTDATA EventData -function RANGE:OnEventBirth(EventData) - self:F({eventbirth = EventData}) +function RANGE:OnEventBirth( EventData ) + self:F( { eventbirth = EventData } ) - local _unitName=EventData.IniUnitName - local _unit, _playername=self:_GetPlayerUnitAndName(_unitName) + 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)) + 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 _uid = _unit:GetID() + local _group = _unit:GetGroup() + local _gid = _group:GetID() + local _callsign = _unit:GetCallsign() -- Debug output. - 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) + 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 ) -- Reset current strafe status. self.strafeStatus[_uid] = nil -- Add Menu commands after a delay of 0.1 seconds. - --SCHEDULER:New(nil, self._AddF10Commands, {self,_unitName}, 0.1) - self:ScheduleOnce(0.1, self._AddF10Commands, self, _unitName) + -- SCHEDULER:New(nil, self._AddF10Commands, {self,_unitName}, 0.1) + self:ScheduleOnce( 0.1, self._AddF10Commands, self, _unitName ) -- By default, some bomb impact points and do not flare each hit on target. - self.PlayerSettings[_playername]={} --#RANGE.PlayerData - 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 + self.PlayerSettings[_playername] = {} -- #RANGE.PlayerData + 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 -- Start check in zone timer. if self.planes[_uid] ~= true then - --SCHEDULER:New(nil, self._CheckInZone, {self, EventData.IniUnitName}, 1, 1) - self.timerCheckZone=TIMER:New(self._CheckInZone, self, EventData.IniUnitName):Start(1, 1) + -- SCHEDULER:New(nil, self._CheckInZone, {self, EventData.IniUnitName}, 1, 1) + self.timerCheckZone = TIMER:New( self._CheckInZone, self, EventData.IniUnitName ):Start( 1, 1 ) self.planes[_uid] = true end @@ -1633,18 +1624,18 @@ end --- Range event handler for event hit. -- @param #RANGE self -- @param Core.Event#EVENTDATA EventData -function RANGE:OnEventHit(EventData) - self:F({eventhit = EventData}) +function RANGE:OnEventHit( EventData ) + self:F( { eventhit = EventData } ) -- Debug info. - 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)) + 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 ) ) -- Player info local _unitName = EventData.IniUnitName - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) - if _unit==nil or _playername==nil then + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) + if _unit == nil or _playername == nil then return end @@ -1652,7 +1643,7 @@ function RANGE:OnEventHit(EventData) local _unitID = _unit:GetID() -- Target - local target = EventData.TgtUnit + local target = EventData.TgtUnit local targetname = EventData.TgtUnitName -- Current strafe target of player. @@ -1665,31 +1656,31 @@ function RANGE:OnEventHit(EventData) local targetPos = target:GetCoordinate() -- Loop over valid targets for this run. - for _,_target in pairs(_currentTarget.zone.targets) do + for _, _target in pairs( _currentTarget.zone.targets ) do -- Check the the target is the same that was actually hit. - if _target and _target:IsAlive() and _target:GetName() == targetname then + if _target and _target:IsAlive() and _target:GetName() == targetname then -- Get distance between player and target. - local dist=playerPos:Get2DDistance(targetPos) + local dist = playerPos:Get2DDistance( targetPos ) if dist > _currentTarget.zone.foulline then -- Increase hit counter of this run. - _currentTarget.hits = _currentTarget.hits + 1 + _currentTarget.hits = _currentTarget.hits + 1 -- Flare target. if _unit and _playername and self.PlayerSettings[_playername].flaredirecthits then - targetPos:Flare(self.PlayerSettings[_playername].flarecolor) + targetPos:Flare( self.PlayerSettings[_playername].flarecolor ) end else -- Too close to the target. - 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 - invalidStrafe = true --Rangeboss Edit + 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 + invalidStrafe = true -- Rangeboss Edit end end @@ -1698,9 +1689,9 @@ function RANGE:OnEventHit(EventData) end -- Bombing Targets - for _,_bombtarget in pairs(self.bombingTargets) do + for _, _bombtarget in pairs( self.bombingTargets ) do - local _target=_bombtarget.target --Wrapper.Positionable#POSITIONABLE + local _target = _bombtarget.target -- Wrapper.Positionable#POSITIONABLE -- Check if one of the bomb targets was hit. if _target and _target:IsAlive() and _bombtarget.name == targetname then @@ -1709,11 +1700,11 @@ function RANGE:OnEventHit(EventData) -- Flare target. if self.PlayerSettings[_playername].flaredirecthits then - + -- Position of target. local targetPos = _target:GetCoordinate() - - targetPos:Flare(self.PlayerSettings[_playername].flarecolor) + + targetPos:Flare( self.PlayerSettings[_playername].flarecolor ) end end @@ -1724,40 +1715,40 @@ end --- Range event handler for event shot (when a unit releases a rocket or bomb (but not a fast firing gun). -- @param #RANGE self -- @param Core.Event#EVENTDATA EventData -function RANGE:OnEventShot(EventData) - self:F({eventshot = EventData}) +function RANGE:OnEventShot( EventData ) + self:F( { eventshot = EventData } ) -- Nil checks. - if EventData.Weapon==nil then + if EventData.Weapon == nil then return end - if EventData.IniDCSUnit==nil then + if EventData.IniDCSUnit == nil then return end -- Weapon data. - local _weapon = EventData.Weapon:getTypeName() -- should be the same as Event.WeaponTypeName - local _weaponStrArray = UTILS.Split(_weapon,"%.") + local _weapon = EventData.Weapon:getTypeName() -- should be the same as Event.WeaponTypeName + local _weaponStrArray = UTILS.Split( _weapon, "%." ) local _weaponName = _weaponStrArray[#_weaponStrArray] -- Weapon descriptor. - local desc=EventData.Weapon:getDesc() + local desc = EventData.Weapon:getDesc() -- Weapon category: 0=SHELL, 1=MISSILE, 2=ROCKET, 3=BOMB (Weapon.Category.X) - local weaponcategory=desc.category + local weaponcategory = desc.category -- Debug info. - 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) + 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 ) -- Tracking conditions for bombs, rockets and missiles. - local _bombs = weaponcategory==Weapon.Category.BOMB --string.match(_weapon, "weapons.bombs") - local _rockets = weaponcategory==Weapon.Category.ROCKET --string.match(_weapon, "weapons.nurs") - local _missiles = weaponcategory==Weapon.Category.MISSILE --string.match(_weapon, "weapons.missiles") or _viggen + local _bombs = weaponcategory == Weapon.Category.BOMB -- string.match(_weapon, "weapons.bombs") + local _rockets = weaponcategory == Weapon.Category.ROCKET -- string.match(_weapon, "weapons.nurs") + local _missiles = weaponcategory == Weapon.Category.MISSILE -- string.match(_weapon, "weapons.missiles") or _viggen -- Check if any condition applies here. local _track = (_bombs and self.trackbombs) or (_rockets and self.trackrockets) or (_missiles and self.trackmissiles) @@ -1766,39 +1757,38 @@ function RANGE:OnEventShot(EventData) local _unitName = EventData.IniUnitName -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) -- Set this to larger value than the threshold. - local dPR=self.BombtrackThreshold*2 + local dPR = self.BombtrackThreshold * 2 -- Distance player to range. 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)) + 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 -- Only track if distance player to range is < 25 km. Also check that a player shot. No need to track AI weapons. - if _track and dPR<=self.BombtrackThreshold and _unit and _playername then + if _track and dPR <= self.BombtrackThreshold and _unit and _playername then -- Player data. - local playerData=self.PlayerSettings[_playername] --#RANGE.PlayerData + local playerData = self.PlayerSettings[_playername] -- #RANGE.PlayerData -- Tracking info and init of last bomb position. - self:T(self.id..string.format("RANGE %s: Tracking %s - %s.", self.rangename, _weapon, EventData.weapon:getName())) + self:T( self.id .. string.format( "RANGE %s: Tracking %s - %s.", self.rangename, _weapon, EventData.weapon:getName() ) ) -- Init bomb position. - local _lastBombPos = {x=0,y=0,z=0} --DCS#Vec3 + local _lastBombPos = { x = 0, y = 0, z = 0 } -- DCS#Vec3 -- Function monitoring the position of a bomb until impact. - local function trackBomb(_ordnance) + local function trackBomb( _ordnance ) -- When the pcall returns a failure the weapon has hit. - local _status,_bombPos = pcall( - function() + local _status, _bombPos = pcall( function() return _ordnance:getPoint() - end) + end ) - self:T2(self.id..string.format("Range %s: Bomb still in air: %s", self.rangename, tostring(_status))) + self:T2( self.id .. string.format( "Range %s: Bomb still in air: %s", self.rangename, tostring( _status ) ) ) if _status then ---------------------------- @@ -1806,7 +1796,7 @@ function RANGE:OnEventShot(EventData) ---------------------------- -- Remember this position. - _lastBombPos = {x = _bombPos.x, y = _bombPos.y, z= _bombPos.z } + _lastBombPos = { x = _bombPos.x, y = _bombPos.y, z = _bombPos.z } -- Check again in ~0.005 seconds ==> 200 checks per second. return timer.getTime() + self.dtBombtrack @@ -1818,57 +1808,57 @@ function RANGE:OnEventShot(EventData) ----------------------------- -- Get closet target to last position. - local _closetTarget=nil --#RANGE.BombTarget - local _distance=nil - local _closeCoord=nil - local _hitquality="POOR" + local _closetTarget = nil -- #RANGE.BombTarget + local _distance = nil + local _closeCoord = nil + local _hitquality = "POOR" -- Get callsign. - local _callsign=self:_myname(_unitName) + local _callsign = self:_myname( _unitName ) -- Coordinate of impact point. - local impactcoord=COORDINATE:NewFromVec3(_lastBombPos) + local impactcoord = COORDINATE:NewFromVec3( _lastBombPos ) -- Check if impact happened in range zone. - local insidezone=self.rangezone:IsCoordinateInZone(impactcoord) + local insidezone = self.rangezone:IsCoordinateInZone( impactcoord ) -- Impact point of bomb. if self.Debug then - impactcoord:MarkToAll("Bomb impact point") + impactcoord:MarkToAll( "Bomb impact point" ) end -- Smoke impact point of bomb. if playerData.smokebombimpact and insidezone then if playerData.delaysmoke then - timer.scheduleFunction(self._DelayedSmoke, {coord=impactcoord, color=playerData.smokecolor}, timer.getTime() + self.TdelaySmoke) + timer.scheduleFunction( self._DelayedSmoke, { coord = impactcoord, color = playerData.smokecolor }, timer.getTime() + self.TdelaySmoke ) else - impactcoord:Smoke(playerData.smokecolor) + impactcoord:Smoke( playerData.smokecolor ) end end -- Loop over defined bombing targets. - for _,_bombtarget in pairs(self.bombingTargets) do + for _, _bombtarget in pairs( self.bombingTargets ) do -- Get target coordinate. - local targetcoord=self:_GetBombTargetCoordinate(_bombtarget) + local targetcoord = self:_GetBombTargetCoordinate( _bombtarget ) if targetcoord then -- Distance between bomb and target. - local _temp = impactcoord:Get2DDistance(targetcoord) + local _temp = impactcoord:Get2DDistance( targetcoord ) -- Find closest target to last known position of the bomb. if _distance == nil or _temp < _distance then _distance = _temp _closetTarget = _bombtarget - _closeCoord=targetcoord + _closeCoord = targetcoord if _distance <= 1.53 then -- Rangeboss Edit _hitquality = "SHACK" -- Rangeboss Edit - elseif _distance <= 0.5*_bombtarget.goodhitrange then --Rangeboss Edit + elseif _distance <= 0.5 * _bombtarget.goodhitrange then -- Rangeboss Edit _hitquality = "EXCELLENT" elseif _distance <= _bombtarget.goodhitrange then _hitquality = "GOOD" - elseif _distance <= 2*_bombtarget.goodhitrange then + elseif _distance <= 2 * _bombtarget.goodhitrange then _hitquality = "INEFFECTIVE" else _hitquality = "POOR" @@ -1882,48 +1872,48 @@ function RANGE:OnEventShot(EventData) if _distance and _distance <= self.scorebombdistance then -- Init bomb player results. if not self.bombPlayerResults[_playername] then - self.bombPlayerResults[_playername]={} + self.bombPlayerResults[_playername] = {} end -- Local results. - local _results=self.bombPlayerResults[_playername] + local _results = self.bombPlayerResults[_playername] - local result={} --#RANGE.BombResult - 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 - result.roundsFired=0 --Rangeboss Edit - result.roundsHit=0 --Rangeboss Edit - result.roundsQuality="N/A" --Rangeboss Edit - result.rangename = self.rangename + local result = {} -- #RANGE.BombResult + 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 + result.roundsFired = 0 -- Rangeboss Edit + result.roundsHit = 0 -- Rangeboss Edit + result.roundsQuality = "N/A" -- Rangeboss Edit + result.rangename = self.rangename -- Add to table. - table.insert(_results, result) + table.insert( _results, result ) -- Call impact. - self:Impact(result, playerData) + self:Impact( result, playerData ) elseif insidezone then -- Send message. - 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) - + 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) + 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.") + self:T( self.id .. "Weapon impacted outside range zone." ) end - --Terminate the timer - self:T(self.id..string.format("Range %s, player %s: Terminating bomb track timer.", self.rangename, _playername)) + -- Terminate the timer + self:T( self.id .. string.format( "Range %s, player %s: Terminating bomb track timer.", self.rangename, _playername ) ) return nil end -- _status check @@ -1931,85 +1921,83 @@ function RANGE:OnEventShot(EventData) end -- end function trackBomb -- Weapon is not yet "alife" just yet. Start timer in one second. - 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) + 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 --if _track (string.match) and player-range distance < threshold. + end -- if _track (string.match) and player-range distance < threshold. end ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM Functions ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- -function RANGE:_SaveTargetSheet(_playername, result) --RangeBoss Specific Function +function RANGE:_SaveTargetSheet( _playername, result ) -- RangeBoss Specific Function --- Function that saves data to file - local function _savefile(filename, data) - local f = io.open(filename, "wb") + local function _savefile( filename, data ) + local f = io.open( filename, "wb" ) if f then - f:write(data) + f:write( data ) f:close() else - env.info("RANGEBOSS EDIT - could not save target sheet to file") - --self:E(self.lid..string.format("ERROR: could not save target sheet to file %s.\nFile may contain invalid characters.", tostring(filename))) + env.info( "RANGEBOSS EDIT - could not save target sheet to file" ) + -- self:E(self.lid..string.format("ERROR: could not save target sheet to file %s.\nFile may contain invalid characters.", tostring(filename))) end end - - -- Set path or default. - local path=self.targetpath + -- Set path or default. + local path = self.targetpath if lfs then - path=path or lfs.writedir()..[[Logs\]] + path = path or lfs.writedir() .. [[Logs\]] end -- Create unused file name. - local filename=nil - for i=1,9999 do + local filename = nil + for i = 1, 9999 do -- Create file name - if self.targetprefix then - filename=string.format("%s_%s-%04d.csv", self.targetprefix, playerData.actype, i) - else - local name=UTILS.ReplaceIllegalCharacters(_playername, "_") - filename=string.format("RANGERESULTS-%s_Targetsheet-%s-%04d.csv",self.rangename,name, i) - end + if self.targetprefix then + filename = string.format( "%s_%s-%04d.csv", self.targetprefix, playerData.actype, i ) + else + local name = UTILS.ReplaceIllegalCharacters( _playername, "_" ) + filename = string.format( "RANGERESULTS-%s_Targetsheet-%s-%04d.csv", self.rangename, name, i ) + end -- Set path. - if path~=nil then - filename=path.."\\"..filename + if path ~= nil then + filename = path .. "\\" .. filename end -- Check if file exists. - local _exists=UTILS.FileExists(filename) + local _exists = UTILS.FileExists( filename ) if not _exists then break end end -- Header line - local data="Name,Target,Distance,Radial,Quality,Rounds Fired,Rounds Hit,Rounds Quality,Attack Heading,Weapon,Airframe,Mission Time,OS Time\n" + local data = "Name,Target,Distance,Radial,Quality,Rounds Fired,Rounds Hit,Rounds Quality,Attack Heading,Weapon,Airframe,Mission Time,OS Time\n" - --local result=_result --#RANGE.BombResult - 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" - local roundsFired=result.roundsFired - local roundsHit=result.roundsHit - local strafeResult=result.roundsQuality - local attackHeading=result.heading + -- local result=_result --#RANGE.BombResult + 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" + local roundsFired = result.roundsFired + local roundsHit = result.roundsHit + local strafeResult = result.roundsQuality + local attackHeading = result.heading if os then - date=os.date() + date = os.date() end - data=data..string.format("%s,%s,%.2f,%03d,%s,%03d,%03d,%s,%03d,%s,%s,%s,%s", _playername, target, distance, radial, quality, roundsFired, roundsHit, strafeResult, attackHeading, weapon, airframe, time, date) - + data = data .. string.format( "%s,%s,%.2f,%03d,%s,%03d,%03d,%s,%03d,%s,%s,%s,%s", _playername, target, distance, radial, quality, roundsFired, roundsHit, strafeResult, attackHeading, weapon, airframe, time, date ) -- Save file. - _savefile(filename, data) + _savefile( filename, data ) end --- Check spawn queue and spawn aircraft if necessary. @@ -2017,47 +2005,46 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function RANGE:onafterStatus(From, Event, To) +function RANGE:onafterStatus( From, Event, To ) - if self.verbose>0 then + 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" + 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) + local relay = UNIT:FindByName( self.instructorrelayname ) if relay then - alive=tostring(relay:IsAlive()) + alive = tostring( relay:IsAlive() ) end end - text=text..string.format(", Instructor %.3f MHz (Relay=%s alive=%s)", self.instructorfreq, tostring(self.instructorrelayname), alive) + 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.rangecontrol then + local alive = "N/A" if self.rangecontrolrelayname then - local relay=UNIT:FindByName(self.rangecontrolrelayname) + local relay = UNIT:FindByName( self.rangecontrolrelayname ) if relay then - alive=tostring(relay:IsAlive()) + alive = tostring( relay:IsAlive() ) end end - text=text..string.format(", Control %.3f MHz (Relay=%s alive=%s)", self.rangecontrolfreq, tostring(self.rangecontrolrelayname), alive) + text = text .. string.format( ", Control %.3f MHz (Relay=%s alive=%s)", self.rangecontrolfreq, tostring( self.rangecontrolrelayname ), alive ) end - - + -- Check range status. - self:I(self.id..text) - + self:I( self.id .. text ) + end -- Check player status. self:_CheckPlayers() -- Check back in ~10 seconds. - self:__Status(-10) + self:__Status( -10 ) end --- Function called after player enters the range zone. @@ -2066,23 +2053,23 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param #RANGE.PlayerData player Player data. -function RANGE:onafterEnterRange(From, Event, To, player) - +function RANGE:onafterEnterRange( From, Event, To, player ) + if self.instructor and self.rangecontrol then - + -- Range control radio frequency split. - local RF=UTILS.Split(string.format("%.3f", self.rangecontrolfreq), ".") - + local RF = UTILS.Split( string.format( "%.3f", self.rangecontrolfreq ), "." ) + -- Radio message that player entered the range - 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]) + 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) + self.instructor:NewTransmission( RANGE.Sound.IRMegaHertz.filename, RANGE.Sound.IRMegaHertz.duration, self.soundpath ) end - + end --- Function called after player leaves the range zone. @@ -2091,15 +2078,14 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param #RANGE.PlayerData player Player data. -function RANGE:onafterExitRange(From, Event, To, player) +function RANGE:onafterExitRange( From, Event, To, player ) if self.instructor then - self.instructor:NewTransmission(RANGE.Sound.IRExitRange.filename, RANGE.Sound.IRExitRange.duration, self.soundpath) + self.instructor:NewTransmission( RANGE.Sound.IRExitRange.filename, RANGE.Sound.IRExitRange.duration, self.soundpath ) end end - --- Function called after bomb impact on range. -- @param #RANGE self -- @param #string From From state. @@ -2107,49 +2093,49 @@ end -- @param #string To To state. -- @param #RANGE.BombResult result Result of bomb impact. -- @param #RANGE.PlayerData player Player data table. -function RANGE:onafterImpact(From, Event, To, result, player) +function RANGE:onafterImpact( From, Event, To, result, player ) -- Only display target name if there is more than one bomb target. - local targetname=nil - if #self.bombingTargets>1 then - local targetname=result.name + local targetname = nil + if #self.bombingTargets > 1 then + local targetname = result.name end -- Send message to player. - local text=string.format("%s, impact %03d° for %d ft", player.playername, result.radial, UTILS.MetersToFeet(result.distance)) + 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.") + text = text .. string.format( " from bulls of target %s." ) else - text=text.."." + text = text .. "." end - text=text..string.format(" %s hit.", result.quality) - + 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) + self.rangecontrol:NewTransmission( RANGE.Sound.RCImpact.filename, RANGE.Sound.RCImpact.duration, self.soundpath, nil, nil, text, self.subduration ) + self.rangecontrol:Number2Transmission( string.format( "%03d", result.radial ), nil, 0.1 ) + self.rangecontrol:NewTransmission( RANGE.Sound.RCDegrees.filename, RANGE.Sound.RCDegrees.duration, self.soundpath ) + self.rangecontrol:NewTransmission( RANGE.Sound.RCFor.filename, RANGE.Sound.RCFor.duration, self.soundpath ) + self.rangecontrol:Number2Transmission( string.format( "%d", UTILS.MetersToFeet( result.distance ) ) ) + self.rangecontrol:NewTransmission( RANGE.Sound.RCFeet.filename, RANGE.Sound.RCFeet.duration, self.soundpath ) + if result.quality == "POOR" then + self.rangecontrol:NewTransmission( RANGE.Sound.RCPoorHit.filename, RANGE.Sound.RCPoorHit.duration, self.soundpath, nil, 0.5 ) + elseif result.quality == "INEFFECTIVE" then + self.rangecontrol:NewTransmission( RANGE.Sound.RCIneffectiveHit.filename, RANGE.Sound.RCIneffectiveHit.duration, self.soundpath, nil, 0.5 ) + elseif result.quality == "GOOD" then + self.rangecontrol:NewTransmission( RANGE.Sound.RCGoodHit.filename, RANGE.Sound.RCGoodHit.duration, self.soundpath, nil, 0.5 ) + elseif result.quality == "EXCELLENT" then + self.rangecontrol:NewTransmission( RANGE.Sound.RCExcellentHit.filename, RANGE.Sound.RCExcellentHit.duration, self.soundpath, nil, 0.5 ) end - - end + + end -- Unit. - local unit=UNIT:FindByName(player.unitname) + local unit = UNIT:FindByName( player.unitname ) -- Send message. - self:_DisplayMessageToGroup(unit, text, nil, true) - self:T(self.id..text) - + self:_DisplayMessageToGroup( unit, text, nil, true ) + self:T( self.id .. text ) + -- Save results. if self.autosave then self:Save() @@ -2162,11 +2148,11 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function RANGE:onbeforeSave(From, Event, To) +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.")) + self:E( self.id .. string.format( "WARNING: io and/or lfs not desanitized. Cannot save player results." ) ) return false end end @@ -2176,50 +2162,50 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function RANGE:onafterSave(From, Event, To) +function RANGE:onafterSave( From, Event, To ) - local function _savefile(filename, data) - local f=io.open(filename, "wb") + local function _savefile( filename, data ) + local f = io.open( filename, "wb" ) if f then - f:write(data) + f:write( data ) f:close() - self:I(self.id..string.format("Saving player results to file %s", tostring(filename))) + 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))) + self:E( self.id .. string.format( "ERROR: Could not save results to file %s", tostring( filename ) ) ) end end -- Path. - local path=lfs.writedir()..[[Logs\]] + local path = lfs.writedir() .. [[Logs\]] -- Set file name. - local filename=path..string.format("RANGE-%s_BombingResults.csv", self.rangename) + local filename = path .. string.format( "RANGE-%s_BombingResults.csv", self.rangename ) -- Header line. - local scores="Name,Pass,Target,Distance,Radial,Quality,Weapon,Airframe,Mission Time" + local scores = "Name,Pass,Target,Distance,Radial,Quality,Weapon,Airframe,Mission Time" -- Loop over all players. - for playername,results in pairs(self.bombPlayerResults) do + for playername, results in pairs( self.bombPlayerResults ) do -- Loop over player grades table. - for i,_result in pairs(results) do - local result=_result --#RANGE.BombResult - 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" + for i, _result in pairs( results ) do + local result = _result -- #RANGE.BombResult + 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() + 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) + 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) + _savefile( filename, scores ) end --- Function called before save event. Checks that io and lfs are desanitized. @@ -2227,11 +2213,11 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function RANGE:onbeforeLoad(From, Event, To) +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.")) + self:E( self.id .. string.format( "WARNING: io and/or lfs not desanitized. Cannot load player results." ) ) return false end end @@ -2241,74 +2227,74 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function RANGE:onafterLoad(From, Event, To) +function RANGE:onafterLoad( From, Event, To ) --- Function that load data from a file. - local function _loadfile(filename) - local f=io.open(filename, "rb") + local function _loadfile( filename ) + local f = io.open( filename, "rb" ) if f then - --self:I(self.id..string.format("Loading player results from file %s", tostring(filename))) - local data=f:read("*all") + -- self:I(self.id..string.format("Loading player results from file %s", tostring(filename))) + 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))) + 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 -- Path in DCS log file. - local path=lfs.writedir()..[[Logs\]] + local path = lfs.writedir() .. [[Logs\]] -- Set file name. - local filename=path..string.format("RANGE-%s_BombingResults.csv", self.rangename) + local filename = path .. string.format( "RANGE-%s_BombingResults.csv", self.rangename ) -- Info message. - local text=string.format("Loading player bomb results from file %s", filename) - self:I(self.id..text) + local text = string.format( "Loading player bomb results from file %s", filename ) + self:I( self.id .. text ) -- Load asset data from file. - local data=_loadfile(filename) + local data = _loadfile( filename ) if data then -- Split by line break. - local results=UTILS.Split(data,"\n") + local results = UTILS.Split( data, "\n" ) -- Remove first header line. - table.remove(results, 1) + table.remove( results, 1 ) -- Init player scores table. - self.bombPlayerResults={} + self.bombPlayerResults = {} -- Loop over all lines. - for _,_result in pairs(results) do + for _, _result in pairs( results ) do -- Parameters are separated by commata. - local resultdata=UTILS.Split(_result, ",") + local resultdata = UTILS.Split( _result, "," ) -- Grade table - local result={} --#RANGE.BombResult + local result = {} -- #RANGE.BombResult -- Player name. - local playername=resultdata[1] - result.player=playername + local playername = resultdata[1] + result.player = playername -- Results data. - 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" + 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" -- Create player array if necessary. - self.bombPlayerResults[playername]=self.bombPlayerResults[playername] or {} + self.bombPlayerResults[playername] = self.bombPlayerResults[playername] or {} -- Add result to table. - table.insert(self.bombPlayerResults[playername], result) + table.insert( self.bombPlayerResults[playername], result ) end end end @@ -2319,50 +2305,52 @@ end --- Start smoking a coordinate with a delay. -- @param #table _args Argements passed. -function RANGE._DelayedSmoke(_args) - trigger.action.smoke(_args.coord:GetVec3(), _args.color) +function RANGE._DelayedSmoke( _args ) + trigger.action.smoke( _args.coord:GetVec3(), _args.color ) end --- Display top 10 stafing results of a specific player. -- @param #RANGE self -- @param #string _unitName Name of the player unit. -function RANGE:_DisplayMyStrafePitResults(_unitName) - self:F(_unitName) +function RANGE:_DisplayMyStrafePitResults( _unitName ) + self:F( _unitName ) -- Get player unit and name - local _unit,_playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) if _unit and _playername then -- Message header. - local _message = string.format("My Top %d Strafe Pit Results:\n", self.ndisplayresult) + local _message = string.format( "My Top %d Strafe Pit Results:\n", self.ndisplayresult ) -- Get player results. local _results = self.strafePlayerResults[_playername] -- Create message. if _results == nil then - -- No score yet. - _message = string.format("%s: No Score yet.", _playername) + -- No score yet. + _message = string.format( "%s: No Score yet.", _playername ) else -- Sort results table wrt number of hits. - local _sort = function( a,b ) return a.hits > b.hits end - table.sort(_results,_sort) + local _sort = function( a, b ) + return a.hits > b.hits + end + table.sort( _results, _sort ) -- Prepare message of best results. local _bestMsg = "" local _count = 1 -- Loop over results - for _,_result in pairs(_results) do + for _, _result in pairs( _results ) do -- Message text. - _message = _message..string.format("\n[%d] Hits %d - %s - %s", _count, _result.hits, _result.zone.name, _result.text) + _message = _message .. string.format( "\n[%d] Hits %d - %s - %s", _count, _result.hits, _result.zone.name, _result.text ) -- Best result. if _bestMsg == "" then - _bestMsg = string.format("Hits %d - %s - %s", _result.hits, _result.zone.name, _result.text) + _bestMsg = string.format( "Hits %d - %s - %s", _result.hits, _result.zone.name, _result.text ) end -- 10 runs @@ -2371,26 +2359,26 @@ function RANGE:_DisplayMyStrafePitResults(_unitName) end -- Increase counter - _count = _count+1 + _count = _count + 1 end -- Message text. - _message = _message .."\n\nBEST: ".._bestMsg + _message = _message .. "\n\nBEST: " .. _bestMsg end -- Send message to group. - self:_DisplayMessageToGroup(_unit, _message, nil, true, true) + self:_DisplayMessageToGroup( _unit, _message, nil, true, true ) end end --- Display top 10 strafing results of all players. -- @param #RANGE self -- @param #string _unitName Name fo the player unit. -function RANGE:_DisplayStrafePitResults(_unitName) - self:F(_unitName) +function RANGE:_DisplayStrafePitResults( _unitName ) + self:F( _unitName ) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) -- Check if we have a unit which is a player. if _unit and _playername then @@ -2399,14 +2387,14 @@ function RANGE:_DisplayStrafePitResults(_unitName) local _playerResults = {} -- Message text. - local _message = string.format("Strafe Pit Results - Top %d Players:\n", self.ndisplayresult) + local _message = string.format( "Strafe Pit Results - Top %d Players:\n", self.ndisplayresult ) -- Loop over player results. - for _playerName,_results in pairs(self.strafePlayerResults) do + for _playerName, _results in pairs( self.strafePlayerResults ) do -- Get the best result of the player. local _best = nil - for _,_result in pairs(_results) do + for _, _result in pairs( _results ) do if _best == nil or _result.hits > _best.hits then _best = _result end @@ -2414,226 +2402,232 @@ function RANGE:_DisplayStrafePitResults(_unitName) -- Add best result to table. 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}) + 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 - --Sort list! - local _sort = function( a,b ) return a.hits > b.hits end - table.sort(_playerResults,_sort) + -- Sort list! + local _sort = function( a, b ) + return a.hits > b.hits + end + table.sort( _playerResults, _sort ) -- Add top 10 results. - for _i = 1, math.min(#_playerResults, self.ndisplayresult) do - _message = _message..string.format("\n[%d] %s", _i, _playerResults[_i].msg) + for _i = 1, math.min( #_playerResults, self.ndisplayresult ) do + _message = _message .. string.format( "\n[%d] %s", _i, _playerResults[_i].msg ) end -- In case there are no scores yet. - if #_playerResults<1 then - _message = _message.."No player scored yet." + if #_playerResults < 1 then + _message = _message .. "No player scored yet." end -- Send message. - self:_DisplayMessageToGroup(_unit, _message, nil, true, true) + self:_DisplayMessageToGroup( _unit, _message, nil, true, true ) end end --- Display top 10 bombing run results of specific player. -- @param #RANGE self -- @param #string _unitName Name of the player unit. -function RANGE:_DisplayMyBombingResults(_unitName) - self:F(_unitName) +function RANGE:_DisplayMyBombingResults( _unitName ) + self:F( _unitName ) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) if _unit and _playername then -- Init message. - local _message = string.format("My Top %d Bombing Results:\n", self.ndisplayresult) + local _message = string.format( "My Top %d Bombing Results:\n", self.ndisplayresult ) -- Results from player. local _results = self.bombPlayerResults[_playername] -- No score so far. if _results == nil then - _message = _playername..": No Score yet." + _message = _playername .. ": No Score yet." else -- Sort results wrt to distance. - local _sort = function( a,b ) return a.distance < b.distance end - table.sort(_results,_sort) + local _sort = function( a, b ) + return a.distance < b.distance + end + table.sort( _results, _sort ) -- Loop over results. local _bestMsg = "" - for i,_result in pairs(_results) do - local result=_result --#RANGE.BombResult + for i, _result in pairs( _results ) do + local result = _result -- #RANGE.BombResult -- Message with name, weapon and distance. - _message = _message.."\n"..string.format("[%d] %d m %03d° - %s - %s - %s hit", i, result.distance, result.radial, result.name, result.weapon, result.quality) + _message = _message .. "\n" .. string.format( "[%d] %d m %03d° - %s - %s - %s hit", i, result.distance, result.radial, result.name, result.weapon, result.quality ) -- Store best/first result. if _bestMsg == "" then - _bestMsg = string.format("%d m %03d° - %s - %s - %s hit", result.distance, result.radial, result.name, result.weapon, result.quality) + _bestMsg = string.format( "%d m %03d° - %s - %s - %s hit", result.distance, result.radial, result.name, result.weapon, result.quality ) end -- Best 10 runs only. - if i==self.ndisplayresult then + if i == self.ndisplayresult then break end end -- Message. - _message = _message .."\n\nBEST: ".._bestMsg + _message = _message .. "\n\nBEST: " .. _bestMsg end -- Send message. - self:_DisplayMessageToGroup(_unit, _message, nil, true, true) + self:_DisplayMessageToGroup( _unit, _message, nil, true, true ) end end --- Display best bombing results of top 10 players. -- @param #RANGE self -- @param #string _unitName Name of player unit. -function RANGE:_DisplayBombingResults(_unitName) - self:F(_unitName) +function RANGE:_DisplayBombingResults( _unitName ) + self:F( _unitName ) -- Results table. local _playerResults = {} -- Get player unit and name. - local _unit, _player = self:_GetPlayerUnitAndName(_unitName) + local _unit, _player = self:_GetPlayerUnitAndName( _unitName ) -- Check if we have a unit with a player. if _unit and _player then -- Message header. - local _message = string.format("Bombing Results - Top %d Players:\n", self.ndisplayresult) + local _message = string.format( "Bombing Results - Top %d Players:\n", self.ndisplayresult ) -- Loop over players. - for _playerName,_results in pairs(self.bombPlayerResults) do + for _playerName, _results in pairs( self.bombPlayerResults ) do -- Find best result of player. local _best = nil - for _,_result in pairs(_results) do + for _, _result in pairs( _results ) do if _best == nil or _result.distance < _best.distance then - _best = _result + _best = _result end end -- Put best result of player into table. if _best ~= nil then - local bestres=string.format("%s: %d m - %s - %s - %s hit", _playerName, _best.distance, _best.name, _best.weapon, _best.quality) - table.insert(_playerResults, {msg = bestres, distance = _best.distance}) + local bestres = string.format( "%s: %d m - %s - %s - %s hit", _playerName, _best.distance, _best.name, _best.weapon, _best.quality ) + table.insert( _playerResults, { msg = bestres, distance = _best.distance } ) end end -- Sort list of player results. - local _sort = function( a,b ) return a.distance < b.distance end - table.sort(_playerResults,_sort) + local _sort = function( a, b ) + return a.distance < b.distance + end + table.sort( _playerResults, _sort ) -- Loop over player results. - for _i = 1, math.min(#_playerResults, self.ndisplayresult) do - _message = _message..string.format("\n[%d] %s", _i, _playerResults[_i].msg) + for _i = 1, math.min( #_playerResults, self.ndisplayresult ) do + _message = _message .. string.format( "\n[%d] %s", _i, _playerResults[_i].msg ) end -- In case there are no scores yet. - if #_playerResults<1 then - _message = _message.."No player scored yet." + if #_playerResults < 1 then + _message = _message .. "No player scored yet." end -- Send message. - self:_DisplayMessageToGroup(_unit, _message, nil, true, true) + self:_DisplayMessageToGroup( _unit, _message, nil, true, true ) end end --- Report information like bearing and range from player unit to range. -- @param #RANGE self -- @param #string _unitname Name of the player unit. -function RANGE:_DisplayRangeInfo(_unitname) - self:F(_unitname) +function RANGE:_DisplayRangeInfo( _unitname ) + self:F( _unitname ) -- Get player unit and player name. - local unit, playername = self:_GetPlayerUnitAndName(_unitname) + local unit, playername = self:_GetPlayerUnitAndName( _unitname ) -- Check if we have a player. if unit and playername then -- Message text. - local text="" + local text = "" -- Current coordinates. - local coord=unit:GetCoordinate() + local coord = unit:GetCoordinate() if self.location then - local settings=_DATABASE:GetPlayerSettings(playername) or _SETTINGS --Core.Settings#SETTINGS + local settings = _DATABASE:GetPlayerSettings( playername ) or _SETTINGS -- Core.Settings#SETTINGS -- Direction vector from current position (coord) to target (position). - local position=self.location --Core.Point#COORDINATE - local bulls=position:ToStringBULLS(unit:GetCoalition(), settings) - local lldms=position:ToStringLLDMS(settings) - local llddm=position:ToStringLLDDM(settings) - local rangealt=position:GetLandHeight() - local vec3=coord:GetDirectionVec3(position) - local angle=coord:GetAngleDegrees(vec3) - local range=coord:Get2DDistance(position) + local position = self.location -- Core.Point#COORDINATE + local bulls = position:ToStringBULLS( unit:GetCoalition(), settings ) + local lldms = position:ToStringLLDMS( settings ) + local llddm = position:ToStringLLDDM( settings ) + local rangealt = position:GetLandHeight() + local vec3 = coord:GetDirectionVec3( position ) + local angle = coord:GetAngleDegrees( vec3 ) + local range = coord:Get2DDistance( position ) -- Bearing string. - local Bs=string.format('%03d°', angle) + local Bs = string.format( '%03d°', angle ) local texthit if self.PlayerSettings[playername].flaredirecthits then - texthit=string.format("Flare direct hits: ON (flare color %s)\n", self:_flarecolor2text(self.PlayerSettings[playername].flarecolor)) + texthit = string.format( "Flare direct hits: ON (flare color %s)\n", self:_flarecolor2text( self.PlayerSettings[playername].flarecolor ) ) else - texthit=string.format("Flare direct hits: OFF\n") + texthit = string.format( "Flare direct hits: OFF\n" ) end local textbomb if self.PlayerSettings[playername].smokebombimpact then - textbomb=string.format("Smoke bomb impact points: ON (smoke color %s)\n", self:_smokecolor2text(self.PlayerSettings[playername].smokecolor)) + textbomb = string.format( "Smoke bomb impact points: ON (smoke color %s)\n", self:_smokecolor2text( self.PlayerSettings[playername].smokecolor ) ) else - textbomb=string.format("Smoke bomb impact points: OFF\n") + textbomb = string.format( "Smoke bomb impact points: OFF\n" ) end local textdelay if self.PlayerSettings[playername].delaysmoke then - textdelay=string.format("Smoke bomb delay: ON (delay %.1f seconds)", self.TdelaySmoke) + textdelay = string.format( "Smoke bomb delay: ON (delay %.1f seconds)", self.TdelaySmoke ) else - textdelay=string.format("Smoke bomb delay: OFF") + textdelay = string.format( "Smoke bomb delay: OFF" ) end -- Player unit settings. - local trange=string.format("%.1f km", range/1000) - local trangealt=string.format("%d m", rangealt) - local tstrafemaxalt=string.format("%d m", self.strafemaxalt) + local trange = string.format( "%.1f km", range / 1000 ) + local trangealt = string.format( "%d m", rangealt ) + local tstrafemaxalt = string.format( "%d m", self.strafemaxalt ) if settings:IsImperial() then - trange=string.format("%.1f NM", UTILS.MetersToNM(range)) - trangealt=string.format("%d feet", UTILS.MetersToFeet(rangealt)) - tstrafemaxalt=string.format("%d feet", UTILS.MetersToFeet(self.strafemaxalt)) + trange = string.format( "%.1f NM", UTILS.MetersToNM( range ) ) + trangealt = string.format( "%d feet", UTILS.MetersToFeet( rangealt ) ) + tstrafemaxalt = string.format( "%d feet", UTILS.MetersToFeet( self.strafemaxalt ) ) end -- Message. - text=text..string.format("Information on %s:\n", self.rangename) - text=text..string.format("-------------------------------------------------------\n") - text=text..string.format("Bearing %s, Range %s\n", Bs, trange) - text=text..string.format("%s\n", bulls) - text=text..string.format("%s\n", lldms) - text=text..string.format("%s\n", llddm) - text=text..string.format("Altitude ASL: %s\n", trangealt) - text=text..string.format("Max strafing alt AGL: %s\n", tstrafemaxalt) - text=text..string.format("# of strafe targets: %d\n", self.nstrafetargets) - text=text..string.format("# of bomb targets: %d\n", self.nbombtargets) - text=text..texthit - text=text..textbomb - text=text..textdelay + text = text .. string.format( "Information on %s:\n", self.rangename ) + text = text .. string.format( "-------------------------------------------------------\n" ) + text = text .. string.format( "Bearing %s, Range %s\n", Bs, trange ) + text = text .. string.format( "%s\n", bulls ) + text = text .. string.format( "%s\n", lldms ) + text = text .. string.format( "%s\n", llddm ) + text = text .. string.format( "Altitude ASL: %s\n", trangealt ) + text = text .. string.format( "Max strafing alt AGL: %s\n", tstrafemaxalt ) + text = text .. string.format( "# of strafe targets: %d\n", self.nstrafetargets ) + text = text .. string.format( "# of bomb targets: %d\n", self.nbombtargets ) + text = text .. texthit + text = text .. textbomb + text = text .. textdelay -- Send message to player group. - self:_DisplayMessageToGroup(unit, text, nil, true, true) + self:_DisplayMessageToGroup( unit, text, nil, true, true ) -- Debug output. - self:T2(self.id..text) + self:T2( self.id .. text ) end end end @@ -2641,150 +2635,148 @@ end --- Display bombing target locations to player. -- @param #RANGE self -- @param #string _unitname Name of the player unit. -function RANGE:_DisplayBombTargets(_unitname) - self:F(_unitname) +function RANGE:_DisplayBombTargets( _unitname ) + self:F( _unitname ) -- Get player unit and player name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitname) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitname ) -- Check if we have a player. if _unit and _playername then -- Player settings. - local _settings=_DATABASE:GetPlayerSettings(_playername) or _SETTINGS --Core.Settings#SETTINGS + local _settings = _DATABASE:GetPlayerSettings( _playername ) or _SETTINGS -- Core.Settings#SETTINGS -- Message text. - local _text="Bomb Target Locations:" + local _text = "Bomb Target Locations:" - for _,_bombtarget in pairs(self.bombingTargets) do - local bombtarget=_bombtarget --#RANGE.BombTarget + for _, _bombtarget in pairs( self.bombingTargets ) do + local bombtarget = _bombtarget -- #RANGE.BombTarget -- Coordinate of bombtarget. - local coord=self:_GetBombTargetCoordinate(bombtarget) + local coord = self:_GetBombTargetCoordinate( bombtarget ) if coord then - + -- Get elevation - local elevation=coord:GetLandHeight() - local eltxt=string.format("%d m", elevation) + local elevation = coord:GetLandHeight() + local eltxt = string.format( "%d m", elevation ) if not _settings:IsMetric() then - elevation=UTILS.MetersToFeet(elevation) - eltxt=string.format("%d ft", elevation) + elevation = UTILS.MetersToFeet( elevation ) + eltxt = string.format( "%d ft", elevation ) end - local ca2g=coord:ToStringA2G(_unit,_settings) - _text=_text..string.format("\n- %s:\n%s @ %s", bombtarget.name or "unknown", ca2g, eltxt) + local ca2g = coord:ToStringA2G( _unit, _settings ) + _text = _text .. string.format( "\n- %s:\n%s @ %s", bombtarget.name or "unknown", ca2g, eltxt ) end end - self:_DisplayMessageToGroup(_unit,_text, 120, true, true) + self:_DisplayMessageToGroup( _unit, _text, 120, true, true ) end end --- Display pit location and heading to player. -- @param #RANGE self -- @param #string _unitname Name of the player unit. -function RANGE:_DisplayStrafePits(_unitname) - self:F(_unitname) +function RANGE:_DisplayStrafePits( _unitname ) + self:F( _unitname ) -- Get player unit and player name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitname) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitname ) -- Check if we have a player. if _unit and _playername then -- Player settings. - local _settings=_DATABASE:GetPlayerSettings(_playername) or _SETTINGS --Core.Settings#SETTINGS + local _settings = _DATABASE:GetPlayerSettings( _playername ) or _SETTINGS -- Core.Settings#SETTINGS -- Message text. - local _text="Strafe Target Locations:" + local _text = "Strafe Target Locations:" - for _,_strafepit in pairs(self.strafeTargets) do - local _target=_strafepit --Wrapper.Positionable#POSITIONABLE + for _, _strafepit in pairs( self.strafeTargets ) do + local _target = _strafepit -- Wrapper.Positionable#POSITIONABLE -- Pit parameters. - local coord=_strafepit.coordinate --Core.Point#COORDINATE - local heading=_strafepit.heading + local coord = _strafepit.coordinate -- Core.Point#COORDINATE + local heading = _strafepit.heading -- Turn heading around ==> approach heading. - if heading>180 then - heading=heading-180 + if heading > 180 then + heading = heading - 180 else - heading=heading+180 + heading = heading + 180 end - local mycoord=coord:ToStringA2G(_unit, _settings) - _text=_text..string.format("\n- %s: heading %03d°\n%s",_strafepit.name, heading, mycoord) + 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) + self:_DisplayMessageToGroup( _unit, _text, nil, true, true ) end end - --- Report weather conditions at range. Temperature, QFE pressure and wind data. -- @param #RANGE self -- @param #string _unitname Name of the player unit. -function RANGE:_DisplayRangeWeather(_unitname) - self:F(_unitname) +function RANGE:_DisplayRangeWeather( _unitname ) + self:F( _unitname ) -- Get player unit and player name. - local unit, playername = self:_GetPlayerUnitAndName(_unitname) + local unit, playername = self:_GetPlayerUnitAndName( _unitname ) -- Check if we have a player. if unit and playername then -- Message text. - local text="" + local text = "" -- Current coordinates. - local coord=unit:GetCoordinate() + local coord = unit:GetCoordinate() if self.location then -- Get atmospheric data at range location. - local position=self.location --Core.Point#COORDINATE - local T=position:GetTemperature() - local P=position:GetPressure() - local Wd,Ws=position:GetWind() + local position = self.location -- Core.Point#COORDINATE + local T = position:GetTemperature() + local P = position:GetPressure() + local Wd, Ws = position:GetWind() -- Get Beaufort wind scale. - local Bn,Bd=UTILS.BeaufortScale(Ws) + local Bn, Bd = UTILS.BeaufortScale( Ws ) - local WD=string.format('%03d°', Wd) - local Ts=string.format("%d°C",T) + local WD = string.format( '%03d°', Wd ) + local Ts = string.format( "%d°C", T ) - local hPa2inHg=0.0295299830714 - local hPa2mmHg=0.7500615613030 + local hPa2inHg = 0.0295299830714 + local hPa2mmHg = 0.7500615613030 - local settings=_DATABASE:GetPlayerSettings(playername) or _SETTINGS --Core.Settings#SETTINGS - local tT=string.format("%d°C",T) - local tW=string.format("%.1f m/s", Ws) - local tP=string.format("%.1f mmHg", P*hPa2mmHg) + local settings = _DATABASE:GetPlayerSettings( playername ) or _SETTINGS -- Core.Settings#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 - --tT=string.format("%d°F", UTILS.CelciusToFarenheit(T)) - tW=string.format("%.1f knots", UTILS.MpsToKnots(Ws)) - tP=string.format("%.2f inHg", P*hPa2inHg) + -- tT=string.format("%d°F", UTILS.CelciusToFarenheit(T)) + tW = string.format( "%.1f knots", UTILS.MpsToKnots( Ws ) ) + tP = string.format( "%.2f inHg", P * hPa2inHg ) end - -- Message text. - 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) + 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) + text = string.format( "No range location defined for range %s.", self.rangename ) end -- Send message to player group. - self:_DisplayMessageToGroup(unit, text, nil, true, true) + self:_DisplayMessageToGroup( unit, text, nil, true, true ) -- Debug output. - self:T2(self.id..text) + self:T2( self.id .. text ) else - self:T(self.id..string.format("ERROR! Could not find player unit in RangeInfo! Name = %s", _unitname)) + self:T( self.id .. string.format( "ERROR! Could not find player unit in RangeInfo! Name = %s", _unitname ) ) end end @@ -2796,23 +2788,23 @@ end -- @param #string _unitName Name of player unit. function RANGE:_CheckPlayers() - for playername,_playersettings in pairs(self.PlayerSettings) do - local playersettings=_playersettings --#RANGE.PlayerData + for playername, _playersettings in pairs( self.PlayerSettings ) do + local playersettings = _playersettings -- #RANGE.PlayerData - local unitname=playersettings.unitname - local unit=UNIT:FindByName(unitname) + local unitname = playersettings.unitname + local unit = UNIT:FindByName( unitname ) if unit and unit:IsAlive() then - if unit:IsInZone(self.rangezone) then + if unit:IsInZone( self.rangezone ) then ------------------------------ -- Player INSIDE Range Zone -- ------------------------------ if not playersettings.inzone then - playersettings.inzone=true - self:EnterRange(playersettings) + playersettings.inzone = true + self:EnterRange( playersettings ) end else @@ -2821,9 +2813,9 @@ function RANGE:_CheckPlayers() -- Player OUTSIDE Range Zone -- ------------------------------- - if playersettings.inzone==true then - playersettings.inzone=false - self:ExitRange(playersettings) + if playersettings.inzone == true then + playersettings.inzone = false + self:ExitRange( playersettings ) end end @@ -2835,66 +2827,66 @@ end --- Check if player is inside a strafing zone. If he is, we start looking for hits. If he was and left the zone again, the result is stored. -- @param #RANGE self -- @param #string _unitName Name of player unit. -function RANGE:_CheckInZone(_unitName) - self:F2(_unitName) +function RANGE:_CheckInZone( _unitName ) + self:F2( _unitName ) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) - local unitheading = 0 --RangeBoss + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) + local unitheading = 0 -- RangeBoss if _unit and _playername then --- Function to check if unit is in zone and facing in the right direction and is below the max alt. - local function checkme(targetheading, _zone) - local zone=_zone --Core.Zone#ZONE + local function checkme( targetheading, _zone ) + local zone = _zone -- Core.Zone#ZONE -- Heading check. - local unitheading = _unit:GetHeading() - unitheadingStrafe = _unit:GetHeading() --RangeBoss - local pitheading = targetheading-180 - local deltaheading = unitheading-pitheading - local towardspit = math.abs(deltaheading)<=90 or math.abs(deltaheading-360)<=90 - + local unitheading = _unit:GetHeading() + unitheadingStrafe = _unit:GetHeading() -- RangeBoss + 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} --DCS#Vec2 - local landheight=land.getHeight(vec2) - local unitalt=vec3.y-landheight - - if unitalt<=self.strafemaxalt then - local unitinzone=zone:IsVec2InZone(vec2) + + local vec3 = _unit:GetVec3() + local vec2 = { x = vec3.x, y = vec3.z } -- DCS#Vec2 + 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 -- Current position of player unit. - local _unitID = _unit:GetID() + local _unitID = _unit:GetID() -- Currently strafing? (strafeStatus is nil if not) local _currentStrafeRun = self.strafeStatus[_unitID] - if _currentStrafeRun then -- player has already registered for a strafing run. + if _currentStrafeRun then -- player has already registered for a strafing run. -- Get the current approach zone and check if player is inside. - local zone=_currentStrafeRun.zone.polygon --Core.Zone#ZONE_POLYGON_BASE + local zone = _currentStrafeRun.zone.polygon -- Core.Zone#ZONE_POLYGON_BASE -- Check if unit in zone and facing the right direction. - local unitinzone=checkme(_currentStrafeRun.zone.heading, zone) + local unitinzone = checkme( _currentStrafeRun.zone.heading, zone ) -- Check if player is in strafe zone and below max alt. if unitinzone then - StrafeAircraftType = _unit:GetTypeName() --RangeBoss + StrafeAircraftType = _unit:GetTypeName() -- RangeBoss -- Still in zone, keep counting hits. Increase counter. - _currentStrafeRun.time = _currentStrafeRun.time+1 + _currentStrafeRun.time = _currentStrafeRun.time + 1 else -- Increase counter - _currentStrafeRun.time = _currentStrafeRun.time+1 + _currentStrafeRun.time = _currentStrafeRun.time + 1 if _currentStrafeRun.time <= 3 then @@ -2902,23 +2894,23 @@ function RANGE:_CheckInZone(_unitName) self.strafeStatus[_unitID] = nil -- Message text. - local _msg = string.format("%s left strafing zone %s too quickly. No Score.", _playername, _currentStrafeRun.zone.name) + local _msg = string.format( "%s left strafing zone %s too quickly. No Score.", _playername, _currentStrafeRun.zone.name ) -- Send message. - self:_DisplayMessageToGroup(_unit, _msg, nil, true) - + self:_DisplayMessageToGroup( _unit, _msg, nil, true ) + if self.rangecontrol then - self.rangecontrol:NewTransmission(RANGE.Sound.RCLeftStrafePitTooQuickly.filename, RANGE.Sound.RCLeftStrafePitTooQuickly.duration, self.soundpath) + self.rangecontrol:NewTransmission( RANGE.Sound.RCLeftStrafePitTooQuickly.filename, RANGE.Sound.RCLeftStrafePitTooQuickly.duration, self.soundpath ) end else -- Get current ammo. - local _ammo=self:_GetAmmo(_unitName) + local _ammo = self:_GetAmmo( _unitName ) -- Result. local _result = self.strafeStatus[_unitID] - local _sound = nil --#RANGE.Soundfile + local _sound = nil -- #RANGE.Soundfile --[[ --RangeBoss commented out in order to implement strafe quality based on accuracy percentage, not the number of rounds on target -- Judge this pass. Text is displayed on summary. if _result.hits >= _result.zone.goodPass*2 then @@ -2936,97 +2928,99 @@ function RANGE:_CheckInZone(_unitName) end ]] -- Calculate accuracy of run. Number of hits wrt number of rounds fired. - local shots=_result.ammo-_ammo - local accur=0 - if shots>0 then - accur=_result.hits/shots*100 - if accur > 100 then accur = 100 end + local shots = _result.ammo - _ammo + local accur = 0 + if shots > 0 then + accur = _result.hits / shots * 100 + if accur > 100 then + accur = 100 + end end - if invalidStrafe == true then-- - _result.text = "* INVALID - PASSED FOUL LINE *" - _sound=RANGE.Sound.RCPoorPass -- + if invalidStrafe == true then -- + _result.text = "* INVALID - PASSED FOUL LINE *" + _sound = RANGE.Sound.RCPoorPass -- else if accur >= 90 then _result.text = "DEADEYE PASS" - _sound=RANGE.Sound.RCExcellentPass + _sound = RANGE.Sound.RCExcellentPass elseif accur >= 75 then _result.text = "EXCELLENT PASS" - _sound=RANGE.Sound.RCExcellentPass + _sound = RANGE.Sound.RCExcellentPass elseif accur >= 50 then _result.text = "GOOD PASS" - _sound=RANGE.Sound.RCGoodPass + _sound = RANGE.Sound.RCGoodPass elseif accur >= 25 then _result.text = "INEFFECTIVE PASS" - _sound=RANGE.Sound.RCIneffectivePass + _sound = RANGE.Sound.RCIneffectivePass else _result.text = "POOR PASS" - _sound=RANGE.Sound.RCPoorPass + _sound = RANGE.Sound.RCPoorPass end end - clientStrafed = true --RANGEBOSS + clientStrafed = true -- RANGEBOSS -- Message text. - local _text=string.format("%s, hits on target %s: %d", self:_myname(_unitName), _result.zone.name, _result.hits) + 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) + _text = _text .. string.format( "\nTotal rounds fired %d. Accuracy %.1f %%.", shots, accur ) end - _text=_text..string.format("\n%s", _result.text) + _text = _text .. string.format( "\n%s", _result.text ) -- Send message. - self:_DisplayMessageToGroup(_unit, _text) - - --RangeBoss Edit for strafe table insert - + self:_DisplayMessageToGroup( _unit, _text ) + + -- RangeBoss Edit for strafe table insert + -- Local results. - local result={} --#RANGE.BombResult - result.name=_result.zone.name or "unknown" - result.distance=0 - result.radial=0 - result.weapon="N/A" - result.quality="N/A" - result.player=_playernamee - result.time=timer.getAbsTime() - result.airframe=StrafeAircraftType - result.roundsFired=shots --RANGEBOSS - result.roundsHit=_result.hits --RANGEBOSS - result.roundsQuality=_result.text --RANGEBOSS - result.strafeAccuracy=accur - result.heading =unitheadingStrafe --RANGEBOSS - - Straferesult.name= _result.zone.name or "unknown" - Straferesult.distance=0 - Straferesult.radial=0 - Straferesult.weapon="N/A" - Straferesult.quality="N/A" - Straferesult.player=_playername - Straferesult.time=timer.getAbsTime() - Straferesult.airframe=StrafeAircraftType - Straferesult.roundsFired=shots - Straferesult.roundsHit= _result.hits - Straferesult.roundsQuality=_result.text - Straferesult.strafeAccuracy=accur - Straferesult.rangename=self.rangename - - -- Save trap sheet. - if playerData.targeton and self.targetsheet then - self:_SaveTargetSheet(_playername, result) - end - --RangeBoss edit for strafe data saved to file + local result = {} -- #RANGE.BombResult + result.name = _result.zone.name or "unknown" + result.distance = 0 + result.radial = 0 + result.weapon = "N/A" + result.quality = "N/A" + result.player = _playernamee + result.time = timer.getAbsTime() + result.airframe = StrafeAircraftType + result.roundsFired = shots -- RANGEBOSS + result.roundsHit = _result.hits -- RANGEBOSS + result.roundsQuality = _result.text -- RANGEBOSS + result.strafeAccuracy = accur + result.heading = unitheadingStrafe -- RANGEBOSS + + Straferesult.name = _result.zone.name or "unknown" + Straferesult.distance = 0 + Straferesult.radial = 0 + Straferesult.weapon = "N/A" + Straferesult.quality = "N/A" + Straferesult.player = _playername + Straferesult.time = timer.getAbsTime() + Straferesult.airframe = StrafeAircraftType + Straferesult.roundsFired = shots + Straferesult.roundsHit = _result.hits + Straferesult.roundsQuality = _result.text + Straferesult.strafeAccuracy = accur + Straferesult.rangename = self.rangename + + -- Save trap sheet. + if playerData.targeton and self.targetsheet then + self:_SaveTargetSheet( _playername, result ) + end + -- RangeBoss edit for strafe data saved to file -- Voice over. - 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 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) + 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) + self.rangecontrol:NewTransmission( _sound.filename, _sound.duration, self.soundpath, nil, 0.5 ) end -- Set strafe status to nil. @@ -3034,7 +3028,7 @@ function RANGE:_CheckInZone(_unitName) -- Save stats so the player can retrieve them. local _stats = self.strafePlayerResults[_playername] or {} - table.insert(_stats, _result) + table.insert( _stats, _result ) self.strafePlayerResults[_playername] = _stats end @@ -3043,34 +3037,34 @@ function RANGE:_CheckInZone(_unitName) else -- Check to see if we're in any of the strafing zones (first time). - for _,_targetZone in pairs(self.strafeTargets) do + for _, _targetZone in pairs( self.strafeTargets ) do -- Get the current approach zone and check if player is inside. - local zone=_targetZone.polygon --Core.Zone#ZONE_POLYGON_BASE + local zone = _targetZone.polygon -- Core.Zone#ZONE_POLYGON_BASE -- Check if unit in zone and facing the right direction. - local unitinzone=checkme(_targetZone.heading, zone) + local unitinzone = checkme( _targetZone.heading, zone ) -- Player is inside zone. if unitinzone then -- Get ammo at the beginning of the run. - local _ammo=self:_GetAmmo(_unitName) + local _ammo = self:_GetAmmo( _unitName ) -- Init strafe status for this player. - self.strafeStatus[_unitID] = {hits = 0, zone = _targetZone, time = 1, ammo=_ammo, pastfoulline=false} + self.strafeStatus[_unitID] = { hits = 0, zone = _targetZone, time = 1, ammo = _ammo, pastfoulline = false } -- Rolling in! - local _msg=string.format("%s, rolling in on strafe pit %s.", self:_myname(_unitName), _targetZone.name) - + 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 - clientRollingIn = true --RANGEBOSS + self.rangecontrol:NewTransmission( RANGE.Sound.RCRollingInOnStrafeTarget.filename, RANGE.Sound.RCRollingInOnStrafeTarget.duration, self.soundpath ) + end + clientRollingIn = true -- RANGEBOSS -- Send message. - self:_DisplayMessageToGroup(_unit, _msg, 10, true) - hypemanStrafeRollIn = _msg --RANGEBOSS + self:_DisplayMessageToGroup( _unit, _msg, 10, true ) + hypemanStrafeRollIn = _msg -- RANGEBOSS -- We found our player. Skip remaining checks. break @@ -3090,18 +3084,18 @@ end --- Add menu commands for player. -- @param #RANGE self -- @param #string _unitName Name of player unit. -function RANGE:_AddF10Commands(_unitName) - self:F(_unitName) +function RANGE:_AddF10Commands( _unitName ) + self:F( _unitName ) -- Get player unit and name. - local _unit, playername = self:_GetPlayerUnitAndName(_unitName) + local _unit, playername = self:_GetPlayerUnitAndName( _unitName ) -- Check for player unit. if _unit and playername then -- Get group and ID. - local group=_unit:GetGroup() - local _gid=group:GetID() + local group = _unit:GetGroup() + local _gid = group:GetID() if group and _gid then @@ -3111,7 +3105,7 @@ function RANGE:_AddF10Commands(_unitName) self.MenuAddedTo[_gid] = true -- Range root menu path. - local _rangePath=nil + local _rangePath = nil if RANGE.MenuF10Root then @@ -3119,8 +3113,8 @@ function RANGE:_AddF10Commands(_unitName) -- MISSION LEVEL -- ------------------- - --_rangePath = missionCommands.addSubMenuForGroup(_gid, self.rangename, RANGE.MenuF10Root) - _rangePath = MENU_GROUP:New(group,"On the Range") + -- _rangePath = missionCommands.addSubMenuForGroup(_gid, self.rangename, RANGE.MenuF10Root) + _rangePath = MENU_GROUP:New( group, "On the Range" ) else @@ -3130,63 +3124,63 @@ function RANGE:_AddF10Commands(_unitName) -- Main F10 menu: F10/On the Range// if RANGE.MenuF10[_gid] == nil then - --RANGE.MenuF10[_gid]=missionCommands.addSubMenuForGroup(_gid, "On the Range") - RANGE.MenuF10[_gid]=MENU_GROUP:New(group,"On the Range") + -- RANGE.MenuF10[_gid]=missionCommands.addSubMenuForGroup(_gid, "On the Range") + RANGE.MenuF10[_gid] = MENU_GROUP:New( group, "On the Range" ) end - --_rangePath = missionCommands.addSubMenuForGroup(_gid, self.rangename, RANGE.MenuF10[_gid]) - _rangePath = MENU_GROUP:New(group,self.rangename,RANGE.MenuF10[_gid]) + -- _rangePath = missionCommands.addSubMenuForGroup(_gid, self.rangename, RANGE.MenuF10[_gid]) + _rangePath = MENU_GROUP:New( group, self.rangename, RANGE.MenuF10[_gid] ) end - - local _statsPath = MENU_GROUP:New(group,"Statistics",_rangePath) - local _markPath = MENU_GROUP:New(group,"Mark Targets",_rangePath) - local _settingsPath = MENU_GROUP:New(group,"My Settings",_rangePath) - local _infoPath = MENU_GROUP:New(group,"Range Info",_rangePath) - - -- F10/On the Range//My Settings/ - local _mysmokePath = MENU_GROUP:New(group,"Smoke Color",_settingsPath) - local _myflarePath = MENU_GROUP:New(group,"Flare Color",_settingsPath) - --F10/On the Range//Mark Targets/ - local _MoMap = MENU_GROUP_COMMAND:New(group,"Mark On Map",_markPath,self._MarkTargetsOnMap, self, _unitName) - local _IllRng = MENU_GROUP_COMMAND:New(group, "Illuminate Range", _markPath, self._IlluminateBombTargets, self, _unitName) - local _SSpit = MENU_GROUP_COMMAND:New(group, "Smoke Strafe Pits", _markPath, self._SmokeStrafeTargetBoxes, self, _unitName) - local _SStgts = MENU_GROUP_COMMAND:New(group, "Smoke Strafe Tgts", _markPath, self._SmokeStrafeTargets, self, _unitName) - local _SBtgts = MENU_GROUP_COMMAND:New(group, "Smoke Bomb Tgts", _markPath, self._SmokeBombTargets, self, _unitName) - -- F10/On the Range//Stats/ - local _AllSR = MENU_GROUP_COMMAND:New(group, "All Strafe Results", _statsPath, self._DisplayStrafePitResults, self, _unitName) - local _AllBR = MENU_GROUP_COMMAND:New(group, "All Bombing Results", _statsPath, self._DisplayBombingResults, self, _unitName) - local _MySR = MENU_GROUP_COMMAND:New(group, "My Strafe Results", _statsPath, self._DisplayMyStrafePitResults, self, _unitName) - local _MyBR = MENU_GROUP_COMMAND:New(group, "My Bomb Results", _statsPath, self._DisplayMyBombingResults, self, _unitName) - local _ResetST = MENU_GROUP_COMMAND:New(group, "Reset All Stats", _statsPath, self._ResetRangeStats, self, _unitName) - -- F10/On the Range//My Settings/Smoke Color/ - local _BlueSM = MENU_GROUP_COMMAND:New(group, "Blue Smoke", _mysmokePath, self._playersmokecolor, self, _unitName, SMOKECOLOR.Blue) - local _GrSM = MENU_GROUP_COMMAND:New(group, "Green Smoke", _mysmokePath, self._playersmokecolor, self, _unitName, SMOKECOLOR.Green) - local _OrSM = MENU_GROUP_COMMAND:New(group, "Orange Smoke", _mysmokePath, self._playersmokecolor, self, _unitName, SMOKECOLOR.Orange) - local _ReSM = MENU_GROUP_COMMAND:New(group, "Red Smoke", _mysmokePath, self._playersmokecolor, self, _unitName, SMOKECOLOR.Red) - local _WhSm = MENU_GROUP_COMMAND:New(group, "White Smoke", _mysmokePath, self._playersmokecolor, self, _unitName, SMOKECOLOR.White) - -- F10/On the Range//My Settings/Flare Color/ - local _GrFl = MENU_GROUP_COMMAND:New(group, "Green Flares", _myflarePath, self._playerflarecolor, self, _unitName, FLARECOLOR.Green) - local _ReFl = MENU_GROUP_COMMAND:New(group, "Red Flares", _myflarePath, self._playerflarecolor, self, _unitName, FLARECOLOR.Red) - local _WhFl = MENU_GROUP_COMMAND:New(group, "White Flares", _myflarePath, self._playerflarecolor, self, _unitName, FLARECOLOR.White) - local _YeFl = MENU_GROUP_COMMAND:New(group, "Yellow Flares", _myflarePath, self._playerflarecolor, self, _unitName, FLARECOLOR.Yellow) + local _statsPath = MENU_GROUP:New( group, "Statistics", _rangePath ) + local _markPath = MENU_GROUP:New( group, "Mark Targets", _rangePath ) + local _settingsPath = MENU_GROUP:New( group, "My Settings", _rangePath ) + local _infoPath = MENU_GROUP:New( group, "Range Info", _rangePath ) + -- F10/On the Range//My Settings/ - local _SmDe = MENU_GROUP_COMMAND:New(group, "Smoke Delay On/Off", _settingsPath, self._SmokeBombDelayOnOff, self, _unitName) - local _SmIm = MENU_GROUP_COMMAND:New(group, "Smoke Impact On/Off", _settingsPath, self._SmokeBombImpactOnOff, self, _unitName) - local _FlHi = MENU_GROUP_COMMAND:New(group, "Flare Hits On/Off", _settingsPath, self._FlareDirectHitsOnOff, self, _unitName) - local _AlMeA = MENU_GROUP_COMMAND:New(group, "All Messages On/Off", _settingsPath, self._MessagesToPlayerOnOff, self, _unitName) - local _TrpSh = MENU_GROUP_COMMAND:New(group, "Targetsheet On/Off", _settingsPath, self._TargetsheetOnOff, self, _unitName) + local _mysmokePath = MENU_GROUP:New( group, "Smoke Color", _settingsPath ) + local _myflarePath = MENU_GROUP:New( group, "Flare Color", _settingsPath ) + + -- F10/On the Range//Mark Targets/ + local _MoMap = MENU_GROUP_COMMAND:New( group, "Mark On Map", _markPath, self._MarkTargetsOnMap, self, _unitName ) + local _IllRng = MENU_GROUP_COMMAND:New( group, "Illuminate Range", _markPath, self._IlluminateBombTargets, self, _unitName ) + local _SSpit = MENU_GROUP_COMMAND:New( group, "Smoke Strafe Pits", _markPath, self._SmokeStrafeTargetBoxes, self, _unitName ) + local _SStgts = MENU_GROUP_COMMAND:New( group, "Smoke Strafe Tgts", _markPath, self._SmokeStrafeTargets, self, _unitName ) + local _SBtgts = MENU_GROUP_COMMAND:New( group, "Smoke Bomb Tgts", _markPath, self._SmokeBombTargets, self, _unitName ) + -- F10/On the Range//Stats/ + local _AllSR = MENU_GROUP_COMMAND:New( group, "All Strafe Results", _statsPath, self._DisplayStrafePitResults, self, _unitName ) + local _AllBR = MENU_GROUP_COMMAND:New( group, "All Bombing Results", _statsPath, self._DisplayBombingResults, self, _unitName ) + local _MySR = MENU_GROUP_COMMAND:New( group, "My Strafe Results", _statsPath, self._DisplayMyStrafePitResults, self, _unitName ) + local _MyBR = MENU_GROUP_COMMAND:New( group, "My Bomb Results", _statsPath, self._DisplayMyBombingResults, self, _unitName ) + local _ResetST = MENU_GROUP_COMMAND:New( group, "Reset All Stats", _statsPath, self._ResetRangeStats, self, _unitName ) + -- F10/On the Range//My Settings/Smoke Color/ + local _BlueSM = MENU_GROUP_COMMAND:New( group, "Blue Smoke", _mysmokePath, self._playersmokecolor, self, _unitName, SMOKECOLOR.Blue ) + local _GrSM = MENU_GROUP_COMMAND:New( group, "Green Smoke", _mysmokePath, self._playersmokecolor, self, _unitName, SMOKECOLOR.Green ) + local _OrSM = MENU_GROUP_COMMAND:New( group, "Orange Smoke", _mysmokePath, self._playersmokecolor, self, _unitName, SMOKECOLOR.Orange ) + local _ReSM = MENU_GROUP_COMMAND:New( group, "Red Smoke", _mysmokePath, self._playersmokecolor, self, _unitName, SMOKECOLOR.Red ) + local _WhSm = MENU_GROUP_COMMAND:New( group, "White Smoke", _mysmokePath, self._playersmokecolor, self, _unitName, SMOKECOLOR.White ) + -- F10/On the Range//My Settings/Flare Color/ + local _GrFl = MENU_GROUP_COMMAND:New( group, "Green Flares", _myflarePath, self._playerflarecolor, self, _unitName, FLARECOLOR.Green ) + local _ReFl = MENU_GROUP_COMMAND:New( group, "Red Flares", _myflarePath, self._playerflarecolor, self, _unitName, FLARECOLOR.Red ) + local _WhFl = MENU_GROUP_COMMAND:New( group, "White Flares", _myflarePath, self._playerflarecolor, self, _unitName, FLARECOLOR.White ) + local _YeFl = MENU_GROUP_COMMAND:New( group, "Yellow Flares", _myflarePath, self._playerflarecolor, self, _unitName, FLARECOLOR.Yellow ) + -- F10/On the Range//My Settings/ + local _SmDe = MENU_GROUP_COMMAND:New( group, "Smoke Delay On/Off", _settingsPath, self._SmokeBombDelayOnOff, self, _unitName ) + local _SmIm = MENU_GROUP_COMMAND:New( group, "Smoke Impact On/Off", _settingsPath, self._SmokeBombImpactOnOff, self, _unitName ) + local _FlHi = MENU_GROUP_COMMAND:New( group, "Flare Hits On/Off", _settingsPath, self._FlareDirectHitsOnOff, self, _unitName ) + local _AlMeA = MENU_GROUP_COMMAND:New( group, "All Messages On/Off", _settingsPath, self._MessagesToPlayerOnOff, self, _unitName ) + local _TrpSh = MENU_GROUP_COMMAND:New( group, "Targetsheet On/Off", _settingsPath, self._TargetsheetOnOff, self, _unitName ) -- F10/On the Range//Range Information - local _WeIn = MENU_GROUP_COMMAND:New(group, "General Info", _infoPath, self._DisplayRangeInfo, self, _unitName) - local _WeRe = MENU_GROUP_COMMAND:New(group, "Weather Report", _infoPath, self._DisplayRangeWeather, self, _unitName) - local _BoTgtgs = MENU_GROUP_COMMAND:New(group, "Bombing Targets", _infoPath, self._DisplayBombTargets, self, _unitName) - local _StrPits = MENU_GROUP_COMMAND:New(group, "Strafe Pits", _infoPath, self._DisplayStrafePits, self, _unitName):Refresh() + local _WeIn = MENU_GROUP_COMMAND:New( group, "General Info", _infoPath, self._DisplayRangeInfo, self, _unitName ) + local _WeRe = MENU_GROUP_COMMAND:New( group, "Weather Report", _infoPath, self._DisplayRangeWeather, self, _unitName ) + local _BoTgtgs = MENU_GROUP_COMMAND:New( group, "Bombing Targets", _infoPath, self._DisplayBombTargets, self, _unitName ) + local _StrPits = MENU_GROUP_COMMAND:New( group, "Strafe Pits", _infoPath, self._DisplayStrafePits, self, _unitName ):Refresh() end else - self:E(self.id.."Could not find group or group ID in AddF10Menu() function. Unit name: ".._unitName) + 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) + self:E( self.id .. "Player unit does not exist in AddF10Menu() function. Unit name: " .. _unitName ) end end @@ -3199,80 +3193,79 @@ end -- @param #RANGE self -- @param #RANGE.BombTarget target Bomb target data. -- @return Core.Point#COORDINATE Target coordinate. -function RANGE:_GetBombTargetCoordinate(target) +function RANGE:_GetBombTargetCoordinate( target ) - local coord=nil --Core.Point#COORDINATE + local coord = nil -- Core.Point#COORDINATE - if target.type==RANGE.TargetType.UNIT then + if target.type == RANGE.TargetType.UNIT then if not target.move then -- Target should not move. - coord=target.coordinate + coord = target.coordinate else -- Moving target. Check if alive and get current position if target.target and target.target:IsAlive() then - coord=target.target:GetCoordinate() + coord = target.target:GetCoordinate() end end - elseif target.type==RANGE.TargetType.STATIC then + elseif target.type == RANGE.TargetType.STATIC then -- Static targets dont move. - coord=target.coordinate + coord = target.coordinate - elseif target.type==RANGE.TargetType.COORD then + elseif target.type == RANGE.TargetType.COORD then -- Coordinates dont move. - coord=target.coordinate + coord = target.coordinate else - self:E(self.id.."ERROR: Unknown target type.") + self:E( self.id .. "ERROR: Unknown target type." ) end return coord end - --- Get the number of shells a unit currently has. -- @param #RANGE self -- @param #string unitname Name of the player unit. -- @return Number of shells left -function RANGE:_GetAmmo(unitname) - self:F2(unitname) +function RANGE:_GetAmmo( unitname ) + self:F2( unitname ) -- Init counter. - local ammo=0 + local ammo = 0 - local unit, playername = self:_GetPlayerUnitAndName(unitname) + local unit, playername = self:_GetPlayerUnitAndName( unitname ) if unit and playername then - local has_ammo=false + local has_ammo = false - local ammotable=unit:GetAmmo() - self:T2({ammotable=ammotable}) + 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)) + local weapons = #ammotable + self:T2( self.id .. string.format( "Number of weapons %d.", weapons ) ) - for w=1,weapons do + for w = 1, weapons do - local Nammo=ammotable[w]["count"] - local Tammo=ammotable[w]["desc"]["typeName"] + local Nammo = ammotable[w]["count"] + local Tammo = ammotable[w]["desc"]["typeName"] -- We are specifically looking for shells here. - if string.match(Tammo, "shell") then + if string.match( Tammo, "shell" ) then -- Add up all shells - ammo=ammo+Nammo + ammo = ammo + Nammo - local text=string.format("Player %s has %d rounds ammo of type %s", playername, Nammo, Tammo) - self:T(self.id..text) + 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) + local text = string.format( "Player %s has %d ammo of type %s", playername, Nammo, Tammo ) + self:T( self.id .. text ) end end end @@ -3284,46 +3277,46 @@ end --- Mark targets on F10 map. -- @param #RANGE self -- @param #string _unitName Name of the player unit. -function RANGE:_MarkTargetsOnMap(_unitName) - self:F(_unitName) +function RANGE:_MarkTargetsOnMap( _unitName ) + self:F( _unitName ) -- Get group. - local group=nil --Wrapper.Group#GROUP + local group = nil -- Wrapper.Group#GROUP if _unitName then - group=UNIT:FindByName(_unitName):GetGroup() + group = UNIT:FindByName( _unitName ):GetGroup() end -- Mark bomb targets. - for _,_bombtarget in pairs(self.bombingTargets) do - local bombtarget=_bombtarget --#RANGE.BombTarget - local coord=self:_GetBombTargetCoordinate(_bombtarget) + for _, _bombtarget in pairs( self.bombingTargets ) do + local bombtarget = _bombtarget -- #RANGE.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) + 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)) + coord:MarkToAll( string.format( "Bomb target %s", bombtarget.name ) ) end end -- Mark strafe targets. - for _,_strafepit in pairs(self.strafeTargets) do - for _,_target in pairs(_strafepit.targets) do - local _target=_target --Wrapper.Positionable#POSITIONABLE + for _, _strafepit in pairs( self.strafeTargets ) do + for _, _target in pairs( _strafepit.targets ) do + local _target = _target -- Wrapper.Positionable#POSITIONABLE if _target and _target:IsAlive() then - local coord=_target:GetCoordinate() --Core.Point#COORDINATE + local coord = _target:GetCoordinate() -- Core.Point#COORDINATE if group then - --coord:MarkToGroup("Strafe target ".._target:GetName(), group) - coord:MarkToGroup(string.format("Strafe target %s:\n%s\n%s", _target:GetName(), coord:ToStringLLDMS(), coord:ToStringBULLS(group:GetCoalition())), group) + -- coord:MarkToGroup("Strafe target ".._target:GetName(), group) + 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()) + 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) + 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 @@ -3331,67 +3324,67 @@ end --- Illuminate targets. Fires illumination bombs at one random bomb and one random strafe target at a random altitude between 400 and 800 m. -- @param #RANGE self -- @param #string _unitName (Optional) Name of the player unit. -function RANGE:_IlluminateBombTargets(_unitName) - self:F(_unitName) +function RANGE:_IlluminateBombTargets( _unitName ) + self:F( _unitName ) -- All bombing target coordinates. - local bomb={} + local bomb = {} - for _,_bombtarget in pairs(self.bombingTargets) do - local _target=_bombtarget.target --Wrapper.Positionable#POSITIONABLE - local coord=self:_GetBombTargetCoordinate(_bombtarget) + for _, _bombtarget in pairs( self.bombingTargets ) do + local _target = _bombtarget.target -- Wrapper.Positionable#POSITIONABLE + local coord = self:_GetBombTargetCoordinate( _bombtarget ) if coord then - table.insert(bomb, coord) + table.insert( bomb, coord ) end end - if #bomb>0 then - local coord=bomb[math.random(#bomb)] --Core.Point#COORDINATE - local c=COORDINATE:New(coord.x,coord.y+math.random(self.illuminationminalt,self.illuminationmaxalt),coord.z) + if #bomb > 0 then + local coord = bomb[math.random( #bomb )] -- Core.Point#COORDINATE + local c = COORDINATE:New( coord.x, coord.y + math.random( self.illuminationminalt, self.illuminationmaxalt ), coord.z ) c:IlluminationBomb() end -- All strafe target coordinates. - local strafe={} + local strafe = {} - for _,_strafepit in pairs(self.strafeTargets) do - for _,_target in pairs(_strafepit.targets) do - local _target=_target --Wrapper.Positionable#POSITIONABLE + for _, _strafepit in pairs( self.strafeTargets ) do + for _, _target in pairs( _strafepit.targets ) do + local _target = _target -- Wrapper.Positionable#POSITIONABLE if _target and _target:IsAlive() then - local coord=_target:GetCoordinate() --Core.Point#COORDINATE - table.insert(strafe, coord) + local coord = _target:GetCoordinate() -- Core.Point#COORDINATE + table.insert( strafe, coord ) end end end -- Pick a random strafe target. - if #strafe>0 then - local coord=strafe[math.random(#strafe)] --Core.Point#COORDINATE - local c=COORDINATE:New(coord.x,coord.y+math.random(self.illuminationminalt,self.illuminationmaxalt),coord.z) + if #strafe > 0 then + local coord = strafe[math.random( #strafe )] -- Core.Point#COORDINATE + 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) + 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 --- Reset player statistics. -- @param #RANGE self -- @param #string _unitName Name of the player unit. -function RANGE:_ResetRangeStats(_unitName) - self:F(_unitName) +function RANGE:_ResetRangeStats( _unitName ) + self:F( _unitName ) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName(_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) + local text = string.format( "%s, %s, your range stats were cleared.", self.rangename, _playername ) + self:DisplayMessageToGroup( _unit, text, 5, false, true ) end end @@ -3402,19 +3395,19 @@ end -- @param #number _time Duration how long the message is displayed. -- @param #boolean _clear Clear up old messages. -- @param #boolean display If true, display message regardless of player setting "Messages Off". -function RANGE:_DisplayMessageToGroup(_unit, _text, _time, _clear, display) - self:F({unit=_unit, text=_text, time=_time, clear=_clear}) +function RANGE:_DisplayMessageToGroup( _unit, _text, _time, _clear, display ) + self:F( { unit = _unit, text = _text, time = _time, clear = _clear } ) -- Defaults - _time=_time or self.Tmsg - if _clear==nil or _clear==false then - _clear=false + _time = _time or self.Tmsg + if _clear == nil or _clear == false then + _clear = false else - _clear=true + _clear = true end -- Messages globally disabled. - if self.messages==false then + if self.messages == false then return end @@ -3422,22 +3415,22 @@ function RANGE:_DisplayMessageToGroup(_unit, _text, _time, _clear, display) if _unit and _unit:IsAlive() then -- Group ID. - local _gid=_unit:GetGroup():GetID() + local _gid = _unit:GetGroup():GetID() -- Get playername and player settings - local _, playername=self:_GetPlayerUnitAndName(_unit:GetName()) - local playermessage=self.PlayerSettings[playername].messages + local _, playername = self:_GetPlayerUnitAndName( _unit:GetName() ) + local playermessage = self.PlayerSettings[playername].messages -- Send message to player if messages enabled and not only for the examiner. - if _gid and (playermessage==true or display) and (not self.examinerexclusive) then - trigger.action.outTextForGroup(_gid, _text, _time, _clear) + if _gid and (playermessage == true or display) and (not self.examinerexclusive) then + trigger.action.outTextForGroup( _gid, _text, _time, _clear ) end -- Send message to examiner. - if self.examinergroupname~=nil then - local _examinerid=GROUP:FindByName(self.examinergroupname):GetID() + if self.examinergroupname ~= nil then + local _examinerid = GROUP:FindByName( self.examinergroupname ):GetID() if _examinerid then - trigger.action.outTextForGroup(_examinerid, _text, _time, _clear) + trigger.action.outTextForGroup( _examinerid, _text, _time, _clear ) end end end @@ -3447,20 +3440,20 @@ end --- Toggle status of smoking bomb impact points. -- @param #RANGE self -- @param #string unitname Name of the player unit. -function RANGE:_SmokeBombImpactOnOff(unitname) - self:F(unitname) +function RANGE:_SmokeBombImpactOnOff( unitname ) + self:F( unitname ) - local unit, playername = self:_GetPlayerUnitAndName(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) + 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) + 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) + self:_DisplayMessageToGroup( unit, text, 5, false, true ) end end @@ -3468,20 +3461,20 @@ end --- Toggle status of time delay for smoking bomb impact points -- @param #RANGE self -- @param #string unitname Name of the player unit. -function RANGE:_SmokeBombDelayOnOff(unitname) - self:F(unitname) +function RANGE:_SmokeBombDelayOnOff( unitname ) + self:F( unitname ) - local unit, playername = self:_GetPlayerUnitAndName(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) + 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) + 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) + self:_DisplayMessageToGroup( unit, text, 5, false, true ) end end @@ -3489,19 +3482,19 @@ end --- Toggle display messages to player. -- @param #RANGE self -- @param #string unitname Name of the player unit. -function RANGE:_MessagesToPlayerOnOff(unitname) - self:F(unitname) +function RANGE:_MessagesToPlayerOnOff( unitname ) + self:F( unitname ) - local unit, playername = self:_GetPlayerUnitAndName(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) + 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) + 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 + self:_DisplayMessageToGroup( unit, text, 5, false, true ) + self.PlayerSettings[playername].messages = not self.PlayerSettings[playername].messages end end @@ -3509,41 +3502,41 @@ end --- Targetsheet saves if player on or off. -- @param #RANGE self -- @param #string _unitname Name of the player unit. -function RANGE:_TargetsheetOnOff(_unitname) - self:F2(_unitname) +function RANGE:_TargetsheetOnOff( _unitname ) + self:F2( _unitname ) -- Get player unit and player name. - local unit, playername = self:_GetPlayerUnitAndName(_unitname) + local unit, playername = self:_GetPlayerUnitAndName( _unitname ) -- Check if we have a player. if unit and playername then -- Player data. - local playerData=self.PlayerSettings[playername] --#RANGE.PlayerData + local playerData = self.PlayerSettings[playername] -- #RANGE.PlayerData if playerData then -- Check if option is enabled at all. - local text="" + local text = "" if self.targetsheet then -- Invert current setting. - playerData.targeton=not playerData.targeton + playerData.targeton = not playerData.targeton -- Inform player. - if playerData.targeton==true then - text=string.format("roger, your targetsheets are now SAVED.") + if playerData.targeton == true then + text = string.format( "roger, your targetsheets are now SAVED." ) else - text=string.format("affirm, your targetsheets are NOT SAVED.") + text = string.format( "affirm, your targetsheets are NOT SAVED." ) end else - text="negative, target sheet data recorder is broken on this range." + text = "negative, target sheet data recorder is broken on this range." end -- Message to player. - --self:MessageToPlayer(playerData, text, nil, playerData.name, 5) - self:_DisplayMessageToGroup(unit,text,5,false,false) + -- self:MessageToPlayer(playerData, text, nil, playerData.name, 5) + self:_DisplayMessageToGroup( unit, text, 5, false, false ) end end @@ -3552,20 +3545,20 @@ end --- Toggle status of flaring direct hits of range targets. -- @param #RANGE self -- @param #string unitname Name of the player unit. -function RANGE:_FlareDirectHitsOnOff(unitname) - self:F(unitname) +function RANGE:_FlareDirectHitsOnOff( unitname ) + self:F( unitname ) - local unit, playername = self:_GetPlayerUnitAndName(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) + 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) + 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) + self:_DisplayMessageToGroup( unit, text, 5, false, true ) end end @@ -3573,21 +3566,21 @@ end --- Mark bombing targets with smoke. -- @param #RANGE self -- @param #string unitname Name of the player unit. -function RANGE:_SmokeBombTargets(unitname) - self:F(unitname) +function RANGE:_SmokeBombTargets( unitname ) + self:F( unitname ) - for _,_bombtarget in pairs(self.bombingTargets) do - local _target=_bombtarget.target --Wrapper.Positionable#POSITIONABLE - local coord=self:_GetBombTargetCoordinate(_bombtarget) + for _, _bombtarget in pairs( self.bombingTargets ) do + local _target = _bombtarget.target -- Wrapper.Positionable#POSITIONABLE + local coord = self:_GetBombTargetCoordinate( _bombtarget ) if coord then - coord:Smoke(self.BombSmokeColor) + 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) + 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 @@ -3595,17 +3588,17 @@ end --- Mark strafing targets with smoke. -- @param #RANGE self -- @param #string unitname Name of the player unit. -function RANGE:_SmokeStrafeTargets(unitname) - self:F(unitname) +function RANGE:_SmokeStrafeTargets( unitname ) + self:F( unitname ) - for _,_target in pairs(self.strafeTargets) do - _target.coordinate:Smoke(self.StrafeSmokeColor) + 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) + 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 @@ -3613,21 +3606,21 @@ end --- Mark approach boxes of strafe targets with smoke. -- @param #RANGE self -- @param #string unitname Name of the player unit. -function RANGE:_SmokeStrafeTargetBoxes(unitname) - self:F(unitname) +function RANGE:_SmokeStrafeTargetBoxes( unitname ) + self:F( unitname ) - for _,_target in pairs(self.strafeTargets) do - local zone=_target.polygon --Core.Zone#ZONE - zone:SmokeZone(self.StrafePitSmokeColor, 4) - for _,_point in pairs(_target.smokepoints) do - _point:SmokeOrange() --Corners are smoked orange. + for _, _target in pairs( self.strafeTargets ) do + local zone = _target.polygon -- Core.Zone#ZONE + zone:SmokeZone( self.StrafePitSmokeColor, 4 ) + for _, _point in pairs( _target.smokepoints ) do + _point:SmokeOrange() -- Corners are smoked orange. 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) + 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 @@ -3636,14 +3629,14 @@ end -- @param #RANGE self -- @param #string _unitName Name of the player unit. -- @param Utilities.Utils#SMOKECOLOR color ID of the smoke color. -function RANGE:_playersmokecolor(_unitName, color) - self:F({unitname=_unitName, color=color}) +function RANGE:_playersmokecolor( _unitName, color ) + self:F( { unitname = _unitName, color = color } ) - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + 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) + 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 @@ -3652,14 +3645,14 @@ end -- @param #RANGE self -- @param #string _unitName Name of the player unit. -- @param Utilities.Utils#FLARECOLOR color ID of flare color. -function RANGE:_playerflarecolor(_unitName, color) - self:F({unitname=_unitName, color=color}) +function RANGE:_playerflarecolor( _unitName, color ) + self:F( { unitname = _unitName, color = color } ) - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + 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) + 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 @@ -3668,22 +3661,22 @@ end -- @param #RANGE self -- @param Utilities.Utils#SMOKECOLOR color Color Id. -- @return #string Color text. -function RANGE:_smokecolor2text(color) - self:F(color) +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" + 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)) + txt = string.format( "unknown color (%s)", tostring( color ) ) end return txt @@ -3693,20 +3686,20 @@ end -- @param #RANGE self -- @param Utilities.Utils#FLARECOLOR color Color Id. -- @return #string Color text. -function RANGE:_flarecolor2text(color) - self:F(color) +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" + 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)) + txt = string.format( "unknown color (%s)", tostring( color ) ) end return txt @@ -3716,33 +3709,33 @@ end -- @param #RANGE self -- @param #string name Name of the potential static object. -- @return #boolean Returns true if a static with this name exists. Retruns false if a unit with this name exists. Returns nil if neither unit or static exist. -function RANGE:_CheckStatic(name) - self:F2(name) +function RANGE:_CheckStatic( name ) + self:F2( name ) -- Get DCS static object. - local _DCSstatic=StaticObject.getByName(name) + local _DCSstatic = StaticObject.getByName( name ) if _DCSstatic and _DCSstatic:isExist() then - --Static does exist at least in DCS. Check if it also in the MOOSE DB. - local _MOOSEstatic=STATIC:FindByName(name, false) + -- Static does exist at least in DCS. Check if it also in the MOOSE DB. + local _MOOSEstatic = STATIC:FindByName( name, false ) -- If static is not yet in MOOSE DB, we add it. Can happen for cargo statics! if not _MOOSEstatic then - self:T(self.id..string.format("Adding DCS static to MOOSE database. Name = %s.", name)) - _DATABASE:AddStatic(name) + 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)) + self:T3( self.id .. string.format( "No static object with name %s exists.", name ) ) end -- Check if a unit has this name. - if UNIT:FindByName(name) then + if UNIT:FindByName( name ) then return false else - self:T3(self.id..string.format("No unit object with name %s exists.", name)) + self:T3( self.id .. string.format( "No unit object with name %s exists.", name ) ) end -- If not unit or static exist, we return nil. @@ -3753,17 +3746,17 @@ end -- @param #RANGE self -- @param Wrapper.Controllable#CONTROLLABLE controllable -- @return Maximum speed in km/h. -function RANGE:_GetSpeed(controllable) - self:F2(controllable) +function RANGE:_GetSpeed( controllable ) + self:F2( controllable ) -- Get DCS descriptors - local desc=controllable:GetDesc() + local desc = controllable:GetDesc() -- Get speed - local speed=0 + local speed = 0 if desc then - speed=desc.speedMax*3.6 - self:T({speed=speed}) + speed = desc.speedMax * 3.6 + self:T( { speed = speed } ) end return speed @@ -3775,20 +3768,20 @@ end -- @return Wrapper.Unit#UNIT Unit of player. -- @return #string Name of the player. -- @return nil If player does not exist. -function RANGE:_GetPlayerUnitAndName(_unitName) - self:F2(_unitName) +function RANGE:_GetPlayerUnitAndName( _unitName ) + self:F2( _unitName ) if _unitName ~= nil then -- Get DCS unit from its name. - local DCSunit=Unit.getByName(_unitName) + local DCSunit = Unit.getByName( _unitName ) if DCSunit then - local playername=DCSunit:getPlayerName() - local unit=UNIT:Find(DCSunit) + local playername = DCSunit:getPlayerName() + local unit = UNIT:Find( DCSunit ) - self:T2({DCSunit=DCSunit, unit=unit, playername=playername}) + self:T2( { DCSunit = DCSunit, unit = unit, playername = playername } ) if DCSunit and unit and playername then return unit, playername end @@ -3798,21 +3791,21 @@ function RANGE:_GetPlayerUnitAndName(_unitName) end -- Return nil if we could not find a player. - return nil,nil + return nil, nil end ---- Returns a string which consits of this callsign and the player name. +--- Returns a string which consists of the player name. -- @param #RANGE self -- @param #string unitname Name of the player unit. -function RANGE:_myname(unitname) - self:F2(unitname) +function RANGE:_myname( unitname ) + self:F2( unitname ) - local unit=UNIT:FindByName(unitname) - local pname=unit:GetPlayerName() - local csign=unit:GetCallsign() + local unit = UNIT:FindByName( unitname ) + local pname = unit:GetPlayerName() + -- local csign = unit:GetCallsign() - --return string.format("%s (%s)", csign, pname) - return string.format("%s", pname) + -- return string.format("%s (%s)", csign, pname) + return string.format( "%s", pname ) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 4df1e310a31efdfceee6714442cd127f884510e4 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 14 Mar 2022 09:12:20 +0100 Subject: [PATCH 081/200] CSAR - remove timer check for "open the door" message to make behaviour more realistic --- Moose Development/Moose/Ops/CSAR.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index f8998b328..de2c30b5f 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -258,7 +258,7 @@ CSAR.AircraftType["UH-60L"] = 10 --- CSAR class version. -- @field #string version -CSAR.version="1.0.4a" +CSAR.version="1.0.4c" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -1332,7 +1332,8 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG _time = self.landedStatus[_lookupKeyHeli] - 10 self.landedStatus[_lookupKeyHeli] = _time end - if _time <= 0 or _distance < self.loadDistance then + --if _time <= 0 or _distance < self.loadDistance then + if _distance < self.loadDistance + 5 or _distance <= 13 then if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true) return true From 57de0b7351b7b139aa874532d7cdcbbf9ded9e75 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 16 Mar 2022 08:45:27 +0100 Subject: [PATCH 082/200] docu fixes --- Moose Development/Moose/Wrapper/Static.lua | 139 +++++++++++---------- 1 file changed, 75 insertions(+), 64 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Static.lua b/Moose Development/Moose/Wrapper/Static.lua index 7394d0861..08c9a0bce 100644 --- a/Moose Development/Moose/Wrapper/Static.lua +++ b/Moose Development/Moose/Wrapper/Static.lua @@ -1,46 +1,52 @@ --- **Wrapper** -- STATIC wraps the DCS StaticObject class. --- +-- -- === --- +-- -- ### Author: **FlightControl** --- +-- -- ### Contributions: **funkyfranky** --- +-- -- === --- +-- -- @module Wrapper.Static -- @image Wrapper_Static.JPG + + --- @type STATIC -- @extends Wrapper.Positionable#POSITIONABLE + --- Wrapper class to handle Static objects. --- +-- -- Note that Statics are almost the same as Units, but they don't have a controller. -- The @{Wrapper.Static#STATIC} class is a wrapper class to handle the DCS Static objects: --- +-- -- * Wraps the DCS Static objects. -- * Support all DCS Static APIs. -- * Enhance with Static specific APIs not in the DCS API set. --- +-- -- ## STATIC reference methods --- +-- -- For each DCS Static will have a STATIC wrapper object (instance) within the _@{DATABASE} object. -- This is done at the beginning of the mission (when the mission starts). --- +-- -- The STATIC class does not contain a :New() method, rather it provides :Find() methods to retrieve the object reference -- using the Static Name. --- +-- -- Another thing to know is that STATIC objects do not "contain" the DCS Static object. -- The STATIc methods will reference the DCS Static object by name when it is needed during API execution. -- If the DCS Static object does not exist or is nil, the STATIC methods will return nil and log an exception in the DCS.log file. --- +-- -- The STATIc class provides the following functions to retrieve quickly the relevant STATIC instance: --- +-- -- * @{#STATIC.FindByName}(): Find a STATIC instance from the _DATABASE object using a DCS Static name. --- --- IMPORTANT: ONE SHOULD NEVER SANITIZE these STATIC OBJECT REFERENCES! (make the STATIC object references nil). --- +-- +-- IMPORTANT: ONE SHOULD NEVER SANATIZE these STATIC OBJECT REFERENCES! (make the STATIC object references nil). +-- -- @field #STATIC -STATIC = { ClassName = "STATIC" } +STATIC = { + ClassName = "STATIC", +} + --- Register a static object. -- @param #STATIC self @@ -52,6 +58,7 @@ function STATIC:Register( StaticName ) return self end + --- Finds a STATIC from the _DATABASE using a DCSStatic object. -- @param #STATIC self -- @param DCS#StaticObject DCSStatic An existing DCS Static object reference. @@ -76,13 +83,13 @@ function STATIC:FindByName( StaticName, RaiseError ) -- Set static name. self.StaticName = StaticName - + if StaticFound then return StaticFound end - - if RaiseError == nil or RaiseError == true then - error( "STATIC not found for: " .. StaticName ) + + if RaiseError == nil or RaiseError == true then + error( "STATIC not found for: " .. StaticName ) end return nil @@ -90,39 +97,38 @@ end --- Destroys the STATIC. -- @param #STATIC self --- @param #boolean GenerateEvent (Optional) true to generate a crash or dead event, false to not generate any event. `nil` (default) creates a remove event. --- @return #nil The DCS StaticObject is not existing or alive. --- +-- @param #boolean GenerateEvent (Optional) true if you want to generate a crash or dead event for the static. +-- @return #nil The DCS StaticObject is not existing or alive. -- @usage -- -- Air static example: destroy the static Helicopter and generate a S_EVENT_CRASH. -- Helicopter = STATIC:FindByName( "Helicopter" ) -- Helicopter:Destroy( true ) --- +-- -- @usage -- -- Ground static example: destroy the static Tank and generate a S_EVENT_DEAD. -- Tanks = UNIT:FindByName( "Tank" ) -- Tanks:Destroy( true ) --- +-- -- @usage -- -- Ship static example: destroy the Ship silently. -- Ship = STATIC:FindByName( "Ship" ) -- Ship:Destroy() --- +-- -- @usage -- -- Destroy without event generation example. -- Ship = STATIC:FindByName( "Boat" ) --- Ship:Destroy( false ) -- Don't generate any event upon destruction. --- +-- Ship:Destroy( false ) -- Don't generate an event upon destruction. +-- 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 ) @@ -134,7 +140,7 @@ function STATIC:Destroy( GenerateEvent ) else self:CreateEventRemoveUnit( timer.getTime(), DCSObject ) end - + DCSObject:destroy() return true end @@ -142,16 +148,17 @@ function STATIC:Destroy( GenerateEvent ) return nil end + --- Get DCS object of static of static. -- @param #STATIC self -- @return DCS static object function STATIC:GetDCSObject() local DCSStatic = StaticObject.getByName( self.StaticName ) - + if DCSStatic then return DCSStatic end - + return nil end @@ -163,7 +170,7 @@ function STATIC:GetUnits() local DCSStatic = self:GetDCSObject() local Statics = {} - + if DCSStatic then Statics[1] = STATIC:Find( DCSStatic ) self:T3( Statics ) @@ -173,6 +180,7 @@ function STATIC:GetUnits() return nil end + --- Get threat level of static. -- @param #STATIC self -- @return #number Threat level 1. @@ -186,62 +194,65 @@ end -- @param Core.Point#COORDINATE Coordinate The coordinate where to spawn the new Static. -- @param #number Heading The heading of the static respawn in degrees. Default is 0 deg. -- @param #number Delay Delay in seconds before the static is spawned. -function STATIC:SpawnAt( Coordinate, Heading, Delay ) +function STATIC:SpawnAt(Coordinate, Heading, Delay) - Heading = Heading or 0 + Heading=Heading or 0 - if Delay and Delay > 0 then - SCHEDULER:New( nil, self.SpawnAt, { self, Coordinate, Heading }, Delay ) + if Delay and Delay>0 then + SCHEDULER:New(nil, self.SpawnAt, {self, Coordinate, Heading}, Delay) else - local SpawnStatic = SPAWNSTATIC:NewFromStatic( self.StaticName ) - + local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName) + SpawnStatic:SpawnFromPointVec2( Coordinate, Heading, self.StaticName ) - + end - + return self end + --- Respawn the @{Wrapper.Unit} at the same location with the same properties. -- This is useful to respawn a cargo after it has been destroyed. -- @param #STATIC self -- @param DCS#country.id CountryID (Optional) The country ID used for spawning the new static. Default is same as currently. -- @param #number Delay (Optional) Delay in seconds before static is respawned. Default now. -function STATIC:ReSpawn( CountryID, Delay ) +function STATIC:ReSpawn(CountryID, Delay) - if Delay and Delay > 0 then - SCHEDULER:New( nil, self.ReSpawn, { self, 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 ) + CountryID=CountryID or self:GetCountry() + local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName, CountryID) + + SpawnStatic:Spawn(nil, self.StaticName) + end - + return self end + --- Respawn the @{Wrapper.Unit} at a defined Coordinate with an optional heading. -- @param #STATIC self -- @param Core.Point#COORDINATE Coordinate The coordinate where to spawn the new Static. --- @param #number Heading (Optional) The heading of the static respawn in degrees. Default is the current heading. --- @param #number Delay (Optional) Delay in seconds before static is respawned. Default is now. -function STATIC:ReSpawnAt( Coordinate, Heading, Delay ) +-- @param #number Heading (Optional) The heading of the static respawn in degrees. Default the current heading. +-- @param #number Delay (Optional) Delay in seconds before static is respawned. Default now. +function STATIC:ReSpawnAt(Coordinate, Heading, Delay) - -- Heading=Heading or 0 + --Heading=Heading or 0 - if Delay and Delay > 0 then - SCHEDULER:New( nil, self.ReSpawnAt, { self, 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 ) + + local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName, self:GetCountry()) + + SpawnStatic:SpawnFromCoordinate(Coordinate, Heading, self.StaticName) + end - + return self end - From 3aee8a49c1c5d71adddd1ec655cd993bb9a3aa21 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 18 Mar 2022 07:39:48 +0100 Subject: [PATCH 083/200] Added CONTROLLABLE SetSpeed() and SetAltitude() --- .../Moose/Wrapper/Controllable.lua | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 963558549..70c048093 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -3770,3 +3770,44 @@ function POSITIONABLE:IsSubmarine() return nil end + +--- Sets the controlled group to go the specified speed in meters per second. +-- @param #CONTROLLABLE self +-- @param #number Speed Speed in meters per second +-- @param #boolean Keep (Optional) When set to true will maintain that speed on passing waypoints. If no present or false the controlled group will return to the speed as defined by their route. +-- @return #CONTROLLABLE self +function CONTROLLABLE:SetSpeed(Speed, Keep) + self:F2( { self.ControllableName } ) + -- Set default if not specified. + local speed = Speed or 5 + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + if Controller then + Controller:setSpeed(speed, Keep) + end + end + return self +end + +--- [AIR] Sets the controlled aircraft group to the specified altitude in meters. +-- @param #CONTROLLABLE self +-- @param #number Altitude Altitude in meters +-- @param #boolean Keep (Optional) When set to true will maintain that altitude on passing waypoints. If no present or false the controlled group will return to the altitude as defined by their route. +-- @param #string AltType (Optional) Will specify the altitude type used. If nil the altitude type of the current waypoint will be used. Accepted values are "BARO" and "RADIO". +-- @return #CONTROLLABLE self +function CONTROLLABLE:SetAltitude(Altitude, Keep, AltType) + self:F2( { self.ControllableName } ) + -- Set default if not specified. + local altitude = Altitude or 1000 + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + if Controller then + if self:IsAir() then + Controller:setAltitude(altitude, Keep, AltType) + end + end + end + return self +end \ No newline at end of file From 327ab4766b251b7171a3402c9305cdebf7d49d2e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 18 Mar 2022 07:59:58 +0100 Subject: [PATCH 084/200] changed descriptions --- Moose Development/Moose/Wrapper/Controllable.lua | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 70c048093..3e280a09d 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -3771,10 +3771,10 @@ function POSITIONABLE:IsSubmarine() return nil end ---- Sets the controlled group to go the specified speed in meters per second. +--- Sets the controlled group to go at the specified speed in meters per second. -- @param #CONTROLLABLE self --- @param #number Speed Speed in meters per second --- @param #boolean Keep (Optional) When set to true will maintain that speed on passing waypoints. If no present or false the controlled group will return to the speed as defined by their route. +-- @param #number Speed Speed in meters per second. +-- @param #boolean Keep (Optional) When set to true, will maintain the speed on passing waypoints. If not present or false, the controlled group will return to the speed as defined by their route. -- @return #CONTROLLABLE self function CONTROLLABLE:SetSpeed(Speed, Keep) self:F2( { self.ControllableName } ) @@ -3790,11 +3790,11 @@ function CONTROLLABLE:SetSpeed(Speed, Keep) return self end ---- [AIR] Sets the controlled aircraft group to the specified altitude in meters. +--- [AIR] Sets the controlled aircraft group to fly at the specified altitude in meters. -- @param #CONTROLLABLE self --- @param #number Altitude Altitude in meters --- @param #boolean Keep (Optional) When set to true will maintain that altitude on passing waypoints. If no present or false the controlled group will return to the altitude as defined by their route. --- @param #string AltType (Optional) Will specify the altitude type used. If nil the altitude type of the current waypoint will be used. Accepted values are "BARO" and "RADIO". +-- @param #number Altitude Altitude in meters. +-- @param #boolean Keep (Optional) When set to true, will maintain the altitude on passing waypoints. If not present or false, the controlled group will return to the altitude as defined by their route. +-- @param #string AltType (Optional) Specifies the altitude type used. If nil, the altitude type of the current waypoint will be used. Accepted values are "BARO" and "RADIO". -- @return #CONTROLLABLE self function CONTROLLABLE:SetAltitude(Altitude, Keep, AltType) self:F2( { self.ControllableName } ) From b0e3f82d27095d0681013fff3eefb868a51e5f30 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 18 Mar 2022 09:48:50 +0100 Subject: [PATCH 085/200] AIRBASE - added 10 new AB names in Syria --- Moose Development/Moose/Wrapper/Airbase.lua | 128 +++++++++++--------- 1 file changed, 74 insertions(+), 54 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 492d5af24..122a27056 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -384,61 +384,81 @@ AIRBASE.TheChannel = { -- * AIRBASE.Syria.Beirut_Rafic_Hariri -- * AIRBASE.Syria.An_Nasiriyah -- * AIRBASE.Syria.Abu_al_Duhur +-- * AIRBASE.Syria.At_Tanf +-- * AIRBASE.Syria.H3 +-- * AIRBASE.Syria.H3_Northwest +-- * AIRBASE.Syria.H3_Southwest +-- * AIRBASE.Syria.Kharab_Ishk +-- * AIRBASE.Syria.Raj_al_Issa_East +-- * AIRBASE.Syria.Raj_al_Issa_West +-- * AIRBASE.Syria.Ruwayshid +-- * AIRBASE.Syria.Sanliurfa +-- * AIRBASE.Syria.Tal_Siman -- --- @field Syria -AIRBASE.Syria = { - ["Kuweires"] = "Kuweires", - ["Marj_Ruhayyil"] = "Marj Ruhayyil", - ["Kiryat_Shmona"] = "Kiryat Shmona", - ["Marj_as_Sultan_North"] = "Marj as Sultan North", - ["Eyn_Shemer"] = "Eyn Shemer", - ["Incirlik"] = "Incirlik", - ["Damascus"] = "Damascus", - ["Bassel_Al_Assad"] = "Bassel Al-Assad", - ["Rosh_Pina"] = "Rosh Pina", - ["Aleppo"] = "Aleppo", - ["Al_Qusayr"] = "Al Qusayr", - ["Wujah_Al_Hajar"] = "Wujah Al Hajar", - ["Al_Dumayr"] = "Al-Dumayr", - ["Gazipasa"] = "Gazipasa", - ["Ru_Convoy_4"] = "Ru Convoy-4", - ["Hatay"] = "Hatay", - ["Nicosia"] = "Nicosia", - ["Pinarbashi"] = "Pinarbashi", - ["Paphos"] = "Paphos", - ["Kingsfield"] = "Kingsfield", - ["Thalah"] = "Tha'lah", - ["Haifa"] = "Haifa", - ["Khalkhalah"] = "Khalkhalah", - ["Megiddo"] = "Megiddo", - ["Lakatamia"] = "Lakatamia", - ["Rayak"] = "Rayak", - ["Larnaca"] = "Larnaca", - ["Mezzeh"] = "Mezzeh", - ["Gecitkale"] = "Gecitkale", - ["Akrotiri"] = "Akrotiri", - ["Naqoura"] = "Naqoura", - ["Gaziantep"] = "Gaziantep", - ["Sayqal"] = "Sayqal", - ["Tiyas"] = "Tiyas", - ["Shayrat"] = "Shayrat", - ["Taftanaz"] = "Taftanaz", - ["H4"] = "H4", - ["King_Hussein_Air_College"] = "King Hussein Air College", - ["Rene_Mouawad"] = "Rene Mouawad", - ["Jirah"] = "Jirah", - ["Ramat_David"] = "Ramat David", - ["Qabr_as_Sitt"] = "Qabr as Sitt", - ["Minakh"] = "Minakh", - ["Adana_Sakirpasa"] = "Adana Sakirpasa", - ["Palmyra"] = "Palmyra", - ["Hama"] = "Hama", - ["Ercan"] = "Ercan", - ["Marj_as_Sultan_South"] = "Marj as Sultan South", - ["Tabqa"] = "Tabqa", - ["Beirut_Rafic_Hariri"] = "Beirut-Rafic Hariri", - ["An_Nasiriyah"] = "An Nasiriyah", - ["Abu_al_Duhur"] = "Abu al-Duhur", +--@field Syria +AIRBASE.Syria={ + ["Kuweires"]="Kuweires", + ["Marj_Ruhayyil"]="Marj Ruhayyil", + ["Kiryat_Shmona"]="Kiryat Shmona", + ["Marj_as_Sultan_North"]="Marj as Sultan North", + ["Eyn_Shemer"]="Eyn Shemer", + ["Incirlik"]="Incirlik", + ["Damascus"]="Damascus", + ["Bassel_Al_Assad"]="Bassel Al-Assad", + ["Rosh_Pina"]="Rosh Pina", + ["Aleppo"]="Aleppo", + ["Al_Qusayr"]="Al Qusayr", + ["Wujah_Al_Hajar"]="Wujah Al Hajar", + ["Al_Dumayr"]="Al-Dumayr", + ["Gazipasa"]="Gazipasa", + ["Ru_Convoy_4"]="Ru Convoy-4", + ["Hatay"]="Hatay", + ["Nicosia"]="Nicosia", + ["Pinarbashi"]="Pinarbashi", + ["Paphos"]="Paphos", + ["Kingsfield"]="Kingsfield", + ["Thalah"]="Tha'lah", + ["Haifa"]="Haifa", + ["Khalkhalah"]="Khalkhalah", + ["Megiddo"]="Megiddo", + ["Lakatamia"]="Lakatamia", + ["Rayak"]="Rayak", + ["Larnaca"]="Larnaca", + ["Mezzeh"]="Mezzeh", + ["Gecitkale"]="Gecitkale", + ["Akrotiri"]="Akrotiri", + ["Naqoura"]="Naqoura", + ["Gaziantep"]="Gaziantep", + ["Sayqal"]="Sayqal", + ["Tiyas"]="Tiyas", + ["Shayrat"]="Shayrat", + ["Taftanaz"]="Taftanaz", + ["H4"]="H4", + ["King_Hussein_Air_College"]="King Hussein Air College", + ["Rene_Mouawad"]="Rene Mouawad", + ["Jirah"]="Jirah", + ["Ramat_David"]="Ramat David", + ["Qabr_as_Sitt"]="Qabr as Sitt", + ["Minakh"]="Minakh", + ["Adana_Sakirpasa"]="Adana Sakirpasa", + ["Palmyra"]="Palmyra", + ["Hama"]="Hama", + ["Ercan"]="Ercan", + ["Marj_as_Sultan_South"]="Marj as Sultan South", + ["Tabqa"]="Tabqa", + ["Beirut_Rafic_Hariri"]="Beirut-Rafic Hariri", + ["An_Nasiriyah"]="An Nasiriyah", + ["Abu_al_Duhur"]="Abu al-Duhur", + ["At_Tanf"]="At Tanf", + ["H3"]="H3", + ["H3_Northwest"]="H3 Northwest", + ["H3_Southwest"]="H3 Southwest", + ["Kharab_Ishk"]="Kharab Ishk", + ["Raj_al_Issa_East"]="Raj al Issa East", + ["Raj_al_Issa_West"]="Raj al Issa West", + ["Ruwayshid"]="Ruwayshid", + ["Sanliurfa"]="Sanliurfa", + ["Tal_Siman"]="Tal Siman", } --- Airbases of the Mariana Islands map: From a1f5c0ab9b549e4963611321182f3da153135d95 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 22 Mar 2022 10:38:20 +0100 Subject: [PATCH 086/200] CSAR/CTLD - added type to script --- Moose Development/Moose/Ops/CSAR.lua | 3 ++- Moose Development/Moose/Ops/CTLD.lua | 1 + Moose Development/Moose/Utilities/Utils.lua | 5 +++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index de2c30b5f..7c012f5a1 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -255,10 +255,11 @@ CSAR.AircraftType["Mi-24P"] = 8 CSAR.AircraftType["Mi-24V"] = 8 CSAR.AircraftType["Bell-47"] = 2 CSAR.AircraftType["UH-60L"] = 10 +CSAR.AircraftType["AH-64D_BLK_II"] = 2 --- CSAR class version. -- @field #string version -CSAR.version="1.0.4c" +CSAR.version="1.0.4d" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 02f334590..4ae5f4720 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1017,6 +1017,7 @@ CTLD.UnitTypes = { ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25, cargoweightlimit = 19000}, -- 19t cargo, 64 paratroopers. --Actually it's longer, but the center coord is off-center of the model. ["UH-60L"] = {type="UH-60L", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats + ["AH-64D_BLK_II"] = {type="AH-64D_BLK_II", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 17, cargoweightlimit = 200}, -- 2 ppl **outside** the helo } --- CTLD class version. diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 84799fb18..ab4fa0ab3 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1761,6 +1761,11 @@ function UTILS.IsLoadingDoorOpen( unit_name ) BASE:T(unit_name .. " front door(s) are open") ret_val = true end + + if string.find(type_name, "AH-64D") then + BASE:T(unit_name .. " front door(s) are open") + ret_val = true -- no doors on this one ;) + end if ret_val == false then BASE:T( unit_name .. " all doors are closed" ) From ca8b0899d0640dbe7687668eee5ffe221a6bdb87 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 23 Mar 2022 07:56:52 +0100 Subject: [PATCH 087/200] docu changes --- Moose Development/Moose/Core/Zone.lua | 638 +++++++++++++++----------- 1 file changed, 373 insertions(+), 265 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index a8759d97b..c97d879a1 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -53,6 +53,7 @@ -- @module Core.Zone -- @image Core_Zones.JPG + --- @type ZONE_BASE -- @field #string ZoneName Name of the zone. -- @field #number ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability. @@ -60,6 +61,7 @@ -- @field #table Color Table with four entries, e.g. {1, 0, 0, 0.15}. First three are RGB color code. Fourth is the transparency Alpha value. -- @extends Core.Fsm#FSM + --- This class is an abstract BASE class for derived classes, and is not meant to be instantiated. -- -- ## Each zone has a name: @@ -105,10 +107,11 @@ ZONE_BASE = { ClassName = "ZONE_BASE", ZoneName = "", ZoneProbability = 1, - DrawID = nil, - Color = {}, + DrawID=nil, + Color={} } + --- The ZONE_BASE.BoundingSquare -- @type ZONE_BASE.BoundingSquare -- @field DCS#Distance x1 The lower x coordinate (left down) @@ -116,6 +119,7 @@ ZONE_BASE = { -- @field DCS#Distance x2 The higher x coordinate (right up) -- @field DCS#Distance y2 The higher y coordinate (right up) + --- ZONE_BASE constructor -- @param #ZONE_BASE self -- @param #string ZoneName Name of the zone. @@ -125,10 +129,14 @@ function ZONE_BASE:New( ZoneName ) self:F( ZoneName ) self.ZoneName = ZoneName - + + --_DATABASE:AddZone(ZoneName,self) + return self end + + --- Returns the name of the zone. -- @param #ZONE_BASE self -- @return #string The name of the zone. @@ -138,6 +146,7 @@ function ZONE_BASE:GetName() return self.ZoneName end + --- Sets the name of the zone. -- @param #ZONE_BASE self -- @param #string ZoneName The name of the zone. @@ -194,6 +203,7 @@ function ZONE_BASE:IsPointVec3InZone( PointVec3 ) return InZone end + --- Returns the @{DCS#Vec2} coordinate of the zone. -- @param #ZONE_BASE self -- @return #nil. @@ -217,6 +227,7 @@ function ZONE_BASE:GetPointVec2() return PointVec2 end + --- Returns the @{DCS#Vec3} of the zone. -- @param #ZONE_BASE self -- @param DCS#Distance Height The height to add to the land height where the center of the zone is located. @@ -255,30 +266,48 @@ end -- @param #ZONE_BASE self -- @param DCS#Distance Height The height to add to the land height where the center of the zone is located. -- @return Core.Point#COORDINATE The Coordinate of the zone. -function ZONE_BASE:GetCoordinate( Height ) -- R2.1 - self:F2( self.ZoneName ) +function ZONE_BASE:GetCoordinate( Height ) --R2.1 + self:F2(self.ZoneName) local Vec3 = self:GetVec3( Height ) if self.Coordinate then -- Update coordinates. - self.Coordinate.x = Vec3.x - self.Coordinate.y = Vec3.y - self.Coordinate.z = Vec3.z + self.Coordinate.x=Vec3.x + self.Coordinate.y=Vec3.y + self.Coordinate.z=Vec3.z - -- env.info("FF GetCoordinate NEW for ZONE_BASE "..tostring(self.ZoneName)) + --env.info("FF GetCoordinate NEW for ZONE_BASE "..tostring(self.ZoneName)) else -- Create a new coordinate object. - self.Coordinate = COORDINATE:NewFromVec3( Vec3 ) + self.Coordinate=COORDINATE:NewFromVec3(Vec3) - -- env.info("FF GetCoordinate NEW for ZONE_BASE "..tostring(self.ZoneName)) + --env.info("FF GetCoordinate NEW for ZONE_BASE "..tostring(self.ZoneName)) end return self.Coordinate end +--- Get 2D distance to a coordinate. +-- @param #ZONE_BASE self +-- @param Core.Point#COORDINATE Coordinate Reference coordinate. Can also be a DCS#Vec2 or DCS#Vec3 object. +-- @return #number Distance to the reference coordinate in meters. +function ZONE_BASE:Get2DDistance(Coordinate) + local a=self:GetVec2() + local b={} + if Coordinate.z then + b.x=Coordinate.x + b.y=Coordinate.z + else + b.x=Coordinate.x + b.y=Coordinate.y + end + local dist=UTILS.VecDist2D(a,b) + return dist +end + --- Define a random @{DCS#Vec2} within the zone. -- @param #ZONE_BASE self -- @return DCS#Vec2 The Vec2 coordinates. @@ -304,7 +333,7 @@ end -- @param #ZONE_BASE self -- @return #nil The bounding square. function ZONE_BASE:GetBoundingSquare() - -- return { x1 = 0, y1 = 0, x2 = 0, y2 = 0 } + --return { x1 = 0, y1 = 0, x2 = 0, y2 = 0 } return nil end @@ -315,21 +344,22 @@ function ZONE_BASE:BoundZone() end + --- Set color of zone. -- @param #ZONE_BASE self -- @param #table RGBcolor RGB color table. Default `{1, 0, 0}`. -- @param #number Alpha Transparacy between 0 and 1. Default 0.15. -- @return #ZONE_BASE self -function ZONE_BASE:SetColor( RGBcolor, Alpha ) +function ZONE_BASE:SetColor(RGBcolor, Alpha) - RGBcolor = RGBcolor or { 1, 0, 0 } - Alpha = Alpha or 0.15 + RGBcolor=RGBcolor or {1, 0, 0} + Alpha=Alpha or 0.15 - self.Color = {} - self.Color[1] = RGBcolor[1] - self.Color[2] = RGBcolor[2] - self.Color[3] = RGBcolor[3] - self.Color[4] = Alpha + self.Color={} + self.Color[1]=RGBcolor[1] + self.Color[2]=RGBcolor[2] + self.Color[3]=RGBcolor[3] + self.Color[4]=Alpha return self end @@ -345,10 +375,10 @@ end -- @param #ZONE_BASE self -- @return #table Table with three entries, e.g. {1, 0, 0}, which is the RGB color code. function ZONE_BASE:GetColorRGB() - local rgb = {} - rgb[1] = self.Color[1] - rgb[2] = self.Color[2] - rgb[3] = self.Color[3] + local rgb={} + rgb[1]=self.Color[1] + rgb[2]=self.Color[2] + rgb[3]=self.Color[3] return rgb end @@ -356,7 +386,7 @@ end -- @param #ZONE_BASE self -- @return #number Alpha value. function ZONE_BASE:GetColorAlpha() - local alpha = self.Color[4] + local alpha=self.Color[4] return alpha end @@ -364,12 +394,12 @@ end -- @param #ZONE_BASE self -- @param #number Delay (Optional) Delay before the drawing is removed. -- @return #ZONE_BASE self -function ZONE_BASE:UndrawZone( Delay ) - if Delay and Delay > 0 then - self:ScheduleOnce( Delay, ZONE_BASE.UndrawZone, self ) +function ZONE_BASE:UndrawZone(Delay) + if Delay and Delay>0 then + self:ScheduleOnce(Delay, ZONE_BASE.UndrawZone, self) else if self.DrawID then - UTILS.RemoveMark( self.DrawID ) + UTILS.RemoveMark(self.DrawID) end end return self @@ -383,6 +413,7 @@ function ZONE_BASE:GetDrawID() return self.DrawID end + --- Smokes the zone boundaries in a color. -- @param #ZONE_BASE self -- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. @@ -447,6 +478,7 @@ function ZONE_BASE:GetZoneMaybe() end end + --- The ZONE_RADIUS class, defined by a zone name, a location and a radius. -- @type ZONE_RADIUS -- @field DCS#Vec2 Vec2 The current location of the zone. @@ -485,8 +517,8 @@ end -- -- @field #ZONE_RADIUS ZONE_RADIUS = { - ClassName = "ZONE_RADIUS", -} + ClassName="ZONE_RADIUS", + } --- Constructor of @{#ZONE_RADIUS}, taking the zone name, the zone location and a radius. -- @param #ZONE_RADIUS self @@ -503,7 +535,7 @@ function ZONE_RADIUS:New( ZoneName, Vec2, Radius ) self.Radius = Radius self.Vec2 = Vec2 - -- self.Coordinate=COORDINATE:NewFromVec2(Vec2) + --self.Coordinate=COORDINATE:NewFromVec2(Vec2) return self end @@ -513,13 +545,13 @@ end -- @param DCS#Vec2 Vec2 The location of the zone. -- @param DCS#Distance Radius The radius of the zone. -- @return #ZONE_RADIUS self -function ZONE_RADIUS:UpdateFromVec2( Vec2, Radius ) +function ZONE_RADIUS:UpdateFromVec2(Vec2, Radius) -- New center of the zone. - self.Vec2 = Vec2 + self.Vec2=Vec2 if Radius then - self.Radius = Radius + self.Radius=Radius end return self @@ -530,14 +562,14 @@ end -- @param DCS#Vec3 Vec3 The location of the zone. -- @param DCS#Distance Radius The radius of the zone. -- @return #ZONE_RADIUS self -function ZONE_RADIUS:UpdateFromVec3( Vec3, Radius ) +function ZONE_RADIUS:UpdateFromVec3(Vec3, Radius) -- New center of the zone. - self.Vec2.x = Vec3.x - self.Vec2.y = Vec3.z + self.Vec2.x=Vec3.x + self.Vec2.y=Vec3.z if Radius then - self.Radius = Radius + self.Radius=Radius end return self @@ -547,7 +579,7 @@ end -- @param #ZONE_RADIUS self -- @param #number Points (Optional) The amount of points in the circle. Default 360. -- @return #ZONE_RADIUS self -function ZONE_RADIUS:MarkZone( Points ) +function ZONE_RADIUS:MarkZone(Points) local Point = {} local Vec2 = self:GetVec2() @@ -555,16 +587,16 @@ function ZONE_RADIUS:MarkZone( Points ) Points = Points and Points or 360 local Angle - local RadialBase = math.pi * 2 + local RadialBase = math.pi*2 - for Angle = 0, 360, (360 / Points) do + 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() ) + COORDINATE:NewFromVec2(Point):MarkToAll(self:GetName()) end @@ -580,18 +612,18 @@ end -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. -- @return #ZONE_RADIUS self -function ZONE_RADIUS:DrawZone( Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly ) +function ZONE_RADIUS:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) - local coordinate = self:GetCoordinate() + local coordinate=self:GetCoordinate() - local Radius = self:GetRadius() + local Radius=self:GetRadius() - Color = Color or self:GetColorRGB() - Alpha = Alpha or 1 - FillColor = FillColor or Color - FillAlpha = FillAlpha or self:GetColorAlpha() + Color=Color or self:GetColorRGB() + Alpha=Alpha or 1 + FillColor=FillColor or Color + FillAlpha=FillAlpha or self:GetColorAlpha() - self.DrawID = coordinate:CircleToAll( Radius, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly ) + self.DrawID=coordinate:CircleToAll(Radius, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) return self end @@ -610,10 +642,10 @@ function ZONE_RADIUS:BoundZone( Points, CountryID, UnBound ) Points = Points and Points or 360 local Angle - local RadialBase = math.pi * 2 + local RadialBase = math.pi*2 -- - for Angle = 0, 360, (360 / Points) do + 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() @@ -621,16 +653,16 @@ function ZONE_RADIUS:BoundZone( Points, CountryID, UnBound ) local CountryName = _DATABASE.COUNTRY_NAME[CountryID] local Tire = { - ["country"] = CountryName, - ["category"] = "Fortifications", - ["canCargo"] = false, - ["shape_name"] = "H-tyre_B_WF", - ["type"] = "Black_Tyre_WF", - -- ["unitId"] = Angle + 10000, - ["y"] = Point.y, - ["x"] = Point.x, - ["name"] = string.format( "%s-Tire #%0d", self:GetName(), Angle ), - ["heading"] = 0, + ["country"] = CountryName, + ["category"] = "Fortifications", + ["canCargo"] = false, + ["shape_name"] = "H-tyre_B_WF", + ["type"] = "Black_Tyre_WF", + --["unitId"] = Angle + 10000, + ["y"] = Point.y, + ["x"] = Point.x, + ["name"] = string.format( "%s-Tire #%0d", self:GetName(), Angle ), + ["heading"] = 0, } -- end of ["group"] local Group = coalition.addStaticObject( CountryID, Tire ) @@ -642,6 +674,7 @@ function ZONE_RADIUS:BoundZone( Points, CountryID, UnBound ) return self end + --- Smokes the zone boundaries in a color. -- @param #ZONE_RADIUS self -- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. @@ -661,10 +694,10 @@ function ZONE_RADIUS:SmokeZone( SmokeColor, Points, AddHeight, AngleOffset ) Points = Points and Points or 360 local Angle - local RadialBase = math.pi * 2 + local RadialBase = math.pi*2 for Angle = 0, 360, 360 / Points do - local Radial = (Angle + AngleOffset) * RadialBase / 360 + 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 ) @@ -673,6 +706,7 @@ function ZONE_RADIUS:SmokeZone( SmokeColor, Points, AddHeight, AngleOffset ) return self end + --- Flares the zone boundaries in a color. -- @param #ZONE_RADIUS self -- @param Utilities.Utils#FLARECOLOR FlareColor The flare color. @@ -691,7 +725,7 @@ function ZONE_RADIUS:FlareZone( FlareColor, Points, Azimuth, AddHeight ) Points = Points and Points or 360 local Angle - local RadialBase = math.pi * 2 + local RadialBase = math.pi*2 for Angle = 0, 360, 360 / Points do local Radial = Angle * RadialBase / 360 @@ -769,6 +803,10 @@ function ZONE_RADIUS:GetVec3( Height ) return Vec3 end + + + + --- Scan the zone for the presence of units of the given ObjectCategories. -- Note that after a zone has been scanned, the zone can be evaluated by: -- @@ -794,27 +832,27 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) local ZoneCoord = self:GetCoordinate() local ZoneRadius = self:GetRadius() - self:F( { ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS() } ) + self:F({ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS()}) local SphereSearch = { id = world.VolumeType.SPHERE, - params = { + params = { point = ZoneCoord:GetVec3(), radius = ZoneRadius, - }, - } + } + } local function EvaluateZone( ZoneObject ) - -- if ZoneObject:isExist() then --FF: isExist always returns false for SCENERY objects since DCS 2.2 and still in DCS 2.5 + --if ZoneObject:isExist() then --FF: isExist always returns false for SCENERY objects since DCS 2.2 and still in DCS 2.5 if ZoneObject then local ObjectCategory = ZoneObject:getCategory() - -- local name=ZoneObject:getName() - -- env.info(string.format("Zone object %s", tostring(name))) - -- self:E(ZoneObject) + --local name=ZoneObject:getName() + --env.info(string.format("Zone object %s", tostring(name))) + --self:E(ZoneObject) - if (ObjectCategory == Object.Category.UNIT and ZoneObject:isExist() and ZoneObject:isActive()) or (ObjectCategory == Object.Category.STATIC and ZoneObject:isExist()) then + if ( ObjectCategory == Object.Category.UNIT and ZoneObject:isExist() and ZoneObject:isActive() ) or (ObjectCategory == Object.Category.STATIC and ZoneObject:isExist()) then local CoalitionDCSUnit = ZoneObject:getCoalition() @@ -853,7 +891,7 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) 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] } ) + self:F2( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } ) end end @@ -874,6 +912,7 @@ function ZONE_RADIUS:GetScannedUnits() return self.ScanData.Units end + --- Get a set of scanned units. -- @param #ZONE_RADIUS self -- @return Core.Set#SET_UNIT Set of units and statics inside the zone. @@ -906,19 +945,19 @@ end -- @return Core.Set#SET_GROUP Set of groups. function ZONE_RADIUS:GetScannedSetGroup() - self.ScanSetGroup = self.ScanSetGroup or SET_GROUP:New() -- Core.Set#SET_GROUP + self.ScanSetGroup=self.ScanSetGroup or SET_GROUP:New() --Core.Set#SET_GROUP - self.ScanSetGroup.Set = {} + self.ScanSetGroup.Set={} if self.ScanData then for ObjectID, UnitObject in pairs( self.ScanData.Units ) do local UnitObject = UnitObject -- DCS#Unit if UnitObject:isExist() then - local FoundUnit = UNIT:FindByName( UnitObject:getName() ) + local FoundUnit=UNIT:FindByName(UnitObject:getName()) if FoundUnit then - local group = FoundUnit:GetGroup() - self.ScanSetGroup:AddGroup( group ) + local group=FoundUnit:GetGroup() + self.ScanSetGroup:AddGroup(group) end end end @@ -927,6 +966,7 @@ function ZONE_RADIUS:GetScannedSetGroup() return self.ScanSetGroup end + --- Count the number of different coalitions inside the zone. -- @param #ZONE_RADIUS self -- @return #number Counted coalitions. @@ -979,6 +1019,7 @@ function ZONE_RADIUS:GetScannedCoalition( Coalition ) end end + --- Get scanned scenery type -- @param #ZONE_RADIUS self -- @return #table Table of DCS scenery type objects. @@ -986,6 +1027,7 @@ function ZONE_RADIUS:GetScannedSceneryType( SceneryType ) return self.ScanData.Scenery[SceneryType] end + --- Get scanned scenery table -- @param #ZONE_RADIUS self -- @return #table Table of DCS scenery objects. @@ -993,8 +1035,9 @@ function ZONE_RADIUS:GetScannedScenery() return self.ScanData.Scenery end + --- Is All in Zone of Coalition? --- Check if only the specified coalition is inside the zone and noone else. +-- Check if only the specifed coalition is inside the zone and noone else. -- @param #ZONE_RADIUS self -- @param #number Coalition Coalition ID of the coalition which is checked to be the only one in the zone. -- @return #boolean True, if **only** that coalition is inside the zone and no one else. @@ -1003,10 +1046,11 @@ end -- local IsGuarded = self.Zone:IsAllInZoneOfCoalition( self.Coalition ) function ZONE_RADIUS:IsAllInZoneOfCoalition( Coalition ) - -- self:E( { Coalitions = self.Coalitions, Count = self:CountScannedCoalitions() } ) + --self:E( { Coalitions = self.Coalitions, Count = self:CountScannedCoalitions() } ) return self:CountScannedCoalitions() == 1 and self:GetScannedCoalition( Coalition ) == true end + --- Is All in Zone of Other Coalition? -- Check if only one coalition is inside the zone and the specified coalition is not the one. -- You first need to use the @{#ZONE_RADIUS.Scan} method to scan the zone before it can be evaluated! @@ -1019,27 +1063,27 @@ end -- local IsCaptured = self.Zone:IsAllInZoneOfOtherCoalition( self.Coalition ) function ZONE_RADIUS:IsAllInZoneOfOtherCoalition( Coalition ) - -- self:E( { Coalitions = self.Coalitions, Count = self:CountScannedCoalitions() } ) + --self:E( { Coalitions = self.Coalitions, Count = self:CountScannedCoalitions() } ) return self:CountScannedCoalitions() == 1 and self:GetScannedCoalition( Coalition ) == nil end + --- Is Some in Zone of Coalition? --- Check if more than one coalition is inside the zone and the specified coalition is one of them. +-- Check if more than one coaltion is inside the zone and the specifed coalition is one of them. -- You first need to use the @{#ZONE_RADIUS.Scan} method to scan the zone before it can be evaluated! -- Note that once a zone has been scanned, multiple evaluations can be done on the scan result set. -- @param #ZONE_RADIUS self --- @param #number Coalition ID of the coalition which is checked to be inside the zone. +-- @param #number Coalition ID of the coaliton which is checked to be inside the zone. -- @return #boolean True if more than one coalition is inside the zone and the specified coalition is one of them. -- @usage --- -- self.Zone:Scan() -- local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition ) --- function ZONE_RADIUS:IsSomeInZoneOfCoalition( Coalition ) return self:CountScannedCoalitions() > 1 and self:GetScannedCoalition( Coalition ) == true end + --- Is None in Zone of Coalition? -- You first need to use the @{#ZONE_RADIUS.Scan} method to scan the zone before it can be evaluated! -- Note that once a zone has been scanned, multiple evaluations can be done on the scan result set. @@ -1047,30 +1091,30 @@ end -- @param Coalition -- @return #boolean -- @usage --- -- self.Zone:Scan() -- local IsOccupied = self.Zone:IsNoneInZoneOfCoalition( self.Coalition ) --- function ZONE_RADIUS:IsNoneInZoneOfCoalition( Coalition ) return self:GetScannedCoalition( Coalition ) == nil end + --- Is None in Zone? -- You first need to use the @{#ZONE_RADIUS.Scan} method to scan the zone before it can be evaluated! -- Note that once a zone has been scanned, multiple evaluations can be done on the scan result set. -- @param #ZONE_RADIUS self -- @return #boolean -- @usage --- -- self.Zone:Scan() -- local IsEmpty = self.Zone:IsNoneInZone() --- function ZONE_RADIUS:IsNoneInZone() return self:CountScannedCoalitions() == 0 end + + + --- Searches the zone -- @param #ZONE_RADIUS self -- @param ObjectCategories A list of categories, which are members of Object.Category @@ -1082,18 +1126,19 @@ function ZONE_RADIUS:SearchZone( EvaluateFunction, ObjectCategories ) local ZoneCoord = self:GetCoordinate() local ZoneRadius = self:GetRadius() - self:F( { ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS() } ) + self:F({ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS()}) local SphereSearch = { id = world.VolumeType.SPHERE, params = { - point = ZoneCoord:GetVec3(), - radius = ZoneRadius / 2, - }, + point = ZoneCoord:GetVec3(), + radius = ZoneRadius / 2, + } } local function EvaluateZone( ZoneDCSUnit ) + local ZoneUnit = UNIT:Find( ZoneDCSUnit ) return EvaluateFunction( ZoneUnit ) @@ -1113,7 +1158,7 @@ function ZONE_RADIUS:IsVec2InZone( Vec2 ) local ZoneVec2 = self:GetVec2() if ZoneVec2 then - if ((Vec2.x - ZoneVec2.x) ^ 2 + (Vec2.y - ZoneVec2.y) ^ 2) ^ 0.5 <= self:GetRadius() then + if (( Vec2.x - ZoneVec2.x )^2 + ( Vec2.y - ZoneVec2.y ) ^2 ) ^ 0.5 <= self:GetRadius() then return true end end @@ -1135,24 +1180,54 @@ end --- Returns a random Vec2 location within the zone. -- @param #ZONE_RADIUS self --- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. --- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. +-- @param #number inner (Optional) Minimal distance from the center of the zone. Default is 0. +-- @param #number outer (Optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. +-- @param #table surfacetypes (Optional) Table of surface types. Can also be a single surface type. We will try max 100 times to find the right type! -- @return DCS#Vec2 The random location within the zone. -function ZONE_RADIUS:GetRandomVec2( inner, outer ) - self:F( self.ZoneName, inner, outer ) +function ZONE_RADIUS:GetRandomVec2(inner, outer, surfacetypes) - 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 ); + if surfacetypes and type(surfacetypes)~="table" then + surfacetypes={surfacetypes} + end - self:T( { Point } ) + local function _getpoint() + local point = {} + local angle = math.random() * math.pi * 2 + point.x = Vec2.x + math.cos(angle) * math.random(_inner, _outer) + point.y = Vec2.y + math.sin(angle) * math.random(_inner, _outer) + return point + end - return Point + local function _checkSurface(point) + local stype=land.getSurfaceType(point) + for _,sf in pairs(surfacetypes) do + if sf==stype then + return true + end + end + return false + end + + local point=_getpoint() + + if surfacetypes then + local N=1 ; local Nmax=100 ; local gotit=false + while gotit==false and N<=Nmax do + gotit=_checkSurface(point) + if gotit then + --env.info(string.format("Got random coordinate with surface type %d after N=%d/%d iterations", land.getSurfaceType(point), N, Nmax)) + else + point=_getpoint() + N=N+1 + end + end + end + + return point end --- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. @@ -1185,6 +1260,7 @@ function ZONE_RADIUS:GetRandomVec3( inner, outer ) return { x = Vec2.x, y = self.y, z = Vec2.y } end + --- Returns a @{Core.Point#POINT_VEC3} object reflecting a random 3D location within the zone. -- @param #ZONE_RADIUS self -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. @@ -1200,24 +1276,28 @@ function ZONE_RADIUS:GetRandomPointVec3( inner, outer ) return PointVec3 end + --- Returns a @{Core.Point#COORDINATE} object reflecting a random 3D location within the zone. -- @param #ZONE_RADIUS self --- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. --- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. --- @return Core.Point#COORDINATE -function ZONE_RADIUS:GetRandomCoordinate( inner, outer ) - self:F( self.ZoneName, inner, outer ) +-- @param #number inner (Optional) Minimal distance from the center of the zone. Default is 0. +-- @param #number outer (Optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. +-- @param #table surfacetypes (Optional) Table of surface types. Can also be a single surface type. We will try max 1000 times to find the right type! +-- @return Core.Point#COORDINATE The random coordinate. +function ZONE_RADIUS:GetRandomCoordinate(inner, outer, surfacetypes) - local Coordinate = COORDINATE:NewFromVec2( self:GetRandomVec2( inner, outer ) ) + local vec2=self:GetRandomVec2(inner, outer, surfacetypes) - self:T3( { Coordinate = Coordinate } ) + local Coordinate = COORDINATE:NewFromVec2(vec2) return Coordinate end + + --- @type ZONE -- @extends #ZONE_RADIUS + --- The ZONE class, defined by the zone name as defined within the Mission Editor. -- This class implements the inherited functions from @{#ZONE_RADIUS} taking into account the own zone format and properties. -- @@ -1247,8 +1327,9 @@ end -- -- @field #ZONE ZONE = { - ClassName = "ZONE", -} + ClassName="ZONE", + } + --- Constructor of ZONE taking the zone name. -- @param #ZONE self @@ -1257,10 +1338,10 @@ ZONE = { function ZONE:New( ZoneName ) -- First try to find the zone in the DB. - local zone = _DATABASE:FindZone( ZoneName ) + local zone=_DATABASE:FindZone(ZoneName) if zone then - -- env.info("FF found zone in DB") + --env.info("FF found zone in DB") return zone end @@ -1269,16 +1350,16 @@ function ZONE:New( ZoneName ) -- Error! if not Zone then - error( "Zone " .. ZoneName .. " does not exist." ) + env.error( "ERROR: Zone " .. ZoneName .. " does not exist!" ) return nil end -- Create a new ZONE_RADIUS. - local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, { x = Zone.point.x, y = Zone.point.z }, Zone.radius ) ) - self:F( ZoneName ) + local self=BASE:Inherit( self, ZONE_RADIUS:New(ZoneName, {x=Zone.point.x, y=Zone.point.z}, Zone.radius)) + self:F(ZoneName) -- Color of zone. - self.Color = { 1, 0, 0, 0.15 } + self.Color={1, 0, 0, 0.15} -- DCS zone. self.Zone = Zone @@ -1296,10 +1377,13 @@ function ZONE:FindByName( ZoneName ) return ZoneFound end + + --- @type ZONE_UNIT -- @field Wrapper.Unit#UNIT ZoneUNIT -- @extends Core.Zone#ZONE_RADIUS + --- # ZONE_UNIT class, extends @{Zone#ZONE_RADIUS} -- -- The ZONE_UNIT class defined by a zone attached to a @{Wrapper.Unit#UNIT} with a radius and optional offsets. @@ -1307,8 +1391,8 @@ end -- -- @field #ZONE_UNIT ZONE_UNIT = { - ClassName = "ZONE_UNIT", -} + ClassName="ZONE_UNIT", + } --- Constructor to create a ZONE_UNIT instance, taking the zone name, a zone unit and a radius and optional offsets in X and Y directions. -- @param #ZONE_UNIT self @@ -1323,12 +1407,12 @@ ZONE_UNIT = { -- relative_to_unit If true, theta is measured clockwise from unit's direction else clockwise from north. If using dx, dy setting this to true makes +x parallel to unit heading. -- dx, dy OR rho, theta may be used, not both. -- @return #ZONE_UNIT self -function ZONE_UNIT:New( ZoneName, ZoneUNIT, Radius, Offset ) +function ZONE_UNIT:New( ZoneName, ZoneUNIT, Radius, Offset) if Offset then -- check if the inputs was reasonable, either (dx, dy) or (rho, theta) can be given, else raise an exception. if (Offset.dx or Offset.dy) and (Offset.rho or Offset.theta) then - error( "Cannot use (dx, dy) with (rho, theta)" ) + error("Cannot use (dx, dy) with (rho, theta)") end self.dy = Offset.dy or 0.0 @@ -1351,6 +1435,7 @@ function ZONE_UNIT:New( ZoneName, ZoneUNIT, Radius, Offset ) return self end + --- Returns the current location of the @{Wrapper.Unit#UNIT}. -- @param #ZONE_UNIT self -- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Unit#UNIT}location and the offset, if any. @@ -1362,9 +1447,9 @@ function ZONE_UNIT:GetVec2() local heading if self.relative_to_unit then - heading = (self.ZoneUNIT:GetHeading() or 0.0) * math.pi / 180.0 - else - heading = 0.0 + heading = ( self.ZoneUNIT:GetHeading() or 0.0 ) * math.pi / 180.0 + else + heading = 0.0 end -- update the zone position with the offsets. @@ -1378,8 +1463,8 @@ function ZONE_UNIT:GetVec2() -- if using the polar coordinates 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 ) + 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 @@ -1400,14 +1485,14 @@ function ZONE_UNIT:GetRandomVec2() self:F( self.ZoneName ) local RandomVec2 = {} - -- local Vec2 = self.ZoneUNIT:GetVec2() -- FF: This does not take care of the new offset feature! + --local Vec2 = self.ZoneUNIT:GetVec2() -- FF: This does not take care of the new offset feature! local Vec2 = self:GetVec2() if not Vec2 then Vec2 = self.LastVec2 end - local angle = math.random() * math.pi * 2; + 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(); @@ -1437,13 +1522,14 @@ end --- @type ZONE_GROUP -- @extends #ZONE_RADIUS + --- The ZONE_GROUP class defines by a zone around a @{Wrapper.Group#GROUP} with a radius. The current leader of the group defines the center of the zone. -- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. -- -- @field #ZONE_GROUP ZONE_GROUP = { - ClassName = "ZONE_GROUP", -} + ClassName="ZONE_GROUP", + } --- Constructor to create a ZONE_GROUP instance, taking the zone name, a zone @{Wrapper.Group#GROUP} and a radius. -- @param #ZONE_GROUP self @@ -1464,6 +1550,7 @@ function ZONE_GROUP:New( ZoneName, ZoneGROUP, Radius ) return self end + --- Returns the current location of the @{Wrapper.Group}. -- @param #ZONE_GROUP self -- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Group} location. @@ -1493,7 +1580,7 @@ function ZONE_GROUP:GetRandomVec2() local Point = {} local Vec2 = self._.ZoneGROUP:GetVec2() - local angle = math.random() * math.pi * 2; + 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(); @@ -1517,10 +1604,12 @@ function ZONE_GROUP:GetRandomPointVec2( inner, outer ) return PointVec2 end + --- @type ZONE_POLYGON_BASE -- --@field #ZONE_POLYGON_BASE.ListVec2 Polygon The polygon defined by an array of @{DCS#Vec2}. -- @extends #ZONE_BASE + --- The ZONE_POLYGON_BASE class defined by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon. -- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. -- This class is an abstract BASE class for derived classes, and is not meant to be instantiated. @@ -1531,7 +1620,7 @@ end -- -- * @{#ZONE_POLYGON_BASE.GetRandomVec2}(): Gets a random 2D point in the zone. -- * @{#ZONE_POLYGON_BASE.GetRandomPointVec2}(): Return a @{Core.Point#POINT_VEC2} object representing a random 2D point within the zone. --- * @{#ZONE_POLYGON_BASE.GetRandomPointVec3}(): Return a @{Core.Point#POINT_VEC3} object representing a random 3D point at land height within the zone. +-- * @{#ZONE_POLYGON_BASE.GetRandomPointVec3}(): Return a @{Core.Point#POINT_VEC3} object representing a random 3D point at landheight within the zone. -- -- ## Draw zone -- @@ -1541,8 +1630,8 @@ end -- -- @field #ZONE_POLYGON_BASE ZONE_POLYGON_BASE = { - ClassName = "ZONE_POLYGON_BASE", -} + ClassName="ZONE_POLYGON_BASE", + } --- A 2D points array. -- @type ZONE_POLYGON_BASE.ListVec2 @@ -1583,14 +1672,14 @@ end -- @param #ZONE_POLYGON_BASE self -- @param #ZONE_POLYGON_BASE.ListVec2 Vec2Array An array of @{DCS#Vec2}, forming a polygon. -- @return #ZONE_POLYGON_BASE self -function ZONE_POLYGON_BASE:UpdateFromVec2( Vec2Array ) +function ZONE_POLYGON_BASE:UpdateFromVec2(Vec2Array) self._.Polygon = {} - for i = 1, #Vec2Array do + for i=1,#Vec2Array do self._.Polygon[i] = {} - self._.Polygon[i].x = Vec2Array[i].x - self._.Polygon[i].y = Vec2Array[i].y + self._.Polygon[i].x=Vec2Array[i].x + self._.Polygon[i].y=Vec2Array[i].y end return self @@ -1600,14 +1689,14 @@ end -- @param #ZONE_POLYGON_BASE self -- @param #ZONE_POLYGON_BASE.ListVec3 Vec2Array An array of @{DCS#Vec3}, forming a polygon. -- @return #ZONE_POLYGON_BASE self -function ZONE_POLYGON_BASE:UpdateFromVec3( Vec3Array ) +function ZONE_POLYGON_BASE:UpdateFromVec3(Vec3Array) self._.Polygon = {} - for i = 1, #Vec3Array do + for i=1,#Vec3Array do self._.Polygon[i] = {} - self._.Polygon[i].x = Vec3Array[i].x - self._.Polygon[i].y = Vec3Array[i].z + self._.Polygon[i].x=Vec3Array[i].x + self._.Polygon[i].y=Vec3Array[i].z end return self @@ -1621,14 +1710,14 @@ function ZONE_POLYGON_BASE:GetVec2() local Bounds = self:GetBoundingSquare() - return { x = (Bounds.x2 + Bounds.x1) / 2, y = (Bounds.y2 + Bounds.y1) / 2 } + return { x = ( Bounds.x2 + Bounds.x1 ) / 2, y = ( Bounds.y2 + Bounds.y1 ) / 2 } end --- Get a vertex of the polygon. -- @param #ZONE_POLYGON_BASE self -- @param #number Index Index of the vertex. Default 1. -- @return DCS#Vec2 Vertex of the polygon. -function ZONE_POLYGON_BASE:GetVertexVec2( Index ) +function ZONE_POLYGON_BASE:GetVertexVec2(Index) return self._.Polygon[Index or 1] end @@ -1636,10 +1725,10 @@ end -- @param #ZONE_POLYGON_BASE self -- @param #number Index Index of the vertex. Default 1. -- @return DCS#Vec3 Vertex of the polygon. -function ZONE_POLYGON_BASE:GetVertexVec3( Index ) - local vec2 = self:GetVertexVec2( Index ) +function ZONE_POLYGON_BASE:GetVertexVec3(Index) + local vec2=self:GetVertexVec2(Index) if vec2 then - local vec3 = { x = vec2.x, y = land.getHeight( vec2 ), z = vec2.y } + local vec3={x=vec2.x, y=land.getHeight(vec2), z=vec2.y} return vec3 end return nil @@ -1649,15 +1738,16 @@ end -- @param #ZONE_POLYGON_BASE self -- @param #number Index Index of the vertex. Default 1. -- @return Core.Point#COORDINATE Vertex of the polygon. -function ZONE_POLYGON_BASE:GetVertexCoordinate( Index ) - local vec2 = self:GetVertexVec2( Index ) +function ZONE_POLYGON_BASE:GetVertexCoordinate(Index) + local vec2=self:GetVertexVec2(Index) if vec2 then - local coord = COORDINATE:NewFromVec2( vec2 ) + local coord=COORDINATE:NewFromVec2(vec2) return coord end return nil end + --- Get a list of verticies of the polygon. -- @param #ZONE_POLYGON_BASE self -- @return List of DCS#Vec2 verticies defining the edges of the polygon. @@ -1670,11 +1760,11 @@ end -- @return #table List of DCS#Vec3 verticies defining the edges of the polygon. function ZONE_POLYGON_BASE:GetVerticiesVec3() - local coords = {} + local coords={} - for i, vec2 in ipairs( self._.Polygon ) do - local vec3 = { x = vec2.x, y = land.getHeight( vec2 ), z = vec2.y } - table.insert( coords, vec3 ) + for i,vec2 in ipairs(self._.Polygon) do + local vec3={x=vec2.x, y=land.getHeight(vec2), z=vec2.y} + table.insert(coords, vec3) end return coords @@ -1685,11 +1775,11 @@ end -- @return #table List of COORDINATES verticies defining the edges of the polygon. function ZONE_POLYGON_BASE:GetVerticiesCoordinates() - local coords = {} + local coords={} - for i, vec2 in ipairs( self._.Polygon ) do - local coord = COORDINATE:NewFromVec2( vec2 ) - table.insert( coords, coord ) + for i,vec2 in ipairs(self._.Polygon) do + local coord=COORDINATE:NewFromVec2(vec2) + table.insert(coords, coord) end return coords @@ -1753,6 +1843,7 @@ function ZONE_POLYGON_BASE:BoundZone( UnBound ) return self end + --- Draw the zone on the F10 map. **NOTE** Currently, only polygons with **exactly four points** are supported! -- @param #ZONE_POLYGON_BASE self -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. @@ -1763,32 +1854,34 @@ end -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. -- @return #ZONE_POLYGON_BASE self -function ZONE_POLYGON_BASE:DrawZone( Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly ) +function ZONE_POLYGON_BASE:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) - local coordinate = COORDINATE:NewFromVec2( self._.Polygon[1] ) + local coordinate=COORDINATE:NewFromVec2(self._.Polygon[1]) - Color = Color or self:GetColorRGB() - Alpha = Alpha or 1 - FillColor = FillColor or Color - FillAlpha = FillAlpha or self:GetColorAlpha() + Color=Color or self:GetColorRGB() + Alpha=Alpha or 1 + FillColor=FillColor or Color + FillAlpha=FillAlpha or self:GetColorAlpha() - if #self._.Polygon == 4 then - local Coord2 = COORDINATE:NewFromVec2( self._.Polygon[2] ) - local Coord3 = COORDINATE:NewFromVec2( self._.Polygon[3] ) - local Coord4 = COORDINATE:NewFromVec2( self._.Polygon[4] ) + if #self._.Polygon==4 then - self.DrawID = coordinate:QuadToAll( Coord2, Coord3, Coord4, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly ) + local Coord2=COORDINATE:NewFromVec2(self._.Polygon[2]) + local Coord3=COORDINATE:NewFromVec2(self._.Polygon[3]) + local Coord4=COORDINATE:NewFromVec2(self._.Polygon[4]) + + self.DrawID=coordinate:QuadToAll(Coord2, Coord3, Coord4, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) else - local Coordinates = self:GetVerticiesCoordinates() - table.remove( Coordinates, 1 ) + local Coordinates=self:GetVerticiesCoordinates() + table.remove(Coordinates, 1) - self.DrawID = coordinate:MarkupToAllFreeForm( Coordinates, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly ) + self.DrawID=coordinate:MarkupToAllFreeForm(Coordinates, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) end + return self end @@ -1800,10 +1893,10 @@ end function ZONE_POLYGON_BASE:SmokeZone( SmokeColor, Segments ) self:F2( SmokeColor ) - Segments = Segments or 10 + Segments=Segments or 10 - local i = 1 - local j = #self._.Polygon + local i=1 + local j=#self._.Polygon while i <= #self._.Polygon do self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) @@ -1812,8 +1905,8 @@ function ZONE_POLYGON_BASE:SmokeZone( SmokeColor, Segments ) local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y for Segment = 0, Segments do -- We divide each line in 5 segments and smoke a point on the line. - local PointX = self._.Polygon[i].x + (Segment * DeltaX / Segments) - local PointY = self._.Polygon[i].y + (Segment * DeltaY / Segments) + 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 @@ -1823,6 +1916,7 @@ function ZONE_POLYGON_BASE:SmokeZone( SmokeColor, Segments ) return self end + --- Flare the zone boundaries in a color. -- @param #ZONE_POLYGON_BASE self -- @param Utilities.Utils#FLARECOLOR FlareColor The flare color. @@ -1831,14 +1925,14 @@ end -- @param #number AddHeight (optional) The height to be added for the smoke. -- @return #ZONE_POLYGON_BASE self function ZONE_POLYGON_BASE:FlareZone( FlareColor, Segments, Azimuth, AddHeight ) - self:F2( FlareColor ) + self:F2(FlareColor) - Segments = Segments or 10 + Segments=Segments or 10 AddHeight = AddHeight or 0 - local i = 1 - local j = #self._.Polygon + local i=1 + local j=#self._.Polygon while i <= #self._.Polygon do self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) @@ -1847,9 +1941,9 @@ function ZONE_POLYGON_BASE:FlareZone( FlareColor, Segments, Azimuth, AddHeight ) local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y for Segment = 0, Segments do -- We divide each line in 5 segments and smoke a point on the line. - 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 ) + 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 @@ -1858,6 +1952,9 @@ function ZONE_POLYGON_BASE:FlareZone( FlareColor, Segments, Azimuth, AddHeight ) return self end + + + --- Returns if a location is within the zone. -- Source learned and taken from: https://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html -- @param #ZONE_POLYGON_BASE self @@ -1875,9 +1972,10 @@ function ZONE_POLYGON_BASE:IsVec2InZone( Vec2 ) 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 + 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 @@ -1892,26 +1990,28 @@ end -- @param #ZONE_POLYGON_BASE self -- @return DCS#Vec2 The Vec2 coordinate. function ZONE_POLYGON_BASE:GetRandomVec2() - self:F2() - --- It is a bit tricky to find a random point within a polygon. Right now i am doing it the dirty and inefficient way... - local Vec2Found = false - local Vec2 + -- It is a bit tricky to find a random point within a polygon. Right now i am doing it the dirty and inefficient way... + + -- Get the bounding square. local BS = self:GetBoundingSquare() - self:T2( BS ) + local Nmax=1000 ; local n=0 + while n self._.Polygon[i].x) and self._.Polygon[i].x or x1 - x2 = (x2 < self._.Polygon[i].x) and self._.Polygon[i].x or x2 - y1 = (y1 > self._.Polygon[i].y) and self._.Polygon[i].y or y1 - y2 = (y2 < self._.Polygon[i].y) and self._.Polygon[i].y or y2 + x1 = ( x1 > self._.Polygon[i].x ) and self._.Polygon[i].x or x1 + x2 = ( x2 < self._.Polygon[i].x ) and self._.Polygon[i].x or x2 + y1 = ( y1 > self._.Polygon[i].y ) and self._.Polygon[i].y or y1 + y2 = ( y2 < self._.Polygon[i].y ) and self._.Polygon[i].y or y2 end @@ -1984,51 +2086,52 @@ end -- @param #number Segments (Optional) Number of segments within boundary line. Default 10. -- @param #boolean Closed (Optional) Link the last point with the first one to obtain a closed boundary. Default false -- @return #ZONE_POLYGON_BASE self -function ZONE_POLYGON_BASE:Boundary( Coalition, Color, Radius, Alpha, Segments, Closed ) - Coalition = Coalition or -1 - Color = Color or { 1, 1, 1 } - Radius = Radius or 1000 - Alpha = Alpha or 1 - Segments = Segments or 10 - Closed = Closed or false - local i = 1 - local j = #self._.Polygon - if (Closed) then - Limit = #self._.Polygon + 1 - else - Limit = #self._.Polygon - end - while i <= #self._.Polygon do - self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) - if j ~= Limit then - local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x - local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y - for Segment = 0, Segments do - local PointX = self._.Polygon[i].x + (Segment * DeltaX / Segments) - local PointY = self._.Polygon[i].y + (Segment * DeltaY / Segments) - ZONE_RADIUS:New( "Zone", { x = PointX, y = PointY }, Radius ):DrawZone( Coalition, Color, 1, Color, Alpha, nil, true ) - end +function ZONE_POLYGON_BASE:Boundary(Coalition, Color, Radius, Alpha, Segments, Closed) + Coalition = Coalition or -1 + Color = Color or {1, 1, 1} + Radius = Radius or 1000 + Alpha = Alpha or 1 + Segments = Segments or 10 + Closed = Closed or false + local i = 1 + local j = #self._.Polygon + if (Closed) then + Limit = #self._.Polygon + 1 + else + Limit = #self._.Polygon end - j = i - i = i + 1 - end - return self + while i <= #self._.Polygon do + self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) + if j ~= Limit then + local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x + local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y + for Segment = 0, Segments do + local PointX = self._.Polygon[i].x + ( Segment * DeltaX / Segments ) + local PointY = self._.Polygon[i].y + ( Segment * DeltaY / Segments ) + ZONE_RADIUS:New( "Zone", {x = PointX, y = PointY}, Radius ):DrawZone(Coalition, Color, 1, Color, Alpha, nil, true) + end + end + j = i + i = i + 1 + end + return self end --- @type ZONE_POLYGON -- @extends #ZONE_POLYGON_BASE + --- The ZONE_POLYGON class defined by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon. -- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. -- -- ## Declare a ZONE_POLYGON directly in the DCS mission editor! -- --- You can declare a ZONE_POLYGON using the DCS mission editor by adding the ~ZONE_POLYGON tag in the group name. +-- You can declare a ZONE_POLYGON using the DCS mission editor by adding the #ZONE_POLYGON tag in the group name. -- --- So, imagine you have a group declared in the mission editor, with group name `DefenseZone~ZONE_POLYGON`. +-- So, imagine you have a group declared in the mission editor, with group name `DefenseZone#ZONE_POLYGON`. -- Then during mission startup, when loading Moose.lua, this group will be detected as a ZONE_POLYGON declaration. -- Within the background, a ZONE_POLYGON object will be created within the @{Core.Database} using the properties of the group. --- The ZONE_POLYGON name will be the group name without the ~ZONE_POLYGON tag. +-- The ZONE_POLYGON name will be the group name without the #ZONE_POLYGON tag. -- -- So, you can search yourself for the ZONE_POLYGON by using the @{#ZONE_POLYGON.FindByName}() method. -- In this example, `local PolygonZone = ZONE_POLYGON:FindByName( "DefenseZone" )` would return the ZONE_POLYGON object @@ -2043,8 +2146,8 @@ end -- -- @field #ZONE_POLYGON ZONE_POLYGON = { - ClassName = "ZONE_POLYGON", -} + ClassName="ZONE_POLYGON", + } --- Constructor to create a ZONE_POLYGON instance, taking the zone name and the @{Wrapper.Group#GROUP} defined within the Mission Editor. -- The @{Wrapper.Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected by ZONE_POLYGON. @@ -2065,6 +2168,7 @@ function ZONE_POLYGON:New( ZoneName, ZoneGroup ) return self end + --- Constructor to create a ZONE_POLYGON instance, taking the zone name and the **name** of the @{Wrapper.Group#GROUP} defined within the Mission Editor. -- The @{Wrapper.Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected by ZONE_POLYGON. -- @param #ZONE_POLYGON self @@ -2085,6 +2189,7 @@ function ZONE_POLYGON:NewFromGroupName( GroupName ) return self end + --- Find a polygon zone in the _DATABASE using the name of the polygon zone. -- @param #ZONE_POLYGON self -- @param #string ZoneName The name of the polygon zone. @@ -2098,15 +2203,21 @@ end do -- ZONE_AIRBASE --- @type ZONE_AIRBASE + -- @field #boolean isShip If `true`, airbase is a ship. + -- @field #boolean isHelipad If `true`, airbase is a helipad. + -- @field #boolean isAirdrome If `true`, airbase is an airdrome. -- @extends #ZONE_RADIUS + --- The ZONE_AIRBASE class defines by a zone around a @{Wrapper.Airbase#AIRBASE} with a radius. -- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. -- -- @field #ZONE_AIRBASE ZONE_AIRBASE = { - ClassName = "ZONE_AIRBASE", - } + ClassName="ZONE_AIRBASE", + } + + --- Constructor to create a ZONE_AIRBASE instance, taking the zone name, a zone @{Wrapper.Airbase#AIRBASE} and a radius. -- @param #ZONE_AIRBASE self @@ -2115,7 +2226,7 @@ do -- ZONE_AIRBASE -- @return #ZONE_AIRBASE self function ZONE_AIRBASE:New( AirbaseName, Radius ) - Radius = Radius or 4000 + Radius=Radius or 4000 local Airbase = AIRBASE:FindByName( AirbaseName ) @@ -2123,6 +2234,20 @@ do -- ZONE_AIRBASE self._.ZoneAirbase = Airbase self._.ZoneVec2Cache = self._.ZoneAirbase:GetVec2() + + if Airbase:IsShip() then + self.isShip=true + self.isHelipad=false + self.isAirdrome=false + elseif Airbase:IsHelipad() then + self.isShip=false + self.isHelipad=true + self.isAirdrome=false + elseif Airbase:IsAirdrome() then + self.isShip=false + self.isHelipad=false + self.isAirdrome=true + end -- Zone objects are added to the _DATABASE and SET_ZONE objects. _EVENTDISPATCHER:CreateEventNewZone( self ) @@ -2137,9 +2262,9 @@ do -- ZONE_AIRBASE return self._.ZoneAirbase end - --- Returns the current location of the @{Wrapper.Group}. + --- Returns the current location of the AIRBASE. -- @param #ZONE_AIRBASE self - -- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Group} location. + -- @return DCS#Vec2 The location of the zone based on the AIRBASE location. function ZONE_AIRBASE:GetVec2() self:F( self.ZoneName ) @@ -2157,24 +2282,6 @@ do -- ZONE_AIRBASE return ZoneVec2 end - --- Returns a random location within the zone of the @{Wrapper.Group}. - -- @param #ZONE_AIRBASE self - -- @return DCS#Vec2 The random location of the zone based on the @{Wrapper.Group} location. - function ZONE_AIRBASE:GetRandomVec2() - self:F( self.ZoneName ) - - local Point = {} - local Vec2 = self._.ZoneAirbase: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 - --- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. -- @param #ZONE_AIRBASE self -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. @@ -2190,4 +2297,5 @@ do -- ZONE_AIRBASE return PointVec2 end + end From 0213bc7aefc777a5e81b974d445de4af1d950dd1 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 26 Mar 2022 14:43:06 +0100 Subject: [PATCH 088/200] Correcting Celcius to Celsius --- Moose Development/Moose/Core/Point.lua | 2 +- Moose Development/Moose/Functional/Range.lua | 2 +- Moose Development/Moose/Utilities/Utils.lua | 1786 +++++++++--------- 3 files changed, 888 insertions(+), 902 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 93327ab62..878fe93e2 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -932,7 +932,7 @@ do -- COORDINATE if Settings:IsMetric() then return string.format( " %-2.2f °C", DegreesCelcius ) else - return string.format( " %-2.2f °F", UTILS.CelciusToFarenheit( DegreesCelcius ) ) + return string.format( " %-2.2f °F", UTILS.CelsiusToFarenheit( DegreesCelcius ) ) end else return " no temperature" diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index 7949c22ac..14e58b8e5 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -2755,7 +2755,7 @@ function RANGE:_DisplayRangeWeather( _unitname ) local tW = string.format( "%.1f m/s", Ws ) local tP = string.format( "%.1f mmHg", P * hPa2mmHg ) if settings:IsImperial() then - -- tT=string.format("%d°F", UTILS.CelciusToFarenheit(T)) + -- tT=string.format("%d°F", UTILS.CelsiusToFarenheit(T)) tW = string.format( "%.1f knots", UTILS.MpsToKnots( Ws ) ) tP = string.format( "%.2f inHg", P * hPa2inHg ) end diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index ab4fa0ab3..e059a353e 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -12,6 +12,7 @@ -- @module Utils -- @image MOOSE.JPG + --- @type SMOKECOLOR -- @field Green -- @field Red @@ -32,14 +33,14 @@ FLARECOLOR = trigger.flareColor -- #FLARECOLOR --- Big smoke preset enum. -- @type BIGSMOKEPRESET BIGSMOKEPRESET = { - SmallSmokeAndFire = 1, - MediumSmokeAndFire = 2, - LargeSmokeAndFire = 3, - HugeSmokeAndFire = 4, - SmallSmoke = 5, - MediumSmoke = 6, - LargeSmoke = 7, - HugeSmoke = 8, + SmallSmokeAndFire=1, + MediumSmokeAndFire=2, + LargeSmokeAndFire=3, + HugeSmokeAndFire=4, + SmallSmoke=5, + MediumSmoke=6, + LargeSmoke=7, + HugeSmoke=8, } --- DCS map as returned by env.mission.theatre. @@ -52,15 +53,16 @@ BIGSMOKEPRESET = { -- @field #string Syria Syria map. -- @field #string MarianaIslands Mariana Islands map. DCSMAP = { - Caucasus = "Caucasus", - NTTR = "Nevada", - Normandy = "Normandy", - PersianGulf = "PersianGulf", - TheChannel = "TheChannel", - Syria = "Syria", - MarianaIslands = "MarianaIslands", + Caucasus="Caucasus", + NTTR="Nevada", + Normandy="Normandy", + PersianGulf="PersianGulf", + TheChannel="TheChannel", + Syria="Syria", + MarianaIslands="MarianaIslands" } + --- See [DCS_enum_callsigns](https://wiki.hoggitworld.com/view/DCS_enum_callsigns) -- @type CALLSIGN CALLSIGN={ @@ -225,11 +227,11 @@ UTILS.IsInstanceOf = function( object, className ) -- Get the name of the Moose class as a string className = className.ClassName - -- className is neither a string nor a Moose class, throw an error + -- className is neither a string nor a Moose class, throw an error else -- I'm not sure if this should take advantage of MOOSE logging function, or throw an error for pcall - local err_str = 'className parameter should be a string; parameter received: ' .. type( className ) + local err_str = 'className parameter should be a string; parameter received: '..type( className ) return false -- error( err_str ) @@ -256,16 +258,17 @@ UTILS.IsInstanceOf = function( object, className ) return false end + --- Deep copy a table. See http://lua-users.org/wiki/CopyTable -- @param #table object The input table. -- @return #table Copy of the input table. -UTILS.DeepCopy = function( object ) +UTILS.DeepCopy = function(object) local lookup_table = {} -- Copy function. - local function _copy( object ) - if type( object ) ~= "table" then + local function _copy(object) + if type(object) ~= "table" then return object elseif lookup_table[object] then return lookup_table[object] @@ -275,27 +278,28 @@ UTILS.DeepCopy = function( object ) lookup_table[object] = new_table - for index, value in pairs( object ) do - new_table[_copy( index )] = _copy( value ) + for index, value in pairs(object) do + new_table[_copy(index)] = _copy(value) end - return setmetatable( new_table, getmetatable( object ) ) + return setmetatable(new_table, getmetatable(object)) end - local objectreturn = _copy( object ) + local objectreturn = _copy(object) return objectreturn end + --- Porting in Slmod's serialize_slmod2. -- @param #table tbl Input table. -UTILS.OneLineSerialize = function( tbl ) -- serialization of a table all on a single line, no comments, made to replace old get_table_string function +UTILS.OneLineSerialize = function( tbl ) -- serialization of a table all on a single line, no comments, made to replace old get_table_string function lookup_table = {} local function _Serialize( tbl ) - if type( tbl ) == 'table' then -- function only works for tables! + if type(tbl) == 'table' then --function only works for tables! if lookup_table[tbl] then return lookup_table[object] @@ -307,127 +311,128 @@ UTILS.OneLineSerialize = function( tbl ) -- serialization of a table all on a si tbl_str[#tbl_str + 1] = '{' - for ind, val in pairs( tbl ) do -- serialize its fields + for ind,val in pairs(tbl) do -- serialize its fields local ind_str = {} - if type( ind ) == "number" then + if type(ind) == "number" then ind_str[#ind_str + 1] = '[' - ind_str[#ind_str + 1] = tostring( ind ) + ind_str[#ind_str + 1] = tostring(ind) ind_str[#ind_str + 1] = ']=' - else -- must be a string + else --must be a string ind_str[#ind_str + 1] = '[' - ind_str[#ind_str + 1] = routines.utils.basicSerialize( ind ) + 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 ) + 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 ) + 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 -- won't ever happen, right? + tbl_str[#tbl_str + 1] = table.concat(ind_str) + tbl_str[#tbl_str + 1] = table.concat(val_str) + elseif type(val) == 'nil' then -- won't ever happen, right? 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 + 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 - -- tbl_str[#tbl_str + 1] = "__index" - -- tbl_str[#tbl_str + 1] = ',' --I think this is right, I just added it + -- tbl_str[#tbl_str + 1] = "__index" + -- tbl_str[#tbl_str + 1] = ',' --I think this is right, I just added it else - val_str[#val_str + 1] = _Serialize( val ) - val_str[#val_str + 1] = ',' -- I think this is right, I just added it - tbl_str[#tbl_str + 1] = table.concat( ind_str ) - tbl_str[#tbl_str + 1] = table.concat( val_str ) + val_str[#val_str + 1] = _Serialize(val) + val_str[#val_str + 1] = ',' --I think this is right, I just added it + 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] = ',' -- I think this is right, I just added it + elseif type(val) == 'function' then + tbl_str[#tbl_str + 1] = "f() " .. tostring(ind) + tbl_str[#tbl_str + 1] = ',' --I think this is right, I just added it else - env.info( 'unable to serialize value type ' .. routines.utils.basicSerialize( type( val ) ) .. ' at index ' .. tostring( ind ) ) + 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 ) + return table.concat(tbl_str) else - return tostring( tbl ) + return tostring(tbl) end end - local objectreturn = _Serialize( tbl ) + local objectreturn = _Serialize(tbl) return objectreturn end --- porting in Slmod's "safestring" basic serialize -UTILS.BasicSerialize = function( s ) +--porting in Slmod's "safestring" basic serialize +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 ) + 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 + +UTILS.ToDegree = function(angle) + return angle*180/math.pi end -UTILS.ToRadian = function( angle ) - return angle * math.pi / 180 +UTILS.ToRadian = function(angle) + return angle*math.pi/180 end -UTILS.MetersToNM = function( meters ) - return meters / 1852 +UTILS.MetersToNM = function(meters) + return meters/1852 end -UTILS.KiloMetersToNM = function( kilometers ) - return kilometers / 1852 * 1000 +UTILS.KiloMetersToNM = function(kilometers) + return kilometers/1852*1000 end -UTILS.MetersToSM = function( meters ) - return meters / 1609.34 +UTILS.MetersToSM = function(meters) + return meters/1609.34 end -UTILS.KiloMetersToSM = function( kilometers ) - return kilometers / 1609.34 * 1000 +UTILS.KiloMetersToSM = function(kilometers) + return kilometers/1609.34*1000 end -UTILS.MetersToFeet = function( meters ) - return meters / 0.3048 +UTILS.MetersToFeet = function(meters) + return meters/0.3048 end -UTILS.KiloMetersToFeet = function( kilometers ) - return kilometers / 0.3048 * 1000 +UTILS.KiloMetersToFeet = function(kilometers) + return kilometers/0.3048*1000 end -UTILS.NMToMeters = function( NM ) - return NM * 1852 +UTILS.NMToMeters = function(NM) + return NM*1852 end -UTILS.NMToKiloMeters = function( NM ) - return NM * 1852 / 1000 +UTILS.NMToKiloMeters = function(NM) + return NM*1852/1000 end -UTILS.FeetToMeters = function( feet ) - return feet * 0.3048 +UTILS.FeetToMeters = function(feet) + return feet*0.3048 end -UTILS.KnotsToKmph = function( knots ) +UTILS.KnotsToKmph = function(knots) return knots * 1.852 end -UTILS.KmphToKnots = function( knots ) +UTILS.KmphToKnots = function(knots) return knots / 1.852 end @@ -454,7 +459,7 @@ end -- @param #number mps Speed in m/s. -- @return #number Speed in knots. UTILS.MpsToKnots = function( mps ) - return mps * 1.94384 -- 3600 / 1852 + return mps * 1.94384 --3600 / 1852 end --- Convert knots to meters per second. @@ -468,21 +473,21 @@ UTILS.KnotsToMps = function( knots ) end end ---- Convert temperature from Celsius to Fahrenheit. --- @param #number Celsius Temperature in degrees Celsius. --- @return #number Temperature in degrees Fahrenheit. -UTILS.CelsiusToFahrenheit = function( Celsius ) - return Celsius * 9 / 5 + 32 +--- Convert temperature from Celsius to Farenheit. +-- @param #number Celcius Temperature in degrees Celsius. +-- @return #number Temperature in degrees Farenheit. +UTILS.CelsiusToFarenheit = function( Celcius ) + return Celcius * 9/5 + 32 end ---- Convert pressure from hectopascal (hPa) to inches of mercury (inHg). +--- Convert pressure from hecto Pascal (hPa) to inches of mercury (inHg). -- @param #number hPa Pressure in hPa. -- @return #number Pressure in inHg. UTILS.hPa2inHg = function( hPa ) return hPa * 0.0295299830714 end ---- Convert knots to altitude corrected KIAS, e.g. for tankers. +--- Convert knots to alitude corrected KIAS, e.g. for tankers. -- @param #number knots Speed in knots. -- @param #number altitude Altitude in feet -- @return #number Corrected KIAS @@ -490,14 +495,14 @@ UTILS.KnotsToAltKIAS = function( knots, altitude ) return (knots * 0.018 * (altitude / 1000)) + knots end ---- Convert pressure from hectopascal (hPa) to millimeters of mercury (mmHg). +--- Convert pressure from hecto Pascal (hPa) to millimeters of mercury (mmHg). -- @param #number hPa Pressure in hPa. -- @return #number Pressure in mmHg. UTILS.hPa2mmHg = function( hPa ) return hPa * 0.7500615613030 end ---- Convert kilograms (kg) to pounds (lbs). +--- Convert kilo gramms (kg) to pounds (lbs). -- @param #number kg Mass in kg. -- @return #number Mass in lbs. UTILS.kg2lbs = function( kg ) @@ -511,7 +516,7 @@ position after the decimal of the least significant digit: So: 42.32 - acc of 2. ]] -UTILS.tostringLL = function( lat, lon, acc, DMS ) +UTILS.tostringLL = function( lat, lon, acc, DMS) local latHemi, lonHemi if lat > 0 then @@ -526,23 +531,23 @@ UTILS.tostringLL = function( lat, lon, acc, DMS ) lonHemi = 'W' end - lat = math.abs( lat ) - lon = math.abs( lon ) + lat = math.abs(lat) + lon = math.abs(lon) - local latDeg = math.floor( lat ) - local latMin = (lat - latDeg) * 60 + local latDeg = math.floor(lat) + local latMin = (lat - latDeg)*60 - local lonDeg = math.floor( lon ) - local lonMin = (lon - lonDeg) * 60 + local lonDeg = math.floor(lon) + local lonMin = (lon - lonDeg)*60 - if DMS then -- degrees, minutes, and seconds. + if DMS then -- degrees, minutes, and seconds. local oldLatMin = latMin - latMin = math.floor( latMin ) - local latSec = UTILS.Round( (oldLatMin - latMin) * 60, acc ) + 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 ) + lonMin = math.floor(lonMin) + local lonSec = UTILS.Round((oldLonMin - lonMin)*60, acc) if latSec == 60 then latSec = 0 @@ -556,19 +561,20 @@ UTILS.tostringLL = function( lat, lon, acc, DMS ) local secFrmtStr -- create the formatting string for the seconds place secFrmtStr = '%02d' - if acc <= 0 then -- no decimal place. + if acc <= 0 then -- no decimal place. secFrmtStr = '%02d' else - local width = 3 + acc -- 01.310 - that's a width of 6, for example. Acc is limited to 2 for DMS! + local width = 3 + acc -- 01.310 - that's a width of 6, for example. Acc is limited to 2 for DMS! secFrmtStr = '%0' .. width .. '.' .. acc .. 'f' end -- 024° 23' 12"N or 024° 23' 12.03"N - 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 + 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 -- degrees, decimal minutes. - latMin = UTILS.Round( latMin, acc ) - lonMin = UTILS.Round( lonMin, acc ) + else -- degrees, decimal minutes. + latMin = UTILS.Round(latMin, acc) + lonMin = UTILS.Round(lonMin, acc) if latMin == 60 then latMin = 0 @@ -581,56 +587,54 @@ UTILS.tostringLL = function( lat, lon, acc, DMS ) end local minFrmtStr -- create the formatting string for the minutes place - if acc <= 0 then -- no decimal place. + if acc <= 0 then -- no decimal place. minFrmtStr = '%02d' else - local width = 3 + acc -- 01.310 - that's a width of 6, for example. + local width = 3 + acc -- 01.310 - that's a width of 6, for example. minFrmtStr = '%0' .. width .. '.' .. acc .. 'f' end -- 024 23'N or 024 23.123'N - return string.format( '%03d°', latDeg ) .. ' ' .. string.format( minFrmtStr, latMin ) .. '\'' .. latHemi .. ' ' .. string.format( '%03d°', lonDeg ) .. ' ' .. string.format( minFrmtStr, lonMin ) .. '\'' .. lonHemi + return string.format('%03d°', latDeg) .. ' ' .. string.format(minFrmtStr, latMin) .. '\'' .. latHemi .. ' ' + .. string.format('%03d°', lonDeg) .. ' ' .. string.format(minFrmtStr, lonMin) .. '\'' .. lonHemi end end -- acc- the accuracy of each easting/northing. 0, 1, 2, 3, 4, or 5. -UTILS.tostringMGRS = function( MGRS, acc ) -- R2.1 +UTILS.tostringMGRS = function(MGRS, acc) --R2.1 if acc == 0 then return MGRS.UTMZone .. ' ' .. MGRS.MGRSDigraph else -- Test if Easting/Northing have less than 4 digits. - -- MGRS.Easting=123 -- should be 00123 - -- MGRS.Northing=5432 -- should be 05432 + --MGRS.Easting=123 -- should be 00123 + --MGRS.Northing=5432 -- should be 05432 -- Truncate rather than round MGRS grid! - local Easting = tostring( MGRS.Easting ) - local Northing = tostring( MGRS.Northing ) + local Easting=tostring(MGRS.Easting) + local Northing=tostring(MGRS.Northing) -- Count number of missing digits. Easting/Northing should have 5 digits. However, it is passed as a number. Therefore, any leading zeros would not be displayed by lua. - local nE = 5 - string.len( Easting ) - local nN = 5 - string.len( Northing ) + local nE=5-string.len(Easting) + local nN=5-string.len(Northing) -- Get leading zeros (if any). - for i = 1, nE do - Easting = "0" .. Easting - end - for i = 1, nN do - Northing = "0" .. Northing - end + for i=1,nE do Easting="0"..Easting end + for i=1,nN do Northing="0"..Northing end -- Return MGRS string. - return string.format( "%s %s %s %s", MGRS.UTMZone, MGRS.MGRSDigraph, string.sub( Easting, 1, acc ), string.sub( Northing, 1, acc ) ) + return string.format("%s %s %s %s", MGRS.UTMZone, MGRS.MGRSDigraph, string.sub(Easting, 1, acc), string.sub(Northing, 1, acc)) end end + --- From http://lua-users.org/wiki/SimpleRound -- use negative idp for rounding ahead of decimal place, positive for rounding after decimal place function UTILS.Round( num, idp ) - local mult = 10 ^ (idp or 0) + local mult = 10 ^ ( idp or 0 ) return math.floor( num * mult + 0.5 ) / mult end @@ -646,87 +650,77 @@ end -- Here is a customized version of pairs, which I called spairs because it iterates over the table in a sorted order. function UTILS.spairs( t, order ) - -- collect the keys - local keys = {} - for k in pairs( t ) do - keys[#keys + 1] = k - end + -- collect the keys + local keys = {} + for k in pairs(t) do keys[#keys+1] = k end - -- if order function given, sort by it by passing the table and keys a, b, - -- otherwise just sort the keys - if order then - table.sort( keys, function( a, b ) - return order( t, a, b ) - end ) - else - table.sort( keys ) - end - - -- return the iterator function - local i = 0 - return function() - i = i + 1 - if keys[i] then - return keys[i], t[keys[i]] + -- if order function given, sort by it by passing the table and keys a, b, + -- otherwise just sort the keys + if order then + table.sort(keys, function(a,b) return order(t, a, b) end) + else + table.sort(keys) + end + + -- return the iterator function + local i = 0 + return function() + i = i + 1 + if keys[i] then + return keys[i], t[keys[i]] + end end - end end + -- Here is a customized version of pairs, which I called kpairs because it iterates over the table in a sorted order, based on a function that will determine the keys as reference first. function UTILS.kpairs( t, getkey, order ) - -- collect the keys - local keys = {} - local keyso = {} - for k, o in pairs( t ) do - keys[#keys + 1] = k - keyso[#keyso + 1] = getkey( o ) - end + -- collect the keys + local keys = {} + local keyso = {} + for k, o in pairs(t) do keys[#keys+1] = k keyso[#keyso+1] = getkey( o ) end - -- if order function given, sort by it by passing the table and keys a, b, - -- otherwise just sort the keys - if order then - table.sort( keys, function( a, b ) - return order( t, a, b ) - end ) - else - table.sort( keys ) - end - - -- return the iterator function - local i = 0 - return function() - i = i + 1 - if keys[i] then - return keyso[i], t[keys[i]] + -- if order function given, sort by it by passing the table and keys a, b, + -- otherwise just sort the keys + if order then + table.sort(keys, function(a,b) return order(t, a, b) end) + else + table.sort(keys) + end + + -- return the iterator function + local i = 0 + return function() + i = i + 1 + if keys[i] then + return keyso[i], t[keys[i]] + end end - end end -- Here is a customized version of pairs, which I called rpairs because it iterates over the table in a random order. function UTILS.rpairs( t ) - -- collect the keys + -- collect the keys - local keys = {} - for k in pairs( t ) do - keys[#keys + 1] = k - end + 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 - - -- return the iterator function - local i = 0 - return function() - i = i + 1 - if random[i] then - return random[i], t[random[i]] + 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 + + -- return the iterator function + local i = 0 + return function() + i = i + 1 + if random[i] then + return random[i], t[random[i]] + end end - end end -- get a new mark ID for markings @@ -740,18 +734,19 @@ end --- Remove an object (marker, circle, arrow, text, quad, ...) on the F10 map. -- @param #number MarkID Unique ID of the object. -- @param #number Delay (Optional) Delay in seconds before the mark is removed. -function UTILS.RemoveMark( MarkID, Delay ) - if Delay and Delay > 0 then - TIMER:New( UTILS.RemoveMark, MarkID ):Start( Delay ) +function UTILS.RemoveMark(MarkID, Delay) + if Delay and Delay>0 then + TIMER:New(UTILS.RemoveMark, MarkID):Start(Delay) else - trigger.action.removeMark( MarkID ) + trigger.action.removeMark(MarkID) end end + -- Test if a Vec2 is in a radius of another Vec2 function UTILS.IsInRadius( InVec2, Vec2, Radius ) - local InRadius = ((InVec2.x - Vec2.x) ^ 2 + (InVec2.y - Vec2.y) ^ 2) ^ 0.5 <= Radius + local InRadius = ( ( InVec2.x - Vec2.x ) ^2 + ( InVec2.y - Vec2.y ) ^2 ) ^ 0.5 <= Radius return InRadius end @@ -759,7 +754,7 @@ end -- Test if a Vec3 is in the sphere of another Vec3 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 + local InSphere = ( ( InVec3.x - Vec3.x ) ^2 + ( InVec3.y - Vec3.y ) ^2 + ( InVec3.z - Vec3.z ) ^2 ) ^ 0.5 <= Radius return InSphere end @@ -768,61 +763,61 @@ end -- @param #number speed Wind speed in m/s. -- @return #number Beaufort number. -- @return #string Beauford wind description. -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" +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" + bn=12 + bd="Hurricane" end - return bn, bd + return bn,bd end ---- Split string at separators. C.f. http://stackoverflow.com/questions/1426954/split-string-in-lua +--- Split string at seperators. C.f. http://stackoverflow.com/questions/1426954/split-string-in-lua -- @param #string str Sting to split. --- @param #string sep Separator for split. +-- @param #string sep Speparator for split. -- @return #table Split text. -function UTILS.Split( str, sep ) +function UTILS.Split(str, sep) local result = {} - local regex = ("([^%s]+)"):format( sep ) - for each in str:gmatch( regex ) do - table.insert( result, each ) + local regex = ("([^%s]+)"):format(sep) + for each in str:gmatch(regex) do + table.insert(result, each) end return result end @@ -830,13 +825,13 @@ end --- Get a table of all characters in a string. -- @param #string str Sting. -- @return #table Individual characters. -function UTILS.GetCharacters( str ) +function UTILS.GetCharacters(str) - local chars = {} + local chars={} - for i = 1, #str do - local c = str:sub( i, i ) - table.insert( chars, c ) + for i=1,#str do + local c=str:sub(i,i) + table.insert(chars, c) end return chars @@ -846,33 +841,33 @@ end -- @param #number seconds Time in seconds, e.g. from timer.getAbsTime() function. -- @param #boolean short (Optional) If true, use short output, i.e. (HH:)MM:SS without day. -- @return #string Time in format Hours:Minutes:Seconds+Days (HH:MM:SS+D). -function UTILS.SecondsToClock( seconds, short ) +function UTILS.SecondsToClock(seconds, short) -- Nil check. - if seconds == nil then + if seconds==nil then return nil end -- Seconds - local seconds = tonumber( seconds ) + local seconds = tonumber(seconds) -- Seconds of this day. - local _seconds = seconds % (60 * 60 * 24) + local _seconds=seconds%(60*60*24) - if seconds < 0 then + 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 + 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=mins..":"..secs - clock = hours .. ":" .. mins .. ":" .. secs + if hours=="00" then + --clock=mins..":"..secs + clock=hours..":"..mins..":"..secs else - clock = hours .. ":" .. mins .. ":" .. secs + clock=hours..":"..mins..":"..secs end end return clock @@ -883,60 +878,60 @@ end -- @return #number Seconds passed since last midnight. function UTILS.SecondsOfToday() - -- Time in seconds. - local time = timer.getAbsTime() + -- Time in seconds. + local time=timer.getAbsTime() - -- Short format without days since mission start. - local clock = UTILS.SecondsToClock( time, true ) + -- Short format without days since mission start. + local clock=UTILS.SecondsToClock(time, true) - -- Time is now the seconds passed since last midnight. - return UTILS.ClockToSeconds( clock ) + -- Time is now the seconds passed since last midnight. + return UTILS.ClockToSeconds(clock) end ---- Count seconds until next midnight. +--- Cound seconds until next midnight. -- @return #number Seconds to midnight. function UTILS.SecondsToMidnight() - return 24 * 60 * 60 - UTILS.SecondsOfToday() + return 24*60*60-UTILS.SecondsOfToday() end --- Convert clock time from hours, minutes and seconds to seconds. -- @param #string clock String of clock time. E.g., "06:12:35" or "5:1:30+1". Format is (H)H:(M)M:((S)S)(+D) H=Hours, M=Minutes, S=Seconds, D=Days. -- @return #number Seconds. Corresponds to what you cet from timer.getAbsTime() function. -function UTILS.ClockToSeconds( clock ) +function UTILS.ClockToSeconds(clock) -- Nil check. - if clock == nil then + if clock==nil then return nil end -- Seconds init. - local seconds = 0 + local seconds=0 -- Split additional days. - local dsplit = UTILS.Split( clock, "+" ) + local dsplit=UTILS.Split(clock, "+") -- Convert days to seconds. - if #dsplit > 1 then - seconds = seconds + tonumber( dsplit[2] ) * 60 * 60 * 24 + if #dsplit>1 then + seconds=seconds+tonumber(dsplit[2])*60*60*24 end -- Split hours, minutes, seconds - local tsplit = UTILS.Split( dsplit[1], ":" ) + local tsplit=UTILS.Split(dsplit[1], ":") -- Get time in seconds - local i = 1 - for _, time in ipairs( tsplit ) do - if i == 1 then + local i=1 + for _,time in ipairs(tsplit) do + if i==1 then -- Hours - seconds = seconds + tonumber( time ) * 60 * 60 - elseif i == 2 then + seconds=seconds+tonumber(time)*60*60 + elseif i==2 then -- Minutes - seconds = seconds + tonumber( time ) * 60 - elseif i == 3 then + seconds=seconds+tonumber(time)*60 + elseif i==3 then -- Seconds - seconds = seconds + tonumber( time ) + seconds=seconds+tonumber(time) end - i = i + 1 + i=i+1 end return seconds @@ -944,24 +939,24 @@ end --- Display clock and mission time on screen as a message to all. -- @param #number duration Duration in seconds how long the time is displayed. Default is 5 seconds. -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() +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 --- Replace illegal characters [<>|/?*:\\] in a string. -- @param #string Text Input text. -- @param #string ReplaceBy Replace illegal characters by this character or string. Default underscore "_". -- @return #string The input text with illegal chars replaced. -function UTILS.ReplaceIllegalCharacters( Text, ReplaceBy ) - ReplaceBy = ReplaceBy or "_" - local text = Text:gsub( "[<>|/?*:\\]", ReplaceBy ) +function UTILS.ReplaceIllegalCharacters(Text, ReplaceBy) + ReplaceBy=ReplaceBy or "_" + local text=Text:gsub("[<>|/?*:\\]", ReplaceBy) return text end @@ -972,29 +967,29 @@ end -- @param #number xmax (Optional) Upper cut-off value. -- @param #number imax (Optional) Max number of tries to get a value between xmin and xmax (if specified). Default 100. -- @return #number Gaussian random number. -function UTILS.RandomGaussian( x0, sigma, xmin, xmax, imax ) +function UTILS.RandomGaussian(x0, sigma, xmin, xmax, imax) -- Standard deviation. Default 10 if not given. - sigma = sigma or 10 + sigma=sigma or 10 -- Max attempts. - imax = imax or 100 + imax=imax or 100 local r - local gotit = false - local i = 0 + local gotit=false + local i=0 while not gotit do -- Uniform numbers in [0,1). We need two. - local x1 = math.random() - local x2 = math.random() + local x1=math.random() + local x2=math.random() -- Transform to Gaussian exp(-(x-x0)°/(2*sigma°). - r = math.sqrt( -2 * sigma * sigma * math.log( x1 ) ) * math.cos( 2 * math.pi * x2 ) + x0 + 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 + i=i+1 + if (r>=xmin and r<=xmax) or i>imax then + gotit=true end end @@ -1009,21 +1004,21 @@ end -- @return #number Randomized value. -- @usage UTILS.Randomize(100, 0.1) returns a value between 90 and 110, i.e. a plus/minus ten percent variation. -- @usage UTILS.Randomize(100, 0.5, nil, 120) returns a value between 50 and 120, i.e. a plus/minus fivty percent variation with upper bound 120. -function UTILS.Randomize( value, fac, lower, upper ) +function UTILS.Randomize(value, fac, lower, upper) local min if lower then - min = math.max( value - value * fac, lower ) + min=math.max(value-value*fac, lower) else - min = value - value * fac + min=value-value*fac end local max if upper then - max = math.min( value + value * fac, upper ) + max=math.min(value+value*fac, upper) else - max = value + value * fac + max=value+value*fac end - local r = math.random( min, max ) + local r=math.random(min, max) return r end @@ -1032,54 +1027,56 @@ end -- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @param DCS#Vec3 b Vector in 3D with x, y, z components. -- @return #number Scalar product of the two vectors a*b. -function UTILS.VecDot( a, b ) - return a.x * b.x + a.y * b.y + a.z * b.z +function UTILS.VecDot(a, b) + return a.x*b.x + a.y*b.y + a.z*b.z end --- Calculate the [dot product](https://en.wikipedia.org/wiki/Dot_product) of two 2D vectors. The result is a number. -- @param DCS#Vec2 a Vector in 2D with x, y components. -- @param DCS#Vec2 b Vector in 2D with x, y components. -- @return #number Scalar product of the two vectors a*b. -function UTILS.Vec2Dot( a, b ) - return a.x * b.x + a.y * b.y +function UTILS.Vec2Dot(a, b) + return a.x*b.x + a.y*b.y end + --- Calculate the [euclidean norm](https://en.wikipedia.org/wiki/Euclidean_distance) (length) of a 3D vector. -- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @return #number Norm of the vector. -function UTILS.VecNorm( a ) - return math.sqrt( UTILS.VecDot( a, a ) ) +function UTILS.VecNorm(a) + return math.sqrt(UTILS.VecDot(a, a)) end --- Calculate the [euclidean norm](https://en.wikipedia.org/wiki/Euclidean_distance) (length) of a 2D vector. -- @param DCS#Vec2 a Vector in 2D with x, y components. -- @return #number Norm of the vector. -function UTILS.Vec2Norm( a ) - return math.sqrt( UTILS.Vec2Dot( a, a ) ) +function UTILS.Vec2Norm(a) + return math.sqrt(UTILS.Vec2Dot(a, a)) end --- Calculate the distance between two 2D vectors. -- @param DCS#Vec2 a Vector in 3D with x, y components. -- @param DCS#Vec2 b Vector in 3D with x, y components. -- @return #number Distance between the vectors. -function UTILS.VecDist2D( a, b ) +function UTILS.VecDist2D(a, b) - local c = { x = b.x - a.x, y = b.y - a.y } + local c={x=b.x-a.x, y=b.y-a.y} - local d = math.sqrt( c.x * c.x + c.y * c.y ) + local d=math.sqrt(c.x*c.x+c.y*c.y) return d end + --- Calculate the distance between two 3D vectors. -- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @param DCS#Vec3 b Vector in 3D with x, y, z components. -- @return #number Distance between the vectors. -function UTILS.VecDist3D( a, b ) +function UTILS.VecDist3D(a, b) - local c = { x = b.x - a.x, y = b.y - a.y, z = b.z - a.z } + 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 ) ) + local d=math.sqrt(UTILS.VecDot(c, c)) return d end @@ -1088,53 +1085,69 @@ end -- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @param DCS#Vec3 b Vector in 3D with x, y, z components. -- @return DCS#Vec3 Vector -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 } +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 --- Calculate the difference between two 3D vectors by substracting the x,y,z components from each other. -- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @param DCS#Vec3 b Vector in 3D with x, y, z components. -- @return DCS#Vec3 Vector c=a-b with c(i)=a(i)-b(i), i=x,y,z. -function UTILS.VecSubstract( a, b ) - return { x = a.x - b.x, y = a.y - b.y, z = a.z - b.z } +function UTILS.VecSubstract(a, b) + return {x=a.x-b.x, y=a.y-b.y, z=a.z-b.z} +end + +--- Calculate the difference between two 2D vectors by substracting the x,y components from each other. +-- @param DCS#Vec2 a Vector in 2D with x, y components. +-- @param DCS#Vec2 b Vector in 2D with x, y components. +-- @return DCS#Vec2 Vector c=a-b with c(i)=a(i)-b(i), i=x,y. +function UTILS.Vec2Substract(a, b) + return {x=a.x-b.x, y=a.y-b.y} end --- Calculate the total vector of two 3D vectors by adding the x,y,z components of each other. -- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @param DCS#Vec3 b Vector in 3D with x, y, z components. -- @return DCS#Vec3 Vector c=a+b with c(i)=a(i)+b(i), i=x,y,z. -function UTILS.VecAdd( a, b ) - return { x = a.x + b.x, y = a.y + b.y, z = a.z + b.z } +function UTILS.VecAdd(a, b) + return {x=a.x+b.x, y=a.y+b.y, z=a.z+b.z} +end + +--- Calculate the total vector of two 2D vectors by adding the x,y components of each other. +-- @param DCS#Vec2 a Vector in 2D with x, y components. +-- @param DCS#Vec2 b Vector in 2D with x, y components. +-- @return DCS#Vec2 Vector c=a+b with c(i)=a(i)+b(i), i=x,y. +function UTILS.Vec2Add(a, b) + return {x=a.x+b.x, y=a.y+b.y} end --- Calculate the angle between two 3D vectors. -- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @param DCS#Vec3 b Vector in 3D with x, y, z components. -- @return #number Angle alpha between and b in degrees. alpha=acos(a*b)/(|a||b|), (* denotes the dot product). -function UTILS.VecAngle( a, b ) +function UTILS.VecAngle(a, b) - local cosalpha = UTILS.VecDot( a, b ) / (UTILS.VecNorm( a ) * UTILS.VecNorm( b )) + local cosalpha=UTILS.VecDot(a,b)/(UTILS.VecNorm(a)*UTILS.VecNorm(b)) - local alpha = 0 - if cosalpha >= 0.9999999999 then -- acos(1) is not defined. - alpha = 0 - elseif cosalpha <= -0.999999999 then -- acos(-1) is not defined. - alpha = math.pi + local alpha=0 + if cosalpha>=0.9999999999 then --acos(1) is not defined. + alpha=0 + elseif cosalpha<=-0.999999999 then --acos(-1) is not defined. + alpha=math.pi else - alpha = math.acos( cosalpha ) + alpha=math.acos(cosalpha) end - return math.deg( alpha ) + return math.deg(alpha) end --- Calculate "heading" of a 3D vector in the X-Z plane. -- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @return #number Heading in degrees in [0,360). -function UTILS.VecHdg( a ) - local h = math.deg( math.atan2( a.z, a.x ) ) - if h < 0 then - h = h + 360 +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 @@ -1142,10 +1155,10 @@ end --- Calculate "heading" of a 2D vector in the X-Y plane. -- @param DCS#Vec2 a Vector in "D with x, y components. -- @return #number Heading in degrees in [0,360). -function UTILS.Vec2Hdg( a ) - local h = math.deg( math.atan2( a.y, a.x ) ) - if h < 0 then - h = h + 360 +function UTILS.Vec2Hdg(a) + local h=math.deg(math.atan2(a.y, a.x)) + if h<0 then + h=h+360 end return h end @@ -1154,35 +1167,36 @@ end -- @param #number h1 Heading one. -- @param #number h2 Heading two. -- @return #number Heading difference in degrees. -function UTILS.HdgDiff( h1, h2 ) +function UTILS.HdgDiff(h1, h2) -- Angle in rad. - local alpha = math.rad( tonumber( h1 ) ) - local beta = math.rad( tonumber( h2 ) ) + local alpha= math.rad(tonumber(h1)) + local beta = math.rad(tonumber(h2)) -- Runway vector. - 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 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 ) + local delta=UTILS.VecAngle(v1, v2) - return math.abs( delta ) + return math.abs(delta) end + --- Translate 3D vector in the 2D (x,z) plane. y-component (usually altitude) unchanged. -- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @param #number distance The distance to translate. -- @param #number angle Rotation angle in degrees. -- @return DCS#Vec3 Vector rotated in the (x,z) plane. -function UTILS.VecTranslate( a, distance, angle ) +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 + 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 } + return {x=TX, y=a.y, z=TY} end --- Translate 2D vector in the 2D (x,z) plane. @@ -1190,33 +1204,33 @@ end -- @param #number distance The distance to translate. -- @param #number angle Rotation angle in degrees. -- @return DCS#Vec2 Translated vector. -function UTILS.Vec2Translate( a, distance, angle ) +function UTILS.Vec2Translate(a, distance, angle) local SX = a.x local SY = a.y - local Radians = math.rad( angle or 0 ) - local TX = distance * math.cos( Radians ) + SX - local TY = distance * math.sin( Radians ) + SY + local Radians=math.rad(angle or 0) + local TX=distance*math.cos(Radians)+SX + local TY=distance*math.sin(Radians)+SY - return { x = TX, y = TY } + return {x=TX, y=TY} end --- Rotate 3D vector in the 2D (x,z) plane. y-component (usually altitude) unchanged. -- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @param #number angle Rotation angle in degrees. -- @return DCS#Vec3 Vector rotated in the (x,z) plane. -function UTILS.Rotate2D( a, angle ) +function UTILS.Rotate2D(a, angle) - local phi = math.rad( angle ) + local phi=math.rad(angle) - local x = a.z - local y = a.x + 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 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 } + local A={x=X, y=Y, z=Z} return A end @@ -1225,38 +1239,39 @@ end -- @param DCS#Vec2 a Vector in 2D with x, y components. -- @param #number angle Rotation angle in degrees. -- @return DCS#Vec2 Vector rotated in the (x,y) plane. -function UTILS.Vec2Rotate2D( a, angle ) +function UTILS.Vec2Rotate2D(a, angle) - local phi = math.rad( angle ) + local phi=math.rad(angle) - local x = a.x - local y = a.y + local x=a.x + local y=a.y - local X = x * math.cos( phi ) - y * math.sin( phi ) - local Y = x * math.sin( phi ) + y * math.cos( phi ) + local X=x*math.cos(phi)-y*math.sin(phi) + local Y=x*math.sin(phi)+y*math.cos(phi) - local A = { x = X, y = Y } + local A={x=X, y=Y} return A end + --- Converts a TACAN Channel/Mode couple into a frequency in Hz. -- @param #number TACANChannel The TACAN channel, i.e. the 10 in "10X". -- @param #string TACANMode The TACAN mode, i.e. the "X" in "10X". -- @return #number Frequency in Hz or #nil if parameters are invalid. -function UTILS.TACANToFrequency( TACANChannel, TACANMode ) +function UTILS.TACANToFrequency(TACANChannel, TACANMode) - if type( TACANChannel ) ~= "number" then + if type(TACANChannel) ~= "number" then return nil -- error in arguments end if TACANMode ~= "X" and TACANMode ~= "Y" then return nil -- error in arguments end - -- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137. - -- I have no idea what it does but it seems to work +-- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137. +-- I have no idea what it does but it seems to work local A = 1151 -- 'X', channel >= 64 - local B = 64 -- channel >= 64 + local B = 64 -- channel >= 64 if TACANChannel < 64 then B = 1 @@ -1276,6 +1291,7 @@ function UTILS.TACANToFrequency( TACANChannel, TACANMode ) return (A + TACANChannel - B) * 1000000 end + --- Returns the DCS map/theatre as optained by env.mission.theatre -- @return #string DCS map name. function UTILS.GetDCSMap() @@ -1288,22 +1304,22 @@ end -- @return #number The month. -- @return #number The day. 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 ) + 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 --- Returns the day of the mission. -- @param #number Time (Optional) Abs. time in seconds. Default now, i.e. the value return from timer.getAbsTime(). -- @return #number Day of the mission. Mission starts on day 0. -function UTILS.GetMissionDay( Time ) +function UTILS.GetMissionDay(Time) - Time = Time or timer.getAbsTime() + Time=Time or timer.getAbsTime() - local clock = UTILS.SecondsToClock( Time, false ) + local clock=UTILS.SecondsToClock(Time, false) - local x = tonumber( UTILS.Split( clock, "+" )[2] ) + local x=tonumber(UTILS.Split(clock, "+")[2]) return x end @@ -1311,33 +1327,13 @@ end --- Returns the current day of the year of the mission. -- @param #number Time (Optional) Abs. time in seconds. Default now, i.e. the value return from timer.getAbsTime(). -- @return #number Current day of year of the mission. For example, January 1st returns 0, January 2nd returns 1 etc. -function UTILS.GetMissionDayOfYear( Time ) +function UTILS.GetMissionDayOfYear(Time) - local Date, Year, Month, Day = UTILS.GetDCSMissionDate() + local Date, Year, Month, Day=UTILS.GetDCSMissionDate() - local d = UTILS.GetMissionDay( Time ) + local d=UTILS.GetMissionDay(Time) - return UTILS.GetDayOfYear( Year, Month, Day ) + d - -end - ---- Returns the current date. --- @return #string Mission date in yyyy/mm/dd format. --- @return #number The year anno domini. --- @return #number The month. --- @return #number The day. -function UTILS.GetDate() - - -- Mission start date - 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 + return UTILS.GetDayOfYear(Year, Month, Day)+d end @@ -1353,28 +1349,28 @@ end -- * Mariana Islands +2 (East) -- @param #string map (Optional) Map for which the declination is returned. Default is from env.mission.theatre -- @return #number Declination in degrees. -function UTILS.GetMagneticDeclination( map ) +function UTILS.GetMagneticDeclination(map) -- Map. - map = map or UTILS.GetDCSMap() + map=map or UTILS.GetDCSMap() - local declination = 0 - if map == DCSMAP.Caucasus then - declination = 6 - elseif map == DCSMAP.NTTR then - declination = 12 - elseif map == DCSMAP.Normandy then - declination = -10 - elseif map == DCSMAP.PersianGulf then - declination = 2 - elseif map == DCSMAP.TheChannel then - declination = -10 - elseif map == DCSMAP.Syria then - declination = 5 - elseif map == DCSMAP.MarianaIslands then - declination = 2 + local declination=0 + if map==DCSMAP.Caucasus then + declination=6 + elseif map==DCSMAP.NTTR then + declination=12 + elseif map==DCSMAP.Normandy then + declination=-10 + elseif map==DCSMAP.PersianGulf then + declination=2 + elseif map==DCSMAP.TheChannel then + declination=-10 + elseif map==DCSMAP.Syria then + declination=5 + elseif map==DCSMAP.MarianaIslands then + declination=2 else - declination = 0 + declination=0 end return declination @@ -1383,11 +1379,11 @@ end --- Checks if a file exists or not. This requires **io** to be desanitized. -- @param #string file File that should be checked. -- @return #boolean True if the file exists, false if the file does not exist or nil if the io module is not available and the check could not be performed. -function UTILS.FileExists( file ) +function UTILS.FileExists(file) if io then - local f = io.open( file, "r" ) - if f ~= nil then - io.close( f ) + local f=io.open(file, "r") + if f~=nil then + io.close(f) return true else return false @@ -1400,27 +1396,28 @@ end --- Checks the current memory usage collectgarbage("count"). Info is printed to the DCS log file. Time stamp is the current mission runtime. -- @param #boolean output If true, print to DCS log file. -- @return #number Memory usage in kByte. -function UTILS.CheckMemory( output ) - local time = timer.getTime() - local clock = UTILS.SecondsToClock( time ) - local mem = collectgarbage( "count" ) +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 ) ) + env.info(string.format("T=%s Memory usage %d kByte = %.2f MByte", clock, mem, mem/1024)) end return mem end ---- Get the coalition name from its numerical ID, e.g. coalition.side.RED. + +--- Get the coalition name from its numerical ID, e.g. coaliton.side.RED. -- @param #number Coalition The coalition ID. -- @return #string The coalition name, i.e. "Neutral", "Red" or "Blue" (or "Unknown"). -function UTILS.GetCoalitionName( Coalition ) +function UTILS.GetCoalitionName(Coalition) if Coalition then - if Coalition == coalition.side.BLUE then + if Coalition==coalition.side.BLUE then return "Blue" - elseif Coalition == coalition.side.RED then + elseif Coalition==coalition.side.RED then return "Red" - elseif Coalition == coalition.side.NEUTRAL then + elseif Coalition==coalition.side.NEUTRAL then return "Neutral" else return "Unknown" @@ -1434,12 +1431,12 @@ end --- Get the modulation name from its numerical value. -- @param #number Modulation The modulation enumerator number. Can be either 0 or 1. -- @return #string The modulation name, i.e. "AM"=0 or "FM"=1. Anything else will return "Unknown". -function UTILS.GetModulationName( Modulation ) +function UTILS.GetModulationName(Modulation) if Modulation then - if Modulation == 0 then + if Modulation==0 then return "AM" - elseif Modulation == 1 then + elseif Modulation==1 then return "FM" else return "Unknown" @@ -1453,28 +1450,28 @@ end --- Get the callsign name from its enumerator value -- @param #number Callsign The enumerator callsign. -- @return #string The callsign name or "Ghostrider". -function UTILS.GetCallsignName( Callsign ) +function UTILS.GetCallsignName(Callsign) - for name, value in pairs( CALLSIGN.Aircraft ) do - if value == Callsign then + 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 + 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 + 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 + for name, value in pairs(CALLSIGN.Tanker) do + if value==Callsign then return name end end @@ -1486,43 +1483,44 @@ end -- @return #number Local time difference in hours compared to GMT. E.g. Dubai is GMT+4 ==> +4 is returned. function UTILS.GMTToLocalTimeDifference() - local theatre = UTILS.GetDCSMap() + local theatre=UTILS.GetDCSMap() - if theatre == DCSMAP.Caucasus then - return 4 -- Caucasus UTC+4 hours - elseif theatre == DCSMAP.PersianGulf then - return 4 -- Abu Dhabi UTC+4 hours - elseif theatre == DCSMAP.NTTR then - return -8 -- Las Vegas UTC-8 hours - elseif theatre == DCSMAP.Normandy then - return 0 -- Calais UTC+1 hour - elseif theatre == DCSMAP.TheChannel then - return 2 -- This map currently needs +2 - elseif theatre == DCSMAP.Syria then - return 3 -- Damascus is UTC+3 hours - elseif theatre == DCSMAP.MarianaIslands then - return 10 -- Guam is UTC+10 hours. + if theatre==DCSMAP.Caucasus then + return 4 -- Caucasus UTC+4 hours + elseif theatre==DCSMAP.PersianGulf then + return 4 -- Abu Dhabi UTC+4 hours + elseif theatre==DCSMAP.NTTR then + return -8 -- Las Vegas UTC-8 hours + elseif theatre==DCSMAP.Normandy then + return 0 -- Calais UTC+1 hour + elseif theatre==DCSMAP.TheChannel then + return 2 -- This map currently needs +2 + elseif theatre==DCSMAP.Syria then + return 3 -- Damascus is UTC+3 hours + elseif theatre==DCSMAP.MarianaIslands then + return 10 -- Guam is UTC+10 hours. else - BASE:E( string.format( "ERROR: Unknown Map %s in UTILS.GMTToLocal function. Returning 0", tostring( theatre ) ) ) + BASE:E(string.format("ERROR: Unknown Map %s in UTILS.GMTToLocal function. Returning 0", tostring(theatre))) return 0 end end + --- Get the day of the year. Counting starts on 1st of January. -- @param #number Year The year. -- @param #number Month The month. -- @param #number Day The day. -- @return #number The day of the year. -function UTILS.GetDayOfYear( Year, Month, Day ) +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 )) + 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 + return n1 - (n2 * n3) + Day - 30 end --- Get sunrise or sun set of a specific day of the year at a specific location. @@ -1532,113 +1530,100 @@ end -- @param #boolean Rising If true, calc sun rise, or sun set otherwise. -- @param #number Tlocal Local time offset in hours. E.g. +4 for a location which has GMT+4. -- @return #number Sun rise/set in seconds of the day. -function UTILS.GetSunRiseAndSet( DayOfYear, Latitude, Longitude, Rising, Tlocal ) +function UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, Rising, Tlocal) -- Defaults - local zenith = 90.83 - local latitude = Latitude - local longitude = Longitude - local rising = Rising - local n = DayOfYear - Tlocal = Tlocal or 0 + local zenith=90.83 + local latitude=Latitude + local longitude=Longitude + local rising=Rising + local n=DayOfYear + Tlocal=Tlocal or 0 + -- Short cuts. 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 ) ) + local frac = function(n) return n - floor(n) end + local cos = function(d) return math.cos(rad(d)) end + local acos = function(d) return deg(math.acos(d)) end + local sin = function(d) return math.sin(rad(d)) end + local asin = function(d) return deg(math.asin(d)) end + local tan = function(d) return math.tan(rad(d)) end + local atan = function(d) return deg(math.atan(d)) end + + local function fit_into_range(val, min, max) + local range = max - min + local count + if val < min then + count = floor((min - val) / range) + 1 + return val + count * range + elseif val >= max then + count = floor((val - max) / range) + 1 + return val - count * range + else + return val + end end - local function fit_into_range( val, min, max ) - local range = max - min - local count - if val < min then - count = floor( (min - val) / range ) + 1 - return val + count * range - elseif val >= max then - count = floor( (val - max) / range ) + 1 - return val - count * range - else - return val - end - end + -- Convert the longitude to hour value and calculate an approximate time + local lng_hour = longitude / 15 - -- Convert the longitude to hour value and calculate an approximate time - local lng_hour = longitude / 15 + local t + if rising then -- Rising time is desired + t = n + ((6 - lng_hour) / 24) + else -- Setting time is desired + t = n + ((18 - lng_hour) / 24) + end - local t - if rising then -- Rising time is desired - t = n + ((6 - lng_hour) / 24) - else -- Setting time is desired - t = n + ((18 - lng_hour) / 24) - end + -- Calculate the Sun's mean anomaly + local M = (0.9856 * t) - 3.289 - -- Calculate the Sun's mean anomaly - local M = (0.9856 * t) - 3.289 + -- Calculate the Sun's true longitude + local L = fit_into_range(M + (1.916 * sin(M)) + (0.020 * sin(2 * M)) + 282.634, 0, 360) - -- Calculate the Sun's true longitude - local L = fit_into_range( M + (1.916 * sin( M )) + (0.020 * sin( 2 * M )) + 282.634, 0, 360 ) + -- Calculate the Sun's right ascension + local RA = fit_into_range(atan(0.91764 * tan(L)), 0, 360) - -- Calculate the Sun's right ascension - local RA = fit_into_range( atan( 0.91764 * tan( L ) ), 0, 360 ) + -- Right ascension value needs to be in the same quadrant as L + local Lquadrant = floor(L / 90) * 90 + local RAquadrant = floor(RA / 90) * 90 + RA = RA + Lquadrant - RAquadrant - -- Right ascension value needs to be in the same quadrant as L - local Lquadrant = floor( L / 90 ) * 90 - local RAquadrant = floor( RA / 90 ) * 90 - RA = RA + Lquadrant - RAquadrant + -- Right ascension value needs to be converted into hours + RA = RA / 15 - -- Right ascension value needs to be converted into hours - RA = RA / 15 + -- Calculate the Sun's declination + local sinDec = 0.39782 * sin(L) + local cosDec = cos(asin(sinDec)) - -- Calculate the Sun's declination - local sinDec = 0.39782 * sin( L ) - local cosDec = cos( asin( sinDec ) ) + -- Calculate the Sun's local hour angle + local cosH = (cos(zenith) - (sinDec * sin(latitude))) / (cosDec * cos(latitude)) - -- Calculate the Sun's local hour angle - local cosH = (cos( zenith ) - (sinDec * sin( latitude ))) / (cosDec * cos( latitude )) + if rising and cosH > 1 then + return "N/R" -- The sun never rises on this location on the specified date + elseif cosH < -1 then + return "N/S" -- The sun never sets on this location on the specified date + end - if rising and cosH > 1 then - return "N/R" -- The sun never rises on this location on the specified date - elseif cosH < -1 then - return "N/S" -- The sun never sets on this location on the specified date - end + -- Finish calculating H and convert into hours + local H + if rising then + H = 360 - acos(cosH) + else + H = acos(cosH) + end + H = H / 15 - -- Finish calculating H and convert into hours - local H - if rising then - H = 360 - acos( cosH ) - else - H = acos( cosH ) - end - H = H / 15 + -- Calculate local mean time of rising/setting + local T = H + RA - (0.06571 * t) - 6.622 - -- Calculate local mean time of rising/setting - local T = H + RA - (0.06571 * t) - 6.622 + -- Adjust back to UTC + local UT = fit_into_range(T - lng_hour +Tlocal, 0, 24) - -- Adjust back to UTC - local UT = fit_into_range( T - lng_hour + Tlocal, 0, 24 ) - - return floor( UT ) * 60 * 60 + frac( UT ) * 60 * 60 -- +Tlocal*60*60 -end + return floor(UT)*60*60+frac(UT)*60*60--+Tlocal*60*60 + end --- Get sun rise of a specific day of the year at a specific location. -- @param #number Day Day of the year. @@ -1649,11 +1634,11 @@ end -- @param #boolean Rising If true, calc sun rise, or sun set otherwise. -- @param #number Tlocal Local time offset in hours. E.g. +4 for a location which has GMT+4. Default 0. -- @return #number Sun rise in seconds of the day. -function UTILS.GetSunrise( Day, Month, Year, Latitude, Longitude, Tlocal ) +function UTILS.GetSunrise(Day, Month, Year, Latitude, Longitude, Tlocal) - local DayOfYear = UTILS.GetDayOfYear( Year, Month, Day ) + local DayOfYear=UTILS.GetDayOfYear(Year, Month, Day) - return UTILS.GetSunRiseAndSet( DayOfYear, Latitude, Longitude, true, Tlocal ) + return UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, true, Tlocal) end --- Get sun set of a specific day of the year at a specific location. @@ -1665,11 +1650,11 @@ end -- @param #boolean Rising If true, calc sun rise, or sun set otherwise. -- @param #number Tlocal Local time offset in hours. E.g. +4 for a location which has GMT+4. Default 0. -- @return #number Sun rise in seconds of the day. -function UTILS.GetSunset( Day, Month, Year, Latitude, Longitude, Tlocal ) +function UTILS.GetSunset(Day, Month, Year, Latitude, Longitude, Tlocal) - local DayOfYear = UTILS.GetDayOfYear( Year, Month, Day ) + local DayOfYear=UTILS.GetDayOfYear(Year, Month, Day) - return UTILS.GetSunRiseAndSet( DayOfYear, Latitude, Longitude, false, Tlocal ) + return UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, false, Tlocal) end --- Get OS time. Needs os to be desanitized! @@ -1683,11 +1668,11 @@ function UTILS.GetOSTime() end --- Shuffle a table accoring to Fisher Yeates algorithm --- @param #table t Table to be shuffled --- @return #table -function UTILS.ShuffleTable( t ) - if t == nil or type( t ) ~= "table" then - BASE:I( "Error in ShuffleTable: Missing or wrong type of Argument" ) +--@param #table t Table to be shuffled +--@return #table +function UTILS.ShuffleTable(t) + if t == nil or type(t) ~= "table" then + BASE:I("Error in ShuffleTable: Missing or wrong type of Argument") return end math.random() @@ -1695,82 +1680,83 @@ function UTILS.ShuffleTable( t ) math.random() local TempTable = {} for i = 1, #t do - local r = math.random( 1, #t ) + local r = math.random(1,#t) TempTable[i] = t[r] - table.remove( t, r ) + table.remove(t,r) end return TempTable end --- (Helicopter) Check if one loading door is open. --- @param #string unit_name Unit name to be checked --- @return #boolean Outcome - true if a (loading door) is open, false if not, nil if none exists. +--@param #string unit_name Unit name to be checked +--@return #boolean Outcome - true if a (loading door) is open, false if not, nil if none exists. function UTILS.IsLoadingDoorOpen( unit_name ) local ret_val = false - local unit = Unit.getByName( unit_name ) + local unit = Unit.getByName(unit_name) if unit ~= nil then - local type_name = unit:getTypeName() + local type_name = unit:getTypeName() - if type_name == "Mi-8MT" and unit:getDrawArgumentValue( 38 ) == 1 or unit:getDrawArgumentValue( 86 ) == 1 or unit:getDrawArgumentValue( 250 ) < 0 then - BASE:T( unit_name .. " Cargo doors are open or cargo door not present" ) - ret_val = true - end + if type_name == "Mi-8MT" and unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(86) == 1 or unit:getDrawArgumentValue(250) < 0 then + BASE:T(unit_name .. " Cargo doors are open or cargo door not present") + ret_val = true + end - if type_name == "Mi-24P" and unit:getDrawArgumentValue( 38 ) == 1 or unit:getDrawArgumentValue( 86 ) == 1 then - BASE:T( unit_name .. " a side door is open" ) - ret_val = true - end + if type_name == "Mi-24P" and unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(86) == 1 then + BASE:T(unit_name .. " a side door is open") + ret_val = true + end - if type_name == "UH-1H" and unit:getDrawArgumentValue( 43 ) == 1 or unit:getDrawArgumentValue( 44 ) == 1 then - BASE:T( unit_name .. " a side door is open " ) - ret_val = true - end + if type_name == "UH-1H" and unit:getDrawArgumentValue(43) == 1 or unit:getDrawArgumentValue(44) == 1 then + BASE:T(unit_name .. " a side door is open ") + ret_val = true + end - if string.find( type_name, "SA342" ) and unit:getDrawArgumentValue( 34 ) == 1 or unit:getDrawArgumentValue( 38 ) == 1 then - BASE:T( unit_name .. " front door(s) are open" ) - ret_val = true - end + if string.find(type_name, "SA342" ) and unit:getDrawArgumentValue(34) == 1 or unit:getDrawArgumentValue(38) == 1 then + BASE:T(unit_name .. " front door(s) are open") + ret_val = true + end - if string.find( type_name, "Hercules" ) and unit:getDrawArgumentValue( 1215 ) == 1 and unit:getDrawArgumentValue( 1216 ) == 1 then - BASE:T( unit_name .. " rear doors are open" ) - ret_val = true - end + if string.find(type_name, "Hercules") and unit:getDrawArgumentValue(1215) == 1 and unit:getDrawArgumentValue(1216) == 1 then + BASE:T(unit_name .. " rear doors are open") + ret_val = true + end - if string.find( type_name, "Hercules" ) and (unit:getDrawArgumentValue( 1220 ) == 1 or unit:getDrawArgumentValue( 1221 ) == 1) then - BASE:T( unit_name .. " para doors are open" ) - ret_val = true - end + if string.find(type_name, "Hercules") and (unit:getDrawArgumentValue(1220) == 1 or unit:getDrawArgumentValue(1221) == 1) then + BASE:T(unit_name .. " para doors are open") + ret_val = true + end - if string.find( type_name, "Hercules" ) and unit:getDrawArgumentValue( 1217 ) == 1 then - BASE:T( unit_name .. " side door is open" ) - ret_val = true - end + if string.find(type_name, "Hercules") and unit:getDrawArgumentValue(1217) == 1 then + BASE:T(unit_name .. " side door is open") + ret_val = true + end - if string.find( type_name, "Bell-47" ) then -- bell aint got no doors so always ready to load injured soldiers - BASE:T( unit_name .. " door is open" ) - ret_val = true - end - - if string.find(type_name, "UH-60L") and (unit:getDrawArgumentValue(401) == 1) or (unit:getDrawArgumentValue(402) == 1) then - BASE:T(unit_name .. " cargo door is open") - ret_val = true - end + if string.find(type_name, "Bell-47") then -- bell aint got no doors so always ready to load injured soldiers + BASE:T(unit_name .. " door is open") + ret_val = true + end + + if string.find(type_name, "UH-60L") and (unit:getDrawArgumentValue(401) == 1) or (unit:getDrawArgumentValue(402) == 1) then + BASE:T(unit_name .. " cargo door is open") + ret_val = true + end - if string.find(type_name, "UH-60L" ) and unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(400) == 1 then - BASE:T(unit_name .. " front door(s) are open") - ret_val = true - end - - if string.find(type_name, "AH-64D") then + if string.find(type_name, "UH-60L" ) and unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(400) == 1 then + BASE:T(unit_name .. " front door(s) are open") + ret_val = true + end + + if string.find(type_name, "AH-64D") then BASE:T(unit_name .. " front door(s) are open") ret_val = true -- no doors on this one ;) end - if ret_val == false then - BASE:T( unit_name .. " all doors are closed" ) - end - return ret_val + if ret_val == false then + BASE:T(unit_name .. " all doors are closed") + end + + return ret_val end -- nil @@ -1780,16 +1766,16 @@ end --- Function to generate valid FM frequencies in mHz for radio beacons (FM). -- @return #table Table of frequencies. function UTILS.GenerateFMFrequencies() - local FreeFMFrequencies = {} - for _first = 3, 7 do - for _second = 0, 5 do - for _third = 0, 9 do - local _frequency = ((100 * _first) + (10 * _second) + _third) * 100000 -- extra 0 because we didnt bother with 4th digit - table.insert( FreeFMFrequencies, _frequency ) - end + local FreeFMFrequencies = {} + for _first = 3, 7 do + for _second = 0, 5 do + for _third = 0, 9 do + local _frequency = ((100 * _first) + (10 * _second) + _third) * 100000 --extra 0 because we didnt bother with 4th digit + table.insert(FreeFMFrequencies, _frequency) + end + end end - end - return FreeFMFrequencies + return FreeFMFrequencies end --- Function to generate valid VHF frequencies in kHz for radio beacons (FM). @@ -1798,73 +1784,73 @@ function UTILS.GenerateVHFrequencies() -- known and sorted map-wise NDBs in kHz local _skipFrequencies = { - 214,274,291.5,295,297.5, - 300.5,304,307,309.5,311,312,312.5,316, - 320,324,328,329,330,332,336,337, - 342,343,348,351,352,353,358, - 363,365,368,372.5,374, - 380,381,384,385,389,395,396, - 414,420,430,432,435,440,450,455,462,470,485, - 507,515,520,525,528,540,550,560,570,577,580, - 602,625,641,662,670,680,682,690, - 705,720,722,730,735,740,745,750,770,795, - 822,830,862,866, - 905,907,920,935,942,950,995, - 1000,1025,1030,1050,1065,1116,1175,1182,1210 + 214,274,291.5,295,297.5, + 300.5,304,307,309.5,311,312,312.5,316, + 320,324,328,329,330,332,336,337, + 342,343,348,351,352,353,358, + 363,365,368,372.5,374, + 380,381,384,385,389,395,396, + 414,420,430,432,435,440,450,455,462,470,485, + 507,515,520,525,528,540,550,560,570,577,580, + 602,625,641,662,670,680,682,690, + 705,720,722,730,735,740,745,750,770,795, + 822,830,862,866, + 905,907,920,935,942,950,995, + 1000,1025,1030,1050,1065,1116,1175,1182,1210 } local FreeVHFFrequencies = {} - -- first range + -- first range local _start = 200000 while _start < 400000 do - -- skip existing NDB frequencies# - local _found = false - for _, value in pairs( _skipFrequencies ) do - if value * 1000 == _start then - _found = true - break + -- skip existing NDB frequencies# + local _found = false + for _, value in pairs(_skipFrequencies) do + if value * 1000 == _start then + _found = true + break + end end - end - if _found == false then - table.insert( FreeVHFFrequencies, _start ) - end - _start = _start + 10000 + if _found == false then + table.insert(FreeVHFFrequencies, _start) + end + _start = _start + 10000 end - -- second range + -- second range _start = 400000 while _start < 850000 do - -- skip existing NDB frequencies - local _found = false - for _, value in pairs( _skipFrequencies ) do - if value * 1000 == _start then - _found = true - break + -- skip existing NDB frequencies + local _found = false + for _, value in pairs(_skipFrequencies) do + if value * 1000 == _start then + _found = true + break + end end - end - if _found == false then - table.insert( FreeVHFFrequencies, _start ) - end - _start = _start + 10000 + if _found == false then + table.insert(FreeVHFFrequencies, _start) + end + _start = _start + 10000 end -- third range _start = 850000 while _start <= 999000 do -- adjusted for Gazelle - -- skip existing NDB frequencies - local _found = false - for _, value in pairs( _skipFrequencies ) do - if value * 1000 == _start then - _found = true - break + -- skip existing NDB frequencies + local _found = false + for _, value in pairs(_skipFrequencies) do + if value * 1000 == _start then + _found = true + break + end end - end - if _found == false then - table.insert( FreeVHFFrequencies, _start ) - end - _start = _start + 50000 + if _found == false then + table.insert(FreeVHFFrequencies, _start) + end + _start = _start + 50000 end return FreeVHFFrequencies @@ -1874,50 +1860,52 @@ end -- @return #table UHF Frequencies function UTILS.GenerateUHFrequencies() - local FreeUHFFrequencies = {} - local _start = 220000000 + local FreeUHFFrequencies = {} + local _start = 220000000 - while _start < 399000000 do - table.insert( FreeUHFFrequencies, _start ) - _start = _start + 500000 - end + while _start < 399000000 do + table.insert(FreeUHFFrequencies, _start) + _start = _start + 500000 + end - return FreeUHFFrequencies + return FreeUHFFrequencies end --- Function to generate valid laser codes for JTAC. -- @return #table Laser Codes. function UTILS.GenerateLaserCodes() - local jtacGeneratedLaserCodes = {} + local jtacGeneratedLaserCodes = {} - -- helper function - 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 + -- helper function + 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 - return false - end - -- generate list of laser codes - local _code = 1111 - local _count = 1 - while _code < 1777 and _count < 30 do - while true do - _code = _code + 1 - if not ContainsDigit( _code, 8 ) and not ContainsDigit( _code, 9 ) and not ContainsDigit( _code, 0 ) then - table.insert( jtacGeneratedLaserCodes, _code ) - break - end + -- generate list of laser codes + local _code = 1111 + local _count = 1 + while _code < 1777 and _count < 30 do + while true do + _code = _code + 1 + if not ContainsDigit(_code, 8) + and not ContainsDigit(_code, 9) + and not ContainsDigit(_code, 0) then + table.insert(jtacGeneratedLaserCodes, _code) + break + end + end + _count = _count + 1 end - _count = _count + 1 - end - return jtacGeneratedLaserCodes + return jtacGeneratedLaserCodes end --- Function to save an object to a file @@ -1925,34 +1913,34 @@ end -- @param #string Filename The name of the file. Existing file will be overwritten. -- @param #table Data The LUA data structure to save. This will be e.g. a table of text lines with an \\n at the end of each line. -- @return #boolean outcome True if saving is possible, else false. -function UTILS.SaveToFile( Path, Filename, Data ) +function UTILS.SaveToFile(Path,Filename,Data) -- Thanks to @FunkyFranky -- Check io module is available. if not io then - BASE:E( "ERROR: io not desanitized. Can't save current file." ) + BASE:E("ERROR: io not desanitized. Can't save current file.") return false end - + -- Check default path. - if Path == nil and not lfs then - BASE:E( "WARNING: lfs not desanitized. File will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder." ) + if Path==nil and not lfs then + BASE:E("WARNING: lfs not desanitized. File will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") end - + -- Set path or default. local path = nil if lfs then - path = Path or lfs.writedir() + path=Path or lfs.writedir() end - + -- Set file name. - local filename = Filename - if path ~= nil then - filename = path .. "\\" .. filename + local filename=Filename + if path~=nil then + filename=path.."\\"..filename end - + -- write - local f = assert( io.open( filename, "wb" ) ) - f:write( Data ) + local f = assert(io.open(filename, "wb")) + f:write(Data) f:close() return true end @@ -1962,43 +1950,43 @@ end -- @param #string Filename The name of the file. -- @return #boolean outcome True if reading is possible and successful, else false. -- @return #table data The data read from the filesystem (table of lines of text). Each line is one single #string! -function UTILS.LoadFromFile( Path, Filename ) +function UTILS.LoadFromFile(Path,Filename) -- Thanks to @FunkyFranky -- Check io module is available. if not io then - BASE:E( "ERROR: io not desanitized. Can't save current state." ) + BASE:E("ERROR: io not desanitized. Can't save current state.") return false end - + -- Check default path. - if Path == nil and not lfs then - BASE:E( "WARNING: lfs not desanitized. Loading will look into your DCS installation root directory rather than your \"Saved Games\\DCS\" folder." ) + if Path==nil and not lfs then + BASE:E("WARNING: lfs not desanitized. Loading will look into your DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") end - + -- Set path or default. local path = nil if lfs then - path = Path or lfs.writedir() + path=Path or lfs.writedir() end - + -- Set file name. - local filename = Filename - if path ~= nil then - filename = path .. "\\" .. filename + local filename=Filename + if path~=nil then + filename=path.."\\"..filename end - + -- Check if file exists. - local exists = UTILS.CheckFileExists( Path, Filename ) + local exists=UTILS.CheckFileExists(Path,Filename) if not exists then - BASE:E( string.format( "ERROR: File %s does not exist!", filename ) ) + BASE:E(string.format("ERROR: File %s does not exist!",filename)) return false end - + -- read - local file = assert( io.open( filename, "rb" ) ) + local file=assert(io.open(filename, "rb")) local loadeddata = {} for line in file:lines() do - loadeddata[#loadeddata + 1] = line + loadeddata[#loadeddata+1] = line end file:close() return true, loadeddata @@ -2008,46 +1996,46 @@ end -- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems. -- @param #string Filename The name of the file. -- @return #boolean outcome True if reading is possible, else false. -function UTILS.CheckFileExists( Path, Filename ) +function UTILS.CheckFileExists(Path,Filename) -- Thanks to @FunkyFranky -- Function that check if a file exists. - local function _fileexists( name ) - local f = io.open( name, "r" ) - if f ~= nil then - io.close( f ) + local function _fileexists(name) + local f=io.open(name,"r") + if f~=nil then + io.close(f) return true else return false end end - + -- Check io module is available. if not io then - BASE:E( "ERROR: io not desanitized. Can't save current state." ) + BASE:E("ERROR: io not desanitized. Can't save current state.") return false end - + -- Check default path. - if Path == nil and not lfs then - BASE:E( "WARNING: lfs not desanitized. Loading will look into your DCS installation root directory rather than your \"Saved Games\\DCS\" folder." ) + if Path==nil and not lfs then + BASE:E("WARNING: lfs not desanitized. Loading will look into your DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") end - + -- Set path or default. local path = nil if lfs then - path = Path or lfs.writedir() + path=Path or lfs.writedir() end - + -- Set file name. - local filename = Filename - if path ~= nil then - filename = path .. "\\" .. filename + local filename=Filename + if path~=nil then + filename=path.."\\"..filename end - + -- Check if file exists. - local exists = _fileexists( filename ) + local exists=_fileexists(filename) if not exists then - BASE:E( string.format( "ERROR: File %s does not exist!", filename ) ) + BASE:E(string.format("ERROR: File %s does not exist!",filename)) return false else return true @@ -2065,21 +2053,21 @@ end -- Position is still saved for your usage. -- The idea is to reduce the number of units when reloading the data again to restart the saved mission. -- The data will be a simple comma separated list of groupname and size, with one header line. -function UTILS.SaveStationaryListOfGroups( List, Path, Filename ) +function UTILS.SaveStationaryListOfGroups(List,Path,Filename) local filename = Filename or "StateListofGroups" - local data = "--Save Stationary List of Groups: " .. Filename .. "\n" - for _, _group in pairs( List ) do - local group = GROUP:FindByName( _group ) -- Wrapper.Group#GROUP + local data = "--Save Stationary List of Groups: "..Filename .."\n" + for _,_group in pairs (List) do + local group = GROUP:FindByName(_group) -- Wrapper.Group#GROUP if group and group:IsAlive() then local units = group:CountAliveUnits() local position = group:GetVec3() - data = string.format( "%s%s,%d,%d,%d,%d\n", data, _group, units, position.x, position.y, position.z ) + data = string.format("%s%s,%d,%d,%d,%d\n",data,_group,units,position.x,position.y,position.z) else - data = string.format( "%s%s,0,0,0,0\n", data, _group ) + data = string.format("%s%s,0,0,0,0\n",data,_group) end end -- save the data - local outcome = UTILS.SaveToFile( Path, Filename, data ) + local outcome = UTILS.SaveToFile(Path,Filename,data) return outcome end @@ -2096,25 +2084,25 @@ end -- **Note** Do NOT use dashes or hashes in group template names (-,#)! -- The data will be a simple comma separated list of groupname and size, with one header line. -- The current task/waypoint/etc cannot be restored. -function UTILS.SaveSetOfGroups( Set, Path, Filename ) +function UTILS.SaveSetOfGroups(Set,Path,Filename) local filename = Filename or "SetOfGroups" - local data = "--Save SET of groups: " .. Filename .. "\n" + local data = "--Save SET of groups: "..Filename .."\n" local List = Set:GetSetObjects() - for _, _group in pairs( List ) do + for _,_group in pairs (List) do local group = _group -- Wrapper.Group#GROUP if group and group:IsAlive() then local name = group:GetName() - local template = string.gsub( name, "-(.+)$", "" ) - if string.find( template, "#" ) then - template = string.gsub( name, "#(%d+)$", "" ) - end + local template = string.gsub(name,"-(.+)$","") + if string.find(template,"#") then + template = string.gsub(name,"#(%d+)$","") + end local units = group:CountAliveUnits() local position = group:GetVec3() - data = string.format( "%s%s,%s,%d,%d,%d,%d\n", data, name, template, units, position.x, position.y, position.z ) + data = string.format("%s%s,%s,%d,%d,%d,%d\n",data,name,template,units,position.x,position.y,position.z) end end -- save the data - local outcome = UTILS.SaveToFile( Path, Filename, data ) + local outcome = UTILS.SaveToFile(Path,Filename,data) return outcome end @@ -2126,20 +2114,20 @@ end -- @usage -- We will go through the set and find the corresponding static and save the current name and postion when alive. -- The data will be a simple comma separated list of name and state etc, with one header line. -function UTILS.SaveSetOfStatics( Set, Path, Filename ) +function UTILS.SaveSetOfStatics(Set,Path,Filename) local filename = Filename or "SetOfStatics" - local data = "--Save SET of statics: " .. Filename .. "\n" + local data = "--Save SET of statics: "..Filename .."\n" local List = Set:GetSetObjects() - for _, _group in pairs( List ) do + for _,_group in pairs (List) do local group = _group -- Wrapper.Static#STATIC if group and group:IsAlive() then local name = group:GetName() local position = group:GetVec3() - data = string.format( "%s%s,%d,%d,%d\n", data, name, position.x, position.y, position.z ) + data = string.format("%s%s,%d,%d,%d\n",data,name,position.x,position.y,position.z) end end -- save the data - local outcome = UTILS.SaveToFile( Path, Filename, data ) + local outcome = UTILS.SaveToFile(Path,Filename,data) return outcome end @@ -2153,20 +2141,20 @@ end -- Position is saved for your usage. **Note** this works on UNIT-name level. -- The idea is to reduce the number of units when reloading the data again to restart the saved mission. -- The data will be a simple comma separated list of name and state etc, with one header line. -function UTILS.SaveStationaryListOfStatics( List, Path, Filename ) +function UTILS.SaveStationaryListOfStatics(List,Path,Filename) local filename = Filename or "StateListofStatics" - local data = "--Save Stationary List of Statics: " .. Filename .. "\n" - for _, _group in pairs( List ) do - local group = STATIC:FindByName( _group, false ) -- Wrapper.Static#STATIC + local data = "--Save Stationary List of Statics: "..Filename .."\n" + for _,_group in pairs (List) do + local group = STATIC:FindByName(_group,false) -- Wrapper.Static#STATIC if group and group:IsAlive() then local position = group:GetVec3() - data = string.format( "%s%s,1,%d,%d,%d\n", data, _group, position.x, position.y, position.z ) + data = string.format("%s%s,1,%d,%d,%d\n",data,_group,position.x,position.y,position.z) else - data = string.format( "%s%s,0,0,0,0\n", data, _group ) + data = string.format("%s%s,0,0,0,0\n",data,_group) end end -- save the data - local outcome = UTILS.SaveToFile( Path, Filename, data ) + local outcome = UTILS.SaveToFile(Path,Filename,data) return outcome end @@ -2175,43 +2163,40 @@ end -- @param #string Filename The name of the file. -- @param #boolean Reduce If false, existing loaded groups will not be reduced to fit the saved number. -- @return #table Table of data objects (tables) containing groupname, coordinate and group object. Returns nil when file cannot be read. -function UTILS.LoadStationaryListOfGroups( Path, Filename, Reduce ) +function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce) local reduce = true - if Reduce == false then - reduce = false - end + if Reduce == false then reduce = false end local filename = Filename or "StateListofGroups" local datatable = {} - if UTILS.CheckFileExists( Path, filename ) then - local outcome, loadeddata = UTILS.LoadFromFile( Path, Filename ) + if UTILS.CheckFileExists(Path,filename) then + local outcome,loadeddata = UTILS.LoadFromFile(Path,Filename) -- remove header - table.remove( loadeddata, 1 ) - for _id, _entry in pairs( loadeddata ) do - local dataset = UTILS.Split( _entry, "," ) + table.remove(loadeddata, 1) + for _id,_entry in pairs (loadeddata) do + local dataset = UTILS.Split(_entry,",") -- groupname,units,position.x,position.y,position.z local groupname = dataset[1] - local size = tonumber( dataset[2] ) - local posx = tonumber( dataset[3] ) - local posy = tonumber( dataset[4] ) - local posz = tonumber( dataset[5] ) - local coordinate = COORDINATE:NewFromVec3( { x = posx, y = posy, z = posz } ) - local data = { groupname = groupname, size = size, coordinate = coordinate, group = GROUP:FindByName( groupname ) } + local size = tonumber(dataset[2]) + local posx = tonumber(dataset[3]) + local posy = tonumber(dataset[4]) + local posz = tonumber(dataset[5]) + local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz}) + local data = { groupname=groupname, size=size, coordinate=coordinate, group=GROUP:FindByName(groupname) } if reduce then - local actualgroup = GROUP:FindByName( groupname ) - local actualsize = actualgroup:CountAliveUnits() - if actualsize > size then - local reduction = actualsize - size - BASE:I( "Reducing groupsize by " .. reduction .. " units!" ) + local actualgroup = GROUP:FindByName(groupname) + if actualgroup and actualgroup:IsAlive() and actualgroup:CountAliveUnits() > size then + local reduction = actualgroup:CountAliveUnits() - size + BASE:I("Reducing groupsize by ".. reduction .. " units!") -- reduce existing group local units = actualgroup:GetUnits() - local units2 = UTILS.ShuffleTable( units ) -- randomize table - for i = 1, reduction do - units2[i]:Destroy( false ) + local units2 = UTILS.ShuffleTable(units) -- randomize table + for i=1,reduction do + units2[i]:Destroy(false) end end end - table.insert( datatable, data ) - end + table.insert(datatable,data) + end else return nil end @@ -2224,55 +2209,58 @@ end -- @param #boolean Spawn If set to false, do not re-spawn the groups loaded in location and reduce to size. -- @return Core.Set#SET_GROUP Set of GROUP objects. -- Returns nil when file cannot be read. Returns a table of data entries if Spawn is false: `{ groupname=groupname, size=size, coordinate=coordinate }` -function UTILS.LoadSetOfGroups( Path, Filename, Spawn ) +function UTILS.LoadSetOfGroups(Path,Filename,Spawn) local spawn = true - if Spawn == false then - spawn = false - end - BASE:I( "Spawn = " .. tostring( spawn ) ) + if Spawn == false then spawn = false end + BASE:I("Spawn = "..tostring(spawn)) local filename = Filename or "SetOfGroups" local setdata = SET_GROUP:New() local datatable = {} - if UTILS.CheckFileExists( Path, filename ) then - local outcome, loadeddata = UTILS.LoadFromFile( Path, Filename ) + if UTILS.CheckFileExists(Path,filename) then + local outcome,loadeddata = UTILS.LoadFromFile(Path,Filename) -- remove header - table.remove( loadeddata, 1 ) - for _id, _entry in pairs( loadeddata ) do - local dataset = UTILS.Split( _entry, "," ) + table.remove(loadeddata, 1) + for _id,_entry in pairs (loadeddata) do + local dataset = UTILS.Split(_entry,",") -- groupname,template,units,position.x,position.y,position.z local groupname = dataset[1] local template = dataset[2] - local size = tonumber( dataset[3] ) - local posx = tonumber( dataset[4] ) - local posy = tonumber( dataset[5] ) - local posz = tonumber( dataset[6] ) - local coordinate = COORDINATE:NewFromVec3( { x = posx, y = posy, z = posz } ) - local group = nil - local data = { groupname = groupname, size = size, coordinate = coordinate } - table.insert( datatable, data ) + local size = tonumber(dataset[3]) + local posx = tonumber(dataset[4]) + local posy = tonumber(dataset[5]) + local posz = tonumber(dataset[6]) + local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz}) + local group=nil + local data = { groupname=groupname, size=size, coordinate=coordinate } + table.insert(datatable,data) if spawn then - local group = SPAWN:New( groupname ):InitDelayOff():OnSpawnGroup( function( spwndgrp ) - setdata:AddObject( spwndgrp ) - local actualsize = spwndgrp:CountAliveUnits() - if actualsize > size then - local reduction = actualsize - size - -- reduce existing group - local units = spwndgrp:GetUnits() - local units2 = UTILS.ShuffleTable( units ) -- randomize table - for i = 1, reduction do - units2[i]:Destroy( false ) + local group = SPAWN:New(groupname) + :InitDelayOff() + :OnSpawnGroup( + function(spwndgrp) + setdata:AddObject(spwndgrp) + local actualsize = spwndgrp:CountAliveUnits() + if actualsize > size then + local reduction = actualsize-size + -- reduce existing group + local units = spwndgrp:GetUnits() + local units2 = UTILS.ShuffleTable(units) -- randomize table + for i=1,reduction do + units2[i]:Destroy(false) + end + end end - end - end ):SpawnFromCoordinate( coordinate ) + ) + :SpawnFromCoordinate(coordinate) end - end + end else return nil end if spawn then return setdata else - return datatable + return datatable end end @@ -2280,23 +2268,23 @@ end -- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems. -- @param #string Filename The name of the file. -- @return Core.Set#SET_STATIC Set SET_STATIC containing the static objects. -function UTILS.LoadSetOfStatics( Path, Filename ) +function UTILS.LoadSetOfStatics(Path,Filename) local filename = Filename or "SetOfStatics" local datatable = SET_STATIC:New() - if UTILS.CheckFileExists( Path, filename ) then - local outcome, loadeddata = UTILS.LoadFromFile( Path, Filename ) + if UTILS.CheckFileExists(Path,filename) then + local outcome,loadeddata = UTILS.LoadFromFile(Path,Filename) -- remove header - table.remove( loadeddata, 1 ) - for _id, _entry in pairs( loadeddata ) do - local dataset = UTILS.Split( _entry, "," ) + table.remove(loadeddata, 1) + for _id,_entry in pairs (loadeddata) do + local dataset = UTILS.Split(_entry,",") -- staticname,position.x,position.y,position.z local staticname = dataset[1] - local posx = tonumber( dataset[2] ) - local posy = tonumber( dataset[3] ) - local posz = tonumber( dataset[4] ) - local coordinate = COORDINATE:NewFromVec3( { x = posx, y = posy, z = posz } ) - datatable:AddObject( STATIC:FindByName( staticname, false ) ) - end + local posx = tonumber(dataset[2]) + local posy = tonumber(dataset[3]) + local posz = tonumber(dataset[4]) + local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz}) + datatable:AddObject(STATIC:FindByName(staticname,false)) + end else return nil end @@ -2309,35 +2297,33 @@ end -- @param #boolean Reduce If false, do not destroy the units with size=0. -- @return #table Table of data objects (tables) containing staticname, size (0=dead else 1), coordinate and the static object. -- Returns nil when file cannot be read. -function UTILS.LoadStationaryListOfStatics( Path, Filename, Reduce ) +function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce) local reduce = true - if Reduce == false then - reduce = false - end + if Reduce == false then reduce = false end local filename = Filename or "StateListofStatics" local datatable = {} - if UTILS.CheckFileExists( Path, filename ) then - local outcome, loadeddata = UTILS.LoadFromFile( Path, Filename ) + if UTILS.CheckFileExists(Path,filename) then + local outcome,loadeddata = UTILS.LoadFromFile(Path,Filename) -- remove header - table.remove( loadeddata, 1 ) - for _id, _entry in pairs( loadeddata ) do - local dataset = UTILS.Split( _entry, "," ) + table.remove(loadeddata, 1) + for _id,_entry in pairs (loadeddata) do + local dataset = UTILS.Split(_entry,",") -- staticname,units(1/0),position.x,position.y,position.z) local staticname = dataset[1] - local size = tonumber( dataset[2] ) - local posx = tonumber( dataset[3] ) - local posy = tonumber( dataset[4] ) - local posz = tonumber( dataset[5] ) - local coordinate = COORDINATE:NewFromVec3( { x = posx, y = posy, z = posz } ) - local data = { staticname = staticname, size = size, coordinate = coordinate, static = STATIC:FindByName( staticname, false ) } - table.insert( datatable, data ) - if size == 0 and reduce then - local static = STATIC:FindByName( staticname, false ) + local size = tonumber(dataset[2]) + local posx = tonumber(dataset[3]) + local posy = tonumber(dataset[4]) + local posz = tonumber(dataset[5]) + local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz}) + local data = { staticname=staticname, size=size, coordinate=coordinate, static=STATIC:FindByName(staticname,false) } + table.insert(datatable,data) + if size==0 and reduce then + local static = STATIC:FindByName(staticname,false) if static then - static:Destroy( false ) + static:Destroy(false) end end - end + end else return nil end From 5260b2b4302c27ac8ca32661397e9da37072d58a Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 26 Mar 2022 14:46:52 +0100 Subject: [PATCH 089/200] And don't forget Fahrenheit --- Moose Development/Moose/Core/Point.lua | 4 ++-- Moose Development/Moose/Functional/Range.lua | 2 +- Moose Development/Moose/Utilities/Utils.lua | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 878fe93e2..2826969bb 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -912,7 +912,7 @@ do -- COORDINATE -- The text will reflect the temperature like this: -- -- - For Russian and European aircraft using the metric system - Degrees Celcius (°C) - -- - For American aircraft we link to the imperial system - Degrees Farenheit (°F) + -- - For American aircraft we link to the imperial system - Degrees Fahrenheit (°F) -- -- A text containing a pressure will look like this: -- @@ -932,7 +932,7 @@ do -- COORDINATE if Settings:IsMetric() then return string.format( " %-2.2f °C", DegreesCelcius ) else - return string.format( " %-2.2f °F", UTILS.CelsiusToFarenheit( DegreesCelcius ) ) + return string.format( " %-2.2f °F", UTILS.CelsiusToFahrenheit( DegreesCelcius ) ) end else return " no temperature" diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index 14e58b8e5..01a095ca9 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -2755,7 +2755,7 @@ function RANGE:_DisplayRangeWeather( _unitname ) local tW = string.format( "%.1f m/s", Ws ) local tP = string.format( "%.1f mmHg", P * hPa2mmHg ) if settings:IsImperial() then - -- tT=string.format("%d°F", UTILS.CelsiusToFarenheit(T)) + -- tT=string.format("%d°F", UTILS.CelsiusToFahrenheit(T)) tW = string.format( "%.1f knots", UTILS.MpsToKnots( Ws ) ) tP = string.format( "%.2f inHg", P * hPa2inHg ) end diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index e059a353e..12f8e7515 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -473,10 +473,10 @@ UTILS.KnotsToMps = function( knots ) end end ---- Convert temperature from Celsius to Farenheit. +--- Convert temperature from Celsius to Fahrenheit. -- @param #number Celcius Temperature in degrees Celsius. --- @return #number Temperature in degrees Farenheit. -UTILS.CelsiusToFarenheit = function( Celcius ) +-- @return #number Temperature in degrees Fahrenheit. +UTILS.CelsiusToFahrenheit = function( Celcius ) return Celcius * 9/5 + 32 end From e6f9b4a125669cb577fc2a3ec36842ccd0b41c4e Mon Sep 17 00:00:00 2001 From: Lt Cdr Gavin Edwards Date: Sat, 26 Mar 2022 11:54:53 -0700 Subject: [PATCH 090/200] Added Hermes section to Airboss. Will require tuning. --- Moose Development/Moose/Ops/Airboss.lua | 104 ++++++++++++++++++------ 1 file changed, 81 insertions(+), 23 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index d2d0f6657..856ace33a 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -1,5 +1,3 @@ ---- **Ops** - Manages aircraft CASE X recoveries for carrier operations (X=I, II, III). --- -- The AIRBOSS class manages recoveries of human pilots and AI aircraft on aircraft carriers. -- -- **Main Features:** @@ -32,6 +30,7 @@ -- * [USS George Washington](https://en.wikipedia.org/wiki/USS_George_Washington_(CVN-73)) (CVN-73) [Super Carrier Module] -- * [USS Harry S. Truman](https://en.wikipedia.org/wiki/USS_Harry_S._Truman) (CVN-75) [Super Carrier Module] -- * [USS Forrestal](https://en.wikipedia.org/wiki/USS_Forrestal_(CV-59)) (CV-59) [Heatblur Carrier Module] +-- * [HMS Hermes](https://en.wikipedia.org/wiki/HMS_Hermes_(R12)) (R12) [**WIP**] -- * [USS Tarawa](https://en.wikipedia.org/wiki/USS_Tarawa_(LHA-1)) (LHA-1) [**WIP**] -- * [USS America](https://en.wikipedia.org/wiki/USS_America_(LHA-6)) (LHA-6) [**WIP**] -- * [Juan Carlos I](https://en.wikipedia.org/wiki/Spanish_amphibious_assault_ship_Juan_Carlos_I) (L61) [**WIP**] @@ -51,7 +50,7 @@ -- -- At the moment, optimized parameters are available for the F/A-18C Hornet (Lot 20) and A-4E community mod as aircraft and the USS John C. Stennis as carrier. -- --- The AV-8B Harrier, the USS Tarawa, USS America, HMAS Canberra and Juan Carlos I are WIP. The AV-8B harrier and the LHA's and LHD can only be used together, i.e. these ships are the only carriers the harrier is supposed to land on and +-- The AV-8B Harrier, HMS Hermes, the USS Tarawa, USS America, HMAS Canberra, and Juan Carlos I are WIP. The AV-8B harrier and the LHA's and LHD can only be used together, i.e. these ships are the only carriers the harrier is supposed to land on and -- no other fixed wing aircraft (human or AI controlled) are supposed to land on these ships. Currently only Case I is supported. Case II/III take slightly different steps from the CVN carrier. -- However, the two Case II/III pattern are very similar so this is not a big drawback. -- @@ -1260,7 +1259,7 @@ AIRBOSS = { --- Aircraft types capable of landing on carrier (human+AI). -- @type AIRBOSS.AircraftCarrier --- @field #string AV8B AV-8B Night Harrier. Works only with the USS Tarawa, USS America and Juan Carlos I. +-- @field #string AV8B AV-8B Night Harrier. Works only with the HMS Hermes, USS Tarawa, USS America, and Juan Carlos I. -- @field #string A4EC A-4E Community mod. -- @field #string HORNET F/A-18C Lot 20 Hornet by Eagle Dynamics. -- @field #string F14A F-14A by Heatblur. @@ -1296,6 +1295,7 @@ AIRBOSS.AircraftCarrier={ -- @field #string TRUMAN USS Harry S. Truman (CVN-75) [Super Carrier Module] -- @field #string FORRESTAL USS Forrestal (CV-59) [Heatblur Carrier Module] -- @field #string VINSON USS Carl Vinson (CVN-70) [Obsolete] +-- @field #string HERMES HMS Hermes (R12) [V/STOL Carrier] -- @field #string TARAWA USS Tarawa (LHA-1) [V/STOL Carrier] -- @field #string AMERICA USS America (LHA-6) [V/STOL Carrier] -- @field #string JCARLOS Juan Carlos I (L61) [V/STOL Carrier] @@ -1309,6 +1309,7 @@ AIRBOSS.CarrierType = { STENNIS = "Stennis", FORRESTAL = "Forrestal", VINSON = "VINSON", + HERMES = "HERMES81", TARAWA = "LHA_Tarawa", AMERICA = "USS America LHA-6", JCARLOS = "L61", @@ -1980,6 +1981,9 @@ function AIRBOSS:New( carriername, alias ) elseif self.carriertype == AIRBOSS.CarrierType.VINSON then -- TODO: Carl Vinson parameters. self:_InitStennis() + elseif self.carriertype == AIRBOSS.CarrierType.HERMES then + -- Hermes parameters. + self:_InitHermes() elseif self.carriertype == AIRBOSS.CarrierType.TARAWA then -- Tarawa parameters. self:_InitTarawa() @@ -2082,7 +2086,7 @@ function AIRBOSS:New( carriername, alias ) -- cL:FlareYellow() -- Carrier specific. - if self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.TARAWA or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.AMERICA or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.JCARLOS or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.CANBERRA then + if self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.HERMES or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.TARAWA or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.AMERICA or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.JCARLOS or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.CANBERRA then -- Flare wires. local w1 = stern:Translate( self.carrierparam.wire1, FB, true ) @@ -4384,6 +4388,46 @@ function AIRBOSS:_InitForrestal() end +--- Init parameters for R12 HMS Hermes carrier. +-- @param #AIRBOSS self +function AIRBOSS:_InitHermes() + + -- Init Stennis as default. + self:_InitStennis() + + -- Carrier Parameters. + self.carrierparam.sterndist = -105 + self.carrierparam.deckheight = 12 -- From model viewer WL0. + + -- Total size of the carrier (approx as rectangle). + self.carrierparam.totlength = 228.19 + self.carrierparam.totwidthport = 20.5 + self.carrierparam.totwidthstarboard = 24.5 + + -- Landing runway. + self.carrierparam.rwyangle = 0 + self.carrierparam.rwylength = 215 + self.carrierparam.rwywidth = 13 + + -- Wires. + self.carrierparam.wire1 = nil + self.carrierparam.wire2 = nil + self.carrierparam.wire3 = nil + self.carrierparam.wire4 = nil + + -- Late break. + self.BreakLate.name = "Late Break" + self.BreakLate.Xmin = -UTILS.NMToMeters( 1 ) -- Not more than 1 NM behind the boat. Last check was at 0. + self.BreakLate.Xmax = UTILS.NMToMeters( 5 ) -- Not more than 5 NM in front of the boat. Enough for late breaks? + self.BreakLate.Zmin = -UTILS.NMToMeters( 0.25 ) -- Not more than 0.25 NM port. + self.BreakLate.Zmax = UTILS.NMToMeters( 0.5 ) -- Not more than 0.5 NM starboard. + self.BreakLate.LimitXmin = 0 -- Check and next step 0.8 NM port and in front of boat. + self.BreakLate.LimitXmax = nil + self.BreakLate.LimitZmin = -UTILS.NMToMeters( 0.5 ) -- 926 m port, closer than the stennis as abeam is 0.8-1.0 rather than 1.2 + self.BreakLate.LimitZmax = nil + +end + --- Init parameters for LHA-1 Tarawa carrier. -- @param #AIRBOSS self function AIRBOSS:_InitTarawa() @@ -6209,7 +6253,7 @@ function AIRBOSS:_GetMarshalAltitude( stack, case ) p2 = Carrier:Translate( UTILS.NMToMeters( 1.5 ), hdg ) -- Tarawa,LHA,LHD Delta patterns. - if self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then -- Pattern is directly overhead the carrier. p1 = Carrier:Translate( UTILS.NMToMeters( 1.0 ), hdg + 90 ) @@ -8048,7 +8092,7 @@ function AIRBOSS:OnEventLand( EventData ) self:T( self.lid .. text ) -- Check carrier type. - if self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then -- Power "Idle". self:RadioTransmission( self.LSORadio, self.LSOCall.IDLE, false, 1, nil, true ) @@ -8083,7 +8127,7 @@ function AIRBOSS:OnEventLand( EventData ) -- AI unit landed -- -------------------- - if self.carriertype ~= AIRBOSS.CarrierType.TARAWA or self.carriertype ~= AIRBOSS.CarrierType.AMERICA or self.carriertype ~= AIRBOSS.CarrierType.JCARLOS or self.carriertype ~= AIRBOSS.CarrierType.CANBERRA then + if self.carriertype ~= AIRBOSS.CarrierType.HERMES or self.carriertype ~= AIRBOSS.CarrierType.TARAWA or self.carriertype ~= AIRBOSS.CarrierType.AMERICA or self.carriertype ~= AIRBOSS.CarrierType.JCARLOS or self.carriertype ~= AIRBOSS.CarrierType.CANBERRA then -- Coordinate at landing event local coord = EventData.IniUnit:GetCoordinate() @@ -9121,7 +9165,7 @@ function AIRBOSS:_CheckForLongDownwind( playerData ) local limit = UTILS.NMToMeters( -1.6 ) -- For the tarawa, other LHA and LHD we give a bit more space. - if self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then limit = UTILS.NMToMeters( -2.0 ) end @@ -9208,7 +9252,7 @@ function AIRBOSS:_Ninety( playerData ) self:_PlayerHint( playerData ) -- Next step: wake. - if self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then -- Harrier has no wake stop. It stays port of the boat. self:_SetPlayerStep( playerData, AIRBOSS.PatternStep.FINAL ) else @@ -9726,9 +9770,8 @@ function AIRBOSS:_CheckWaveOff( glideslopeError, lineupError, AoA, playerData ) -- For the harrier, we allow a bit more room. if playerData.actype == AIRBOSS.AircraftCarrier.AV8B then glMax = 2.6 - glMin = -2.0 + glMin = -2.6 -- Testing, @Engines may be just dragging it in on Hermes, or the carrier parameters need adjusting. luAbs = 4.1 -- Testing Pene (WIP) needs feedback to tighten up tolerences. - end -- Too high or too low? @@ -9900,7 +9943,7 @@ function AIRBOSS:_GetSternCoord() -- local stern=self:GetCoordinate() -- Stern coordinate (sterndist<0). - if self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then -- Tarawa: Translate 8 meters port. self.sterncoord:Translate( self.carrierparam.sterndist, hdg, true, true ):Translate( 8, FB - 90, true, true ) elseif self.carriertype == AIRBOSS.CarrierType.STENNIS then @@ -10536,7 +10579,7 @@ function AIRBOSS:_GetZoneRunwayBox() return self.zoneRunwaybox end ---- Get zone of primary abeam landing position of USS Tarawa, USS America and Juan Carlos. Box length 50 meters and width 30 meters. +--- Get zone of primary abeam landing position of HMS Hermes, USS Tarawa, USS America and Juan Carlos. Box length 50 meters and width 30 meters. --- Allow for Clear to land call from LSO approaching abeam the landing spot if stable as per NATOPS 00-80T -- @param #AIRBOSS self -- @return Core.Zone#ZONE_POLYGON Zone surrounding landing runway. @@ -10640,7 +10683,7 @@ function AIRBOSS:_GetZoneHolding( case, stack ) self.zoneHolding = ZONE_RADIUS:New( "CASE I Holding Zone", Post:GetVec2(), self.marshalradius ) -- Delta pattern. - if self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then self.zoneHolding = ZONE_RADIUS:New( "CASE I Holding Zone", self.carrier:GetVec2(), UTILS.NMToMeters( 5 ) ) end @@ -10692,7 +10735,7 @@ function AIRBOSS:_GetZoneCommence( case, stack ) -- Three position local Three = self:GetCoordinate():Translate( D, hdg + 275 ) - if self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then local Dx = UTILS.NMToMeters( 2.25 ) @@ -10997,9 +11040,17 @@ function AIRBOSS:_GetOptLandingCoordinate() -- Final bearing. local FB = self:GetFinalBearing( false ) - if self.carriertype == AIRBOSS.CarrierType.TARAWA then + if self.carriertype == AIRBOSS.CarrierType.HERMES then - -- Landing 100 ft abeam, 120 ft alt. + -- Landing 100 ft abeam, 100 ft alt. + self.landingcoord:UpdateFromCoordinate( self:_GetLandingSpotCoordinate() ):Translate( 25, FB - 90, true, true ) + -- stern=self:_GetLandingSpotCoordinate():Translate(35, FB-90) + + -- Alitude 100 ft. + self.landingcoord:SetAltitude( UTILS.FeetToMeters( 100 ) ) + elseif self.carriertype == AIRBOSS.CarrierType.TARAWA then + + -- Landing 100 ft abeam, 100 ft alt. self.landingcoord:UpdateFromCoordinate( self:_GetLandingSpotCoordinate() ):Translate( 35, FB - 90, true, true ) -- stern=self:_GetLandingSpotCoordinate():Translate(35, FB-90) @@ -11059,7 +11110,14 @@ function AIRBOSS:_GetLandingSpotCoordinate() -- Stern coordinate. -- local stern=self:_GetSternCoord() - if self.carriertype == AIRBOSS.CarrierType.TARAWA then + if self.carriertype == AIRBOSS.CarrierType.HERMES then + + -- Landing 100 ft abeam, 100 alt. + local hdg = self:GetHeading() + + -- Primary landing spot 5 + self.landingspotcoord:Translate( 69, hdg, true, true ):SetAltitude( self.carrierparam.deckheight ) + elseif self.carriertype == AIRBOSS.CarrierType.TARAWA then -- Landing 100 ft abeam, 120 alt. local hdg = self:GetHeading() @@ -11994,7 +12052,7 @@ function AIRBOSS:_GS( step, n ) if n == -1 then gp = AIRBOSS.GroovePos.IC elseif n == 1 then - if self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then gp = AIRBOSS.GroovePos.AL else gp = AIRBOSS.GroovePos.IW @@ -13873,7 +13931,7 @@ function AIRBOSS:_IsCarrierAircraft( unit ) -- Special case for Harrier which can only land on Tarawa, LHA and LHD. if aircrafttype == AIRBOSS.AircraftCarrier.AV8B then - if self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then return true else return false @@ -13881,7 +13939,7 @@ function AIRBOSS:_IsCarrierAircraft( unit ) end -- Also only Harriers can land on the Tarawa, LHA and LHD. - if self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then if aircrafttype ~= AIRBOSS.AircraftCarrier.AV8B then return false end @@ -17238,7 +17296,7 @@ function AIRBOSS:_MarkCaseZones( _unitName, flare ) end -- Tarawa, LHA and LHD landing spots. - if self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then text = text .. "\n* abeam landing stop with RED flares" -- Abeam landing spot zone. local ALSPT = self:_GetZoneAbeamLandingSpot() From b1e5e1840eae8e7e3cb0f6d170f0fa79cab43895 Mon Sep 17 00:00:00 2001 From: Lt Cdr Gavin Edwards Date: Sat, 26 Mar 2022 15:23:38 -0700 Subject: [PATCH 091/200] Adding leading lines that I accidentally truncated. --- .vs/VSWorkspaceState.json | 10 ++++++++++ .vs/slnx.sqlite | Bin 0 -> 98304 bytes Moose Development/Moose/Ops/Airboss.lua | 2 ++ 3 files changed, 12 insertions(+) create mode 100644 .vs/VSWorkspaceState.json create mode 100644 .vs/slnx.sqlite diff --git a/.vs/VSWorkspaceState.json b/.vs/VSWorkspaceState.json new file mode 100644 index 000000000..4e376c288 --- /dev/null +++ b/.vs/VSWorkspaceState.json @@ -0,0 +1,10 @@ +{ + "ExpandedNodes": [ + "", + "\\Moose Development", + "\\Moose Development\\Moose", + "\\Moose Development\\Moose\\Ops" + ], + "SelectedNode": "\\Moose Development\\Moose\\Ops\\Airboss.lua", + "PreviewInSolutionExplorer": false +} \ No newline at end of file diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..cf066726d3907bcc3ac74baa21f0e3e08e266d0c GIT binary patch literal 98304 zcmeHw3w&codGC>qbo4rp-SyhCE!md$?cJ5vmS5{^Hi=f&?keloTJ~->izAMuW3OUa z((18~JW-Y;Bq4zi0;C}%5V8pY!Ug)GDW#?KL7^>_wm_imuW0Yu_?enq%|9R7anjoCd{3=eaQS0k=Ap)BPRc7v zW8=h27-vXc!X(5vABs(lkE6a$PQ{|hc+Z{td6z#Nw%r@9$O}28zU%d$1=X0@=z;Ng zgPsfC9R!zEASN>NfbVSlP<$pdJ(HM-&YTDxiJu5X=Vqr8lc07YJ~<0k!SCGo_-<9p zbg6JAn^8(;X^n@lph1|4AB@k$Cu8ws=%gkQ&sVaQ^Yc6O3Q*Bwh`_kbO^+cU0ESp} zEN&r<#mD0YNi&83IC(5O6FVH8*%28U>d_6Z!&MVGSk2|mAFax{>|$2QG*Vz44^8d} zc!`0R)?`;;{AxbI+f&=)bNN#+uTpqkVN26;KChHwa{82#T5UR+W%Z1_ zb(YnW*VJR0bM-Rdd#hWY@3}DN#WU`%c|7ClrRqQXtTV2j4DD+(uAXF`Gp}OCsRP=` z=2f0!nQ^$?OFPWv`HMEK&7IQx-C~W#s5`G#v1lO0Eu*c=!(__0s!k(x#i}a;Xp;ZvWyy~#dO(u@c>31HDM;9`= zmqt@N2KN}PhL~Kpkm%H7N~sJJZPm(u2jh16dwOga_F+HNlo9pTPadleT8SnP)ClH5 zdKI2WJVQ5_bjMg``!2c4YVGP>eKo>p$gXg?{PD0hWc7}mnwCpSzLG-Ac0JAAaQy1I z%oB@8wsFJW9G;Cn6{c92CnW-QVswCe42S|><7*)&D! zNw00H5StAze-%5CHOIhM>+m{;_EYHRHEankZ-q~k5jOLo1Bun!z;U@;nOZ0-r85d_ zi;R_5-7SX3;s>L1V4}R4SEhehJTMb-zt&99Dl? z&4=9xZ}*A7T9dYC+RpiR?XsP~o>M*zdo(3gR!&!yJnY)6A6bjv)Dd^&!TEQCWKZpo z&E*dSY^N_L- zytj5c9ZYQfn6gk^nw3jSN~Jsp=Wq2aSw5niKRJKlxWmcpyPHL)^MsLCOOp6|xljEG zqibEE(;^FlgQJ$-Bol@v2E!wRk;RA-i3|;=BjLS$Ljxm2U_@rPe=t1Qzc>U; zqXXfgy@Lab%J6_9?~R1h!;!tiBk6Q_ctqYC9vn>%hvm^sUpTU$EDj8hX7(;Dj)XI# zk^V?}C=(tX8IplX0j80mfpBDTXz%dC;=*Fz2rwNd&zDOk0KXgT8;EKM! z7Mp|F?oUDnW<^$`vi-dSy+i2yZy6;)53j1K{zSIg|N`X|D698|GWHS{O$ak`78KV+{m}JQ*QZ<$ zxr(kC*Ql%0W#eAt{*wDm?lG>$Epg-AR?g*o$@y2#-*&#wdAn0_9(L|>Zg9Nh_^RVm zj`ujuIj(n%ID!rb`vdlm*^jXIv8UM+?0$9&>#_g9{w4c|?04GJ_Wky5yW94X?QdVzttntI7fQ3$D+|>8a~GZzJ>>Ig>3U zi}010Qx@|b?Ks#FyVx%C!~>C(s+O2^Hrpi)j(mBUJ|fXMAyyWKZ&Og|D- z(uFfh>HMsmUsg&M7XNvU=>V44?D&|Ka84y8qcc{*Swe`;CXyCH`x%be4635p(n6tJ zw$QdzIc5ZC*BA22p@N)?!7qNuA949LdqWkD*<uq!v)zPdLizW21b|G(Rrmes+-5{MjRVc|B z3(21+*d&!YLi-BACYdgj;29>1P)m+u0-$duI%}zKUDk#@zht#Acq6fYx~x<)h3IU| zA{WRKj3!{rSKuK?3$gnYafyj6bS$ULmgFovaBX2}TO$3Nkl{IeJd8RtEsMm|gN1We z5rv4v%h12N1~a6=F{J`uivui?3s|)V)1*5(i$!;EL8F$lOL_Rxl7-qX6JTRGMb6I^ zEwqj|li2|Pr4E(Q+_aE`DWW)9s$_FHm>w2t_YK5uWG=jkLNy+-G;8kapC_Y@SS*CV zo5%*|#DX)@FkFeha)BXpX7il8&0@V0#%vyT`}IN+X}O&5w4;@~>pt}8euZss#gU9Gf# zlw*#r!Muk4oqIVZwuVS_8s86eTlS3TKogryw!po*n3Ku!r3-8uuIr>BV1cV^NOuIH zxW45rgSsQA42_x#43K%Hp>AoTEn*t2X$z^dpDcVJcR+?tr0d3@txp#{5KUM_H|^n= z0RkT&kV>Ae%9#?GwmJ%-UfmfGPlJgN3R^hh`6YE((sgXvtpk8`tbt?OF5OVzvKZRA zQ}1ImawwIINAWJrXrRYv0O)Hp(6K|k5hhYdOTF87Y`2i>!3u3Nx*3T!0-|H9NkKGq zD4n%*V#^kjk-8H09ODNGd{Q8_a6Z`3&M{3;J5^LQKwW#AdbcZ}yDZ)?>HO`jgmx%b zSdep8TALp^lbbtO&ExHsoYTcxTF5PLEQXgoy}qfLW12xERwyZY<;IP~Qao|>+VxEw zvmRKZ3B9Ui1LK5ZB`OB^!<^Ahh)3;ATa zlr5rX{*exyDIk*dB7+`qSR#{NCk6%NCT=+Lz=p7_geD6WrOasqwvi{DnkW>?3aJIC z{XTSqc60bt4GMKarpfG}s@hoq300NJBOPZ8rDcuQW=9ryID{%}&C<)Iv`(_Aq@`?Sg?Jv2 z;Lz*8-Rt@k^Bn82xtLx zxIq{bdWBYj;eW#a9sd{nXZa`j2lx^{!H0Ry_dVYieV_Jy*!M0t{ZIRbece9E@o%id z6Zb@14||{TzK8u^;y?Io-hcLf%lifIr#zb+-{-#S_$%>mU2k*U?7dU`GwBAY-?PsB zAKY)bUu3`UoOgf2d)EDB@3Q#3_)+f-jz4sKT>6sxvyR8SN4-(^Med^1Dn2UBO1}Z8 z{blJX_G|3t*^jXkY@hpK$Nl0%-T}v1=Mnb>SIpJt3c9QAti((2b!DUjj-3ucDtouM zjygvji(ZfCW%mj9xaWtS7u@^Bdt7eMmp#Aj`6%~u$1&$FcMtbIuEHJX;@l31K>W!0 z4d>^aPdM*z=D54WTb-?*#~g<}Z|A-*7Mvc>&1|#VFQz?t&vDnkyMEyMn(H$FG5vXi zBH;QE)4Yz|Y=>L47T#b!(aw(8k0p=8^?i5<0*0|OO3O$2-{lDsk*QyelM|Hd8;=#YPD=L}WtRu}+3X{S@=j5F4}7RVBSS!tiV}e4U51 zP2zfJrZXQw*5lgVMf5-!^UOwem#Sx0S%G~ZES1dDJR7jb)46Q1tS}e-Y`=X0J*k#j zK>>q8zTCSYSD2?z=PXNXDAF70?dvUO7MTyD&NU)t3h8sqlLFge*A6?(ha|S!uJsOV z{2;P1nN}bsfltlM6R0cGg)$Bnq1PPZt~`#!rWVkHcKGuL(B}!cq(--x$BUzsPpk^5X|G(t_^z_`b`$cLTfK zuEU3|c@OGha&{KqWtoT9vs>+G#{z*CBJyrj10h~C3V6H6JcM+{^VJo?@GfK-+B&2l z1_OIa!t@}*71xr@G{kQ5W$paTyt9+N+Dx@>1(|oWvIAyvObzQX4>YkoDj_;^XbN}W ze&p6_OE`>{(n1`}+mY{CNOcX}hg<_Qpsi*d@m|z(jfk7N$HZR0HDumqVvl0>yS17` zei26onY$3!NF}D~8pB*fBqK}!&rkM4dZj*AnL$nK z)d-a|sWHVQ7f?q`aYz{rur>DhW+Y^ZH>OZ~Xbh||#v2eqs2Vjjk9uj2CB6w&nB#{h z5l(u{@Olid8Yet~hr=2hybiJJ@xbFqn>hw}48fzu{pOIxS~L%@L$mGds9s~8-${g} z5!0JN&FFEwqX-^T>~0!asm1H2&<8C>H;IRb#N{TC4m}n(j!ZSj-;SUbjhNfDNR1X} zOCZznQmNf;4kNH}jU}FT2n`gj)^836^(sAX7Dtv~E;J@%cqGm7u^39DK`g=&6FYz= zA5^LlE>j#Vx`mxEQ4Ao>v9D{;)HB2lCS=0ZstJbkUWL#AcJ+Y2AI)#{(GttL5+w-r zT5Ei1UxGsKJE#KCFUG z6sTHKtbm1QtdIve0FNWt8S}3c%iYxc6OMm)DCRdOtQpqw&{#| z+-9rZQ2h;gi-}o{&uqp{XpYHj!c3MpOt-m;#9l(Uht_yY7n=8KjHMIJHa)J=fhg!E z53zuthxL;iaKObj*&t@pj#m~VPSS=xn_?ra2wWo`;z!EV7)XnoZMLDfM>Eoj9cLrQ zu7`ncLR@N0V*^r%#zl=|tVc98cCoIN9U^y}x_E_ze9j!B5D`?CeX)QV#XE8yEnHgs z!H4<bp@}$Xau- zfYth}MhNU);Ry4b?|Hj$2(thGD*Z(Ik@TYU9qI3+uS;L`eO~&K^heU?c&G0_d@uWc z?E8WA8R^r~$EByG55hME9+uuA-6LI)Zk5hTr=?{{ky6qLzKQ3hq%gpUYM3Xciz6&{3dE!-*GE}R$25XV>&WcUWdoG>L^ zEsP2S@J)v8zUPH*As{phlHe9>{J--*<$uI~kN-<;vFY=$~pXYy_|2ThzvnT=- z0g3=cfFeKm9O>0~}cE*~V9C&=Y-a`^zcJVq|>CznUb<=4pN5psDSx^yt_ zC71V*%fsaIZgP2uT;7E)LFPenc_+ENgIpdUm;1@(?c{PFx!g-G_mIom$mMQwxrTxM~33YSZ`T*Re<%M32lxLm-cjLSFU zGKI?K8*n*~%QxZjBrdPVwJ}E~jugiOUIGj^pwO zF0aL90+kyNkD--*i(Tn2F&z-2ow+i=;6OFu4KaM_H@jks*W8*jQB9z1&xr=WNf~x!)JRBfSLY{@bLK*alhu zmxOPLe=UAV_+$8w01ped3a5mmFaqb}b^O2Z-{!x-Kg&M?=id^a;$!@7Ui7`_`y=1` zedm2AeKB9e*XjLF?>D@^3mN|h*{|51b-%-X6ZZ*k!0WYtTKt^1=FNGJd$02Dbf02> z&-pj(Me*0UC%6YaFN+tYX{p0K>%PYM8TWSkhaFLOi|0kR+w)EFVex|J&)A3ATctm7 zz059qp7(sj_4m$;l3hG4jYev^H{_V2bATwh{Oh)bSF*r+4y`YkTW zUB&HiJ?(l#{1@q++&QVw{v-RJOQ$?<4o&PNEXTR(I zlI^pu{o)??@4BCaCm4L(Pr0uH#FzQQ{T}1r;Mi<4r>SA-4?0F{jT6;yL+Ensv#aUi z)v0Q@QG^`(HF|?=_0!D9HaVgyL*s-s+*~$0Vs$KaIqQqevwla|wkmB6cOu*$Q{vji zJk#RXrLu=UCdLRj(xUs#;Wwwcn{oy-TZ&89?lC-VgEUVSRNlX)Bq8_DcW<^#CKNN0C4k73#R zgmx$M{&kKnn~~D)WFEzg^-1kc=GU-FBdy)ZJfeGvn%M4S-iNj6scpCmZ**+8S(Dox z%zJR>we)re^Kg@6D}ZH6aCb28#&zZtcL(zj)~+YHJD7K2TX8nnl$V7&Iz}JYlSsZL z(H&&o8FE|=R-yDbp<6@gLFOIpjsXjUmh28P4>UV^G?JF?4l?)S>7i=0CcJ~p+c!C4 znh1HQq%L+JWbVW6gymN!y@SlX7!8e#;>bbf9i#3;OV6%!UN1(nqd;w zr@{lb=`=2n=*4U*#RH;FI_^|W~VdGb+v zTIn4AsHMi+&%!s>9Pphtb9Vf%85g8j1F$mUOzW!jxWDQLbPSr7Aoo``fXD0g2zYzl5999spXzWFrIVSFj>IQ;t&D z_&Y8B3^qh3VARrTohga#U%(wSrP2K|b_;7F-T!7RYDuO0Q@HKb$@JzMFos$>y?Gw@ z-<(iyeiN=Tr_`HIVkGsXdh_*UAhgGCPLScVCf1v;!$Nv$z4GkG0 z+z(5Fy?Hk17)4`D9lLwjyqsZxi=rd78ptQ=4-J+E$!Z%z+J*ivzB<@co+{2uCt`xHy*-+ ziwS!2ed9r+Qcu5cjAP3%Cvu*RW5ff@Dfp&X3v3}!me!JlZ#sY{IaIGs!#72@IVP}( zN;BX#C*qr~!4uq2VXz@%ny%K2F{VP(RTw7_iK&m7?8oZ~s<0&Fo38AD-3YRp)LT>O zP5U-E;wGxmu%47}x?+dpXk(T-U8CJ_FEd&)>CztE*sH-oY%z0~^65<@R+9}jjK;7w z3`B{hdq^7wwc4z35c`>i0oF2L;71<(h~Oy)JmxfgQ@_ThJ-5);2Dg9I$@)}%(;jUA ztjYSOUUR*Xu5Su!2Cq)oH|^G%Ra5p&yEN;}N&BXqx{6h4`=%bUuvrrKO*_n$T9>x# z23nH$P1_7+J$>J_)o8MQW6`w5ETBDNv6(opIf=hv6JfNZ@i%l^D%FQ5LZsi;RQ`r8 zysBsqOmyP8tEcn%4opSc7W8mL(C6^OdWXJm#|J!)CL2oX^X+6EGt&Be8?G@W_W4#k z2u5n3_hZG{0}d@-M>A{@&|?kFxDD)h{6@E9J#rAf33F=?E^NT+k?U!XE3C(K?IDGA z?XZz*nCkaQ*n7>%exHazwWRww0ULo&G8~T=O)cfm`P_~s1}FV7oOM}$)=O+N^8Tzx zbaXMM%>O#r-l(a5xVKb`t`^50t-_r9=N$pYoc#CTM%O#GLhGhy1z4;PfuZI9(fj|I zb(eWOrQ#F;iU37`B0v$K2v7tl0u%v?07ZZzKoOt_yrB_5@BgRg|2OmyQR65A6ak6= zMSvne5ugZA1SkR&0g3=cfFf|&2+;ihWkX06Py{Ff6ak6=MSvne5ugZA1SkR&0g3=c z;0=!e&HumQhlmOu5ugZA z1SkR&0g3=cfFeK5K;vc0g3=cfFeK2f zIPnt38IqSU2{F!xVpHSesIQY#v1l^hbLW2E8r zTS#N^@wh?Kj3EF{9*fSz4o7EpM23cXbc5?~)kF?fbGh?Jt8y;8m{l^36j;YYlRE-l zV&J9qSffVMX{B7wE@X44i#n|JY;``kiqGh>89+2qlIP16WkrXuz6wDZGNfM%mg9xA zjOOP|;q2NS(3oO{Ty-VCnoscd)b{vX{uIos6rNYu(zKkdy$tx?>elCbF3frHjJsfHtyu zmFHMy9B%j04s&__qD^aar!;@JSferO&Z||dn*WU_`Rw^3tdiPFr8S|=yxHuEQm)7= z#rY6gJZ2LUu#&=hg*-9raryW3*zS<9Cu+?ieJNCE6~r*FI;?Y(iKBD+ok!!*g-q_H z(bSH?Jw~e`Cf6+_I`x=RD#JuuwesJ=xLy999@~X|*bg;jM7{Ns$LfPtqR9g_f_ac$ zh365^&IIN5*A8$l|1zw6=Z%%mGz-;i?j)?dOMxg2~tlsO_6%i zYnv*>X2Z*0#ZF|+F)-FTypEy$6#97$TY}45;S*(q&3x!UV)Zs~TrO9p7RpNLi~`#t zW93zMi=naj!RXxhY$#nV;Tc=emT8#e1=i*757=s5c$y@YN+p|L!gE^PZ<88_)t^@L zVK>6teIl^dr0toubN*esY$veiluyGRO-YrN(^Vx8yEf}b*5WsH#2tBX{@oziQ#)jH z`2zvl9SYW|+NNIY79A(mT9eDEHLshe9g}la7{@yABsWQX{%0Owq)Gk(-@p2%-2JXw zxIb}>vmb=Y*Zt?NbsJsH;jsI@<7cwUS+rN5mMf?59Wbku>p!bFA4|lKhw5n|xh!~( zCuR@Byot@tBooKtt9hbHb*qNvR}zjGvDwh-^&k{Yh7QPO%N{(D*&SMI0wewy@q5BT z?%+rSEfW@U3%{?rmV6mE~Zv_rGrLDizrLTW$$$ zf7A95T&8>VZbCoIy{?=xWJ&+(h7xg48$5~0NjQaG3+K*NNFdIGNjTOU+|Zc@qqN^_ z4_b6I*kQ>LyxQm{dw1@xv%9Q2hTwfAV_%JtVCq`7tsWzA(8F*{ZtN_fL=tagYde}; z{*yhn+KIYd#h6@?*E|Io_#n0x>r!eZuBoHpZ5pXV3$39;gL%H^t>-r24eUL;>h94< z$PhrEnyqW3m1tTjt%TRMVj4PhQ;l1!dc!iD>oweFFZ1NBZI;cw(~dcoyS~Oqy75Yp zlvg@Q8MlE;@8e&?wnn|#%*L;?299Rs(vnhXbQfA9QWGoWE6TZ7BY_SMk@XOD7_dYg z@b+*mbVF5_G`db|zUQuhfI8f&gBrH||2>FU>_QvD`H;T6tg(3f zKU{6}$dffLrN*4Lh%TD^NT8gKX=$lXTC90eqsb4N{CG`~j?3JFX?cxbz2pad{{M9k za?l1T0u%v?07ZZzKoOt_Py{Ff6ak6=MSvpkx+6f(|F1jbv_XmhMSvne5ugZA1SkR& z0g3=cfFeKE* zP${RY%3&o}K;(9<-R_$>rXL9^>B1SMbbeOOFDoSri~l^wbO1|ic6`iAIHwYl(HSe@ zEFnZ^6G;m}YO`_7X2!$yE@dk#g-kW4lr>AF-(=h)(9D)9$Dr%=g}id8Am?K6i(mGS zH2F2|P=#amn7QDMV7O;GN1)6xTTL7m@$Ds!*{>2~a zEo0dAHad&ys8)-`5_)NSA#Z7>t-vweAe}r_D9ITM$)6_}C6zit`wBpWWYUEayvx)g z)RN{4E?SfOZ_39zx8BIoCd7Rin`lUV`)r4E(Q9JP>xDWW)9 zs$_FHm?jo#_YK5uWG?(i3e|YT(yY0wf1ZpsVzCecZz3ab3?^n8-uG`Mo+QLMcsK6B zoQ!=zSJ`?!8FTc~ay%+J#eYI|tzrQKtP?t~Q{A;FpUo#L@QVJlrM=eUL}e1uEtJ49 zVu|{;Sv)AZ+Lk#o)IhWd1!oCrNqAL1n(9_7wB^(XkL(CRA50uUP6^ z$4UF~GwRgQ+Xx&XaG|$TF2LJfEn;ogk{%sUefTjK5Rl51cf~2?Gwx5g?sDW2$Sp-8@lKT%Lp)pA% zPh~Nry70Dr9J7xYO*r7*n@tyr72@DJ->xe-CT`|4OkJ(Cf0Sd6uED&9{+)X{Cbou1 zbQ<3ebX)d}=s**jO}0d{rD9Gd%a<;&ZMd$JhJXdGt|8qKh~oN|w+!lzpfWUSE-*mm zm4>>djkbtsw5Bbj&VI7+f!qNZI+3m$gSI|h^guLW5#6+hV+II(fIupFx+-T%WZLQ| zgnD&nKs*g5LMUwEi07BoWl7huWw#Ch(y<1PZM$?sfy-iO=T5zk(a51xG9JadHKTzZ zqXD3=(Ll!z^+uRTAuaW8-?807t_Lf$&FE$%+6ah_ttJK0)S-0N(uplwOh!i6G;*^= z32fjjN`jlzNu92wW(rlZ4cMTqNgds)Fi@kgvmtAZK@M5TOZY~plRLXql#z5g4Lb$A zRO$4VPIRwubH__^v8a@It<}~Jw1T?1IoLqJZdNJll$Icon3c=RuyX758v`8U2MK&) z&}%oeb4(M|P8AIe?QQDaj%=tA7CL`>E1@0A6&B1q@=XX?OuVkELW zyohfUhRix*3H%SQvEGTePXlzb#4$rKFY)fQkWZ#d*&=%PKhmKy1w^u5WR!*K(h`~U zIx#39H*v$!2R4LdB{W&6C}mF5zmX@MnkW>?ieBsYp&PWD8&ehPg!K*>7sk!?o+)b@ zI=#eIN_gi2|ADK#+*>(UVKsWYN9}dFke5p-*#9l-G`}0%1Gw5+kW*^vcsgae>qx0YTmr8N@z{(qn6w;Ab0=})Dnq}!zh>9Dj@;>GWZeX$Gd4A#f8)$(3Py{Ff6ak6=MSvne5ugZM4g%fnH8|v=m@}Ly7Yb#3 zO3*U|TWWB+MFII^$>XpMgdkm7L9s)n_eR*bI<&pt_8J^<_t{}XjyB9G5?xGh9M$$Iiol!pf!_vhJ+Gc{i%DhKRP17P*w++;WP!sOjBRgA;J9o>^7j zo2%pQs=;{}#ioe8v67+&y2X5;o69EDLLqY-1KkUpn#`fG41Mrj-^df2JyXsf}A7-uQ;h_Fs;ufcJc zWFr&^{^RhpPVB0|VHiO;T`1!W4f-F$=vbh0wAJ7^j8so8+z284IvN~_5p6;)sVOI2 zuB!%zVx&2df%6OecimN*TWfGUMj0U8j8Uh9GOw3NOuL)Tb=B$!O0oHpPZe=M{1oC zsKN0WC8|)U0x~RCwSgKOqY)MUjR^8U9P`m-TWfHlM)t(>)fKB?R}Bu;2n7^ZNI`-I z0!~;ss9C(d21je;JD7t+-5|-6FKdBzopVPG4%=5-Ij!*{opnOkc^Mg85 zM-7hND4R)`qBDo45Lh~WOAQX+XvtBP;QU-l%bn Date: Mon, 28 Mar 2022 10:12:30 +0200 Subject: [PATCH 092/200] AIRBASE - Add'l AB for the Channels map --- Moose Development/Moose/Wrapper/Airbase.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 122a27056..25ba34b76 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -315,7 +315,10 @@ AIRBASE.PersianGulf = { -- * AIRBASE.TheChannel.Lympne -- * AIRBASE.TheChannel.Detling -- * AIRBASE.TheChannel.High_Halden --- +-- * AIRBASE.TheChannel.Biggin_Hill +-- * AIRBASE.TheChannel.Eastchurch +-- * AIRBASE.TheChannel.Headcorn +-- -- @field TheChannel AIRBASE.TheChannel = { ["Abbeville_Drucat"] = "Abbeville Drucat", @@ -327,6 +330,9 @@ AIRBASE.TheChannel = { ["Lympne"] = "Lympne", ["Detling"] = "Detling", ["High_Halden"] = "High Halden", + ["Biggin_Hill"] = "Biggin Hill", + ["Eastchurch"] = "Eastchurch", + ["Headcorn"] = "Headcorn", } --- Airbases of the Syria map: From 4a4257192533fa2d30d80da6c6a2585bbfc4146f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 29 Mar 2022 08:52:33 +0200 Subject: [PATCH 093/200] CTLD - corrected error in setting Hercules min and max drop height, added documentation --- Moose Development/Moose/Ops/CTLD.lua | 33 ++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 4ae5f4720..f3514cd7d 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -870,8 +870,12 @@ do -- -- ## 5. Support for Hercules mod by Anubis -- --- Basic support for the Hercules mod By Anubis has been build into CTLD. Currently this does **not** cover objects and troops which can --- be loaded from the Rearm/Refuel menu, i.e. you can drop them into the field, but you cannot use them in functions scripted with this class. +-- Basic support for the Hercules mod By Anubis has been build into CTLD - that is you can load/drop/build the same objects as the helicopters. +-- To also cover objects and troops which can be loaded from the groud crew Rearm/Refuel menu, you need to use @{#CTLD_HERCULES.New}() and link +-- this object to your CTLD setup. In this case, do **not** use the `Hercules_Cargo.lua` or `Hercules_Cargo_CTLD.lua` which are part of the mod +-- in your mission! +-- +-- ### 5.1 Create an own CTLD instance and allow the usage of the Hercules mod: -- -- local my_ctld = CTLD:New(coalition.side.BLUE,{"Helicargo", "Hercules"},"Lufttransportbrigade I") -- @@ -882,10 +886,31 @@ do -- my_ctld.HercMaxAngels = 2000 -- for troop/cargo drop via chute in meters, ca 6000 ft -- my_ctld.HercMaxSpeed = 77 -- 77mps or 270kph or 150kn -- +-- Hint: you can **only** airdrop from the Hercules if you are "in parameters", i.e. at or below `HercMaxSpeed` and in the AGL bracket between +-- `HercMinAngels` and `HercMaxAngels`! +-- -- Also, the following options need to be set to `true`: -- -- my_ctld.useprefix = true -- this is true by default and MUST BE ON. -- +-- ### 5.2 Integrate Hercules ground crew loadable objects +-- +-- Add ground crew loadable objects to your CTLD instance like so, where `my_ctld` is the previously created CTLD instance: +-- +-- local herccargo = CTLD_HERCULES:New("blue", "Hercules Test", my_ctld) +-- +-- You also need: +-- * A template called "Infantry" for 10 Paratroopers (as set via herccargo.infantrytemplate). +-- * Depending on what you are loading with the help of the ground crew, there are 42 more templates for the various vehicles that are loadable. +-- There's a **quick check output in the `dcs.log`** which tells you what's there and what not. +-- E.g.: +-- ...Checking template for APC BTR-82A Air [24998lb] (BTR-82A) ... MISSING) +-- ...Checking template for ART 2S9 NONA Skid [19030lb] (SAU 2-C9) ... MISSING) +-- ...Checking template for EWR SBORKA Air [21624lb] (Dog Ear radar) ... MISSING) +-- ...Checking template for Transport Tigr Air [15900lb] (Tigr_233036) ... OK) +-- +-- Expected template names are the ones in the rounded brackets. +-- -- Standard transport capabilities as per the real Hercules are: -- -- ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64}, -- 19t cargo, 64 paratroopers @@ -3787,8 +3812,8 @@ end local ucoord = Unit:GetCoordinate() local gheight = ucoord:GetLandHeight() local aheight = uheight - gheight -- height above ground - local maxh = self.HercMinAngels-- 1500m - local minh = self.HercMaxAngels -- 5000m + local minh = self.HercMinAngels-- 1500m + local maxh = self.HercMaxAngels -- 5000m local maxspeed = self.HercMaxSpeed -- 77 mps -- DONE: TEST - Speed test for Herc, should not be above 280kph/150kn local kmspeed = uspeed * 3.6 From fb2031d7ca774103bab7fce7702ec1c8a06a8f01 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 29 Mar 2022 12:01:28 +0200 Subject: [PATCH 094/200] docu nicefy --- Moose Development/Moose/Ops/CTLD.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index f3514cd7d..de3225630 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -899,9 +899,11 @@ do -- -- local herccargo = CTLD_HERCULES:New("blue", "Hercules Test", my_ctld) -- --- You also need: +-- You also need: +-- -- * A template called "Infantry" for 10 Paratroopers (as set via herccargo.infantrytemplate). -- * Depending on what you are loading with the help of the ground crew, there are 42 more templates for the various vehicles that are loadable. +-- -- There's a **quick check output in the `dcs.log`** which tells you what's there and what not. -- E.g.: -- ...Checking template for APC BTR-82A Air [24998lb] (BTR-82A) ... MISSING) From d2a5144a23fd42a811a47e67be68539769d48a72 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 30 Mar 2022 12:06:25 +0200 Subject: [PATCH 095/200] AIRBASE, added ["Deir_ez-Zor"] = "Deir ez-Zor", --- Moose Development/Moose/Wrapper/Airbase.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 25ba34b76..f24590551 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -351,7 +351,6 @@ AIRBASE.TheChannel = { -- * AIRBASE.Syria.Wujah_Al_Hajar -- * AIRBASE.Syria.Al_Dumayr -- * AIRBASE.Syria.Gazipasa --- * AIRBASE.Syria.Ru_Convoy_4 -- * AIRBASE.Syria.Hatay -- * AIRBASE.Syria.Nicosia -- * AIRBASE.Syria.Pinarbashi @@ -369,7 +368,6 @@ AIRBASE.TheChannel = { -- * AIRBASE.Syria.Akrotiri -- * AIRBASE.Syria.Naqoura -- * AIRBASE.Syria.Gaziantep --- * AIRBASE.Syria.CVN_71 -- * AIRBASE.Syria.Sayqal -- * AIRBASE.Syria.Tiyas -- * AIRBASE.Syria.Shayrat @@ -417,7 +415,7 @@ AIRBASE.Syria={ ["Wujah_Al_Hajar"]="Wujah Al Hajar", ["Al_Dumayr"]="Al-Dumayr", ["Gazipasa"]="Gazipasa", - ["Ru_Convoy_4"]="Ru Convoy-4", + -- ["Ru_Convoy_4"]="Ru Convoy-4", ["Hatay"]="Hatay", ["Nicosia"]="Nicosia", ["Pinarbashi"]="Pinarbashi", @@ -465,6 +463,7 @@ AIRBASE.Syria={ ["Ruwayshid"]="Ruwayshid", ["Sanliurfa"]="Sanliurfa", ["Tal_Siman"]="Tal Siman", + ["Deir_ez-Zor"] = "Deir ez-Zor", } --- Airbases of the Mariana Islands map: From 5ed43a319080859d6e5e3d2698a10fee4b0c5d68 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 4 Apr 2022 11:31:35 +0200 Subject: [PATCH 096/200] CTLD - build object at the 1st crate location not randomly around helo --- Moose Development/Moose/Ops/CTLD.lua | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index de3225630..7ca809bab 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -28,7 +28,7 @@ do ------------------------------------------------------ --- **CTLD_ENGINEERING** class, extends Core.Base#BASE ---- @type CTLD_ENGINEERING +-- @type CTLD_ENGINEERING -- @field #string ClassName -- @field #string lid -- @field #string Name @@ -951,7 +951,6 @@ CTLD = { FreeUHFFrequencies = {}, -- Table of UHF FreeFMFrequencies = {}, -- Table of FM CargoCounter = 0, - wpZones = {}, Cargo_Troops = {}, -- generic troops objects Cargo_Crates = {}, -- generic crate objects Loaded_Cargo = {}, -- cargo aboard units @@ -1850,6 +1849,8 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) local drop = drop or false local ship = nil local width = 20 + local distance = nil + local zone = nil if not drop then inzone = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) if not inzone then @@ -2714,6 +2715,8 @@ function CTLD:_BuildCrates(Group, Unit,Engineering) local required = Crate:GetCratesNeeded() local template = Crate:GetTemplates() local ctype = Crate:GetType() + local ccoord = Crate:GetPositionable():GetCoordinate() -- Core.Point#COORDINATE + --local testmarker = ccoord:MarkToAll("Crate found",true,"Build Position") if not buildables[name] then local object = {} -- #CTLD.Buildable object.Name = name @@ -2722,6 +2725,7 @@ function CTLD:_BuildCrates(Group, Unit,Engineering) object.Template = template object.CanBuild = false object.Type = ctype -- #CTLD_CARGO.Enum + object.Coord = ccoord:GetVec2() buildables[name] = object foundbuilds = true else @@ -2883,7 +2887,8 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation) temptable = {temptable} end local zone = ZONE_GROUP:New(string.format("Unload zone-%s",unitname),Group,100) - local randomcoord = zone:GetRandomCoordinate(35):GetVec2() + --local randomcoord = zone:GetRandomCoordinate(35):GetVec2() + local randomcoord = Build.Coord or zone:GetRandomCoordinate(35):GetVec2() if Repair then randomcoord = RepairLocation:GetVec2() end @@ -2892,7 +2897,7 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation) local alias = string.format("%s-%d", _template, math.random(1,100000)) if canmove then self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) - :InitRandomizeUnits(true,20,2) + --:InitRandomizeUnits(true,20,2) :InitDelayOff() :SpawnFromVec2(randomcoord) else -- don't random position of e.g. SAM units build as FOB @@ -5124,7 +5129,7 @@ function CTLD_HERCULES:Cargo_SpawnObjects(Cargo_Drop_initiator,Cargo_Drop_Direct self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 5) self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 10) else - self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 0) + self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country) end else if all_cargo_gets_destroyed == true or Cargo_over_water == true then From b17507d0faa1fc5b63f1c159279f4e5389aacf52 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 4 Apr 2022 12:18:45 +0200 Subject: [PATCH 097/200] Update SpawnStatic.lua - Fixed isCargo flag not honored. --- Moose Development/Moose/Core/SpawnStatic.lua | 35 +++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/Moose Development/Moose/Core/SpawnStatic.lua b/Moose Development/Moose/Core/SpawnStatic.lua index 5fde1c12c..b2ad3523d 100644 --- a/Moose Development/Moose/Core/SpawnStatic.lua +++ b/Moose Development/Moose/Core/SpawnStatic.lua @@ -46,14 +46,14 @@ -- @field #number InitOffsetAngle Link offset angle in degrees. -- @field #number InitStaticHeading Heading of the static. -- @field #string InitStaticLivery Livery for aircraft. --- @field #string InitStaticShape Shape of teh static. +-- @field #string InitStaticShape Shape of the static. -- @field #string InitStaticType Type of the static. -- @field #string InitStaticCategory Categrory of the static. -- @field #string InitStaticName Name of the static. -- @field Core.Point#COORDINATE InitStaticCoordinate Coordinate where to spawn the static. --- @field #boolean InitDead Set static to be dead if true. --- @field #boolean InitCargo If true, static can act as cargo. --- @field #number InitCargoMass Mass of cargo in kg. +-- @field #boolean InitStaticDead Set static to be dead if true. +-- @field #boolean InitStaticCargo If true, static can act as cargo. +-- @field #number InitStaticCargoMass Mass of cargo in kg. -- @extends Core.Base#BASE @@ -260,7 +260,7 @@ end -- @param #number Mass Mass of the cargo in kg. -- @return #SPAWNSTATIC self function SPAWNSTATIC:InitCargoMass(Mass) - self.InitCargoMass=Mass + self.InitStaticCargoMass=Mass return self end @@ -269,7 +269,16 @@ end -- @param #boolean IsCargo If true, this static can act as cargo. -- @return #SPAWNSTATIC self function SPAWNSTATIC:InitCargo(IsCargo) - self.InitCargo=IsCargo + self.InitStaticCargo=IsCargo + return self +end + +--- Initialize as dead. +-- @param #SPAWNSTATIC self +-- @param #boolean IsCargo If true, this static is dead. +-- @return #SPAWNSTATIC self +function SPAWNSTATIC:InitDead(IsDead) + self.InitStaticDead=IsDead return self end @@ -417,16 +426,16 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID) Template.livery_id=self.InitStaticLivery end - if self.InitDead~=nil then - Template.dead=self.InitDead + if self.InitStaticDead~=nil then + Template.dead=self.InitStaticDead end - if self.InitCargo~=nil then - Template.canCargo=self.InitCargo + if self.InitStaticCargo~=nil then + Template.canCargo=self.InitStaticCargo end - if self.InitCargoMass~=nil then - Template.mass=self.InitCargoMass + if self.InitStaticCargoMass~=nil then + Template.mass=self.InitStaticCargoMass end if self.InitLinkUnit then @@ -479,6 +488,8 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID) -- ED's dirty way to spawn FARPS. Static=coalition.addGroup(CountryID, -1, TemplateGroup) else + self:T("Spawning Static") + self:T2({Template=Template}) Static=coalition.addStaticObject(CountryID, Template) end From fa5afae7832a4115c080e15846d5d8055a388daa Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 4 Apr 2022 12:58:28 +0200 Subject: [PATCH 098/200] A2A Dispatcher - nil checks to evade dead units --- Moose Development/Moose/AI/AI_A2A_Dispatcher.lua | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua index 960bfce89..7d48e4ce5 100644 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua @@ -2971,7 +2971,20 @@ do -- AI_A2A_DISPATCHER for FriendlyDistance, AIFriendly in UTILS.spairs( DefenderFriendlies or {} ) do -- We only allow to ENGAGE targets as long as the Units on both sides are balanced. if AttackerCount > DefenderCount then - local Friendly = AIFriendly:GetGroup() -- Wrapper.Group#GROUP + --self:I("***** AI_A2A_DISPATCHER:CountDefendersToBeEngaged() *****\nThis is supposed to be a UNIT:") + if AIFriendly then + local classname = AIFriendly.ClassName or "No Class Name" + local unitname = AIFriendly.IdentifiableName or "No Unit Name" + --self:I("Class Name: " .. classname) + --self:I("Unit Name: " .. unitname) + --self:I({AIFriendly}) + end + local Friendly = nil + if AIFriendly and AIFriendly:IsAlive() then + --self:I("AIFriendly alive, getting GROUP") + Friendly = AIFriendly:GetGroup() -- Wrapper.Group#GROUP + end + if Friendly and Friendly:IsAlive() then -- Ok, so we have a friendly near the potential target. -- Now we need to check if the AIGroup has a Task. From 7f5be2829c13cd3e074e477fc4f761e850bff3c1 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 4 Apr 2022 16:50:15 +0200 Subject: [PATCH 099/200] Update Database.lua - Template statics saved under first statics unit name so they can be found. --- Moose Development/Moose/Core/Database.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index c5f19f1ef..002b43c6e 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -757,7 +757,9 @@ function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, Category local StaticTemplate = UTILS.DeepCopy( StaticTemplate ) - local StaticTemplateName = env.getValueDictByKey( StaticTemplate.name ) + local StaticTemplateGroupName = env.getValueDictByKey( StaticTemplate.name ) + + local StaticTemplateName=StaticTemplate.units[1].name self.Templates.Statics[StaticTemplateName] = self.Templates.Statics[StaticTemplateName] or {} @@ -765,7 +767,7 @@ function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, Category StaticTemplate.CoalitionID = CoalitionID StaticTemplate.CountryID = CountryID - self.Templates.Statics[StaticTemplateName].StaticName = StaticTemplateName + self.Templates.Statics[StaticTemplateName].StaticName = StaticTemplateGroupName self.Templates.Statics[StaticTemplateName].GroupTemplate = StaticTemplate self.Templates.Statics[StaticTemplateName].UnitTemplate = StaticTemplate.units[1] self.Templates.Statics[StaticTemplateName].CategoryID = CategoryID From 7b907df816d526b967e85f84688cf26065289073 Mon Sep 17 00:00:00 2001 From: Penecruz <73371761+Penecruz@users.noreply.github.com> Date: Fri, 8 Apr 2022 15:16:23 +1000 Subject: [PATCH 100/200] Airboss V/STOL Case III and grading (#1708) * Update Airboss.lua * Fix syntax error C --- Moose Development/Moose/Ops/Airboss.lua | 291 +++++++++++++----------- 1 file changed, 160 insertions(+), 131 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 27f883440..8ebf78c15 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -36,6 +36,7 @@ -- * [USS Tarawa](https://en.wikipedia.org/wiki/USS_Tarawa_(LHA-1)) (LHA-1) [**WIP**] -- * [USS America](https://en.wikipedia.org/wiki/USS_America_(LHA-6)) (LHA-6) [**WIP**] -- * [Juan Carlos I](https://en.wikipedia.org/wiki/Spanish_amphibious_assault_ship_Juan_Carlos_I) (L61) [**WIP**] +-- * [HMAS Canberra](https://en.wikipedia.org/wiki/HMAS_Canberra_(L02)) (L02) [**WIP**] -- -- **Supported Aircraft:** -- @@ -54,7 +55,7 @@ -- -- The AV-8B Harrier, HMS Hermes, the USS Tarawa, USS America, HMAS Canberra, and Juan Carlos I are WIP. The AV-8B harrier and the LHA's and LHD can only be used together, i.e. these ships are the only carriers the harrier is supposed to land on and -- no other fixed wing aircraft (human or AI controlled) are supposed to land on these ships. Currently only Case I is supported. Case II/III take slightly different steps from the CVN carrier. --- However, the two Case II/III pattern are very similar so this is not a big drawback. +-- However, if no offset is used for the holding radial this provides a very close representation of the V/STOL Case III, allowing for an approach to over the deck and a vertical landing. -- -- Heatblur's mighty F-14B Tomcat has been added (March 13th 2019) as well. Same goes for the A version. -- @@ -114,10 +115,11 @@ -- * [Harrier Ship Landing Mission with Auto LSO!](https://www.youtube.com/watch?v=lqmVvpunk2c) -- * [Updated Airboss V/STOL Features USS Tarawa](https://youtu.be/K7I4pU6j718) -- * [Harrier Practice pattern USS America](https://youtu.be/99NigITYmcI) +-- * [Harrier CASE III TACAN Approach USS Tarawa](https://www.youtube.com/watch?v=bTgJXZ9Mhdc&t=1s) -- -- === -- --- ### Author: **funkyfranky** +-- ### Author: **funkyfranky** LHA and LHD V/STOL additions by **Pene** -- ### Special Thanks To **Bankler** -- For his great [Recovery Trainer](https://forums.eagle.ru/showthread.php?t=221412) mission and script! -- His work was the initial inspiration for this class. Also note that this implementation uses some routines for determining the player position in Case I recoveries he developed. @@ -2817,13 +2819,29 @@ end -- @param #number Low -- @param #number LOW -- @return #AIRBOSS self -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 + +function AIRBOSS:SetGlideslopeErrorThresholds(_max,_min, High, HIGH, Low, LOW) + + --Check if V/STOL Carrier + if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + + -- allow a larger GSE for V/STOL operations --Pene Testing + self.gle._max=_max or 0.7 + self.gle.High=High or 1.4 + self.gle.HIGH=HIGH or 1.9 + self.gle._min=_min or -0.5 + self.gle.Low=Low or -1.2 + self.gle.LOW=LOW or -1.5 + -- CVN values + else + self.gle._max=_max or 0.4 + self.gle.High=High or 0.8 + self.gle.HIGH=HIGH or 1.5 + self.gle._min=_min or -0.3 + self.gle.Low=Low or -0.6 + self.gle.LOW=LOW or -0.9 + end + return self end @@ -2838,15 +2856,33 @@ end -- @param #number RightMed -- @param #number RIGHT -- @return #AIRBOSS self -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 + +function AIRBOSS:SetLineupErrorThresholds(_max,_min, Left, LeftMed, LEFT, Right, RightMed, RIGHT) + + --Check if V/STOL Carrier -- Pene testing + if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + + -- V/STOL Values -- allow a larger LUE for V/STOL operations + self.lue._max=_max or 1.8 + self.lue._min=_min or -1.8 + self.lue.Left=Left or -2.8 + self.lue.LeftMed=LeftMed or -3.8 + self.lue.LEFT=LEFT or -4.5 + self.lue.Right=Right or 2.8 + self.lue.RightMed=RightMed or 3.8 + self.lue.RIGHT=RIGHT or 4.5 + -- CVN Values + else + self.lue._max=_max or 0.5 + self.lue._min=_min or -0.5 + self.lue.Left=Left or -1.0 + self.lue.LeftMed=LeftMed or -2.0 + self.lue.LEFT=LEFT or -3.0 + self.lue.Right=Right or 1.0 + self.lue.RightMed=RightMed or 2.0 + self.lue.RIGHT=RIGHT or 3.0 + end + return self end @@ -5053,14 +5089,16 @@ function AIRBOSS:_GetAircraftAoA( playerData ) aoa.Fast = 8.25 -- =17.5/2 aoa.FAST = 8.00 -- =16.5/2 elseif harrier then - -- AV-8B Harrier parameters. Tuning done on the Fast AoA to allow for abeam and ninety at Nozzles 60 - 73. - aoa.SLOW = 14.0 - aoa.Slow = 13.0 - aoa.OnSpeedMax = 12.0 - aoa.OnSpeed = 11.0 - aoa.OnSpeedMin = 10.0 - aoa.Fast = 8.0 - aoa.FAST = 7.5 + + -- AV-8B Harrier parameters. Tuning done on the Fast AoA to allow for abeam and ninety at Nozzles 55. Pene testing + aoa.SLOW = 16.0 + aoa.Slow = 13.5 + aoa.OnSpeedMax = 12.5 + aoa.OnSpeed = 10.0 + aoa.OnSpeedMin = 9.5 + aoa.Fast = 8.0 + aoa.FAST = 7.5 + end return aoa @@ -5319,8 +5357,8 @@ function AIRBOSS:_GetAircraftParameters( playerData, step ) elseif skyhawk then alt = UTILS.FeetToMeters( 300 ) -- ? elseif harrier then - -- 300-325 ft - alt = UTILS.FeetToMeters( 300 ) -- Need to verify + alt=UTILS.FeetToMeters(312)-- 300-325 ft + end aoa = aoaac.OnSpeed @@ -9510,8 +9548,9 @@ function AIRBOSS:_Groove( playerData ) -- Speed difference. local dv = math.abs( vplayer - vcarrier ) - -- Stable when speed difference < 20 km/h. - local stable = dv < 20 + + -- Stable when speed difference < 30 km/h.(16 Kts)Pene Testing + local stable=dv<30 -- Check if player is inside the zone. if playerData.unit:IsInZone( ZoneALS ) and stable then @@ -9543,8 +9582,8 @@ function AIRBOSS:_Groove( playerData ) -- Speed difference. local dv = math.abs( vplayer - vcarrier ) - -- Stable when v<10 km/h. - local stable = dv < 10 + -- Stable when v<15 km/h. + local stable=dv<15 -- Radio Transmission "Stabilized" once the aircraft has been cleared to cross and is over the Landing Spot and stable. if playerData.unit:IsInZone( ZoneLS ) and stable and playerData.stable == true then @@ -9583,25 +9622,26 @@ function AIRBOSS:_Groove( playerData ) -- Nothing else necessary. return end - end - -- Long V/STOL groove time Wave Off over 75 seconds to IC - TOPGUN level Only. --pene testing (WIP) - - -- if rho>=RAR and rho<=RIC and not playerData.waveoff and playerData.difficulty==AIRBOSS.Difficulty.HARD and playerData.actype== AIRBOSS.AircraftCarrier.AV8B then - -- Get groove time - -- local vSlow=groovedata.time - -- If too slow wave off. - -- if vSlow >75 then - - -- LSO Wave off! - -- self:RadioTransmission(self.LSORadio, self.LSOCall.WAVEOFF, nil, nil, nil, true) - -- playerData.Tlso=timer.getTime() - - -- Player was waved Off - -- playerData.waveoff=true - -- return - -- end - -- end + end + + -- Long V/STOL groove time Wave Off over 75 seconds to IC - TOPGUN level Only. --pene testing (WIP)--- Need to think more about this. + + --if rho>=RAR and rho<=RIC and not playerData.waveoff and playerData.difficulty==AIRBOSS.Difficulty.HARD and playerData.actype== AIRBOSS.AircraftCarrier.AV8B then + -- Get groove time + --local vSlow=groovedata.time + -- If too slow wave off. + --if vSlow >75 then + + -- LSO Wave off! + --self:RadioTransmission(self.LSORadio, self.LSOCall.WAVEOFF, nil, nil, nil, true) + --playerData.Tlso=timer.getTime() + + -- Player was waved Off + --playerData.waveoff=true + --return + --end + --end -- Groovedata step. groovedata.Step = playerData.step @@ -9772,8 +9812,9 @@ function AIRBOSS:_CheckWaveOff( glideslopeError, lineupError, AoA, playerData ) -- For the harrier, we allow a bit more room. if playerData.actype == AIRBOSS.AircraftCarrier.AV8B then glMax = 2.6 - glMin = -2.6 -- Testing, @Engines may be just dragging it in on Hermes, or the carrier parameters need adjusting. - luAbs = 4.1 -- Testing Pene (WIP) needs feedback to tighten up tolerences. + glMin = -2.2 -- Testing, @Engines may be just dragging it in on Hermes, or the carrier parameters need adjusting. + luAbs = 4.1 -- Testing Pene. + end -- Too high or too low? @@ -9938,17 +9979,23 @@ function AIRBOSS:_GetSternCoord() local hdg = self.carrier:GetHeading() -- Final bearing (true). - local FB = self:GetFinalBearing() + local FB=self:GetFinalBearing() + local case=self.case -- Stern coordinate (sterndist<0). Also translate 10 meters starboard wrt Final bearing. self.sterncoord:UpdateFromCoordinate( self:GetCoordinate() ) -- local stern=self:GetCoordinate() - -- Stern coordinate (sterndist<0). - if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then - -- Tarawa: Translate 8 meters port. - self.sterncoord:Translate( self.carrierparam.sterndist, hdg, true, true ):Translate( 8, FB - 90, true, true ) - elseif self.carriertype == AIRBOSS.CarrierType.STENNIS then + -- Stern coordinate (sterndist<0). --Pene testing Case III + if self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then + if case==3 then + -- CASE III V/STOL translation Due over deck approach if needed. + self.sterncoord:Translate(self.carrierparam.sterndist, hdg, true, true):Translate(8, FB-90, true, true) + elseif case==2 or case==1 then + -- V/Stol: Translate 8 meters port. + self.sterncoord:Translate(self.carrierparam.sterndist, hdg, true, true):Translate(8, FB-90, true, true) + end + elseif self.carriertype==AIRBOSS.CarrierType.STENNIS then -- Stennis: translate 7 meters starboard wrt Final bearing. self.sterncoord:Translate( self.carrierparam.sterndist, hdg, true, true ):Translate( 7, FB + 90, true, true ) elseif self.carriertype == AIRBOSS.CarrierType.FORRESTAL then @@ -10582,6 +10629,7 @@ function AIRBOSS:_GetZoneRunwayBox() end --- Get zone of primary abeam landing position of HMS Hermes, USS Tarawa, USS America and Juan Carlos. Box length 50 meters and width 30 meters. + --- Allow for Clear to land call from LSO approaching abeam the landing spot if stable as per NATOPS 00-80T -- @param #AIRBOSS self -- @return Core.Zone#ZONE_POLYGON Zone surrounding landing runway. @@ -10593,9 +10641,9 @@ function AIRBOSS:_GetZoneAbeamLandingSpot() -- Current carrier heading. local FB = self:GetFinalBearing( false ) - -- Coordinate array. - local p = {} - + -- Coordinate array. Pene Testing extended Abeam landing spot V/STOL. + local p={} + -- Points. p[1] = S:Translate( 15, FB ):Translate( 15, FB + 90 ) -- Top-Right p[2] = S:Translate( -45, FB ):Translate( 15, FB + 90 ) -- Bottom-Right @@ -11028,7 +11076,7 @@ function AIRBOSS:_GetAltCarrier( unit ) return h end ---- Get optimal landing position of the aircraft. Usually between second and third wire. In case of Tarawa and America we take the abeam landing spot 120 ft abeam the 7.5 position, for the Juan Carlos I it is 120 ft and abeam the 5 position. +--- Get optimal landing position of the aircraft. Usually between second and third wire. In case of Tarawa, Canberrra, Juan Carlos and America we take the abeam landing spot 120 ft above and 21 ft abeam the 7.5 position, for the Juan Carlos I and HMS Hermes it is 120 ft above and 21 ft abeam the 5 position. For CASE III it is 120ft directly above the landing spot. -- @param #AIRBOSS self -- @return Core.Point#COORDINATE Optimal landing coordinate. function AIRBOSS:_GetOptLandingCoordinate() @@ -11038,53 +11086,28 @@ function AIRBOSS:_GetOptLandingCoordinate() -- Stern coordinate. -- local stern=self:_GetSternCoord() - -- Final bearing. - local FB = self:GetFinalBearing( false ) - if self.carriertype == AIRBOSS.CarrierType.HERMES then - - -- Landing 100 ft abeam, 100 ft alt. - self.landingcoord:UpdateFromCoordinate( self:_GetLandingSpotCoordinate() ):Translate( 25, FB - 90, true, true ) - -- stern=self:_GetLandingSpotCoordinate():Translate(35, FB-90) - - -- Alitude 100 ft. - self.landingcoord:SetAltitude( UTILS.FeetToMeters( 100 ) ) - elseif self.carriertype == AIRBOSS.CarrierType.TARAWA then - - -- Landing 100 ft abeam, 100 ft alt. - self.landingcoord:UpdateFromCoordinate( self:_GetLandingSpotCoordinate() ):Translate( 35, FB - 90, true, true ) - -- stern=self:_GetLandingSpotCoordinate():Translate(35, FB-90) - - -- Alitude 120 ft. - self.landingcoord:SetAltitude( UTILS.FeetToMeters( 120 ) ) - elseif self.carriertype == AIRBOSS.CarrierType.AMERICA then - - -- Landing 100 ft abeam, 120 ft alt. To allow adjustments to match different deck configurations. - self.landingcoord:UpdateFromCoordinate( self:_GetLandingSpotCoordinate() ):Translate( 35, FB - 90, true, true ) - -- stern=self:_GetLandingSpotCoordinate():Translate(35, FB-90) - - -- Alitude 120 ft. - self.landingcoord:SetAltitude( UTILS.FeetToMeters( 120 ) ) - - elseif self.carriertype == AIRBOSS.CarrierType.JCARLOS then + local FB=self:GetFinalBearing(false) + local case=self.case + -- set Case III V/STOL abeam landing spot over deck -- Pene Testing + if self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then + + if case==3 then + self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate()) + -- Altitude 120ft -- is this corect for Case III? + self.landingcoord:SetAltitude(UTILS.FeetToMeters(120)) + + elseif case==2 or case==1 then -- Landing 100 ft abeam, 120 ft alt. - self.landingcoord:UpdateFromCoordinate( self:_GetLandingSpotCoordinate() ):Translate( 35, FB - 90, true, true ) - -- stern=self:_GetLandingSpotCoordinate():Translate(35, FB-90) + self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate()):Translate(35, FB-90, true, true) + --stern=self:_GetLandingSpotCoordinate():Translate(35, FB-90) -- Alitude 120 ft. - self.landingcoord:SetAltitude( UTILS.FeetToMeters( 120 ) ) - - elseif self.carriertype == AIRBOSS.CarrierType.CANBERRA then - - -- Landing 100 ft abeam, 120 ft alt. - self.landingcoord:UpdateFromCoordinate( self:_GetLandingSpotCoordinate() ):Translate( 35, FB - 90, true, true ) - -- stern=self:_GetLandingSpotCoordinate():Translate(35, FB-90) - - -- Alitude 120 ft. - self.landingcoord:SetAltitude( UTILS.FeetToMeters( 120 ) ) - + self.landingcoord:SetAltitude(UTILS.FeetToMeters(120)) + end + else -- Ideally we want to land between 2nd and 3rd wire. @@ -11112,7 +11135,7 @@ function AIRBOSS:_GetLandingSpotCoordinate() -- Stern coordinate. -- local stern=self:_GetSternCoord() - if self.carriertype == AIRBOSS.CarrierType.HERMES then + if self.carriertype==AIRBOSS.CarrierType.HERMES then -- Landing 100 ft abeam, 100 alt. local hdg = self:GetHeading() @@ -11709,15 +11732,17 @@ function AIRBOSS:_LSOgrade( playerData ) local G = GXX .. " " .. GIM .. " " .. " " .. GIC .. " " .. GAR -- Count number of minor, normal and major deviations. - local N = nXX + nIM + nIC + nAR - local nL = count( G, '_' ) / 2 - local nS = count( G, '%(' ) - local nN = N - nS - nL - - -- Groove time 15-18.99 sec for a unicorn. Or 65-70 for V/STOL unicorn. - local Tgroove = playerData.Tgroove - local TgrooveUnicorn = Tgroove and (Tgroove >= 15.0 and Tgroove <= 18.99) or false - local TgrooveVstolUnicorn = Tgroove and (Tgroove >= 60.0 and Tgroove <= 65.0) and playerData.actype == AIRBOSS.AircraftCarrier.AV8B or false + local N=nXX+nIM+nIC+nAR + local Nv=nXX+nIM + local nL=count(G, '_')/2 + local nS=count(G, '%(') + local nN=N-nS-nL + local nNv=Nv-nS-nL + + -- Groove time 15-18.99 sec for a unicorn. Or 60-65 for V/STOL unicorn. + local Tgroove=playerData.Tgroove + local TgrooveUnicorn=Tgroove and (Tgroove>=15.0 and Tgroove<=18.99) or false + local TgrooveVstolUnicorn=Tgroove and (Tgroove>=60.0 and Tgroove<=65.0)and playerData.actype==AIRBOSS.AircraftCarrier.AV8B or false local grade local points @@ -11728,29 +11753,33 @@ function AIRBOSS:_LSOgrade( playerData ) G = "Unicorn" else - -- Add AV-8B Harrier devation allowances due to lower groundspeed and 3x conventional groove time, this allows to maintain LSO tolerances while respecting the deviations are not unsafe. (WIP requires feedback) - -- Large devaitions still result in a No Grade, A Unicorn still requires a clean pass with no deviation. - if nL > 3 and playerData.actype == AIRBOSS.AircraftCarrier.AV8B then + -- Add AV-8B Harrier devation allowances due to lower groundspeed and 3x conventional groove time, this allows to maintain LSO tolerances while respecting the deviations are not unsafe.--Pene testing + -- Large devaitions still result in a No Grade, A Unicorn still requires a clean pass with no deviation. + if nL > 1 and playerData.actype==AIRBOSS.AircraftCarrier.AV8B then -- Larger deviations ==> "No grade" 2.0 points. - grade = "--" - points = 2.0 - elseif nN > 2 and playerData.actype == AIRBOSS.AircraftCarrier.AV8B then + grade="--" + points=2.0 + elseif nNv >= 1 and playerData.actype==AIRBOSS.AircraftCarrier.AV8B then -- Only average deviations ==> "Fair Pass" Pass with average deviations and corrections. - grade = "(OK)" - points = 3.0 - elseif nL > 0 then + grade="(OK)" + points=3.0 + elseif nNv < 1 and playerData.actype==AIRBOSS.AircraftCarrier.AV8B then + -- Only minor average deviations ==> "OK" Pass with minor deviations and corrections. (test nNv<=1 and) + grade="OK" + points=4.0 + elseif nL > 0 then -- Larger deviations ==> "No grade" 2.0 points. - grade = "--" - points = 2.0 - elseif nN > 0 then + grade="--" + points=2.0 + elseif nN> 0 then -- No larger but average deviations ==> "Fair Pass" Pass with average deviations and corrections. - grade = "(OK)" - points = 3.0 - else + grade="(OK)" + points=3.0 + else -- Only minor corrections - grade = "OK" - points = 4.0 - end + grade="OK" + points=4.0 + end end From f7e14bb60c9f32372ac5511437257b1a195ebf67 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 8 Apr 2022 11:27:31 +0200 Subject: [PATCH 101/200] SET - logic correction in :Remove() --- Moose Development/Moose/Core/Set.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index e834da16c..e49685e37 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -201,7 +201,7 @@ do -- SET_BASE self:F2( { ObjectName = ObjectName } ) local TriggerEvent = true - if NoTriggerEvent == false then + if NoTriggerEvent then TriggerEvent = false end From 0ce1c31e1cc34402e8981e2868fcf02ec934eb72 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 10 Apr 2022 09:29:22 +0200 Subject: [PATCH 102/200] CSAR - added SRS port option --- Moose Development/Moose/Ops/CSAR.lua | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 7c012f5a1..a213f5632 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -124,6 +124,7 @@ -- self.SRSPath = "E:\\Progra~1\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your SRS installation -- server(!) -- self.SRSchannel = 300 -- radio channel -- self.SRSModulation = radio.modulation.AM -- modulation +-- self.SRSport = 5002 -- and SRS port -- -- -- self.csarUsePara = false -- If set to true, will use the LandingAfterEjection Event instead of Ejection --shagrat -- self.wetfeettemplate = "man in floating thingy" -- if you use a mod to have a pilot in a rescue float, put the template name in here for wet feet spawns. Note: in conjunction with csarUsePara this might create dual ejected pilots in edge cases. @@ -254,12 +255,12 @@ CSAR.AircraftType["Mi-8MT"] = 12 CSAR.AircraftType["Mi-24P"] = 8 CSAR.AircraftType["Mi-24V"] = 8 CSAR.AircraftType["Bell-47"] = 2 -CSAR.AircraftType["UH-60L"] = 10 -CSAR.AircraftType["AH-64D_BLK_II"] = 2 +CSAR.AircraftType["UH-60L"] = 10 +CSAR.AircraftType["AH-64D_BLK_II"] = 2 --- CSAR class version. -- @field #string version -CSAR.version="1.0.4d" +CSAR.version="1.0.4e" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -413,6 +414,7 @@ function CSAR:New(Coalition, Template, Alias) self.SRSPath = "E:\\Progra~1\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your server(!) self.SRSchannel = 300 -- radio channel self.SRSModulation = radio.modulation.AM -- modulation + self.SRSport = 5002 -- port ------------------------ --- Pseudo Functions --- @@ -1523,6 +1525,7 @@ function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear, _speak, _overrid local modulation = self.SRSModulation local channel = self.SRSchannel local msrs = MSRS:New(path,channel,modulation) + msrs:SetPort(self.SRSport) msrs:PlaySoundText(srstext, 2) end return self From e84156d2e9dba95c4121706ed1f32653560ab044 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 10 Apr 2022 10:18:12 +0200 Subject: [PATCH 103/200] SRS - add port to docu --- Moose Development/Moose/Sound/SRS.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 8f27c6dbf..7146387df 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -106,6 +106,10 @@ -- -- Use @{#MSRS.SetCoordinate} to define the origin from where the transmission is broadcasted. -- +-- ## Set SRS Port +-- +-- Use @{#MSRS.SetPort} to define the SRS port. Defaults to 5002. +-- -- @field #MSRS MSRS = { ClassName = "MSRS", From a685f3ffbd84aa1f5cac36399a4764f8dc6c5d4d Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 10 Apr 2022 18:28:37 +0200 Subject: [PATCH 104/200] MSRS - honor port setting and coalition. Repaired command string for .bat sound file production Added mission slash in SOUNDFILE --- Moose Development/Moose/Sound/SRS.lua | 12 +++++++----- Moose Development/Moose/Sound/SoundOutput.lua | 8 ++++++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 7146387df..2c4411ac1 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -109,7 +109,7 @@ -- ## Set SRS Port -- -- Use @{#MSRS.SetPort} to define the SRS port. Defaults to 5002. --- +-- -- @field #MSRS MSRS = { ClassName = "MSRS", @@ -212,6 +212,7 @@ end -- @return #MSRS self function MSRS:SetPort(Port) self.port=Port or 5002 + return self end --- Get port. @@ -227,6 +228,7 @@ end -- @return #MSRS self function MSRS:SetCoalition(Coalition) self.coalition=Coalition or 0 + return self end --- Get coalition. @@ -395,7 +397,7 @@ function MSRS:PlaySoundFile(Soundfile, Delay) local command=self:_GetCommand() -- Append file. - command=command.." --file="..tostring(soundfile) + command=command..' --file="'..tostring(soundfile)..'"' self:_ExecCommand(command) @@ -661,12 +663,12 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp --local command=string.format("%s --freqs=%s --modulations=%s --coalition=%d --port=%d --volume=%.2f --speed=%d", exe, freqs, modus, coal, port, volume, speed) -- Command from orig STTS script. Works better for some unknown reason! - local command=string.format("start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -h", path, exe, freqs, modus, coal, port, "ROBOT") + --local command=string.format("start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -h", path, exe, freqs, modus, coal, port, "ROBOT") --local command=string.format('start /b "" /d "%s" "%s" -f %s -m %s -c %s -p %s -n "%s" > bla.txt', path, exe, freqs, modus, coal, port, "ROBOT") -- Command. - local command=string.format('%s/%s -f %s -m %s -c %s -p %s -n "%s"', path, exe, freqs, modus, coal, port, "ROBOT") + local command=string.format('"%s\\%s" -f %s -m %s -c %s -p %s -n "%s"', path, exe, freqs, modus, coal, port, "ROBOT") -- Set voice or gender/culture. if voice then @@ -675,7 +677,7 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp else -- Add gender. if gender and gender~="female" then - command=command..string.format(" --gender=%s", tostring(gender)) + command=command..string.format(" -g %s", tostring(gender)) end -- Add culture. if culture and culture~="en-GB" then diff --git a/Moose Development/Moose/Sound/SoundOutput.lua b/Moose Development/Moose/Sound/SoundOutput.lua index 01fd00483..57ce2f9a4 100644 --- a/Moose Development/Moose/Sound/SoundOutput.lua +++ b/Moose Development/Moose/Sound/SoundOutput.lua @@ -183,13 +183,17 @@ do -- Sound File --- Set path, where the sound file is located. -- @param #SOUNDFILE self - -- @param #string Path Path to the directory, where the sound file is located. + -- @param #string Path Path to the directory, where the sound file is located. In case this is nil, it defaults to the DCS mission temp directory. -- @return #SOUNDFILE self function SOUNDFILE:SetPath(Path) -- Init path. self.path=Path or "l10n/DEFAULT/" - + + if not Path and self.useSRS then -- use path to mission temp dir + self.path = os.getenv('TMP') .. "\\DCS\\Mission\\l10n\\DEFAULT" + end + -- Remove (back)slashes. local nmax=1000 ; local n=1 while (self.path:sub(-1)=="/" or self.path:sub(-1)==[[\]]) and n<=nmax do From 6828f7e262d412ccff939a02a21e283f589c01c0 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 12 Apr 2022 08:23:18 +0200 Subject: [PATCH 105/200] UTILS - corrected door check for AH64 --- Moose Development/Moose/Utilities/Utils.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 12f8e7515..f40b56671 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1747,7 +1747,7 @@ function UTILS.IsLoadingDoorOpen( unit_name ) ret_val = true end - if string.find(type_name, "AH-64D") then + if type_name == "AH-64D_BLK_II" then BASE:T(unit_name .. " front door(s) are open") ret_val = true -- no doors on this one ;) end From 5065d3b068a0def54ebf47cbf5228ec2746e932d Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 13 Apr 2022 16:12:53 +0200 Subject: [PATCH 106/200] UTILS - added FiFo CTLD - correct imperial hover check messages --- Moose Development/Moose/Ops/CTLD.lua | 2 +- Moose Development/Moose/Utilities/Utils.lua | 242 +++++++++++++++++++- 2 files changed, 241 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 7ca809bab..4a7a5e66c 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -3848,7 +3848,7 @@ end else local minheight = UTILS.MetersToFeet(self.minimumHoverHeight) local maxheight = UTILS.MetersToFeet(self.maximumHoverHeight) - text = string.format("Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 6fts \n - In parameter: %s", minheight, maxheight, htxt) + text = string.format("Hover parameters (autoload/drop):\n - Min height %dft \n - Max height %dft \n - Max speed 6ftps \n - In parameter: %s", minheight, maxheight, htxt) end self:_SendMessage(text, 10, false, Group) return self diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index f40b56671..f78dda58f 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1668,8 +1668,8 @@ function UTILS.GetOSTime() end --- Shuffle a table accoring to Fisher Yeates algorithm ---@param #table t Table to be shuffled ---@return #table +--@param #table t Table to be shuffled. +--@return #table Shuffled table. function UTILS.ShuffleTable(t) if t == nil or type(t) ~= "table" then BASE:I("Error in ShuffleTable: Missing or wrong type of Argument") @@ -1687,6 +1687,32 @@ function UTILS.ShuffleTable(t) return TempTable end +--- Get a random element of a table. +--@param #table t Table. +--@param #boolean replace If `true`, the drawn element is replaced, i.e. not deleted. +--@return #number Table element. +function UTILS.GetRandomTableElement(t, replace) + + if t == nil or type(t) ~= "table" then + BASE:I("Error in ShuffleTable: Missing or wrong type of Argument") + return + end + + math.random() + math.random() + math.random() + + local r=math.random(#t) + + local element=t[r] + + if not replace then + table.remove(t, r) + end + + return element +end + --- (Helicopter) Check if one loading door is open. --@param #string unit_name Unit name to be checked --@return #boolean Outcome - true if a (loading door) is open, false if not, nil if none exists. @@ -2329,3 +2355,215 @@ function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce) end return datatable end + +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- FIFO +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +do +--- **UTILS** - FiFo Stack. +-- +-- **Main Features:** +-- +-- * Build a simple multi-purpose FiFo (First-In, First-Out) stack for generic data. +-- +-- === +-- +-- ### Author: **applevangelist** +-- @module Utils.FiFo +-- @image MOOSE.JPG + + +--- FIFO class. +-- @type FIFO +-- @field #string ClassName Name of the class. +-- @field #boolean debug +-- @field #string lid Class id string for output to DCS log file. +-- @field #string version Version of FiFo +-- @field #number counter +-- @field #number pointer +-- @field #number nextin +-- @field #number nextout +-- @field #table stackbypointer +-- @field #table stackbyid +-- @extends Core.Base#BASE +-- + +--- +-- @type FIFO.IDEntry +-- @field #number pointer +-- @field #table data +-- @field #table uniqueID + +--- +-- @field #FIFO +FIFO = { + ClassName = "FIFO", + debug = true, + lid = "", + version = "0.0.1", + counter = 0, + pointer = 0, + nextin = 0, + nextout = 0, + stackbypointer = {}, + stackbyid = {} +} + +--- Instantiate a new FIFO Stack +-- @param #FIFO self +-- @return #FIFO self +function FIFO:New() + -- Inherit everything from BASE class. + local self=BASE:Inherit(self, BASE:New()) -- #INTEL + self.pointer = 0 + self.counter = 0 + self.stackbypointer = {} + self.stackbyid = {} + -- Set some string id for output to DCS.log file. + self.lid=string.format("%s (%s) | ", "FiFo", self.version) + self:I(self.lid .."Created.") + return self +end + +--- FIFO Push Object to Stack +-- @param #FIFO self +-- @param #table Object +-- @param #string UniqueID (optional) - will default to current pointer + 1 +-- @return #FIFO self +function FIFO:Push(Object,UniqueID) + self:T(self.lid.."Push") + self:T({Object,UniqueID}) + self.pointer = self.pointer + 1 + self.counter = self.counter + 1 + self.stackbypointer[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = UniqueID } + if UniqueID then + self.stackbyid[UniqueID] = { pointer = self.pointer, data = Object, uniqueID = UniqueID } + else + self.stackbyid[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = UniqueID } + end + return self +end + +--- FIFO Pull Object from Stack +-- @param #FIFO self +-- @return #table Object or nil if stack is empty +function FIFO:Pull() + self:T(self.lid.."Pull") + if self.counter == 0 then return nil end + local object = self.stackbypointer[self.pointer].data + self.stackbypointer[self.pointer] = nil + self.counter = self.counter - 1 + self.pointer = self.pointer - 1 + self:Flatten() + return object +end + +--- FIFO Pull Object from Stack by Pointer +-- @param #FIFO self +-- @param #number Pointer +-- @return #table Object or nil if stack is empty +function FIFO:PullByPointer(Pointer) + self:T(self.lid.."PullByPointer " .. tostring(Pointer)) + if self.counter == 0 then return nil end + local object = self.stackbypointer[Pointer] -- #FIFO.IDEntry + self.stackbypointer[Pointer] = nil + self.stackbyid[object.uniqueID] = nil + self.counter = self.counter - 1 + self:Flatten() + return object.data +end + +--- FIFO Pull Object from Stack by UniqueID +-- @param #FIFO self +-- @param #tableUniqueID +-- @return #table Object or nil if stack is empty +function FIFO:PullByID(UniqueID) + self:T(self.lid.."PullByID " .. tostring(UniqueID)) + if self.counter == 0 then return nil end + local object = self.stackbyid[UniqueID] -- #FIFO.IDEntry + --self.stackbyid[UniqueID] = nil + return self:PullByPointer(object.pointer) +end + +--- FIFO Housekeeping +-- @param #FIFO self +-- @return #FIFO self +function FIFO:Flatten() + self:T(self.lid.."Flatten") + -- rebuild stacks + local pointerstack = {} + local idstack = {} + local counter = 0 + for _ID,_entry in pairs(self.stackbypointer) do + counter = counter + 1 + pointerstack[counter] = { pointer = counter, data = _entry.data, uniqueID = _entry.uniqueID} + end + for _ID,_entry in pairs(pointerstack) do + idstack[_entry.uniqueID] = { pointer = _entry.pointer , data = _entry.data, uniqueID = _entry.uniqueID} + end + self.stackbypointer = nil + self.stackbypointer = pointerstack + self.stackbyid = nil + self.stackbyid = idstack + self.counter = counter + self.pointer = counter + return self +end + +--- FIFO Check Stack is empty +-- @param #FIFO self +-- @return #boolean empty +function FIFO:IsEmpty() + self:T(self.lid.."IsEmpty") + return self.counter == 0 and true or false +end + +--- FIFO Check Stack is NOT empty +-- @param #FIFO self +-- @return #boolean notempty +function FIFO:IsNotEmpty() + self:T(self.lid.."IsNotEmpty") + return not self:IsEmpty() +end + +--- FIFO Get the data stack by pointer +-- @param #FIFO self +-- @return #table Table of #FIFO.IDEntry entries +function FIFO:GetPointerStack() + self:T(self.lid.."GetPointerStack") + return self.stackbypointer +end + +--- FIFO Get the data stack by UniqueID +-- @param #FIFO self +-- @return #table Table of #FIFO.IDEntry entries +function FIFO:GetIDStack() + self:T(self.lid.."GetIDStack") + return self.stackbyid +end + +--- FIFO Print stacks to dcs.log +-- @param #FIFO self +-- @return #FIFO self +function FIFO:Flush() + self:T(self.lid.."FiFo Flush") + self:I("FIFO Flushing Stack by Pointer") + for _id,_data in pairs (self.stackbypointer) do + local data = _data -- #FIFO.IDEntry + self:I(string.format("Pointer: %s | Entry: Number = %s Data = %s UniID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) + end + self:I("FIFO Flushing Stack by ID") + for _id,_data in pairs (self.stackbyid) do + local data = _data -- #FIFO.IDEntry + self:I(string.format("ID: %s | Entry: Number = %s Data = %s UniID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) + end + self:I("Counter = " .. self.counter) + self:I("Pointer = ".. self.pointer) + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- End FIFO +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +end \ No newline at end of file From 98039b90486b12df3c84de63c8ee01b30ed742df Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 14 Apr 2022 08:12:29 +0200 Subject: [PATCH 107/200] UTISL - FiFo/LiFo stacks --- Moose Development/Moose/Utilities/Utils.lua | 286 +++++++++++++++++++- 1 file changed, 276 insertions(+), 10 deletions(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index f78dda58f..213e4d1ae 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2356,7 +2356,7 @@ function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce) return datatable end --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FIFO ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -2377,13 +2377,10 @@ do --- FIFO class. -- @type FIFO -- @field #string ClassName Name of the class. --- @field #boolean debug -- @field #string lid Class id string for output to DCS log file. -- @field #string version Version of FiFo -- @field #number counter -- @field #number pointer --- @field #number nextin --- @field #number nextout -- @field #table stackbypointer -- @field #table stackbyid -- @extends Core.Base#BASE @@ -2399,13 +2396,10 @@ do -- @field #FIFO FIFO = { ClassName = "FIFO", - debug = true, lid = "", version = "0.0.1", counter = 0, pointer = 0, - nextin = 0, - nextout = 0, stackbypointer = {}, stackbyid = {} } @@ -2451,10 +2445,12 @@ end function FIFO:Pull() self:T(self.lid.."Pull") if self.counter == 0 then return nil end - local object = self.stackbypointer[self.pointer].data - self.stackbypointer[self.pointer] = nil + --local object = self.stackbypointer[self.pointer].data + --self.stackbypointer[self.pointer] = nil + local object = self.stackbypointer[1].data + self.stackbypointer[1] = nil self.counter = self.counter - 1 - self.pointer = self.pointer - 1 + --self.pointer = self.pointer - 1 self:Flatten() return object end @@ -2519,6 +2515,14 @@ function FIFO:IsEmpty() return self.counter == 0 and true or false end +--- FIFO Get stack size +-- @param #FIFO self +-- @return #number size +function FIFO:GetSize() + self:T(self.lid.."GetSize") + return self.counter +end + --- FIFO Check Stack is NOT empty -- @param #FIFO self -- @return #boolean notempty @@ -2543,6 +2547,29 @@ function FIFO:GetIDStack() return self.stackbyid end +--- FIFO Get table of UniqueIDs sorthed smallest to largest +-- @param #FIFO self +-- @return #table Table of #FIFO.IDEntry entries +function FIFO:GetIDStackSorted() + self:T(self.lid.."GetIDStackSorted") + + local stack = self:GetIDStack() + local idstack = {} + for _id,_entry in pairs(stack) do + idstack[#idstack+1] = _id + + self:I({"pre",_id}) + end + + local function sortID(a, b) + return a < b + end + + table.sort(idstack) + + return idstack +end + --- FIFO Print stacks to dcs.log -- @param #FIFO self -- @return #FIFO self @@ -2566,4 +2593,243 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- End FIFO ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- LIFO +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +do +--- **UTILS** - FiFo Stack. +-- +-- **Main Features:** +-- +-- * Build a simple multi-purpose LiFo (Last-In, First-Out) stack for generic data. +-- +-- === +-- +-- ### Author: **applevangelist** +-- @module Utils.LiFo +-- @image MOOSE.JPG + + +--- LIFO class. +-- @type LIFO +-- @field #string ClassName Name of the class. +-- @field #string lid Class id string for output to DCS log file. +-- @field #string version Version of LiFo +-- @field #number counter +-- @field #number pointer +-- @field #table stackbypointer +-- @field #table stackbyid +-- @extends Core.Base#BASE +-- + +--- +-- @type LIFO.IDEntry +-- @field #number pointer +-- @field #table data +-- @field #table uniqueID + +--- +-- @field #LIFO +LIFO = { + ClassName = "LIFO", + lid = "", + version = "0.0.1", + counter = 0, + pointer = 0, + stackbypointer = {}, + stackbyid = {} +} + +--- Instantiate a new LIFO Stack +-- @param #LIFO self +-- @return #LIFO self +function LIFO:New() + -- Inherit everything from BASE class. + local self=BASE:Inherit(self, BASE:New()) -- #INTEL + self.pointer = 0 + self.counter = 0 + self.stackbypointer = {} + self.stackbyid = {} + -- Set some string id for output to DCS.log file. + self.lid=string.format("%s (%s) | ", "LiFo", self.version) + self:I(self.lid .."Created.") + return self +end + +--- LIFO Push Object to Stack +-- @param #LIFO self +-- @param #table Object +-- @param #string UniqueID (optional) - will default to current pointer + 1 +-- @return #LIFO self +function LIFO:Push(Object,UniqueID) + self:T(self.lid.."Push") + self:T({Object,UniqueID}) + self.pointer = self.pointer + 1 + self.counter = self.counter + 1 + self.stackbypointer[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = UniqueID } + if UniqueID then + self.stackbyid[UniqueID] = { pointer = self.pointer, data = Object, uniqueID = UniqueID } + else + self.stackbyid[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = UniqueID } + end + return self +end + +--- LIFO Pull Object from Stack +-- @param #LIFO self +-- @return #table Object or nil if stack is empty +function LIFO:Pull() + self:T(self.lid.."Pull") + if self.counter == 0 then return nil end + local object = self.stackbypointer[self.pointer].data + self.stackbypointer[self.pointer] = nil + --local object = self.stackbypointer[1].data + --self.stackbypointer[1] = nil + self.counter = self.counter - 1 + self.pointer = self.pointer - 1 + self:Flatten() + return object +end + +--- LIFO Pull Object from Stack by Pointer +-- @param #LIFO self +-- @param #number Pointer +-- @return #table Object or nil if stack is empty +function LIFO:PullByPointer(Pointer) + self:T(self.lid.."PullByPointer " .. tostring(Pointer)) + if self.counter == 0 then return nil end + local object = self.stackbypointer[Pointer] -- #LIFO.IDEntry + self.stackbypointer[Pointer] = nil + self.stackbyid[object.uniqueID] = nil + self.counter = self.counter - 1 + self:Flatten() + return object.data +end + +--- LIFO Pull Object from Stack by UniqueID +-- @param #LIFO self +-- @param #tableUniqueID +-- @return #table Object or nil if stack is empty +function LIFO:PullByID(UniqueID) + self:T(self.lid.."PullByID " .. tostring(UniqueID)) + if self.counter == 0 then return nil end + local object = self.stackbyid[UniqueID] -- #LIFO.IDEntry + --self.stackbyid[UniqueID] = nil + return self:PullByPointer(object.pointer) +end + +--- LIFO Housekeeping +-- @param #LIFO self +-- @return #LIFO self +function LIFO:Flatten() + self:T(self.lid.."Flatten") + -- rebuild stacks + local pointerstack = {} + local idstack = {} + local counter = 0 + for _ID,_entry in pairs(self.stackbypointer) do + counter = counter + 1 + pointerstack[counter] = { pointer = counter, data = _entry.data, uniqueID = _entry.uniqueID} + end + for _ID,_entry in pairs(pointerstack) do + idstack[_entry.uniqueID] = { pointer = _entry.pointer , data = _entry.data, uniqueID = _entry.uniqueID} + end + self.stackbypointer = nil + self.stackbypointer = pointerstack + self.stackbyid = nil + self.stackbyid = idstack + self.counter = counter + self.pointer = counter + return self +end + +--- LIFO Check Stack is empty +-- @param #LIFO self +-- @return #boolean empty +function LIFO:IsEmpty() + self:T(self.lid.."IsEmpty") + return self.counter == 0 and true or false +end + +--- LIFO Get stack size +-- @param #LIFO self +-- @return #number size +function LIFO:GetSize() + self:T(self.lid.."GetSize") + return self.counter +end + +--- LIFO Check Stack is NOT empty +-- @param #LIFO self +-- @return #boolean notempty +function LIFO:IsNotEmpty() + self:T(self.lid.."IsNotEmpty") + return not self:IsEmpty() +end + +--- LIFO Get the data stack by pointer +-- @param #LIFO self +-- @return #table Table of #LIFO.IDEntry entries +function LIFO:GetPointerStack() + self:T(self.lid.."GetPointerStack") + return self.stackbypointer +end + +--- LIFO Get the data stack by UniqueID +-- @param #LIFO self +-- @return #table Table of #LIFO.IDEntry entries +function LIFO:GetIDStack() + self:T(self.lid.."GetIDStack") + return self.stackbyid +end + +--- LIFO Get table of UniqueIDs sorthed smallest to largest +-- @param #LIFO self +-- @return #table Table of #LIFO.IDEntry entries +function LIFO:GetIDStackSorted() + self:T(self.lid.."GetIDStackSorted") + + local stack = self:GetIDStack() + local idstack = {} + for _id,_entry in pairs(stack) do + idstack[#idstack+1] = _id + + self:I({"pre",_id}) + end + + local function sortID(a, b) + return a < b + end + + table.sort(idstack) + + return idstack +end + +--- LIFO Print stacks to dcs.log +-- @param #LIFO self +-- @return #LIFO self +function LIFO:Flush() + self:T(self.lid.."FiFo Flush") + self:I("LIFO Flushing Stack by Pointer") + for _id,_data in pairs (self.stackbypointer) do + local data = _data -- #LIFO.IDEntry + self:I(string.format("Pointer: %s | Entry: Number = %s Data = %s UniID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) + end + self:I("LIFO Flushing Stack by ID") + for _id,_data in pairs (self.stackbyid) do + local data = _data -- #LIFO.IDEntry + self:I(string.format("ID: %s | Entry: Number = %s Data = %s UniID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) + end + self:I("Counter = " .. self.counter) + self:I("Pointer = ".. self.pointer) + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- End LIFO +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- end \ No newline at end of file From 03c294354589d6fc53da7e2f897d209bf7dcc611 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 14 Apr 2022 08:53:37 +0200 Subject: [PATCH 108/200] Nicefy docs --- Moose Development/Moose/AI/AI_A2A_Dispatcher.lua | 2 +- Moose Development/Moose/Utilities/Utils.lua | 14 +++----------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua index 7d48e4ce5..fe53bfe28 100644 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua @@ -3583,7 +3583,7 @@ do -- AI_A2A_DISPATCHER end --- Assigns A2G AI Tasks in relation to the detected items. - -- @param #AI_A2G_DISPATCHER self + -- @param #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:Order( DetectedItem ) local detection = self.Detection -- Functional.Detection#DETECTION_AREAS diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 213e4d1ae..5991c2327 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2370,9 +2370,6 @@ do -- === -- -- ### Author: **applevangelist** --- @module Utils.FiFo --- @image MOOSE.JPG - --- FIFO class. -- @type FIFO @@ -2384,7 +2381,6 @@ do -- @field #table stackbypointer -- @field #table stackbyid -- @extends Core.Base#BASE --- --- -- @type FIFO.IDEntry @@ -2409,7 +2405,7 @@ FIFO = { -- @return #FIFO self function FIFO:New() -- Inherit everything from BASE class. - local self=BASE:Inherit(self, BASE:New()) -- #INTEL + local self=BASE:Inherit(self, BASE:New()) self.pointer = 0 self.counter = 0 self.stackbypointer = {} @@ -2600,7 +2596,7 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- do ---- **UTILS** - FiFo Stack. +--- **UTILS** - LiFo Stack. -- -- **Main Features:** -- @@ -2609,9 +2605,6 @@ do -- === -- -- ### Author: **applevangelist** --- @module Utils.LiFo --- @image MOOSE.JPG - --- LIFO class. -- @type LIFO @@ -2623,7 +2616,6 @@ do -- @field #table stackbypointer -- @field #table stackbyid -- @extends Core.Base#BASE --- --- -- @type LIFO.IDEntry @@ -2648,7 +2640,7 @@ LIFO = { -- @return #LIFO self function LIFO:New() -- Inherit everything from BASE class. - local self=BASE:Inherit(self, BASE:New()) -- #INTEL + local self=BASE:Inherit(self, BASE:New()) self.pointer = 0 self.counter = 0 self.stackbypointer = {} From c56763b68fae5e8d8c83e03f1bf6c9b4ee605359 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 14 Apr 2022 11:11:33 +0200 Subject: [PATCH 109/200] FIFO:HasUniqueID(UniqueID) --- Moose Development/Moose/Utilities/Utils.lua | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 5991c2327..998df18ad 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2535,6 +2535,14 @@ function FIFO:GetPointerStack() return self.stackbypointer end +--- FIFO Check if a certain UniqeID exists +-- @param #FIFO self +-- @return #boolean exists +function FIFO:HasUniqueID(UniqueID) + self:T(self.lid.."HasUniqueID") + return self.stackbyid[UniqueID] and true or false +end + --- FIFO Get the data stack by UniqueID -- @param #FIFO self -- @return #table Table of #FIFO.IDEntry entries @@ -2801,6 +2809,14 @@ function LIFO:GetIDStackSorted() return idstack end +--- LIFO Check if a certain UniqeID exists +-- @param #LIFO self +-- @return #boolean exists +function LIFO:HasUniqueID(UniqueID) + self:T(self.lid.."HasUniqueID") + return self.stackbyid[UniqueID] and true or false +end + --- LIFO Print stacks to dcs.log -- @param #LIFO self -- @return #LIFO self From fba359d389504161c675dd03d6293b251fd3ba67 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 14 Apr 2022 15:06:14 +0200 Subject: [PATCH 110/200] LIFO/FIFO enforce unique id --- Moose Development/Moose/Utilities/Utils.lua | 22 ++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 998df18ad..4f85f47db 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2410,6 +2410,7 @@ function FIFO:New() self.counter = 0 self.stackbypointer = {} self.stackbyid = {} + self.uniquecounter = 0 -- Set some string id for output to DCS.log file. self.lid=string.format("%s (%s) | ", "FiFo", self.version) self:I(self.lid .."Created.") @@ -2430,7 +2431,8 @@ function FIFO:Push(Object,UniqueID) if UniqueID then self.stackbyid[UniqueID] = { pointer = self.pointer, data = Object, uniqueID = UniqueID } else - self.stackbyid[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = UniqueID } + self.uniquecounter = self.uniquecounter + 1 + self.stackbyid[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = self.uniquecounter } end return self end @@ -2540,7 +2542,7 @@ end -- @return #boolean exists function FIFO:HasUniqueID(UniqueID) self:T(self.lid.."HasUniqueID") - return self.stackbyid[UniqueID] and true or false + return self.stackbyid[UniqueID] and true or false end --- FIFO Get the data stack by UniqueID @@ -2562,7 +2564,7 @@ function FIFO:GetIDStackSorted() for _id,_entry in pairs(stack) do idstack[#idstack+1] = _id - self:I({"pre",_id}) + self:T({"pre",_id}) end local function sortID(a, b) @@ -2582,12 +2584,12 @@ function FIFO:Flush() self:I("FIFO Flushing Stack by Pointer") for _id,_data in pairs (self.stackbypointer) do local data = _data -- #FIFO.IDEntry - self:I(string.format("Pointer: %s | Entry: Number = %s Data = %s UniID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) + self:I(string.format("Pointer: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) end self:I("FIFO Flushing Stack by ID") for _id,_data in pairs (self.stackbyid) do local data = _data -- #FIFO.IDEntry - self:I(string.format("ID: %s | Entry: Number = %s Data = %s UniID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) + self:I(string.format("ID: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) end self:I("Counter = " .. self.counter) self:I("Pointer = ".. self.pointer) @@ -2651,6 +2653,7 @@ function LIFO:New() local self=BASE:Inherit(self, BASE:New()) self.pointer = 0 self.counter = 0 + self.uniquecounter = 0 self.stackbypointer = {} self.stackbyid = {} -- Set some string id for output to DCS.log file. @@ -2673,7 +2676,8 @@ function LIFO:Push(Object,UniqueID) if UniqueID then self.stackbyid[UniqueID] = { pointer = self.pointer, data = Object, uniqueID = UniqueID } else - self.stackbyid[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = UniqueID } + self.uniquecounter = self.uniquecounter + 1 + self.stackbyid[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = self.uniquecounter } end return self end @@ -2797,7 +2801,7 @@ function LIFO:GetIDStackSorted() for _id,_entry in pairs(stack) do idstack[#idstack+1] = _id - self:I({"pre",_id}) + self:T({"pre",_id}) end local function sortID(a, b) @@ -2825,12 +2829,12 @@ function LIFO:Flush() self:I("LIFO Flushing Stack by Pointer") for _id,_data in pairs (self.stackbypointer) do local data = _data -- #LIFO.IDEntry - self:I(string.format("Pointer: %s | Entry: Number = %s Data = %s UniID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) + self:I(string.format("Pointer: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) end self:I("LIFO Flushing Stack by ID") for _id,_data in pairs (self.stackbyid) do local data = _data -- #LIFO.IDEntry - self:I(string.format("ID: %s | Entry: Number = %s Data = %s UniID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) + self:I(string.format("ID: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) end self:I("Counter = " .. self.counter) self:I("Pointer = ".. self.pointer) From ba8505c9839ea359ae2de2ad1706190b4a4c236e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 14 Apr 2022 15:54:38 +0200 Subject: [PATCH 111/200] FIFO --- Moose Development/Moose/Utilities/Utils.lua | 24 ++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 4f85f47db..b782c9a93 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2427,13 +2427,13 @@ function FIFO:Push(Object,UniqueID) self:T({Object,UniqueID}) self.pointer = self.pointer + 1 self.counter = self.counter + 1 - self.stackbypointer[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = UniqueID } - if UniqueID then - self.stackbyid[UniqueID] = { pointer = self.pointer, data = Object, uniqueID = UniqueID } - else - self.uniquecounter = self.uniquecounter + 1 - self.stackbyid[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = self.uniquecounter } + local uniID = UniqueID + if not UniqueID then + self.uniquecounter = self.uniquecounter + 1 + uniID = self.uniquecounter end + self.stackbyid[uniID] = { pointer = self.pointer, data = Object, uniqueID = uniID } + self.stackbypointer[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = uniID } return self end @@ -2672,13 +2672,13 @@ function LIFO:Push(Object,UniqueID) self:T({Object,UniqueID}) self.pointer = self.pointer + 1 self.counter = self.counter + 1 - self.stackbypointer[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = UniqueID } - if UniqueID then - self.stackbyid[UniqueID] = { pointer = self.pointer, data = Object, uniqueID = UniqueID } - else - self.uniquecounter = self.uniquecounter + 1 - self.stackbyid[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = self.uniquecounter } + local uniID = UniqueID + if not UniqueID then + self.uniquecounter = self.uniquecounter + 1 + uniID = self.uniquecounter end + self.stackbyid[uniID] = { pointer = self.pointer, data = Object, uniqueID = uniID } + self.stackbypointer[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = uniID } return self end From 09dafe4b1dcb99c455a27c3a716e157ad61fa5fd Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 20 Apr 2022 14:02:16 +0200 Subject: [PATCH 112/200] UTILS - added BearingToCardinal, ToStringBRAANATO --- Moose Development/Moose/Utilities/Utils.lua | 43 +++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index b782c9a93..730920206 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2356,6 +2356,49 @@ function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce) return datatable end +--- Heading Degrees (0-360) to Cardinal +-- @param #number Heading The heading +-- @return #string Cardinal, e.g. "NORTH" +function Utils.BearingToCardinal(Heading) + if Heading >= 0 and Heading <= 22 then return "North" + elseif Heading >= 23 and Heading <= 66 then return "North-East" + elseif Heading >= 67 and Heading <= 101 then return "East" + elseif Heading >= 102 and Heading <= 146 then return "South-East" + elseif Heading >= 147 and Heading <= 201 then return "South" + elseif Heading >= 202 and Heading <= 246 then return "South-West" + elseif Heading >= 247 and Heading <= 291 then return "West" + elseif Heading >= 292 and Heading <= 338 then return "North-West" + elseif Heading >= 339 then return "North" + end +end + +--- Create a BRAA NATO call string BRAA between two GROUP objects +-- @param Wrapper.Group#GROUP FromGrp GROUP object +-- @param Wrapper.Group#GROUP ToGrp GROUP object +-- @return #string Formatted BRAA NATO call +function Utils.ToStringBRAANATO(FromGrp,ToGrp) + local BRAANATO = "Merged." + local GroupNumber = FromGrp:GetSize() + local GroupWords = "Singleton" + if GroupNumber == 2 then GroupWords = "Two-Ship" + elseif GroupNumber >= 3 then GroupWords = "Heavy" + end + local grpLeadUnit = ToGrp:GetUnit(1) + local tgtCoord = grpLeadUnit:GetCoordinate() + local currentCoord = FromGrp:GetCoordinate() + local hdg = UTILS.Round(ToGrp:GetHeading()/100,1)*100 + local bearing = UTILS.Round(currentCoord:HeadingTo(tgtCoord),0) + local rangeMetres = tgtCoord:Get2DDistance(currentCoord) + local rangeNM = UTILS.Round( UTILS.MetersToNM(rangeMetres), 0) + local aspect = tgtCoord:ToStringAspect(currentCoord) + local alt = UTILS.Round(UTILS.MetersToFeet(grpLeadUnit:GetAltitude())/1000,0)--*1000 + local track = Utils.BearingToCardinal(hdg) + if rangeNM > 3 then + BRAANATO = string.format("%s, BRAA, %s, %d miles, Angels %d, %s, Track %s, Spades.",GroupWords,bearing, rangeNM, alt, aspect, track) + end + return BRAANATO +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FIFO ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From a385ed57fb1c1efc951be719ae8810747e8fe462 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 20 Apr 2022 14:02:55 +0200 Subject: [PATCH 113/200] ZONE - added example to Scan, some minor changes SET_GROUP - clarified return value to be a table, not a SET --- Moose Development/Moose/Core/Set.lua | 2 +- Moose Development/Moose/Core/Zone.lua | 17 ++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index e49685e37..800116360 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -1004,7 +1004,7 @@ do -- SET_GROUP --- Gets the Set. -- @param #SET_GROUP self - -- @return #SET_GROUP self + -- @return #table Table of objects function SET_GROUP:GetAliveSet() self:F2() diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index c97d879a1..7f4828df6 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -59,6 +59,7 @@ -- @field #number ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability. -- @field #number DrawID Unique ID of the drawn zone on the F10 map. -- @field #table Color Table with four entries, e.g. {1, 0, 0, 0.15}. First three are RGB color code. Fourth is the transparency Alpha value. +-- @field #number ZoneID ID of zone. Only zones defined in the ME have an ID! -- @extends Core.Fsm#FSM @@ -108,7 +109,8 @@ ZONE_BASE = { ZoneName = "", ZoneProbability = 1, DrawID=nil, - Color={} + Color={}, + ZoneID=nil, } @@ -620,7 +622,7 @@ function ZONE_RADIUS:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, Lin Color=Color or self:GetColorRGB() Alpha=Alpha or 1 - FillColor=FillColor or Color + FillColor=FillColor or UTILS.DeepCopy(Color) FillAlpha=FillAlpha or self:GetColorAlpha() self.DrawID=coordinate:CircleToAll(Radius, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) @@ -808,7 +810,7 @@ end --- Scan the zone for the presence of units of the given ObjectCategories. --- Note that after a zone has been scanned, the zone can be evaluated by: +-- Note that **only after** a zone has been scanned, the zone can be evaluated by: -- -- * @{ZONE_RADIUS.IsAllInZoneOfCoalition}(): Scan the presence of units in the zone of a coalition. -- * @{ZONE_RADIUS.IsAllInZoneOfOtherCoalition}(): Scan the presence of units in the zone of an other coalition. @@ -817,10 +819,10 @@ end -- * @{ZONE_RADIUS.IsNoneInZone}(): Scan if the zone is empty. -- @{#ZONE_RADIUS. -- @param #ZONE_RADIUS self --- @param ObjectCategories An array of categories of the objects to find in the zone. --- @param UnitCategories An array of unit categories of the objects to find in the zone. +-- @param ObjectCategories An array of categories of the objects to find in the zone. E.g. `{Object.Category.UNIT}` +-- @param UnitCategories An array of unit categories of the objects to find in the zone. E.g. `{Unit.Category.GROUND_UNIT,Unit.Category.SHIP}` -- @usage --- self.Zone:Scan() +-- self.Zone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT}) -- local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition ) function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) @@ -1860,7 +1862,8 @@ function ZONE_POLYGON_BASE:DrawZone(Coalition, Color, Alpha, FillColor, FillAlph Color=Color or self:GetColorRGB() Alpha=Alpha or 1 - FillColor=FillColor or Color + + FillColor=FillColor or UTILS.DeepCopy(Color) FillAlpha=FillAlpha or self:GetColorAlpha() From e6fc301b0dac71003170954e26dd8c126762c7f5 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 20 Apr 2022 19:14:36 +0200 Subject: [PATCH 114/200] Utils Typo --- Moose Development/Moose/Utilities/Utils.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 730920206..d087bf153 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2359,7 +2359,7 @@ end --- Heading Degrees (0-360) to Cardinal -- @param #number Heading The heading -- @return #string Cardinal, e.g. "NORTH" -function Utils.BearingToCardinal(Heading) +function UTILS.BearingToCardinal(Heading) if Heading >= 0 and Heading <= 22 then return "North" elseif Heading >= 23 and Heading <= 66 then return "North-East" elseif Heading >= 67 and Heading <= 101 then return "East" @@ -2376,7 +2376,7 @@ end -- @param Wrapper.Group#GROUP FromGrp GROUP object -- @param Wrapper.Group#GROUP ToGrp GROUP object -- @return #string Formatted BRAA NATO call -function Utils.ToStringBRAANATO(FromGrp,ToGrp) +function UTILS.ToStringBRAANATO(FromGrp,ToGrp) local BRAANATO = "Merged." local GroupNumber = FromGrp:GetSize() local GroupWords = "Singleton" @@ -2392,7 +2392,7 @@ function Utils.ToStringBRAANATO(FromGrp,ToGrp) local rangeNM = UTILS.Round( UTILS.MetersToNM(rangeMetres), 0) local aspect = tgtCoord:ToStringAspect(currentCoord) local alt = UTILS.Round(UTILS.MetersToFeet(grpLeadUnit:GetAltitude())/1000,0)--*1000 - local track = Utils.BearingToCardinal(hdg) + local track = UTILS.BearingToCardinal(hdg) if rangeNM > 3 then BRAANATO = string.format("%s, BRAA, %s, %d miles, Angels %d, %s, Track %s, Spades.",GroupWords,bearing, rangeNM, alt, aspect, track) end From c02ae82003c1222ab01f2aede1e1c818aa4963f9 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 21 Apr 2022 18:59:31 +0200 Subject: [PATCH 115/200] Point - added ToStringBRAANATO --- Moose Development/Moose/Core/Point.lua | 41 ++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 2826969bb..cc305083a 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -2727,7 +2727,48 @@ do -- COORDINATE local Altitude = self:GetAltitudeText() return "BRA, " .. self:GetBRAText( AngleRadians, Distance, Settings, Language ) end + + --- Create a BRAA NATO call string to this COORDINATE from the FromCOORDINATE. Note - BRA delivered if no aspect can be obtained and "Merged" if range < 3nm + -- @param #COORDINATE self + -- @param #COORDINATE FromCoordinate The coordinate to measure the distance and the bearing from. + -- @param #boolean Spades Add "Spades" at the end if true (no IFF/VID ID yet known) + -- @return #string The BRAA text. + function COORDINATE:ToStringBRAANATO(FromCoordinate,Spades) + + -- Thanks to @Pikey + local BRAANATO = "Merged." + local currentCoord = FromCoordinate + local DirectionVec3 = FromCoordinate:GetDirectionVec3( self ) + local AngleRadians = self:GetAngleRadians( DirectionVec3 ) + + local bearing = UTILS.Round( UTILS.ToDegree( AngleRadians ),0 ) + + local rangeMetres = self:Get2DDistance(currentCoord) + local rangeNM = UTILS.Round( UTILS.MetersToNM(rangeMetres), 0) + + local aspect = self:ToStringAspect(currentCoord) + + local alt = UTILS.Round(UTILS.MetersToFeet(self.y)/1000,0)--*1000 + + local track = UTILS.BearingToCardinal(bearing) or "North" + + if rangeNM > 3 then + if aspect == "" then + BRAANATO = string.format("BRA, %s, %d miles, Angels %d, Track %s",bearing, rangeNM, alt, track) + else + BRAANATO = string.format("BRAA, %s, %d miles, Angels %d, %s, Track %s",bearing, rangeNM, alt, aspect, track) + end + if Spades then + BRAANATO = BRAANATO..", Spades." + else + BRAANATO = BRAANATO.."." + end + end + + return BRAANATO + end + --- Return a BULLS string out of the BULLS of the coalition to the COORDINATE. -- @param #COORDINATE self -- @param DCS#coalition.side Coalition The coalition. From e08fb2e9723b21b849ea8c90b79368f14cfaaf0d Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 22 Apr 2022 13:31:58 +0200 Subject: [PATCH 116/200] Make BRAA heading a 3digit number --- Moose Development/Moose/Core/Point.lua | 4 ++-- Moose Development/Moose/Utilities/Utils.lua | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index cc305083a..182133a1f 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -2755,9 +2755,9 @@ do -- COORDINATE if rangeNM > 3 then if aspect == "" then - BRAANATO = string.format("BRA, %s, %d miles, Angels %d, Track %s",bearing, rangeNM, alt, track) + BRAANATO = string.format("BRA, %03d, %d miles, Angels %d, Track %s",bearing, rangeNM, alt, track) else - BRAANATO = string.format("BRAA, %s, %d miles, Angels %d, %s, Track %s",bearing, rangeNM, alt, aspect, track) + BRAANATO = string.format("BRAA, %03d, %d miles, Angels %d, %s, Track %s",bearing, rangeNM, alt, aspect, track) end if Spades then BRAANATO = BRAANATO..", Spades." diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index d087bf153..46805e997 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2394,7 +2394,11 @@ function UTILS.ToStringBRAANATO(FromGrp,ToGrp) local alt = UTILS.Round(UTILS.MetersToFeet(grpLeadUnit:GetAltitude())/1000,0)--*1000 local track = UTILS.BearingToCardinal(hdg) if rangeNM > 3 then - BRAANATO = string.format("%s, BRAA, %s, %d miles, Angels %d, %s, Track %s, Spades.",GroupWords,bearing, rangeNM, alt, aspect, track) + if aspect == "" then + BRAANATO = string.format("%s, BRA, %03d, %d miles, Angels %d, Track %s",GroupWords,bearing, rangeNM, alt, track) + else + BRAANATO = string.format("%s, BRAA, %03d, %d miles, Angels %d, %s, Track %s",GroupWords, bearing, rangeNM, alt, aspect, track) + end end return BRAANATO end From e0397dff478a2da4d6ed6832f3e664e1cc3be39c Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 24 Apr 2022 13:50:07 +0200 Subject: [PATCH 117/200] GROUP - overwrite GetHeight() inherited from POSITIONABLE with something that is actually working for groups --- Moose Development/Moose/Wrapper/Group.lua | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 7e39503f8..5773a12a2 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -797,11 +797,19 @@ function GROUP:GetVelocityVec3() return nil end +--- Returns the average group altitude in meters. +-- @param Wrapper.Group#GROUP self +-- @param #boolean FromGround Measure from the ground or from sea level (ASL). Provide **true** for measuring from the ground (AGL). **false** or **nil** if you measure from sea level. +-- @return #number The altitude of the group or nil if is not existing or alive. +function GROUP:GetAltitude(FromGround) + self:F2( self.GroupName ) + return self:GetHeight(FromGround) +end --- Returns the average group height in meters. -- @param Wrapper.Group#GROUP self --- @param #boolean FromGround Measure from the ground or from sea level. Provide **true** for measuring from the ground. **false** or **nil** if you measure from sea level. --- @return DCS#Vec3 The height of the group or nil if is not existing or alive. +-- @param #boolean FromGround Measure from the ground or from sea level (ASL). Provide **true** for measuring from the ground (AGL). **false** or **nil** if you measure from sea level. +-- @return #number The height of the group or nil if is not existing or alive. function GROUP:GetHeight( FromGround ) self:F2( self.GroupName ) From c5ecba3389feee2d68d873b48527a51b06c48e39 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 24 Apr 2022 14:05:45 +0200 Subject: [PATCH 118/200] Menu cleanup for Refresh() --- Moose Development/Moose/Core/Menu.lua | 508 +++++++++++--------------- 1 file changed, 208 insertions(+), 300 deletions(-) diff --git a/Moose Development/Moose/Core/Menu.lua b/Moose Development/Moose/Core/Menu.lua index b3d6ab618..ab62ed465 100644 --- a/Moose Development/Moose/Core/Menu.lua +++ b/Moose Development/Moose/Core/Menu.lua @@ -1,9 +1,9 @@ --- **Core** - Manage hierarchical menu structures and commands for players within a mission. --- +-- -- === --- +-- -- ### Features: --- +-- -- * Setup mission sub menus. -- * Setup mission command menus. -- * Setup coalition sub menus. @@ -21,35 +21,35 @@ -- * Update the parameters and the receiving methods, without updating the menu within DCS! -- * Provide a great performance boost in menu management. -- * Provide a great tool to manage menus in your code. --- --- DCS Menus can be managed using the MENU classes. +-- +-- DCS Menus can be managed using the MENU classes. -- The advantage of using MENU classes is that it hides the complexity of dealing with menu management in more advanced scenarios where you need to -- set menus and later remove them, and later set them again. You'll find while using use normal DCS scripting functions, that setting and removing --- menus is not a easy feat if you have complex menu hierarchies defined. +-- menus is not a easy feat if you have complex menu hierarchies defined. -- Using the MOOSE menu classes, the removal and refreshing of menus are nicely being handled within these classes, and becomes much more easy. --- On top, MOOSE implements **variable parameter** passing for command menus. --- +-- On top, MOOSE implements **variable parameter** passing for command menus. +-- -- There are basically two different MENU class types that you need to use: --- +-- -- ### To manage **main menus**, the classes begin with **MENU_**: --- +-- -- * @{Core.Menu#MENU_MISSION}: Manages main menus for whole mission file. -- * @{Core.Menu#MENU_COALITION}: Manages main menus for whole coalition. -- * @{Core.Menu#MENU_GROUP}: Manages main menus for GROUPs. --- +-- -- ### To manage **command menus**, which are menus that allow the player to issue **functions**, the classes begin with **MENU_COMMAND_**: --- +-- -- * @{Core.Menu#MENU_MISSION_COMMAND}: Manages command menus for whole mission file. -- * @{Core.Menu#MENU_COALITION_COMMAND}: Manages command menus for whole coalition. -- * @{Core.Menu#MENU_GROUP_COMMAND}: Manages command menus for GROUPs. --- +-- -- === ---- +--- -- ### Author: **FlightControl** --- ### Contributions: --- +-- ### Contributions: +-- -- === --- +-- -- @module Core.Menu -- @image Core_Menu.JPG @@ -64,20 +64,19 @@ 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 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 } ) + 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 } ) + BASE:E( { Path = Path, Coalition = Coalition } ) error( "Parent path not found in menu index for coalition menu" ) return nil end @@ -89,25 +88,23 @@ function MENU_INDEX:ParentPath( ParentMenu, MenuText ) end end end - + Path = Path .. "@" .. MenuText return Path - end function MENU_INDEX:PrepareMission() - self.MenuMission.Menus = self.MenuMission.Menus or {} + 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 {} + self.Coalition[CoalitionSide] = self.Coalition[CoalitionSide] or {} + self.Coalition[CoalitionSide].Menus = self.Coalition[CoalitionSide].Menus or {} end - --- -- @param Wrapper.Group#GROUP Group function MENU_INDEX:PrepareGroup( Group ) - if Group and Group:IsAlive() ~= nil then -- something was changed here! + if Group and Group:IsAlive() ~= nil then -- something was changed here! local GroupName = Group:GetName() self.Group[GroupName] = self.Group[GroupName] or {} self.Group[GroupName].Menus = self.Group[GroupName].Menus or {} @@ -115,32 +112,22 @@ function MENU_INDEX:PrepareGroup( Group ) 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 @@ -151,46 +138,36 @@ function MENU_INDEX:HasGroupMenu( Group, Path ) end return nil end - function MENU_INDEX:SetGroupMenu( Group, Path, Menu ) - local MenuGroupName = Group:GetName() - Group:F( { MenuGroupName = MenuGroupName, Path = Path } ) + 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 - + for MenuID, Menu in pairs( self.MenuMission.Menus ) do + Menu:Refresh() + end + for MenuID, Menu in pairs( self.Coalition[coalition.side.BLUE].Menus ) do + Menu:Refresh() + end + for MenuID, Menu in pairs( self.Coalition[coalition.side.RED].Menus ) do + Menu:Refresh() + end + local GroupName = Group:GetName() + for MenuID, Menu in pairs( self.Group[GroupName].Menus ) do + Menu:Refresh() + end + + return self end do -- MENU_BASE - --- @type MENU_BASE -- @extends Core.Base#BASE - --- Defines the main MENU class where other MENU classes are derived from. -- This is an abstract class, so don't use it. -- @field #MENU_BASE @@ -200,37 +177,35 @@ do -- MENU_BASE MenuText = "", MenuParentPath = nil, } - + --- Constructor -- @param #MENU_BASE -- @return #MENU_BASE 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.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.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 {} @@ -238,27 +213,25 @@ do -- MENU_BASE 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 - -- self.ParentMenu:Remove() + --self.ParentMenu:Remove() end end end - --- Sets a @{Menu} to remove automatically the parent menu when the menu removed is the last child menu of that parent @{Menu}. -- @param #MENU_BASE self -- @param #boolean RemoveParent If true, the parent menu is automatically removed when this menu is the last child menu of that parent @{Menu}. -- @return #MENU_BASE function MENU_BASE:SetRemoveParent( RemoveParent ) - -- self:F( { RemoveParent } ) + --self:F( { RemoveParent } ) self.MenuRemoveParent = RemoveParent return self end - + --- Gets a @{Menu} from a parent @{Menu} -- @param #MENU_BASE self -- @param #string MenuText The text of the child menu. @@ -266,7 +239,6 @@ do -- MENU_BASE function MENU_BASE:GetMenu( MenuText ) return self.Menus[MenuText] end - --- Sets a menu stamp for later prevention of menu removal. -- @param #MENU_BASE self -- @param MenuStamp @@ -275,14 +247,16 @@ do -- MENU_BASE self.MenuStamp = MenuStamp return self end - + + --- Gets a menu stamp for later prevention of menu removal. -- @param #MENU_BASE self -- @return MenuStamp function MENU_BASE:GetStamp() return timer.getTime() end - + + --- Sets a time stamp for later prevention of menu removal. -- @param #MENU_BASE self -- @param MenuStamp @@ -291,7 +265,7 @@ do -- MENU_BASE self.MenuStamp = MenuStamp return self end - + --- Sets a tag for later selection of menu refresh. -- @param #MENU_BASE self -- @param #string MenuTag A Tag or Key that will filter only menu items set with this key. @@ -300,18 +274,16 @@ do -- MENU_BASE self.MenuTag = MenuTag return self end - + end - do -- MENU_COMMAND_BASE - --- @type MENU_COMMAND_BASE -- @field #function MenuCallHandler -- @extends Core.Menu#MENU_BASE - - --- Defines the main MENU class where other MENU COMMAND_ + + --- Defines the main MENU class where other MENU COMMAND_ -- classes are derived from, in order to set commands. - -- + -- -- @field #MENU_COMMAND_BASE MENU_COMMAND_BASE = { ClassName = "MENU_COMMAND_BASE", @@ -319,14 +291,13 @@ do -- MENU_COMMAND_BASE CommandMenuArgument = nil, MenuCallHandler = nil, } - + --- Constructor -- @param #MENU_COMMAND_BASE -- @return #MENU_COMMAND_BASE function MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, CommandMenuArguments ) - + local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) -- #MENU_COMMAND_BASE - -- When a menu function goes into error, DCS displays an obscure menu message. -- This error handler catches the menu error and displays the full call stack. local ErrorHandler = function( errmsg ) @@ -336,20 +307,20 @@ do -- MENU_COMMAND_BASE end return errmsg end - + self:SetCommandMenuFunction( CommandMenuFunction ) self:SetCommandMenuArguments( CommandMenuArguments ) self.MenuCallHandler = function() - local function MenuFunction() + local function MenuFunction() return self.CommandMenuFunction( unpack( self.CommandMenuArguments ) ) end local Status, Result = xpcall( MenuFunction, ErrorHandler ) end - + return self end - - --- This sets the new command function of a menu, + + --- This sets the new command function of a menu, -- so that if a menu is regenerated, or if command function changes, -- that the function set for the menu is loosely coupled with the menu itself!!! -- If the function changes, no new menu needs to be generated if the menu text is the same!!! @@ -359,7 +330,6 @@ do -- MENU_COMMAND_BASE self.CommandMenuFunction = CommandMenuFunction return self end - --- This sets the new command arguments of a menu, -- so that if a menu is regenerated, or if command arguments change, -- that the arguments set for the menu are loosely coupled with the menu itself!!! @@ -370,85 +340,78 @@ do -- MENU_COMMAND_BASE self.CommandMenuArguments = CommandMenuArguments return self end - end do -- MENU_MISSION - --- @type MENU_MISSION -- @extends Core.Menu#MENU_BASE - - --- Manages the main menus for a complete mission. - -- + --- Manages the main menus for a complete mission. + -- -- You can add menus with the @{#MENU_MISSION.New} method, which constructs a MENU_MISSION object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_MISSION.Remove}. -- @field #MENU_MISSION MENU_MISSION = { ClassName = "MENU_MISSION", } - + --- MENU_MISSION constructor. Creates a new MENU_MISSION object and creates the menu for a complete mission file. -- @param #MENU_MISSION self -- @param #string MenuText The text for the menu. -- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the parent menu of DCS world (under F10 other). -- @return #MENU_MISSION function MENU_MISSION:New( MenuText, ParentMenu ) - + MENU_INDEX:PrepareMission() local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) - local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) - + 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 - --- Refreshes a radio item for a mission -- @param #MENU_MISSION self -- @return #MENU_MISSION function MENU_MISSION:Refresh() - do missionCommands.removeItem( self.MenuPath ) self.MenuPath = missionCommands.addSubMenu( self.MenuText, self.MenuParentPath ) end - + return self end - + --- Removes the sub menus recursively of this MENU_MISSION. Note that the main menu is kept! -- @param #MENU_MISSION self -- @return #MENU_MISSION function MENU_MISSION:RemoveSubMenus() - + for MenuID, Menu in pairs( self.Menus or {} ) do Menu:Remove() end - + self.Menus = nil - + end - + --- Removes the main menu and the sub menus recursively of this MENU_MISSION. -- @param #MENU_MISSION self -- @return #nil function MENU_MISSION:Remove( MenuStamp, MenuTag ) - + MENU_INDEX:PrepareMission() local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) - local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) - + 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 + 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 ) @@ -461,27 +424,26 @@ do -- MENU_MISSION else BASE:E( { "Cannot Remove MENU_MISSION", Path = Path, ParentMenu = self.ParentMenu, MenuText = self.MenuText } ) end - + return self end end - do -- MENU_MISSION_COMMAND - + --- @type MENU_MISSION_COMMAND -- @extends Core.Menu#MENU_COMMAND_BASE - - --- Manages the command menus for a complete mission, which allow players to execute functions during mission execution. - -- + + --- Manages the command menus for a complete mission, which allow players to execute functions during mission execution. + -- -- You can add menus with the @{#MENU_MISSION_COMMAND.New} method, which constructs a MENU_MISSION_COMMAND object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_MISSION_COMMAND.Remove}. - -- + -- -- @field #MENU_MISSION_COMMAND MENU_MISSION_COMMAND = { ClassName = "MENU_MISSION_COMMAND", } - + --- MENU_MISSION constructor. Creates a new radio command item for a complete mission file, which can invoke a function with parameters. -- @param #MENU_MISSION_COMMAND self -- @param #string MenuText The text for the menu. @@ -490,11 +452,10 @@ do -- MENU_MISSION_COMMAND -- @param CommandMenuArgument An argument for the function. There can only be ONE argument given. So multiple arguments must be wrapped into a table. See the below example how to do this. -- @return #MENU_MISSION_COMMAND self function MENU_MISSION_COMMAND:New( MenuText, ParentMenu, CommandMenuFunction, ... ) - + MENU_INDEX:PrepareMission() local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) - local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) - + local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) if MissionMenu then MissionMenu:SetCommandMenuFunction( CommandMenuFunction ) MissionMenu:SetCommandMenuArguments( arg ) @@ -502,37 +463,34 @@ do -- MENU_MISSION_COMMAND 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 - --- Refreshes a radio item for a mission -- @param #MENU_MISSION_COMMAND self -- @return #MENU_MISSION_COMMAND function MENU_MISSION_COMMAND:Refresh() - do missionCommands.removeItem( self.MenuPath ) missionCommands.addCommand( self.MenuText, self.MenuParentPath, self.MenuCallHandler ) end - + return self end - + --- Removes a radio command item for a coalition -- @param #MENU_MISSION_COMMAND self -- @return #nil function MENU_MISSION_COMMAND:Remove() - + MENU_INDEX:PrepareMission() local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) - local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) - + 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 + 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 ) @@ -545,22 +503,19 @@ do -- MENU_MISSION_COMMAND else BASE:E( { "Cannot Remove MENU_MISSION_COMMAND", Path = Path, ParentMenu = self.ParentMenu, MenuText = self.MenuText } ) end - + return self end - end - do -- MENU_COALITION - --- @type MENU_COALITION -- @extends Core.Menu#MENU_BASE - - --- Manages the main menus for @{DCS.coalition}s. - -- + + --- Manages the main menus for @{DCS.coalition}s. + -- -- You can add menus with the @{#MENU_COALITION.New} method, which constructs a MENU_COALITION object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_COALITION.Remove}. - -- + -- -- -- @usage -- -- This demo creates a menu structure for the planes within the red coalition. @@ -590,7 +545,7 @@ do -- MENU_COALITION -- end -- -- local function AddStatusMenu() - -- + -- -- -- This would create a menu for the red coalition under the MenuCoalitionRed menu object. -- MenuStatus = MENU_COALITION:New( coalition.side.RED, "Status for Planes" ) -- MenuStatusShow = MENU_COALITION_COMMAND:New( coalition.side.RED, "Show Status", MenuStatus, ShowStatus, "Status of planes is ok!", "Message to Red Coalition" ) @@ -601,9 +556,9 @@ do -- MENU_COALITION -- -- @field #MENU_COALITION MENU_COALITION = { - ClassName = "MENU_COALITION", + ClassName = "MENU_COALITION" } - + --- MENU_COALITION constructor. Creates a new MENU_COALITION object and creates the menu for a complete coalition. -- @param #MENU_COALITION self -- @param DCS#coalition.side Coalition The coalition owning the menu. @@ -611,63 +566,57 @@ do -- MENU_COALITION -- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the parent menu of DCS world (under F10 other). -- @return #MENU_COALITION self 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 ) - + 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 - --- Refreshes a radio item for a coalition -- @param #MENU_COALITION self -- @return #MENU_COALITION function MENU_COALITION:Refresh() - do missionCommands.removeItemForCoalition( self.Coalition, self.MenuPath ) missionCommands.addSubMenuForCoalition( self.Coalition, self.MenuText, self.MenuParentPath ) end - + return self end - + --- Removes the sub menus recursively of this MENU_COALITION. Note that the main menu is kept! -- @param #MENU_COALITION self -- @return #MENU_COALITION function MENU_COALITION:RemoveSubMenus() - + for MenuID, Menu in pairs( self.Menus or {} ) do Menu:Remove() end - + self.Menus = nil end - + --- Removes the main menu and the sub menus recursively of this MENU_COALITION. -- @param #MENU_COALITION self -- @return #nil 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 ) - + 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 + 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 ) @@ -680,27 +629,25 @@ do -- MENU_COALITION 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 - + --- @type MENU_COALITION_COMMAND -- @extends Core.Menu#MENU_COMMAND_BASE - - --- Manages the command menus for coalitions, which allow players to execute functions during mission execution. - -- + + --- Manages the command menus for coalitions, which allow players to execute functions during mission execution. + -- -- You can add menus with the @{#MENU_COALITION_COMMAND.New} method, which constructs a MENU_COALITION_COMMAND object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_COALITION_COMMAND.Remove}. -- -- @field #MENU_COALITION_COMMAND MENU_COALITION_COMMAND = { - ClassName = "MENU_COALITION_COMMAND", + ClassName = "MENU_COALITION_COMMAND" } - + --- MENU_COALITION constructor. Creates a new radio command item for a coalition, which can invoke a function with parameters. -- @param #MENU_COALITION_COMMAND self -- @param DCS#coalition.side Coalition The coalition owning the menu. @@ -710,52 +657,48 @@ do -- MENU_COALITION_COMMAND -- @param CommandMenuArgument An argument for the function. There can only be ONE argument given. So multiple arguments must be wrapped into a table. See the below example how to do this. -- @return #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 ) - + 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 - --- Refreshes a radio item for a coalition -- @param #MENU_COALITION_COMMAND self -- @return #MENU_COALITION_COMMAND function MENU_COALITION_COMMAND:Refresh() - do missionCommands.removeItemForCoalition( self.Coalition, self.MenuPath ) missionCommands.addCommandForCoalition( self.Coalition, self.MenuText, self.MenuParentPath, self.MenuCallHandler ) end - + + return self end - + --- Removes a radio command item for a coalition -- @param #MENU_COALITION_COMMAND self -- @return #nil 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 ) - + 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 + 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 ) @@ -768,14 +711,12 @@ do -- MENU_COALITION_COMMAND else BASE:E( { "Cannot Remove MENU_COALITION_COMMAND", Path = Path, ParentMenu = self.ParentMenu, MenuText = self.MenuText, Coalition = self.Coalition } ) end - + return self end - end --- MENU_GROUP - do -- This local variable is used to cache the menus registered under groups. -- Menus don't disappear when groups for players are destroyed and restarted. @@ -783,22 +724,22 @@ do -- the same menus twice during initialization logic. -- These menu classes are handling this logic with this variable. local _MENUGROUPS = {} - --- @type MENU_GROUP -- @extends Core.Menu#MENU_BASE - - --- Manages the main menus for @{Wrapper.Group}s. - -- + + + --- Manages the main menus for @{Wrapper.Group}s. + -- -- You can add menus with the @{#MENU_GROUP.New} method, which constructs a MENU_GROUP object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_GROUP.Remove}. - -- + -- -- @usage -- -- This demo creates a menu structure for the two groups of planes. -- -- Each group will receive a different menu structure. -- -- To test, join the planes, then look at the other radio menus (Option F10). -- -- Then switch planes and check if the menu is still there. -- -- And play with the Add and Remove menu options. - -- + -- -- -- Note that in multi player, this will only work after the DCS groups bug is solved. -- -- local function ShowStatus( PlaneGroup, StatusText, Coalition ) @@ -844,9 +785,9 @@ do -- -- @field #MENU_GROUP MENU_GROUP = { - ClassName = "MENU_GROUP", + ClassName = "MENU_GROUP" } - + --- MENU_GROUP constructor. Creates a new radio menu item for a group. -- @param #MENU_GROUP self -- @param Wrapper.Group#GROUP Group The Group owning the menu. @@ -854,57 +795,52 @@ do -- @param #table ParentMenu The parent menu. -- @return #MENU_GROUP self 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 - --- Refreshes a new radio item for a group and submenus -- @param #MENU_GROUP self -- @return #MENU_GROUP function MENU_GROUP:Refresh() - do missionCommands.removeItemForGroup( self.GroupID, self.MenuPath ) missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath ) - + for MenuText, Menu in pairs( self.Menus or {} ) do Menu:Refresh() end end - + + return self end - + --- Removes the sub menus recursively of this MENU_GROUP. -- @param #MENU_GROUP self -- @param MenuStamp -- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set. -- @return #MENU_GROUP self function MENU_GROUP:RemoveSubMenus( MenuStamp, MenuTag ) - for MenuText, Menu in pairs( self.Menus or {} ) do Menu:Remove( MenuStamp, MenuTag ) end - + self.Menus = nil - + end --- Removes the main menu and sub menus recursively of this MENU_GROUP. @@ -913,15 +849,13 @@ do -- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set. -- @return #nil 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 ) - + 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 ( 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 ) @@ -935,22 +869,23 @@ do BASE:E( { "Cannot Remove MENU_GROUP", Path = Path, ParentMenu = self.ParentMenu, MenuText = self.MenuText, Group = self.Group } ) return nil end - + return self end - + + --- @type MENU_GROUP_COMMAND -- @extends Core.Menu#MENU_COMMAND_BASE - - --- The @{Core.Menu#MENU_GROUP_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. + + --- The @{Core.Menu#MENU_GROUP_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. -- You can add menus with the @{#MENU_GROUP_COMMAND.New} method, which constructs a MENU_GROUP_COMMAND object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_GROUP_COMMAND.Remove}. -- -- @field #MENU_GROUP_COMMAND MENU_GROUP_COMMAND = { - ClassName = "MENU_GROUP_COMMAND", + ClassName = "MENU_GROUP_COMMAND" } - + --- Creates a new radio command item for a group -- @param #MENU_GROUP_COMMAND self -- @param Wrapper.Group#GROUP Group The Group owning the menu. @@ -960,59 +895,52 @@ do -- @param CommandMenuArgument An argument for the function. -- @return #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 ) - + 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 - --- Refreshes a radio item for a group -- @param #MENU_GROUP_COMMAND self -- @return #MENU_GROUP_COMMAND function MENU_GROUP_COMMAND:Refresh() - do missionCommands.removeItemForGroup( self.GroupID, self.MenuPath ) missionCommands.addCommandForGroup( self.GroupID, self.MenuText, self.MenuParentPath, self.MenuCallHandler ) end - + + return self end - + --- Removes a menu structure for a group. -- @param #MENU_GROUP_COMMAND self -- @param MenuStamp -- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set. -- @return #nil 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 ) - + 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 ( 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 } ) + self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } ) missionCommands.removeItemForGroup( self.GroupID, self.MenuPath ) end MENU_INDEX:ClearGroupMenu( self.Group, Path ) @@ -1023,20 +951,17 @@ do else BASE:E( { "Cannot Remove MENU_GROUP_COMMAND", Path = Path, ParentMenu = self.ParentMenu, MenuText = self.MenuText, Group = self.Group } ) end - + return self end - end - --- MENU_GROUP_DELAYED - do - --- @type MENU_GROUP_DELAYED -- @extends Core.Menu#MENU_BASE - - --- The MENU_GROUP_DELAYED class manages the main menus for groups. + + + --- The MENU_GROUP_DELAYED class manages the main menus for groups. -- You can add menus with the @{#MENU_GROUP.New} method, which constructs a MENU_GROUP object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_GROUP.Remove}. -- The creation of the menu item is delayed however, and must be created using the @{#MENU_GROUP.Set} method. @@ -1045,9 +970,9 @@ do -- -- @field #MENU_GROUP_DELAYED MENU_GROUP_DELAYED = { - ClassName = "MENU_GROUP_DELAYED", + ClassName = "MENU_GROUP_DELAYED" } - + --- MENU_GROUP_DELAYED constructor. Creates a new radio menu item for a group. -- @param #MENU_GROUP_DELAYED self -- @param Wrapper.Group#GROUP Group The Group owning the menu. @@ -1055,80 +980,74 @@ do -- @param #table ParentMenu The parent menu. -- @return #MENU_GROUP_DELAYED self 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 --- Refreshes a new radio item for a group and submenus -- @param #MENU_GROUP_DELAYED self -- @return #MENU_GROUP_DELAYED 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 --- Refreshes a new radio item for a group and submenus -- @param #MENU_GROUP_DELAYED self -- @return #MENU_GROUP_DELAYED function MENU_GROUP_DELAYED:Refresh() - do missionCommands.removeItemForGroup( self.GroupID, self.MenuPath ) missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath ) - + for MenuText, Menu in pairs( self.Menus or {} ) do Menu:Refresh() end end - + + return self end - + --- Removes the sub menus recursively of this MENU_GROUP_DELAYED. -- @param #MENU_GROUP_DELAYED self -- @param MenuStamp -- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set. -- @return #MENU_GROUP_DELAYED self 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 --- Removes the main menu and sub menus recursively of this MENU_GROUP. @@ -1137,15 +1056,13 @@ do -- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set. -- @return #nil 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 ) - + 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 ( 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 ) @@ -1159,23 +1076,24 @@ do BASE:E( { "Cannot Remove MENU_GROUP_DELAYED", Path = Path, ParentMenu = self.ParentMenu, MenuText = self.MenuText, Group = self.Group } ) return nil end - + return self end - + + --- @type MENU_GROUP_COMMAND_DELAYED -- @extends Core.Menu#MENU_COMMAND_BASE - - --- Manages the command menus for coalitions, which allow players to execute functions during mission execution. - -- + + --- Manages the command menus for coalitions, which allow players to execute functions during mission execution. + -- -- You can add menus with the @{#MENU_GROUP_COMMAND_DELAYED.New} method, which constructs a MENU_GROUP_COMMAND_DELAYED object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_GROUP_COMMAND_DELAYED.Remove}. -- -- @field #MENU_GROUP_COMMAND_DELAYED MENU_GROUP_COMMAND_DELAYED = { - ClassName = "MENU_GROUP_COMMAND_DELAYED", + ClassName = "MENU_GROUP_COMMAND_DELAYED" } - + --- Creates a new radio command item for a group -- @param #MENU_GROUP_COMMAND_DELAYED self -- @param Wrapper.Group#GROUP Group The Group owning the menu. @@ -1185,76 +1103,67 @@ do -- @param CommandMenuArgument An argument for the function. -- @return #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 ) - + 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 - --- Refreshes a radio item for a group -- @param #MENU_GROUP_COMMAND_DELAYED self -- @return #MENU_GROUP_COMMAND_DELAYED 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 - + --- Refreshes a radio item for a group -- @param #MENU_GROUP_COMMAND_DELAYED self -- @return #MENU_GROUP_COMMAND_DELAYED function MENU_GROUP_COMMAND_DELAYED:Refresh() - do missionCommands.removeItemForGroup( self.GroupID, self.MenuPath ) missionCommands.addCommandForGroup( self.GroupID, self.MenuText, self.MenuParentPath, self.MenuCallHandler ) end - + + return self end - + --- Removes a menu structure for a group. -- @param #MENU_GROUP_COMMAND_DELAYED self -- @param MenuStamp -- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set. -- @return #nil 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 ) - + 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 ( 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 ) @@ -1267,9 +1176,8 @@ do 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 From cac0f306730469d66d4375caaaf8467f524567eb Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 25 Apr 2022 10:35:38 +0200 Subject: [PATCH 119/200] Update Moose.files (#1717) --- Moose Setup/Moose.files | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index c903cbb90..e820e19ef 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -5,6 +5,7 @@ Utilities/Enums.lua Utilities/Profiler.lua Utilities/Templates.lua Utilities/STTS.lua +Utilities/FiFo.lua Core/Base.lua Core/Beacon.lua From 2d91647e0baa33c32a91c24bae3ae7d6de7e217c Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 25 Apr 2022 10:36:30 +0200 Subject: [PATCH 120/200] QOL changes from DEVELOP --- Moose Development/Moose/Core/Point.lua | 67 +- Moose Development/Moose/Core/Zone.lua | 12 + Moose Development/Moose/Modules.lua | 1 + Moose Development/Moose/Sound/SRS.lua | 27 +- Moose Development/Moose/Utilities/Enums.lua | 259 ++++-- Moose Development/Moose/Utilities/FiFo.lua | 769 ++++++++++++++++++ Moose Development/Moose/Utilities/Utils.lua | 553 ++----------- Moose Development/Moose/Wrapper/Group.lua | 38 +- .../Moose/Wrapper/Positionable.lua | 3 +- Moose Development/Moose/Wrapper/Unit.lua | 126 ++- 10 files changed, 1250 insertions(+), 605 deletions(-) create mode 100644 Moose Development/Moose/Utilities/FiFo.lua diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 182133a1f..36f467284 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -194,7 +194,12 @@ do -- COORDINATE -- ## 9) Coordinate text generation -- -- * @{#COORDINATE.ToStringBR}(): Generates a Bearing & Range text in the format of DDD for DI where DDD is degrees and DI is distance. - -- * @{#COORDINATE.ToStringLL}(): Generates a Latutude & Longutude text. + -- * @{#COORDINATE.ToStringBRA}(): Generates a Bearing, Range & Altitude text. + -- * @{#COORDINATE.ToStringBRAANATO}(): Generates a Generates a Bearing, Range, Aspect & Altitude text in NATOPS. + -- * @{#COORDINATE.ToStringLL}(): Generates a Latutide & Longitude text. + -- * @{#COORDINATE.ToStringLLDMS}(): Generates a Lat, Lon, Degree, Minute, Second text. + -- * @{#COORDINATE.ToStringLLDDM}(): Generates a Lat, Lon, Degree, decimal Minute text. + -- * @{#COORDINATE.ToStringMGRS}(): Generates a MGRS grid coordinate text. -- -- ## 10) Drawings on F10 map -- @@ -1112,25 +1117,28 @@ do -- COORDINATE -- @param #COORDINATE self -- @param #number Distance The distance in meters. -- @param Core.Settings#SETTINGS Settings + -- @param #string Language (optional) "EN" or "RU" + -- @param #number Precision (optional) round to this many decimal places -- @return #string The distance text expressed in the units of measurement. - function COORDINATE:GetDistanceText( Distance, Settings, Language ) + function COORDINATE:GetDistanceText( Distance, Settings, Language, Precision ) local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS local Language = Language or "EN" - + local Precision = Precision or 0 + local DistanceText if Settings:IsMetric() then if Language == "EN" then - DistanceText = " for " .. UTILS.Round( Distance / 1000, 2 ) .. " km" + DistanceText = " for " .. UTILS.Round( Distance / 1000, Precision ) .. " km" elseif Language == "RU" then - DistanceText = " за " .. UTILS.Round( Distance / 1000, 2 ) .. " километров" + DistanceText = " за " .. UTILS.Round( Distance / 1000, Precision ) .. " километров" end else if Language == "EN" then - DistanceText = " for " .. UTILS.Round( UTILS.MetersToNM( Distance ), 2 ) .. " miles" + DistanceText = " for " .. UTILS.Round( UTILS.MetersToNM( Distance ), Precision ) .. " miles" elseif Language == "RU" then - DistanceText = " за " .. UTILS.Round( UTILS.MetersToNM( Distance ), 2 ) .. " миль" + DistanceText = " за " .. UTILS.Round( UTILS.MetersToNM( Distance ), Precision ) .. " миль" end end @@ -1208,7 +1216,7 @@ do -- COORDINATE local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS local BearingText = self:GetBearingText( AngleRadians, 0, Settings, Language ) - local DistanceText = self:GetDistanceText( Distance, Settings, Language ) + local DistanceText = self:GetDistanceText( Distance, Settings, Language, 0 ) local BRText = BearingText .. DistanceText @@ -1226,7 +1234,7 @@ do -- COORDINATE local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS local BearingText = self:GetBearingText( AngleRadians, 0, Settings, Language ) - local DistanceText = self:GetDistanceText( Distance, Settings, Language ) + local DistanceText = self:GetDistanceText( Distance, Settings, Language, 0 ) local AltitudeText = self:GetAltitudeText( Settings, Language ) local BRAText = BearingText .. DistanceText .. AltitudeText -- When the POINT is a VEC2, there will be no altitude shown. @@ -2164,14 +2172,21 @@ do -- COORDINATE 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 Color + + FillColor=FillColor or UTILS.DeepCopy(Color) FillColor[4]=FillAlpha or 0.15 + trigger.action.circleToAll(Coalition, MarkID, vec3, Radius, Color, FillColor, LineType, ReadOnly, Text or "") return MarkID end @@ -2196,13 +2211,19 @@ do -- COORDINATE if ReadOnly==nil then ReadOnly=false end + local vec3=Endpoint:GetVec3() + Coalition=Coalition or -1 + Color=Color or {1,0,0} Color[4]=Alpha or 1.0 + LineType=LineType or 1 - FillColor=FillColor or Color + + FillColor=FillColor or UTILS.DeepCopy(Color) FillColor[4]=FillAlpha or 0.15 + trigger.action.rectToAll(Coalition, MarkID, self:GetVec3(), vec3, Color, FillColor, LineType, ReadOnly, Text or "") return MarkID end @@ -2226,17 +2247,23 @@ do -- COORDINATE if ReadOnly==nil then ReadOnly=false end + local point1=self:GetVec3() local point2=Coord2:GetVec3() local point3=Coord3:GetVec3() local point4=Coord4:GetVec3() + Coalition=Coalition or -1 + Color=Color or {1,0,0} Color[4]=Alpha or 1.0 + LineType=LineType or 1 - FillColor=FillColor or Color + + FillColor=FillColor or UTILS.DeepCopy(Color) FillColor[4]=FillAlpha or 0.15 - trigger.action.quadToAll(Coalition, MarkID, self:GetVec3(), point2, point3, point4, Color, FillColor, LineType, ReadOnly, Text or "") + + trigger.action.quadToAll(Coalition, MarkID, point1, point2, point3, point4, Color, FillColor, LineType, ReadOnly, Text or "") return MarkID end @@ -2320,11 +2347,15 @@ do -- COORDINATE ReadOnly=false end Coalition=Coalition or -1 + Color=Color or {1,0,0} Color[4]=Alpha or 1.0 - FillColor=FillColor or Color + + FillColor=FillColor or UTILS.DeepCopy(Color) FillColor[4]=FillAlpha or 0.3 + FontSize=FontSize or 14 + trigger.action.textToAll(Coalition, MarkID, self:GetVec3(), Color, FillColor, FontSize, ReadOnly, Text or "Hello World") return MarkID end @@ -2346,13 +2377,19 @@ do -- COORDINATE if ReadOnly==nil then ReadOnly=false end + local vec3=Endpoint:GetVec3() + Coalition=Coalition or -1 + Color=Color or {1,0,0} Color[4]=Alpha or 1.0 + LineType=LineType or 1 - FillColor=FillColor or Color + + FillColor=FillColor or UTILS.DeepCopy(Color) FillColor[4]=FillAlpha or 0.15 + --trigger.action.textToAll(Coalition, MarkID, self:GetVec3(), Color, FillColor, FontSize, ReadOnly, Text or "Hello World") trigger.action.arrowToAll(Coalition, MarkID, vec3, self:GetVec3(), Color, FillColor, LineType, ReadOnly, Text or "") return MarkID diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 7f4828df6..5d88d833e 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1989,6 +1989,18 @@ function ZONE_POLYGON_BASE:IsVec2InZone( Vec2 ) return InPolygon end +--- Returns if a point is within the zone. +-- @param #ZONE_POLYGON_BASE self +-- @param DCS#Vec3 Vec3 The point to test. +-- @return #boolean true if the point is within the zone. +function ZONE_POLYGON_BASE:IsVec3InZone( Vec3 ) + self:F2( Vec3 ) + + local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } ) + + return InZone +end + --- Define a random @{DCS#Vec2} within the zone. -- @param #ZONE_POLYGON_BASE self -- @return DCS#Vec2 The Vec2 coordinate. diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index ad27b6b84..29c10ad0c 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -4,6 +4,7 @@ __Moose.Include( 'Scripts/Moose/Utilities/Utils.lua' ) __Moose.Include( 'Scripts/Moose/Utilities/Profiler.lua' ) __Moose.Include( 'Scripts/Moose/Utilities/Templates.lua' ) __Moose.Include( 'Scripts/Moose/Utilities/STTS.lua' ) +__Moose.Include( 'Scripts/Moose/Utilities/FiFo.lua' ) __Moose.Include( 'Scripts/Moose/Core/Base.lua' ) __Moose.Include( 'Scripts/Moose/Core/Beacon.lua' ) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 2c4411ac1..3ec73c6f1 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -45,6 +45,7 @@ -- @field Core.Point#COORDINATE coordinate Coordinate from where the transmission is send. -- @field #string path Path to the SRS exe. This includes the final slash "/". -- @field #string google Full path google credentials JSON file, e.g. "C:\Users\username\Downloads\service-account-file.json". +-- @field #string Label Label showing up on the SRS radio overlay. Default is "ROBOT". No spaces allowed. -- @extends Core.Base#BASE --- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde @@ -125,11 +126,12 @@ MSRS = { volume = 1, speed = 1, coordinate = nil, + Label = "ROBOT", } --- MSRS class version. -- @field #string version -MSRS.version="0.0.3" +MSRS.version="0.0.4" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -164,6 +166,7 @@ function MSRS:New(PathToSRS, Frequency, Modulation) self:SetModulations(Modulation) self:SetGender() self:SetCoalition() + self:SetLabel() return self end @@ -206,6 +209,22 @@ function MSRS:GetPath() return self.path end +--- Set label. +-- @param #MSRS self +-- @param #number Label. Default "ROBOT" +-- @return #MSRS self +function MSRS:SetLabel(Label) + self.Label=Label or "ROBOT" + return self +end + +--- Get label. +-- @param #MSRS self +-- @return #number Label. +function MSRS:GetLabel() + return self.Label +end + --- Set port. -- @param #MSRS self -- @param #number Port Port. Default 5002. @@ -640,8 +659,9 @@ end -- @param #number volume Volume. -- @param #number speed Speed. -- @param #number port Port. +-- @param #string label Label, defaults to "ROBOT" (displayed sender name in the radio overlay of SRS) - No spaces allowed! -- @return #string Command. -function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, speed, port) +function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, speed, port,label) local path=self:GetPath() or STTS.DIRECTORY local exe=STTS.EXECUTABLE or "DCS-SR-ExternalAudio.exe" @@ -654,6 +674,7 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp volume=volume or self.volume speed=speed or self.speed port=port or self.port + label=label or self.Label -- Replace modulation modus=modus:gsub("0", "AM") @@ -668,7 +689,7 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp --local command=string.format('start /b "" /d "%s" "%s" -f %s -m %s -c %s -p %s -n "%s" > bla.txt', path, exe, freqs, modus, coal, port, "ROBOT") -- Command. - local command=string.format('"%s\\%s" -f %s -m %s -c %s -p %s -n "%s"', path, exe, freqs, modus, coal, port, "ROBOT") + local command=string.format('"%s\\%s" -f %s -m %s -c %s -p %s -n "%s"', path, exe, freqs, modus, coal, port, label) -- Set voice or gender/culture. if voice then diff --git a/Moose Development/Moose/Utilities/Enums.lua b/Moose Development/Moose/Utilities/Enums.lua index f4de6e4d7..423bd85e9 100644 --- a/Moose Development/Moose/Utilities/Enums.lua +++ b/Moose Development/Moose/Utilities/Enums.lua @@ -1,18 +1,18 @@ --- **Utilities** Enumerators. --- +-- -- An enumerator is a variable that holds a constant value. Enumerators are very useful because they make the code easier to read and to change in general. --- +-- -- For example, instead of using the same value at multiple different places in your code, you should use a variable set to that value. -- If, for whatever reason, the value needs to be changed, you only have to change the variable once and do not have to search through you code and reset -- every value by hand. --- +-- -- Another big advantage is that the LDT intellisense "knows" the enumerators. So you can use the autocompletion feature and do not have to keep all the --- values in your head or look them up in the docs. --- +-- values in your head or look them up in the docs. +-- -- DCS itself provides a lot of enumerators for various things. See [Enumerators](https://wiki.hoggitworld.com/view/Category:Enumerators) on Hoggit. --- +-- -- Other Moose classes also have enumerators. For example, the AIRBASE class has enumerators for airbase names. --- +-- -- @module ENUMS -- @image MOOSE.JPG @@ -20,7 +20,7 @@ -- @type ENUMS --- Because ENUMS are just better practice. --- +-- -- The ENUMS class adds some handy variables, which help you to make your code better and more general. -- -- @field #ENUMS @@ -30,16 +30,16 @@ ENUMS = {} -- @type ENUMS.ROE -- @field #number WeaponFree AI will engage any enemy group it detects. Target prioritization is based based on the threat of the target. -- @field #number OpenFireWeaponFree AI will engage any enemy group it detects, but will prioritize targets specified in the groups tasking. --- @field #number OpenFire AI will engage only targets specified in its tasking. +-- @field #number OpenFire AI will engage only targets specified in its taskings. -- @field #number ReturnFire AI will only engage threats that shoot first. -- @field #number WeaponHold AI will hold fire under all circumstances. ENUMS.ROE = { - WeaponFree = 0, - OpenFireWeaponFree = 1, - OpenFire = 2, - ReturnFire = 3, - WeaponHold = 4, -} + WeaponFree=0, + OpenFireWeaponFree=1, + OpenFire=2, + ReturnFire=3, + WeaponHold=4, + } --- Reaction On Threat. -- @type ENUMS.ROT @@ -49,11 +49,11 @@ ENUMS.ROE = { -- @field #number BypassAndEscape AI will attempt to avoid enemy threat zones all together. This includes attempting to fly above or around threats. -- @field #number AllowAbortMission If a threat is deemed severe enough the AI will abort its mission and return to base. ENUMS.ROT = { - NoReaction = 0, - PassiveDefense = 1, - EvadeFire = 2, - BypassAndEscape = 3, - AllowAbortMission = 4, + NoReaction=0, + PassiveDefense=1, + EvadeFire=2, + BypassAndEscape=3, + AllowAbortMission=4, } --- Alarm state. @@ -62,12 +62,12 @@ ENUMS.ROT = { -- @field #number Green Group is not combat ready. Sensors are stowed if possible. -- @field #number Red Group is combat ready and actively searching for targets. Some groups like infantry will not move in this state. ENUMS.AlarmState = { - Auto = 0, - Green = 1, - Red = 2, + Auto=0, + Green=1, + Red=2, } ---- Weapon types. See the [Weapon Flag](https://wiki.hoggitworld.com/view/DCS_enum_weapon_flag) enumerator on Hoggit wiki. +--- Weapon types. See the [Weapon Flag](https://wiki.hoggitworld.com/view/DCS_enum_weapon_flag) enumerotor on hoggit wiki. -- @type ENUMS.WeaponFlag ENUMS.WeaponFlag={ -- Bombs @@ -111,7 +111,7 @@ ENUMS.WeaponFlag={ -- -- Bombs GuidedBomb = 14, -- (LGB + TvGB + SNSGB) - AnyUnguidedBomb = 2147485680, -- (HeBomb + Penetrator + NapalmBomb + FAEBomb + ClusterBomb + Dispenser + CandleBomb + ParachuteBomb) + AnyUnguidedBomb = 2147485680, -- (HeBomb + Penetrator + NapalmBomb + FAEBomb + ClusterBomb + Dispencer + CandleBomb + ParachuteBomb) AnyBomb = 2147485694, -- (GuidedBomb + AnyUnguidedBomb) --- Rockets AnyRocket = 30720, -- LightRocket + MarkerRocket + CandleRocket + HeavyRocket @@ -123,9 +123,11 @@ ENUMS.WeaponFlag={ --- Air-To-Air Missiles AnyAAM = 264241152, -- IR_AAM + SAR_AAM + AR_AAM + SRAAM + MRAAM + LRAAM AnyAutonomousMissile = 36012032, -- IR_AAM + AntiRadarMissile + AntiShipMissile + FireAndForgetASM + CruiseMissile - AnyMissile = 268402688, -- AnyASM + AnyAAM + AnyMissile = 268402688, -- AnyASM + AnyAAM --- Guns Cannons = 805306368, -- GUN_POD + BuiltInCannon + --- Torpedo + Torpedo = 4294967296, --- -- Even More Genral Auto = 3221225470, -- Any Weapon (AnyBomb + AnyRocket + AnyMissile + Cannons) @@ -133,9 +135,96 @@ ENUMS.WeaponFlag={ AnyAG = 2956984318, -- Any Air-To-Ground Weapon AnyAA = 264241152, -- Any Air-To-Air Weapon AnyUnguided = 2952822768, -- Any Unguided Weapon - AnyGuided = 268402702, -- Any Guided Weapon + AnyGuided = 268402702, -- Any Guided Weapon } +--- Weapon types by category. See the [Weapon Flag](https://wiki.hoggitworld.com/view/DCS_enum_weapon_flag) enumerator on hoggit wiki. +-- @type ENUMS.WeaponType +-- @field #table Bomb Bombs. +-- @field #table Rocket Rocket. +-- @field #table Gun Guns. +-- @field #table Missile Missiles. +-- @field #table AAM Air-to-Air missiles. +-- @field #table Torpedo Torpedos. +-- @field #table Any Combinations. +ENUMS.WeaponType={} +ENUMS.WeaponType.Bomb={ + -- Bombs + LGB = 2, + TvGB = 4, + SNSGB = 8, + HEBomb = 16, + Penetrator = 32, + NapalmBomb = 64, + FAEBomb = 128, + ClusterBomb = 256, + Dispencer = 512, + CandleBomb = 1024, + ParachuteBomb = 2147483648, + -- Combinations + GuidedBomb = 14, -- (LGB + TvGB + SNSGB) + AnyUnguidedBomb = 2147485680, -- (HeBomb + Penetrator + NapalmBomb + FAEBomb + ClusterBomb + Dispencer + CandleBomb + ParachuteBomb) + AnyBomb = 2147485694, -- (GuidedBomb + AnyUnguidedBomb) +} +ENUMS.WeaponType.Rocket={ + -- Rockets + LightRocket = 2048, + MarkerRocket = 4096, + CandleRocket = 8192, + HeavyRocket = 16384, + -- Combinations + AnyRocket = 30720, -- LightRocket + MarkerRocket + CandleRocket + HeavyRocket +} +ENUMS.WeaponType.Gun={ + -- Guns + GunPod = 268435456, + BuiltInCannon = 536870912, + -- Combinations + Cannons = 805306368, -- GUN_POD + BuiltInCannon +} +ENUMS.WeaponType.Missile={ + -- Missiles + AntiRadarMissile = 32768, + AntiShipMissile = 65536, + AntiTankMissile = 131072, + FireAndForgetASM = 262144, + LaserASM = 524288, + TeleASM = 1048576, + CruiseMissile = 2097152, + AntiRadarMissile2 = 1073741824, + -- Combinations + GuidedASM = 1572864, -- (LaserASM + TeleASM) + TacticalASM = 1835008, -- (GuidedASM + FireAndForgetASM) + AnyASM = 4161536, -- (AntiRadarMissile + AntiShipMissile + AntiTankMissile + FireAndForgetASM + GuidedASM + CruiseMissile) + AnyASM2 = 1077903360, -- 4161536+1073741824, + AnyAutonomousMissile = 36012032, -- IR_AAM + AntiRadarMissile + AntiShipMissile + FireAndForgetASM + CruiseMissile + AnyMissile = 268402688, -- AnyASM + AnyAAM +} +ENUMS.WeaponType.AAM={ + -- Air-To-Air Missiles + SRAM = 4194304, + MRAAM = 8388608, + LRAAM = 16777216, + IR_AAM = 33554432, + SAR_AAM = 67108864, + AR_AAM = 134217728, + -- Combinations + AnyAAM = 264241152, -- IR_AAM + SAR_AAM + AR_AAM + SRAAM + MRAAM + LRAAM +} +ENUMS.WeaponType.Torpedo={ + -- Torpedo + Torpedo = 4294967296, +} +ENUMS.WeaponType.Any={ + -- General combinations + Weapon = 3221225470, -- Any Weapon (AnyBomb + AnyRocket + AnyMissile + Cannons) + AG = 2956984318, -- Any Air-To-Ground Weapon + AA = 264241152, -- Any Air-To-Air Weapon + Unguided = 2952822768, -- Any Unguided Weapon + Guided = 268402702, -- Any Guided Weapon +} + + --- Mission tasks. -- @type ENUMS.MissionTask -- @field #string NOTHING No special task. Group can perform the minimal tasks: Orbit, Refuelling, Follow and Aerobatics. @@ -173,7 +262,7 @@ ENUMS.MissionTask={ TRANSPORT="Transport", } ---- Formations (new). See the [Formations](https://wiki.hoggitworld.com/view/DCS_enum_formation) on Hoggit wiki. +--- Formations (new). See the [Formations](https://wiki.hoggitworld.com/view/DCS_enum_formation) on hoggit wiki. -- @type ENUMS.Formation ENUMS.Formation={} ENUMS.Formation.FixedWing={} @@ -216,23 +305,23 @@ 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.Column.D70=720896 ENUMS.Formation.RotaryWing.Wedge={} -ENUMS.Formation.RotaryWing.Wedge.D70 = 8 +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.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.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.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.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" @@ -244,34 +333,34 @@ ENUMS.Formation.Vehicle.Cone="Cone" ENUMS.Formation.Vehicle.Diamond="Diamond" --- Formations (old). The old format is a simplified version of the new formation enums, which allow more sophisticated settings. --- See the [Formations](https://wiki.hoggitworld.com/view/DCS_enum_formation) on Hoggit wiki. +-- See the [Formations](https://wiki.hoggitworld.com/view/DCS_enum_formation) on hoggit wiki. -- @type ENUMS.FormationOld 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.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.FormationOld.RotaryWing.Wedge=8 +ENUMS.FormationOld.RotaryWing.Echelon=9 +ENUMS.FormationOld.RotaryWing.Front=10 +ENUMS.FormationOld.RotaryWing.Column=11 --- Morse Code. See the [Wikipedia](https://en.wikipedia.org/wiki/Morse_code). --- +-- -- * Short pulse "*" -- * Long pulse "-" --- +-- -- Pulses are separated by a blank character " ". --- +-- -- @type ENUMS.Morse ENUMS.Morse={} ENUMS.Morse.A="* -" @@ -313,9 +402,9 @@ ENUMS.Morse.N0="- - - - -" ENUMS.Morse[" "]=" " --- ISO (639-1) 2-letter Language Codes. See the [Wikipedia](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes). --- +-- -- @type ENUMS.ISOLang -ENUMS.ISOLang = +ENUMS.ISOLang = { Arabic = 'AR', Chinese = 'ZH', @@ -329,7 +418,7 @@ ENUMS.ISOLang = } --- Phonetic Alphabet (NATO). See the [Wikipedia](https://en.wikipedia.org/wiki/NATO_phonetic_alphabet). --- +-- -- @type ENUMS.Phonetic ENUMS.Phonetic = { @@ -359,4 +448,50 @@ ENUMS.Phonetic = X = 'Xray', Y = 'Yankee', Z = 'Zulu', -} \ No newline at end of file +} + +--- Reporting Names (NATO). See the [Wikipedia](https://en.wikipedia.org/wiki/List_of_NATO_reporting_names_for_fighter_aircraft). +-- DCS known aircraft types +-- +-- @type ENUMS.ReportingName +ENUMS.ReportingName = +{ + NATO = { + -- Fighters + Dragon = "JF-17", -- China, correct? + Fagot = "MiG-15", + Farmer = "MiG-19", -- Shenyang J-6 and Mikoyan-Gurevich MiG-19 + Felon = "Su-57", + Fencer = "Su-24", + Fishbed = "MiG-21", + Fitter = "Su-17", -- Sukhoi Su-7 and Su-17/Su-20/Su-22 + Flogger = "MiG-23", --and MiG-27 + Flogger_D = "MiG-27", --and MiG-23 + Flagon = "Su-15", + Foxbat = "MiG-25", + Fulcrum = "MiG-29", + Foxhound = "MiG-31", + Flanker = "Su-27", -- Sukhoi Su-27/Su-30/Su-33/Su-35/Su-37 and Shenyang J-11/J-15/J-16 + Flanker_C = "Su-30", + Flanker_E = "Su-35", + Flanker_F = "Su-37", + Flanker_Dragon = "J-11A", + Sea_Flanker = "Su-33", + Fullback = "Su-32", -- also Su-34 + Frogfoot = "Su-25", + Tomcat = "F-14", -- Iran + Mirage = "Mirage", -- various non-NATO + -- Bomber + H6J = "H6-J", + Sea_Bear = "Tu-142", -- also Tu-95 + Bear = "Tu-95", -- also Tu-142 + Blinder = "Tu-22", + Blackjack = "Tu-160", + -- AIC / Transport / Other + Clank = "An-30", + Curl = "An-26", + Candid = "IL-76", + Midas = "IL-78", + Mainstay = "A-50", -- KJ-2000 China + } +} diff --git a/Moose Development/Moose/Utilities/FiFo.lua b/Moose Development/Moose/Utilities/FiFo.lua new file mode 100644 index 000000000..38b318a2d --- /dev/null +++ b/Moose Development/Moose/Utilities/FiFo.lua @@ -0,0 +1,769 @@ +--- **UTILS** - ClassicFiFo Stack. +-- +-- === +-- +-- ## Main Features: +-- +-- * Build a simple multi-purpose FiFo (First-In, First-Out) stack for generic data. +-- * [Wikipedia](https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics) +-- +-- === +-- +-- ### Author: **applevangelist** +-- @module Utils.FiFo +-- @image MOOSE.JPG + +-- Date: April 2022 + +do +--- FIFO class. +-- @type FIFO +-- @field #string ClassName Name of the class. +-- @field #string lid Class id string for output to DCS log file. +-- @field #string version Version of FiFo +-- @field #number counter +-- @field #number pointer +-- @field #table stackbypointer +-- @field #table stackbyid +-- @extends Core.Base#BASE + +--- +-- @type FIFO.IDEntry +-- @field #number pointer +-- @field #table data +-- @field #table uniqueID + +--- +-- @field #FIFO +FIFO = { + ClassName = "FIFO", + lid = "", + version = "0.0.5", + counter = 0, + pointer = 0, + stackbypointer = {}, + stackbyid = {} +} + +--- Instantiate a new FIFO Stack +-- @param #FIFO self +-- @return #FIFO self +function FIFO:New() + -- Inherit everything from BASE class. + local self=BASE:Inherit(self, BASE:New()) + self.pointer = 0 + self.counter = 0 + self.stackbypointer = {} + self.stackbyid = {} + self.uniquecounter = 0 + -- Set some string id for output to DCS.log file. + self.lid=string.format("%s (%s) | ", "FiFo", self.version) + self:T(self.lid .."Created.") + return self +end + +--- Empty FIFO Stack +-- @param #FIFO self +-- @return #FIFO self +function FIFO:Clear() + self:T(self.lid.."Clear") + self.pointer = 0 + self.counter = 0 + self.stackbypointer = nil + self.stackbyid = nil + self.stackbypointer = {} + self.stackbyid = {} + self.uniquecounter = 0 + return self +end + +--- FIFO Push Object to Stack +-- @param #FIFO self +-- @param #table Object +-- @param #string UniqueID (optional) - will default to current pointer + 1. Note - if you intend to use `FIFO:GetIDStackSorted()` keep the UniqueID numerical! +-- @return #FIFO self +function FIFO:Push(Object,UniqueID) + self:T(self.lid.."Push") + self:T({Object,UniqueID}) + self.pointer = self.pointer + 1 + self.counter = self.counter + 1 + local uniID = UniqueID + if not UniqueID then + self.uniquecounter = self.uniquecounter + 1 + uniID = self.uniquecounter + end + self.stackbyid[uniID] = { pointer = self.pointer, data = Object, uniqueID = uniID } + self.stackbypointer[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = uniID } + return self +end + +--- FIFO Pull Object from Stack +-- @param #FIFO self +-- @return #table Object or nil if stack is empty +function FIFO:Pull() + self:T(self.lid.."Pull") + if self.counter == 0 then return nil end + --local object = self.stackbypointer[self.pointer].data + --self.stackbypointer[self.pointer] = nil + local object = self.stackbypointer[1].data + self.stackbypointer[1] = nil + self.counter = self.counter - 1 + --self.pointer = self.pointer - 1 + self:Flatten() + return object +end + +--- FIFO Pull Object from Stack by Pointer +-- @param #FIFO self +-- @param #number Pointer +-- @return #table Object or nil if stack is empty +function FIFO:PullByPointer(Pointer) + self:T(self.lid.."PullByPointer " .. tostring(Pointer)) + if self.counter == 0 then return nil end + local object = self.stackbypointer[Pointer] -- #FIFO.IDEntry + self.stackbypointer[Pointer] = nil + if object then self.stackbyid[object.uniqueID] = nil end + self.counter = self.counter - 1 + self:Flatten() + if object then + return object.data + else + return nil + end +end + + +--- FIFO Read, not Pull, Object from Stack by Pointer +-- @param #FIFO self +-- @param #number Pointer +-- @return #table Object or nil if stack is empty or pointer does not exist +function FIFO:ReadByPointer(Pointer) + self:T(self.lid.."ReadByPointer " .. tostring(Pointer)) + if self.counter == 0 or not Pointer or not self.stackbypointer[Pointer] then return nil end + local object = self.stackbypointer[Pointer] -- #FIFO.IDEntry + if object then + return object.data + else + return nil + end +end + +--- FIFO Read, not Pull, Object from Stack by UniqueID +-- @param #FIFO self +-- @param #number UniqueID +-- @return #table Object data or nil if stack is empty or ID does not exist +function FIFO:ReadByID(UniqueID) + self:T(self.lid.."ReadByID " .. tostring(UniqueID)) + if self.counter == 0 or not UniqueID or not self.stackbyid[UniqueID] then return nil end + local object = self.stackbyid[UniqueID] -- #FIFO.IDEntry + if object then + return object.data + else + return nil + end +end + +--- FIFO Pull Object from Stack by UniqueID +-- @param #FIFO self +-- @param #tableUniqueID +-- @return #table Object or nil if stack is empty +function FIFO:PullByID(UniqueID) + self:T(self.lid.."PullByID " .. tostring(UniqueID)) + if self.counter == 0 then return nil end + local object = self.stackbyid[UniqueID] -- #FIFO.IDEntry + --self.stackbyid[UniqueID] = nil + if object then + return self:PullByPointer(object.pointer) + else + return nil + end +end + +--- FIFO Housekeeping +-- @param #FIFO self +-- @return #FIFO self +function FIFO:Flatten() + self:T(self.lid.."Flatten") + -- rebuild stacks + local pointerstack = {} + local idstack = {} + local counter = 0 + for _ID,_entry in pairs(self.stackbypointer) do + counter = counter + 1 + pointerstack[counter] = { pointer = counter, data = _entry.data, uniqueID = _entry.uniqueID} + end + for _ID,_entry in pairs(pointerstack) do + idstack[_entry.uniqueID] = { pointer = _entry.pointer , data = _entry.data, uniqueID = _entry.uniqueID} + end + self.stackbypointer = nil + self.stackbypointer = pointerstack + self.stackbyid = nil + self.stackbyid = idstack + self.counter = counter + self.pointer = counter + return self +end + +--- FIFO Check Stack is empty +-- @param #FIFO self +-- @return #boolean empty +function FIFO:IsEmpty() + self:T(self.lid.."IsEmpty") + return self.counter == 0 and true or false +end + +--- FIFO Get stack size +-- @param #FIFO self +-- @return #number size +function FIFO:GetSize() + self:T(self.lid.."GetSize") + return self.counter +end + +--- FIFO Get stack size +-- @param #FIFO self +-- @return #number size +function FIFO:Count() + self:T(self.lid.."Count") + return self.counter +end + +--- FIFO Check Stack is NOT empty +-- @param #FIFO self +-- @return #boolean notempty +function FIFO:IsNotEmpty() + self:T(self.lid.."IsNotEmpty") + return not self:IsEmpty() +end + +--- FIFO Get the data stack by pointer +-- @param #FIFO self +-- @return #table Table of #FIFO.IDEntry entries +function FIFO:GetPointerStack() + self:T(self.lid.."GetPointerStack") + return self.stackbypointer +end + +--- FIFO Check if a certain UniqeID exists +-- @param #FIFO self +-- @return #boolean exists +function FIFO:HasUniqueID(UniqueID) + self:T(self.lid.."HasUniqueID") + return self.stackbyid[UniqueID] and true or false +end + +--- FIFO Get the data stack by UniqueID +-- @param #FIFO self +-- @return #table Table of #FIFO.IDEntry entries +function FIFO:GetIDStack() + self:T(self.lid.."GetIDStack") + return self.stackbyid +end + +--- FIFO Get table of UniqueIDs sorted smallest to largest +-- @param #FIFO self +-- @return #table Table with index [1] to [n] of UniqueID entries +function FIFO:GetIDStackSorted() + self:T(self.lid.."GetIDStackSorted") + + local stack = self:GetIDStack() + local idstack = {} + for _id,_entry in pairs(stack) do + idstack[#idstack+1] = _id + + self:T({"pre",_id}) + end + + local function sortID(a, b) + return a < b + end + + table.sort(idstack) + + return idstack +end + +--- FIFO Get table of data entries +-- @param #FIFO self +-- @return #table Raw table indexed [1] to [n] of object entries - might be empty! +function FIFO:GetDataTable() + self:T(self.lid.."GetDataTable") + local datatable = {} + for _,_entry in pairs(self.stackbypointer) do + datatable[#datatable+1] = _entry.data + end + return datatable +end + +--- FIFO Get sorted table of data entries by UniqueIDs (must be numerical UniqueIDs only!) +-- @param #FIFO self +-- @return #table Table indexed [1] to [n] of sorted object entries - might be empty! +function FIFO:GetSortedDataTable() + self:T(self.lid.."GetSortedDataTable") + local datatable = {} + local idtablesorted = self:GetIDStackSorted() + for _,_entry in pairs(idtablesorted) do + datatable[#datatable+1] = self:ReadByID(_entry) + end + return datatable +end + +--- Iterate the FIFO and call an iterator function for the given FIFO data, providing the object for each element of the stack and optional parameters. +-- @param #FIFO self +-- @param #function IteratorFunction The function that will be called. +-- @param #table Arg (Optional) Further Arguments of the IteratorFunction. +-- @param #function Function (Optional) A function returning a #boolean true/false. Only if true, the IteratorFunction is called. +-- @param #table FunctionArguments (Optional) Function arguments. +-- @return #FIFO self +function FIFO:ForEach( IteratorFunction, Arg, Function, FunctionArguments ) + self:T(self.lid.."ForEach") + + local Set = self:GetPointerStack() or {} + Arg = Arg or {} + + local function CoRoutine() + local Count = 0 + for ObjectID, ObjectData in pairs( Set ) do + local Object = ObjectData.data + self:T( {Object} ) + if Function then + if Function( unpack( FunctionArguments or {} ), Object ) == true then + IteratorFunction( Object, unpack( Arg ) ) + end + else + IteratorFunction( Object, unpack( Arg ) ) + end + Count = Count + 1 + end + return true + end + + local co = CoRoutine + + local function Schedule() + + local status, res = co() + self:T( { status, res } ) + + if status == false then + error( res ) + end + if res == false then + return true -- resume next time the loop + end + + return false + end + + Schedule() + + return self +end + +--- FIFO Print stacks to dcs.log +-- @param #FIFO self +-- @return #FIFO self +function FIFO:Flush() + self:T(self.lid.."FiFo Flush") + self:I("FIFO Flushing Stack by Pointer") + for _id,_data in pairs (self.stackbypointer) do + local data = _data -- #FIFO.IDEntry + self:I(string.format("Pointer: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) + end + self:I("FIFO Flushing Stack by ID") + for _id,_data in pairs (self.stackbyid) do + local data = _data -- #FIFO.IDEntry + self:I(string.format("ID: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) + end + self:I("Counter = " .. self.counter) + self:I("Pointer = ".. self.pointer) + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- End FIFO +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- LIFO +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +do +--- **UTILS** - LiFo Stack. +-- +-- **Main Features:** +-- +-- * Build a simple multi-purpose LiFo (Last-In, First-Out) stack for generic data. +-- +-- === +-- +-- ### Author: **applevangelist** + +--- LIFO class. +-- @type LIFO +-- @field #string ClassName Name of the class. +-- @field #string lid Class id string for output to DCS log file. +-- @field #string version Version of LiFo +-- @field #number counter +-- @field #number pointer +-- @field #table stackbypointer +-- @field #table stackbyid +-- @extends Core.Base#BASE + +--- +-- @type LIFO.IDEntry +-- @field #number pointer +-- @field #table data +-- @field #table uniqueID + +--- +-- @field #LIFO +LIFO = { + ClassName = "LIFO", + lid = "", + version = "0.0.5", + counter = 0, + pointer = 0, + stackbypointer = {}, + stackbyid = {} +} + +--- Instantiate a new LIFO Stack +-- @param #LIFO self +-- @return #LIFO self +function LIFO:New() + -- Inherit everything from BASE class. + local self=BASE:Inherit(self, BASE:New()) + self.pointer = 0 + self.counter = 0 + self.uniquecounter = 0 + self.stackbypointer = {} + self.stackbyid = {} + -- Set some string id for output to DCS.log file. + self.lid=string.format("%s (%s) | ", "LiFo", self.version) + self:T(self.lid .."Created.") + return self +end + +--- Empty LIFO Stack +-- @param #LIFO self +-- @return #LIFO self +function LIFO:Clear() + self:T(self.lid.."Clear") + self.pointer = 0 + self.counter = 0 + self.stackbypointer = nil + self.stackbyid = nil + self.stackbypointer = {} + self.stackbyid = {} + self.uniquecounter = 0 + return self +end + +--- LIFO Push Object to Stack +-- @param #LIFO self +-- @param #table Object +-- @param #string UniqueID (optional) - will default to current pointer + 1 +-- @return #LIFO self +function LIFO:Push(Object,UniqueID) + self:T(self.lid.."Push") + self:T({Object,UniqueID}) + self.pointer = self.pointer + 1 + self.counter = self.counter + 1 + local uniID = UniqueID + if not UniqueID then + self.uniquecounter = self.uniquecounter + 1 + uniID = self.uniquecounter + end + self.stackbyid[uniID] = { pointer = self.pointer, data = Object, uniqueID = uniID } + self.stackbypointer[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = uniID } + return self +end + +--- LIFO Pull Object from Stack +-- @param #LIFO self +-- @return #table Object or nil if stack is empty +function LIFO:Pull() + self:T(self.lid.."Pull") + if self.counter == 0 then return nil end + local object = self.stackbypointer[self.pointer].data + self.stackbypointer[self.pointer] = nil + --local object = self.stackbypointer[1].data + --self.stackbypointer[1] = nil + self.counter = self.counter - 1 + self.pointer = self.pointer - 1 + self:Flatten() + return object +end + +--- LIFO Pull Object from Stack by Pointer +-- @param #LIFO self +-- @param #number Pointer +-- @return #table Object or nil if stack is empty +function LIFO:PullByPointer(Pointer) + self:T(self.lid.."PullByPointer " .. tostring(Pointer)) + if self.counter == 0 then return nil end + local object = self.stackbypointer[Pointer] -- #FIFO.IDEntry + self.stackbypointer[Pointer] = nil + if object then self.stackbyid[object.uniqueID] = nil end + self.counter = self.counter - 1 + self:Flatten() + if object then + return object.data + else + return nil + end +end + +--- LIFO Read, not Pull, Object from Stack by Pointer +-- @param #LIFO self +-- @param #number Pointer +-- @return #table Object or nil if stack is empty or pointer does not exist +function LIFO:ReadByPointer(Pointer) + self:T(self.lid.."ReadByPointer " .. tostring(Pointer)) + if self.counter == 0 or not Pointer or not self.stackbypointer[Pointer] then return nil end + local object = self.stackbypointer[Pointer] -- #LIFO.IDEntry + if object then + return object.data + else + return nil + end +end + +--- LIFO Read, not Pull, Object from Stack by UniqueID +-- @param #LIFO self +-- @param #number UniqueID +-- @return #table Object or nil if stack is empty or ID does not exist +function LIFO:ReadByID(UniqueID) + self:T(self.lid.."ReadByID " .. tostring(UniqueID)) + if self.counter == 0 or not UniqueID or not self.stackbyid[UniqueID] then return nil end + local object = self.stackbyid[UniqueID] -- #LIFO.IDEntry + if object then + return object.data + else + return nil + end +end + +--- LIFO Pull Object from Stack by UniqueID +-- @param #LIFO self +-- @param #tableUniqueID +-- @return #table Object or nil if stack is empty +function LIFO:PullByID(UniqueID) + self:T(self.lid.."PullByID " .. tostring(UniqueID)) + if self.counter == 0 then return nil end + local object = self.stackbyid[UniqueID] -- #LIFO.IDEntry + --self.stackbyid[UniqueID] = nil + if object then + return self:PullByPointer(object.pointer) + else + return nil + end +end + +--- LIFO Housekeeping +-- @param #LIFO self +-- @return #LIFO self +function LIFO:Flatten() + self:T(self.lid.."Flatten") + -- rebuild stacks + local pointerstack = {} + local idstack = {} + local counter = 0 + for _ID,_entry in pairs(self.stackbypointer) do + counter = counter + 1 + pointerstack[counter] = { pointer = counter, data = _entry.data, uniqueID = _entry.uniqueID} + end + for _ID,_entry in pairs(pointerstack) do + idstack[_entry.uniqueID] = { pointer = _entry.pointer , data = _entry.data, uniqueID = _entry.uniqueID} + end + self.stackbypointer = nil + self.stackbypointer = pointerstack + self.stackbyid = nil + self.stackbyid = idstack + self.counter = counter + self.pointer = counter + return self +end + +--- LIFO Check Stack is empty +-- @param #LIFO self +-- @return #boolean empty +function LIFO:IsEmpty() + self:T(self.lid.."IsEmpty") + return self.counter == 0 and true or false +end + +--- LIFO Get stack size +-- @param #LIFO self +-- @return #number size +function LIFO:GetSize() + self:T(self.lid.."GetSize") + return self.counter +end + +--- LIFO Get stack size +-- @param #LIFO self +-- @return #number size +function LIFO:Count() + self:T(self.lid.."Count") + return self.counter +end + +--- LIFO Check Stack is NOT empty +-- @param #LIFO self +-- @return #boolean notempty +function LIFO:IsNotEmpty() + self:T(self.lid.."IsNotEmpty") + return not self:IsEmpty() +end + +--- LIFO Get the data stack by pointer +-- @param #LIFO self +-- @return #table Table of #LIFO.IDEntry entries +function LIFO:GetPointerStack() + self:T(self.lid.."GetPointerStack") + return self.stackbypointer +end + +--- LIFO Get the data stack by UniqueID +-- @param #LIFO self +-- @return #table Table of #LIFO.IDEntry entries +function LIFO:GetIDStack() + self:T(self.lid.."GetIDStack") + return self.stackbyid +end + +--- LIFO Get table of UniqueIDs sorted smallest to largest +-- @param #LIFO self +-- @return #table Table of #LIFO.IDEntry entries +function LIFO:GetIDStackSorted() + self:T(self.lid.."GetIDStackSorted") + + local stack = self:GetIDStack() + local idstack = {} + for _id,_entry in pairs(stack) do + idstack[#idstack+1] = _id + + self:T({"pre",_id}) + end + + local function sortID(a, b) + return a < b + end + + table.sort(idstack) + + return idstack +end + +--- LIFO Check if a certain UniqeID exists +-- @param #LIFO self +-- @return #boolean exists +function LIFO:HasUniqueID(UniqueID) + self:T(self.lid.."HasUniqueID") + return self.stackbyid[UniqueID] and true or false +end + +--- LIFO Print stacks to dcs.log +-- @param #LIFO self +-- @return #LIFO self +function LIFO:Flush() + self:T(self.lid.."FiFo Flush") + self:I("LIFO Flushing Stack by Pointer") + for _id,_data in pairs (self.stackbypointer) do + local data = _data -- #LIFO.IDEntry + self:I(string.format("Pointer: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) + end + self:I("LIFO Flushing Stack by ID") + for _id,_data in pairs (self.stackbyid) do + local data = _data -- #LIFO.IDEntry + self:I(string.format("ID: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) + end + self:I("Counter = " .. self.counter) + self:I("Pointer = ".. self.pointer) + return self +end + +--- LIFO Get table of data entries +-- @param #LIFO self +-- @return #table Raw table indexed [1] to [n] of object entries - might be empty! +function LIFO:GetDataTable() + self:T(self.lid.."GetDataTable") + local datatable = {} + for _,_entry in pairs(self.stackbypointer) do + datatable[#datatable+1] = _entry.data + end + return datatable +end + +--- LIFO Get sorted table of data entries by UniqueIDs (must be numerical UniqueIDs only!) +-- @param #LIFO self +-- @return #table Table indexed [1] to [n] of sorted object entries - might be empty! +function LIFO:GetSortedDataTable() + self:T(self.lid.."GetSortedDataTable") + local datatable = {} + local idtablesorted = self:GetIDStackSorted() + for _,_entry in pairs(idtablesorted) do + datatable[#datatable+1] = self:ReadByID(_entry) + end + return datatable +end + +--- Iterate the LIFO and call an iterator function for the given LIFO data, providing the object for each element of the stack and optional parameters. +-- @param #LIFO self +-- @param #function IteratorFunction The function that will be called. +-- @param #table Arg (Optional) Further Arguments of the IteratorFunction. +-- @param #function Function (Optional) A function returning a #boolean true/false. Only if true, the IteratorFunction is called. +-- @param #table FunctionArguments (Optional) Function arguments. +-- @return #LIFO self +function LIFO:ForEach( IteratorFunction, Arg, Function, FunctionArguments ) + self:T(self.lid.."ForEach") + + local Set = self:GetPointerStack() or {} + Arg = Arg or {} + + local function CoRoutine() + local Count = 0 + for ObjectID, ObjectData in pairs( Set ) do + local Object = ObjectData.data + self:T( {Object} ) + if Function then + if Function( unpack( FunctionArguments or {} ), Object ) == true then + IteratorFunction( Object, unpack( Arg ) ) + end + else + IteratorFunction( Object, unpack( Arg ) ) + end + Count = Count + 1 + end + return true + end + + local co = CoRoutine + + local function Schedule() + + local status, res = co() + self:T( { status, res } ) + + if status == false then + error( res ) + end + if res == false then + return true -- resume next time the loop + end + + return false + end + + Schedule() + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- End LIFO +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +end \ No newline at end of file diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 46805e997..5e7a411e5 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -809,7 +809,7 @@ function UTILS.BeaufortScale(speed) return bn,bd end ---- Split string at seperators. C.f. http://stackoverflow.com/questions/1426954/split-string-in-lua +--- Split string at seperators. C.f. [split-string-in-lua](http://stackoverflow.com/questions/1426954/split-string-in-lua). -- @param #string str Sting to split. -- @param #string sep Speparator for split. -- @return #table Split text. @@ -1447,6 +1447,23 @@ function UTILS.GetModulationName(Modulation) end +--- Get the NATO reporting name of a unit type name +-- @param #number Typename The type name. +-- @return #string The Reporting name or "Bogey". +function UTILS.GetReportingName(Typename) + + local typename = string.lower(Typename) + + for name, value in pairs(ENUMS.ReportingName.NATO) do + local svalue = string.lower(value) + if string.find(typename,svalue,1,true) then + return name + end + end + + return "Bogey" +end + --- Get the callsign name from its enumerator value -- @param #number Callsign The enumerator callsign. -- @return #string The callsign name or "Ghostrider". @@ -1475,7 +1492,49 @@ function UTILS.GetCallsignName(Callsign) return name end end - + + for name, value in pairs(CALLSIGN.B1B) do + if value==Callsign then + return name + end + end + + for name, value in pairs(CALLSIGN.B52) do + if value==Callsign then + return name + end + end + + for name, value in pairs(CALLSIGN.F15E) do + if value==Callsign then + return name + end + end + + for name, value in pairs(CALLSIGN.F16) do + if value==Callsign then + return name + end + end + + for name, value in pairs(CALLSIGN.F18) do + if value==Callsign then + return name + end + end + + for name, value in pairs(CALLSIGN.FARP) do + if value==Callsign then + return name + end + end + + for name, value in pairs(CALLSIGN.TransportAircraft) do + if value==Callsign then + return name + end + end + return "Ghostrider" end @@ -2402,493 +2461,3 @@ function UTILS.ToStringBRAANATO(FromGrp,ToGrp) end return BRAANATO end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- FIFO -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - -do ---- **UTILS** - FiFo Stack. --- --- **Main Features:** --- --- * Build a simple multi-purpose FiFo (First-In, First-Out) stack for generic data. --- --- === --- --- ### Author: **applevangelist** - ---- FIFO class. --- @type FIFO --- @field #string ClassName Name of the class. --- @field #string lid Class id string for output to DCS log file. --- @field #string version Version of FiFo --- @field #number counter --- @field #number pointer --- @field #table stackbypointer --- @field #table stackbyid --- @extends Core.Base#BASE - ---- --- @type FIFO.IDEntry --- @field #number pointer --- @field #table data --- @field #table uniqueID - ---- --- @field #FIFO -FIFO = { - ClassName = "FIFO", - lid = "", - version = "0.0.1", - counter = 0, - pointer = 0, - stackbypointer = {}, - stackbyid = {} -} - ---- Instantiate a new FIFO Stack --- @param #FIFO self --- @return #FIFO self -function FIFO:New() - -- Inherit everything from BASE class. - local self=BASE:Inherit(self, BASE:New()) - self.pointer = 0 - self.counter = 0 - self.stackbypointer = {} - self.stackbyid = {} - self.uniquecounter = 0 - -- Set some string id for output to DCS.log file. - self.lid=string.format("%s (%s) | ", "FiFo", self.version) - self:I(self.lid .."Created.") - return self -end - ---- FIFO Push Object to Stack --- @param #FIFO self --- @param #table Object --- @param #string UniqueID (optional) - will default to current pointer + 1 --- @return #FIFO self -function FIFO:Push(Object,UniqueID) - self:T(self.lid.."Push") - self:T({Object,UniqueID}) - self.pointer = self.pointer + 1 - self.counter = self.counter + 1 - local uniID = UniqueID - if not UniqueID then - self.uniquecounter = self.uniquecounter + 1 - uniID = self.uniquecounter - end - self.stackbyid[uniID] = { pointer = self.pointer, data = Object, uniqueID = uniID } - self.stackbypointer[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = uniID } - return self -end - ---- FIFO Pull Object from Stack --- @param #FIFO self --- @return #table Object or nil if stack is empty -function FIFO:Pull() - self:T(self.lid.."Pull") - if self.counter == 0 then return nil end - --local object = self.stackbypointer[self.pointer].data - --self.stackbypointer[self.pointer] = nil - local object = self.stackbypointer[1].data - self.stackbypointer[1] = nil - self.counter = self.counter - 1 - --self.pointer = self.pointer - 1 - self:Flatten() - return object -end - ---- FIFO Pull Object from Stack by Pointer --- @param #FIFO self --- @param #number Pointer --- @return #table Object or nil if stack is empty -function FIFO:PullByPointer(Pointer) - self:T(self.lid.."PullByPointer " .. tostring(Pointer)) - if self.counter == 0 then return nil end - local object = self.stackbypointer[Pointer] -- #FIFO.IDEntry - self.stackbypointer[Pointer] = nil - self.stackbyid[object.uniqueID] = nil - self.counter = self.counter - 1 - self:Flatten() - return object.data -end - ---- FIFO Pull Object from Stack by UniqueID --- @param #FIFO self --- @param #tableUniqueID --- @return #table Object or nil if stack is empty -function FIFO:PullByID(UniqueID) - self:T(self.lid.."PullByID " .. tostring(UniqueID)) - if self.counter == 0 then return nil end - local object = self.stackbyid[UniqueID] -- #FIFO.IDEntry - --self.stackbyid[UniqueID] = nil - return self:PullByPointer(object.pointer) -end - ---- FIFO Housekeeping --- @param #FIFO self --- @return #FIFO self -function FIFO:Flatten() - self:T(self.lid.."Flatten") - -- rebuild stacks - local pointerstack = {} - local idstack = {} - local counter = 0 - for _ID,_entry in pairs(self.stackbypointer) do - counter = counter + 1 - pointerstack[counter] = { pointer = counter, data = _entry.data, uniqueID = _entry.uniqueID} - end - for _ID,_entry in pairs(pointerstack) do - idstack[_entry.uniqueID] = { pointer = _entry.pointer , data = _entry.data, uniqueID = _entry.uniqueID} - end - self.stackbypointer = nil - self.stackbypointer = pointerstack - self.stackbyid = nil - self.stackbyid = idstack - self.counter = counter - self.pointer = counter - return self -end - ---- FIFO Check Stack is empty --- @param #FIFO self --- @return #boolean empty -function FIFO:IsEmpty() - self:T(self.lid.."IsEmpty") - return self.counter == 0 and true or false -end - ---- FIFO Get stack size --- @param #FIFO self --- @return #number size -function FIFO:GetSize() - self:T(self.lid.."GetSize") - return self.counter -end - ---- FIFO Check Stack is NOT empty --- @param #FIFO self --- @return #boolean notempty -function FIFO:IsNotEmpty() - self:T(self.lid.."IsNotEmpty") - return not self:IsEmpty() -end - ---- FIFO Get the data stack by pointer --- @param #FIFO self --- @return #table Table of #FIFO.IDEntry entries -function FIFO:GetPointerStack() - self:T(self.lid.."GetPointerStack") - return self.stackbypointer -end - ---- FIFO Check if a certain UniqeID exists --- @param #FIFO self --- @return #boolean exists -function FIFO:HasUniqueID(UniqueID) - self:T(self.lid.."HasUniqueID") - return self.stackbyid[UniqueID] and true or false -end - ---- FIFO Get the data stack by UniqueID --- @param #FIFO self --- @return #table Table of #FIFO.IDEntry entries -function FIFO:GetIDStack() - self:T(self.lid.."GetIDStack") - return self.stackbyid -end - ---- FIFO Get table of UniqueIDs sorthed smallest to largest --- @param #FIFO self --- @return #table Table of #FIFO.IDEntry entries -function FIFO:GetIDStackSorted() - self:T(self.lid.."GetIDStackSorted") - - local stack = self:GetIDStack() - local idstack = {} - for _id,_entry in pairs(stack) do - idstack[#idstack+1] = _id - - self:T({"pre",_id}) - end - - local function sortID(a, b) - return a < b - end - - table.sort(idstack) - - return idstack -end - ---- FIFO Print stacks to dcs.log --- @param #FIFO self --- @return #FIFO self -function FIFO:Flush() - self:T(self.lid.."FiFo Flush") - self:I("FIFO Flushing Stack by Pointer") - for _id,_data in pairs (self.stackbypointer) do - local data = _data -- #FIFO.IDEntry - self:I(string.format("Pointer: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) - end - self:I("FIFO Flushing Stack by ID") - for _id,_data in pairs (self.stackbyid) do - local data = _data -- #FIFO.IDEntry - self:I(string.format("ID: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) - end - self:I("Counter = " .. self.counter) - self:I("Pointer = ".. self.pointer) - return self -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- End FIFO -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- LIFO -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - -do ---- **UTILS** - LiFo Stack. --- --- **Main Features:** --- --- * Build a simple multi-purpose LiFo (Last-In, First-Out) stack for generic data. --- --- === --- --- ### Author: **applevangelist** - ---- LIFO class. --- @type LIFO --- @field #string ClassName Name of the class. --- @field #string lid Class id string for output to DCS log file. --- @field #string version Version of LiFo --- @field #number counter --- @field #number pointer --- @field #table stackbypointer --- @field #table stackbyid --- @extends Core.Base#BASE - ---- --- @type LIFO.IDEntry --- @field #number pointer --- @field #table data --- @field #table uniqueID - ---- --- @field #LIFO -LIFO = { - ClassName = "LIFO", - lid = "", - version = "0.0.1", - counter = 0, - pointer = 0, - stackbypointer = {}, - stackbyid = {} -} - ---- Instantiate a new LIFO Stack --- @param #LIFO self --- @return #LIFO self -function LIFO:New() - -- Inherit everything from BASE class. - local self=BASE:Inherit(self, BASE:New()) - self.pointer = 0 - self.counter = 0 - self.uniquecounter = 0 - self.stackbypointer = {} - self.stackbyid = {} - -- Set some string id for output to DCS.log file. - self.lid=string.format("%s (%s) | ", "LiFo", self.version) - self:I(self.lid .."Created.") - return self -end - ---- LIFO Push Object to Stack --- @param #LIFO self --- @param #table Object --- @param #string UniqueID (optional) - will default to current pointer + 1 --- @return #LIFO self -function LIFO:Push(Object,UniqueID) - self:T(self.lid.."Push") - self:T({Object,UniqueID}) - self.pointer = self.pointer + 1 - self.counter = self.counter + 1 - local uniID = UniqueID - if not UniqueID then - self.uniquecounter = self.uniquecounter + 1 - uniID = self.uniquecounter - end - self.stackbyid[uniID] = { pointer = self.pointer, data = Object, uniqueID = uniID } - self.stackbypointer[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = uniID } - return self -end - ---- LIFO Pull Object from Stack --- @param #LIFO self --- @return #table Object or nil if stack is empty -function LIFO:Pull() - self:T(self.lid.."Pull") - if self.counter == 0 then return nil end - local object = self.stackbypointer[self.pointer].data - self.stackbypointer[self.pointer] = nil - --local object = self.stackbypointer[1].data - --self.stackbypointer[1] = nil - self.counter = self.counter - 1 - self.pointer = self.pointer - 1 - self:Flatten() - return object -end - ---- LIFO Pull Object from Stack by Pointer --- @param #LIFO self --- @param #number Pointer --- @return #table Object or nil if stack is empty -function LIFO:PullByPointer(Pointer) - self:T(self.lid.."PullByPointer " .. tostring(Pointer)) - if self.counter == 0 then return nil end - local object = self.stackbypointer[Pointer] -- #LIFO.IDEntry - self.stackbypointer[Pointer] = nil - self.stackbyid[object.uniqueID] = nil - self.counter = self.counter - 1 - self:Flatten() - return object.data -end - ---- LIFO Pull Object from Stack by UniqueID --- @param #LIFO self --- @param #tableUniqueID --- @return #table Object or nil if stack is empty -function LIFO:PullByID(UniqueID) - self:T(self.lid.."PullByID " .. tostring(UniqueID)) - if self.counter == 0 then return nil end - local object = self.stackbyid[UniqueID] -- #LIFO.IDEntry - --self.stackbyid[UniqueID] = nil - return self:PullByPointer(object.pointer) -end - ---- LIFO Housekeeping --- @param #LIFO self --- @return #LIFO self -function LIFO:Flatten() - self:T(self.lid.."Flatten") - -- rebuild stacks - local pointerstack = {} - local idstack = {} - local counter = 0 - for _ID,_entry in pairs(self.stackbypointer) do - counter = counter + 1 - pointerstack[counter] = { pointer = counter, data = _entry.data, uniqueID = _entry.uniqueID} - end - for _ID,_entry in pairs(pointerstack) do - idstack[_entry.uniqueID] = { pointer = _entry.pointer , data = _entry.data, uniqueID = _entry.uniqueID} - end - self.stackbypointer = nil - self.stackbypointer = pointerstack - self.stackbyid = nil - self.stackbyid = idstack - self.counter = counter - self.pointer = counter - return self -end - ---- LIFO Check Stack is empty --- @param #LIFO self --- @return #boolean empty -function LIFO:IsEmpty() - self:T(self.lid.."IsEmpty") - return self.counter == 0 and true or false -end - ---- LIFO Get stack size --- @param #LIFO self --- @return #number size -function LIFO:GetSize() - self:T(self.lid.."GetSize") - return self.counter -end - ---- LIFO Check Stack is NOT empty --- @param #LIFO self --- @return #boolean notempty -function LIFO:IsNotEmpty() - self:T(self.lid.."IsNotEmpty") - return not self:IsEmpty() -end - ---- LIFO Get the data stack by pointer --- @param #LIFO self --- @return #table Table of #LIFO.IDEntry entries -function LIFO:GetPointerStack() - self:T(self.lid.."GetPointerStack") - return self.stackbypointer -end - ---- LIFO Get the data stack by UniqueID --- @param #LIFO self --- @return #table Table of #LIFO.IDEntry entries -function LIFO:GetIDStack() - self:T(self.lid.."GetIDStack") - return self.stackbyid -end - ---- LIFO Get table of UniqueIDs sorthed smallest to largest --- @param #LIFO self --- @return #table Table of #LIFO.IDEntry entries -function LIFO:GetIDStackSorted() - self:T(self.lid.."GetIDStackSorted") - - local stack = self:GetIDStack() - local idstack = {} - for _id,_entry in pairs(stack) do - idstack[#idstack+1] = _id - - self:T({"pre",_id}) - end - - local function sortID(a, b) - return a < b - end - - table.sort(idstack) - - return idstack -end - ---- LIFO Check if a certain UniqeID exists --- @param #LIFO self --- @return #boolean exists -function LIFO:HasUniqueID(UniqueID) - self:T(self.lid.."HasUniqueID") - return self.stackbyid[UniqueID] and true or false -end - ---- LIFO Print stacks to dcs.log --- @param #LIFO self --- @return #LIFO self -function LIFO:Flush() - self:T(self.lid.."FiFo Flush") - self:I("LIFO Flushing Stack by Pointer") - for _id,_data in pairs (self.stackbypointer) do - local data = _data -- #LIFO.IDEntry - self:I(string.format("Pointer: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) - end - self:I("LIFO Flushing Stack by ID") - for _id,_data in pairs (self.stackbyid) do - local data = _data -- #LIFO.IDEntry - self:I(string.format("ID: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) - end - self:I("Counter = " .. self.counter) - self:I("Pointer = ".. self.pointer) - return self -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- End LIFO -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -end \ No newline at end of file diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 5773a12a2..8127ffba9 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -384,7 +384,7 @@ end -- So all event listeners will catch the destroy event of this group for each unit in the group. -- To raise these events, provide the `GenerateEvent` parameter. -- @param #GROUP self --- @param #boolean GenerateEvent If true, a crash or dead event for each unit is generated. If false, if no event is triggered. If nil, a RemoveUnit event is triggered. +-- @param #boolean GenerateEvent If true, a crash [AIR] or dead [GROUND] event for each unit is generated. If false, if no event is triggered. If nil, a RemoveUnit event is triggered. -- @param #number delay Delay in seconds before despawning the group. -- @usage -- -- Air unit example: destroy the Helicopter and generate a S_EVENT_CRASH for each unit in the Helicopter group. @@ -765,8 +765,7 @@ end --- Returns the average velocity Vec3 vector. -- @param Wrapper.Group#GROUP self --- @return DCS#Vec3 The velocity Vec3 vector --- @return #nil The GROUP is not existing or alive. +-- @return DCS#Vec3 The velocity Vec3 vector or `#nil` if the GROUP is not existing or alive. function GROUP:GetVelocityVec3() self:F2( self.GroupName ) @@ -909,6 +908,24 @@ function GROUP:GetTypeName() return nil end +--- [AIRPLANE] Get the NATO reporting name (platform, e.g. "Flanker") of a GROUP (note - first unit the group). "Bogey" if not found. Currently airplanes only! +--@param #GROUP self +--@return #string NatoReportingName or "Bogey" if unknown. +function GROUP:GetNatoReportingName() + self:F2( self.GroupName ) + + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local GroupTypeName = DCSGroup:getUnit(1):getTypeName() + self:T3( GroupTypeName ) + return UTILS.GetReportingName(GroupTypeName) + end + + return "Bogey" + +end + --- Gets the player name of the group. -- @param #GROUP self -- @return #string The player name of the group. @@ -1006,6 +1023,8 @@ function GROUP:GetCoordinate() if FirstUnit then local FirstUnitCoordinate = FirstUnit:GetCoordinate() + local Heading = self:GetHeading() + FirstUnitCoordinate.Heading = Heading return FirstUnitCoordinate end @@ -1017,9 +1036,8 @@ end --- Returns a random @{DCS#Vec3} vector (point in 3D of the UNIT within the mission) within a range around the first UNIT of the GROUP. -- @param #GROUP self --- @param #number Radius --- @return DCS#Vec3 The random 3D point vector around the first UNIT of the GROUP. --- @return #nil The GROUP is invalid or empty +-- @param #number Radius Radius in meters. +-- @return DCS#Vec3 The random 3D point vector around the first UNIT of the GROUP or #nil The GROUP is invalid or empty. -- @usage -- -- If Radius is ignored, returns the DCS#Vec3 of first UNIT of the GROUP function GROUP:GetRandomVec3(Radius) @@ -1040,8 +1058,7 @@ end --- Returns the mean heading of every UNIT in the GROUP in degrees -- @param #GROUP self --- @return #number mean heading of the GROUP --- @return #nil The first UNIT is not existing or alive. +-- @return #number Mean heading of the GROUP in degrees or #nil The first UNIT is not existing or alive. function GROUP:GetHeading() self:F2(self.GroupName) @@ -1069,8 +1086,8 @@ end --- Return the fuel state and unit reference for the unit with the least -- amount of fuel in the group. -- @param #GROUP self --- @return #number The fuel state of the unit with the least amount of fuel --- @return #Unit reference to #Unit object for further processing +-- @return #number The fuel state of the unit with the least amount of fuel. +-- @return Wrapper.Unit#UNIT reference to #Unit object for further processing. function GROUP:GetFuelMin() self:F3(self.ControllableName) @@ -2617,6 +2634,7 @@ function GROUP:GetSkill() return skill end + --- Get the unit in the group with the highest threat level, which is still alive. -- @param #GROUP self -- @return Wrapper.Unit#UNIT The most dangerous unit in the group. diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index dc5955d3d..ff5563023 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -382,7 +382,8 @@ function POSITIONABLE:GetCoordinate() local PositionableVec3 = self:GetVec3() local coord = COORDINATE:NewFromVec3( PositionableVec3 ) - + local heading = self:GetHeading() + coord.Heading = heading -- Return a new coordinate object. return coord diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index ad2ddc433..bb6b61551 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -88,8 +88,8 @@ -- -- @field #UNIT UNIT UNIT = { - ClassName="UNIT", - UnitName=nil, + ClassName="UNIT", + UnitName=nil, } @@ -102,7 +102,7 @@ UNIT = { -- Registration. - + --- Create a new UNIT from DCSUnit. -- @param #UNIT self -- @param #string UnitName The name of the DCS unit. @@ -168,6 +168,9 @@ function UNIT:GetDCSObject() return nil end + + + --- Respawn the @{Wrapper.Unit} using a (tweaked) template of the parent Group. -- -- This function will: @@ -260,6 +263,8 @@ function UNIT:ReSpawnAt( Coordinate, Heading ) _DATABASE:Spawn( SpawnGroupTemplate ) end + + --- Returns if the unit is activated. -- @param #UNIT self -- @return #boolean `true` if Unit is activated. `nil` The DCS Unit is not existing or alive. @@ -296,6 +301,8 @@ function UNIT:IsAlive() return nil end + + --- Returns the Unit's callsign - the localized string. -- @param #UNIT self -- @return #string The Callsign of the Unit. @@ -401,6 +408,17 @@ function UNIT:GetClient() return nil end +--- [AIRPLANE] Get the NATO reporting name of a UNIT. Currently airplanes only! +--@param #UNIT self +--@return #string NatoReportingName or "Bogey" if unknown. +function UNIT:GetNatoReportingName() + + local typename = self:GetTypeName() + return UTILS.GetReportingName(typename) + +end + + --- Returns the unit's number in the group. -- The number is the same number the unit has in ME. -- It may not be changed during the mission. @@ -517,6 +535,63 @@ function UNIT:IsTanker() return tanker, system end +--- Check if the unit can supply ammo. Currently, we have +-- +-- * M 818 +-- * Ural-375 +-- * ZIL-135 +-- +-- This list needs to be extended, if DCS adds other units capable of supplying ammo. +-- +-- @param #UNIT self +-- @return #boolean If `true`, unit can supply ammo. +function UNIT:IsAmmoSupply() + + -- Type name is the only thing we can check. There is no attribute (Sep. 2021) which would tell us. + local typename=self:GetTypeName() + + if typename=="M 818" then + -- Blue ammo truck. + return true + elseif typename=="Ural-375" then + -- Red ammo truck. + return true + elseif typename=="ZIL-135" then + -- Red ammo truck. Checked that it can also provide ammo. + return true + end + + return false +end + +--- Check if the unit can supply fuel. Currently, we have +-- +-- * M978 HEMTT Tanker +-- * ATMZ-5 +-- * ATMZ-10 +-- * ATZ-5 +-- +-- This list needs to be extended, if DCS adds other units capable of supplying fuel. +-- +-- @param #UNIT self +-- @return #boolean If `true`, unit can supply fuel. +function UNIT:IsFuelSupply() + + -- Type name is the only thing we can check. There is no attribute (Sep. 2021) which would tell us. + local typename=self:GetTypeName() + + if typename=="M978 HEMTT Tanker" then + return true + elseif typename=="ATMZ-5" then + return true + elseif typename=="ATMZ-10" then + return true + elseif typename=="ATZ-5" then + return true + end + + return false +end --- Returns the unit's group if it exist and nil otherwise. -- @param Wrapper.Unit#UNIT self @@ -544,14 +619,14 @@ end -- @return #string The name of the DCS Unit. -- @return #nil The DCS Unit is not existing or alive. function UNIT:GetPrefix() - self:F2( self.UnitName ) + 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 + local UnitPrefix = string.match( self.UnitName, ".*#" ):sub( 1, -2 ) + self:T3( UnitPrefix ) + return UnitPrefix end return nil @@ -676,8 +751,6 @@ function UNIT:GetAmmunition() return nammo, nshells, nrockets, nbombs, nmissiles end - - --- Returns the unit sensors. -- @param #UNIT self -- @return DCS#Unit.Sensors Table of sensors. @@ -954,6 +1027,7 @@ end -- @return #string Some text. function UNIT:GetThreatLevel() + local ThreatLevel = 0 local ThreatText = "" @@ -979,6 +1053,7 @@ function UNIT:GetThreatLevel() "LR SAMs" } + if Attributes["LR SAM"] then ThreatLevel = 10 elseif Attributes["MR SAM"] then ThreatLevel = 9 elseif Attributes["SR SAM"] and @@ -992,7 +1067,7 @@ function UNIT:GetThreatLevel() 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 + elseif Attributes["Infantry"] or Attributes["EWR"] then ThreatLevel = 1 end ThreatText = ThreatLevels[ThreatLevel+1] @@ -1014,6 +1089,7 @@ function UNIT:GetThreatLevel() "Fighter" } + if Attributes["Fighters"] then ThreatLevel = 10 elseif Attributes["Multirole fighters"] then ThreatLevel = 9 elseif Attributes["Battleplanes"] then ThreatLevel = 8 @@ -1111,26 +1187,32 @@ end -- @return true If the other DCS Unit is within the radius of the 2D point of the DCS Unit. -- @return #nil The DCS Unit is not existing or alive. function UNIT:OtherUnitInRadius( AwaitUnit, Radius ) - self:F2( { self.UnitName, AwaitUnit.UnitName, Radius } ) + self:F2( { self.UnitName, AwaitUnit.UnitName, Radius } ) local DCSUnit = self:GetDCSObject() if DCSUnit then - local UnitVec3 = self:GetVec3() - local AwaitUnitVec3 = AwaitUnit:GetVec3() + 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 + 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 + return nil end + + + + + + --- Returns if the unit is a friendly unit. -- @param #UNIT self -- @return #boolean IsFriendly evaluation result. From 3d38f4d17afb6748bab717bfcff7c32ea11a8475 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 26 Apr 2022 10:08:50 +0200 Subject: [PATCH 121/200] Enums - added a couple of names --- Moose Development/Moose/Utilities/Enums.lua | 77 +++++++++++++++++++-- 1 file changed, 70 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Utilities/Enums.lua b/Moose Development/Moose/Utilities/Enums.lua index 423bd85e9..095c5f873 100644 --- a/Moose Development/Moose/Utilities/Enums.lua +++ b/Moose Development/Moose/Utilities/Enums.lua @@ -458,7 +458,7 @@ ENUMS.ReportingName = { NATO = { -- Fighters - Dragon = "JF-17", -- China, correct? + Dragon = "JF-17", -- China, correctly Fierce Dragon, Thunder for PAC Fagot = "MiG-15", Farmer = "MiG-19", -- Shenyang J-6 and Mikoyan-Gurevich MiG-19 Felon = "Su-57", @@ -475,15 +475,54 @@ ENUMS.ReportingName = Flanker_C = "Su-30", Flanker_E = "Su-35", Flanker_F = "Su-37", - Flanker_Dragon = "J-11A", + Flanker_L = "J-11A", + Firebird = "J-10", Sea_Flanker = "Su-33", - Fullback = "Su-32", -- also Su-34 + Fullback = "Su-34", -- also Su-32 Frogfoot = "Su-25", Tomcat = "F-14", -- Iran Mirage = "Mirage", -- various non-NATO - -- Bomber - H6J = "H6-J", - Sea_Bear = "Tu-142", -- also Tu-95 + Codling = "Yak-40", + Maya = "L-39", + -- Fighters US/NATO + Warthog = "A-10", + --Mosquito = "A-20", + Skyhawk = "A-4E", + Viggen = "AJS37", + Harrier = "AV-8B", + Spirit = "B-2", + Aviojet = "C-101", + Nighthawk = "F-117A", + Eagle = "F-15C", + Mudhen = "F-15E", + Viper = "F-16", + Phantom = "F-4E", + Tiger = "F-5", -- was thinking to name this MiG-25 ;) + Sabre = "F-86", + Hornet = "A-18", -- avoiding the slash + Hawk = "Hawk", + Albatros = "L-39", + Goshawk = "T-45", + Starfighter = "F-104", + Tornado = "Tornado", + -- Transport / Bomber / Others + Atlas = "A400", + Lancer = "B1-B", + Stratofortress = "B-52H", + Hercules = "C-130", -- modded version has type name "Hercules", unfortunately + Globemaster = "C-17", + Greyhound = "C-2A", + Galaxy = "C-5", + Hawkexe = "E-2D", + Sentry = "E-3A", + Stratotanker = "KC-135", + Extender = "KC-10", + Orion = "P-3C", + Viking = "S-3B", + Osprey = "V-22", + -- Bomber Rus + Badger = "H6-J", + Bear_J = "Tu-142", -- also Tu-95 Bear = "Tu-95", -- also Tu-142 Blinder = "Tu-22", Blackjack = "Tu-160", @@ -492,6 +531,30 @@ ENUMS.ReportingName = Curl = "An-26", Candid = "IL-76", Midas = "IL-78", - Mainstay = "A-50", -- KJ-2000 China + Mainstay = "A-50", + Mainring = "KJ-2000", -- A-50 China + Yak = "Yak-52", + -- Helos + Helix = "Ka-27", + Shark = "Ka-50", + Hind = "Mi-24", + Halo = "Mi-26", + Hip = "Mi-8", + Havoc = "Mi-28", + Gazelle = "SA342", + -- Helos US + Huey = "UH-1H", + Cobra = "AH-1", + Apache = "AH-64", + Chinook = "CH-47", + Sea_Stallion = "CH-53", + Kiowa = "OH-58", + Seahawk = "SH-60", + Blackhawk = "UH-60", + Sea_King = "S-61", + -- Drones + UCAV = "WingLoong", + Reaper = "MQ-9", + Predator = "MQ-1A", } } From e5eeb592a2c39e2ffbb79f41912c7bd422942b33 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 28 Apr 2022 13:10:34 +0200 Subject: [PATCH 122/200] Enums - corrected Hawkeye, added Super_Hercules --- Moose Development/Moose/Utilities/Enums.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Utilities/Enums.lua b/Moose Development/Moose/Utilities/Enums.lua index 095c5f873..f48f584a3 100644 --- a/Moose Development/Moose/Utilities/Enums.lua +++ b/Moose Development/Moose/Utilities/Enums.lua @@ -509,11 +509,12 @@ ENUMS.ReportingName = Atlas = "A400", Lancer = "B1-B", Stratofortress = "B-52H", - Hercules = "C-130", -- modded version has type name "Hercules", unfortunately + Hercules = "C-130", + Super_Hercules = "Hercules", Globemaster = "C-17", Greyhound = "C-2A", Galaxy = "C-5", - Hawkexe = "E-2D", + Hawkeye = "E-2D", Sentry = "E-3A", Stratotanker = "KC-135", Extender = "KC-10", From d35e5cc0f78837dc8f95659d39a18d156bad4ecd Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 28 Apr 2022 16:50:59 +0200 Subject: [PATCH 123/200] Added USERSOUND:ToUnit --- Moose Development/Moose/Sound/UserSound.lua | 36 +++++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Sound/UserSound.lua b/Moose Development/Moose/Sound/UserSound.lua index 8b94ad114..ecc437c34 100644 --- a/Moose Development/Moose/Sound/UserSound.lua +++ b/Moose Development/Moose/Sound/UserSound.lua @@ -40,7 +40,7 @@ do -- UserSound -- @param #USERSOUND self -- @param #string UserSoundFileName The filename of the usersound. -- @return #USERSOUND - function USERSOUND:New( UserSoundFileName ) --R2.3 + function USERSOUND:New( UserSoundFileName ) local self = BASE:Inherit( self, BASE:New() ) -- #USERSOUND @@ -58,7 +58,7 @@ do -- UserSound -- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" ) -- BlueVictory:SetFileName( "BlueVictoryLoud.ogg" ) -- Set the BlueVictory to change the file name to play a louder sound. -- - function USERSOUND:SetFileName( UserSoundFileName ) --R2.3 + function USERSOUND:SetFileName( UserSoundFileName ) self.UserSoundFileName = UserSoundFileName @@ -75,7 +75,7 @@ do -- UserSound -- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" ) -- BlueVictory:ToAll() -- Play the sound that Blue has won. -- - function USERSOUND:ToAll() --R2.3 + function USERSOUND:ToAll() trigger.action.outSound( self.UserSoundFileName ) @@ -91,7 +91,7 @@ do -- UserSound -- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" ) -- BlueVictory:ToCoalition( coalition.side.BLUE ) -- Play the sound that Blue has won to the blue coalition. -- - function USERSOUND:ToCoalition( Coalition ) --R2.3 + function USERSOUND:ToCoalition( Coalition ) trigger.action.outSoundForCoalition(Coalition, self.UserSoundFileName ) @@ -107,7 +107,7 @@ do -- UserSound -- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" ) -- BlueVictory:ToCountry( country.id.USA ) -- Play the sound that Blue has won to the USA country. -- - function USERSOUND:ToCountry( Country ) --R2.3 + function USERSOUND:ToCountry( Country ) trigger.action.outSoundForCountry( Country, self.UserSoundFileName ) @@ -123,9 +123,9 @@ do -- UserSound -- @usage -- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" ) -- local PlayerGroup = GROUP:FindByName( "PlayerGroup" ) -- Search for the active group named "PlayerGroup", that contains a human player. - -- BlueVictory:ToGroup( PlayerGroup ) -- Play the sound that Blue has won to the player group. + -- BlueVictory:ToGroup( PlayerGroup ) -- Play the victory sound to the player group. -- - function USERSOUND:ToGroup( Group, Delay ) --R2.3 + function USERSOUND:ToGroup( Group, Delay ) Delay=Delay or 0 if Delay>0 then @@ -136,5 +136,27 @@ do -- UserSound return self end + + --- Play the usersound to the given @{Wrapper.Unit}. + -- @param #USERSOUND self + -- @param Wrapper.Unit#UNIT Unit The @{Wrapper.Unit} to play the usersound to. + -- @param #number Delay (Optional) Delay in seconds, before the sound is played. Default 0. + -- @return #USERSOUND The usersound instance. + -- @usage + -- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" ) + -- local PlayerUnit = UNIT:FindByName( "PlayerUnit" ) -- Search for the active group named "PlayerUnit", that contains a human player. + -- BlueVictory:ToUnit( PlayerUnit ) -- Play the victory sound to the player unit. + -- + function USERSOUND:ToUnit( Unit, Delay ) + + Delay=Delay or 0 + if Delay>0 then + SCHEDULER:New(nil, USERSOUND.ToUnit,{self, Unit}, Delay) + else + trigger.action.outSoundForUnit( Unit:GetID(), self.UserSoundFileName ) + end + + return self + end end \ No newline at end of file From 3209843318262928eba8060b282976b83e7f436e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 28 Apr 2022 16:55:31 +0200 Subject: [PATCH 124/200] Added POSITIONABLE:MessageToSetUnit and POSITIONABLE:MessageToUnit --- .../Moose/Wrapper/Positionable.lua | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index ff5563023..8af77e1ac 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -1177,6 +1177,30 @@ function POSITIONABLE:MessageToClient( Message, Duration, Client, Name ) return nil end +--- Send a message to a @{Wrapper.Unit}. +-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. +-- @param #POSITIONABLE self +-- @param #string Message The message text +-- @param DCS#Duration Duration The duration of the message. +-- @param Wrapper.Unit#UNIT MessageUnit The UNIT object receiving the message. +-- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. +function POSITIONABLE:MessageToUnit( Message, Duration, MessageUnit, Name ) + self:F2( { Message, Duration } ) + + local DCSObject = self:GetDCSObject() + if DCSObject then + if DCSObject:isExist() then + if MessageUnit:IsAlive() then + self:GetMessage( Message, Duration, Name ):ToUnit( MessageUnit ) + else + BASE:E( { "Message not sent to Unit; Unit is not alive...", Message = Message, MessageUnit = MessageUnit } ) + end + else + BASE:E( { "Message not sent to Unit; Positionable is not alive ...", Message = Message, Positionable = self, MessageUnit = MessageUnit } ) + end + end +end + --- Send a message to a @{Wrapper.Group}. -- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. -- @param #POSITIONABLE self @@ -1250,6 +1274,30 @@ function POSITIONABLE:MessageToSetGroup( Message, Duration, MessageSetGroup, Nam return nil end +--- Send a message to a @{Core.Set#SET_UNIT}. +-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. +-- @param #POSITIONABLE self +-- @param #string Message The message text +-- @param DCS#Duration Duration The duration of the message. +-- @param Core.Set#SET_UNIT MessageSetGroup The SET_UNIT collection receiving the message. +-- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. +function POSITIONABLE:MessageToSetUnit( Message, Duration, MessageSetUnit, Name ) + self:F2( { Message, Duration } ) + + local DCSObject = self:GetDCSObject() + if DCSObject then + if DCSObject:isExist() then + MessageSetUnit:ForEachUnit( + function( MessageGroup ) + self:GetMessage( Message, Duration, Name ):ToUnit( MessageGroup ) + end + ) + end + end + + return nil +end + --- Send a message to the players in the @{Wrapper.Group}. -- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. -- @param #POSITIONABLE self From c283b66c1db4aa915cd29268b2ad785d4391013d Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 28 Apr 2022 16:58:37 +0200 Subject: [PATCH 125/200] added MESSAGE:ToUnit), altered MESSAGE:ToClient() accordingly --- Moose Development/Moose/Core/Message.lua | 38 ++++++++++++++++++++---- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Core/Message.lua b/Moose Development/Moose/Core/Message.lua index 7daf9c671..3e4731831 100644 --- a/Moose Development/Moose/Core/Message.lua +++ b/Moose Development/Moose/Core/Message.lua @@ -194,18 +194,21 @@ function MESSAGE:ToClient( Client, Settings ) if Client and Client:GetClientGroupID() then if self.MessageType then - local Settings = Settings or (Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() )) or _SETTINGS -- Core.Settings#SETTINGS + local Settings = Settings or ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS self.MessageDuration = Settings:GetMessageTime( self.MessageType ) self.MessageCategory = "" -- self.MessageType .. ": " end - + + local Unit = Client:GetClient() + if self.MessageDuration ~= 0 then local ClientGroupID = Client:GetClientGroupID() - self:T( self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ) .. " / " .. self.MessageDuration ) - trigger.action.outTextForGroup( ClientGroupID, self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ), self.MessageDuration, self.ClearScreen ) + 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) + trigger.action.outTextForUnit( Unit:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration , self.ClearScreen) end end - + return self end @@ -232,6 +235,31 @@ function MESSAGE:ToGroup( Group, Settings ) return self end + +--- Sends a MESSAGE to a Unit. +-- @param #MESSAGE self +-- @param Wrapper.Unit#UNIT Unit to which the message is displayed. +-- @return #MESSAGE Message object. +function MESSAGE:ToUnit( Unit, Settings ) + self:F( Unit.IdentifiableName ) + + if Unit then + + if self.MessageType then + local Settings = Settings or ( Unit and _DATABASE:GetPlayerSettings( Unit:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS + self.MessageDuration = Settings:GetMessageTime( self.MessageType ) + self.MessageCategory = "" -- self.MessageType .. ": " + end + + if self.MessageDuration ~= 0 then + self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) + trigger.action.outTextForUnit( Unit:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen ) + end + end + + return self +end + --- Sends a MESSAGE to the Blue coalition. -- @param #MESSAGE self -- @return #MESSAGE From b3d4024f21915be0ca89e28b099f783dfe1fe75e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 28 Apr 2022 17:10:26 +0200 Subject: [PATCH 126/200] Nicefy docs --- Moose Development/Moose/Core/Message.lua | 3 +++ Moose Development/Moose/Sound/UserSound.lua | 2 +- Moose Development/Moose/Wrapper/Positionable.lua | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Message.lua b/Moose Development/Moose/Core/Message.lua index 3e4731831..662a3926c 100644 --- a/Moose Development/Moose/Core/Message.lua +++ b/Moose Development/Moose/Core/Message.lua @@ -10,6 +10,8 @@ -- * Send message to all players. -- * Send messages to a coalition. -- * Send messages to a specific group. +-- * Send messages to a specific unit or client. +-- -- -- === -- @@ -35,6 +37,7 @@ -- -- * To a @{Client} using @{#MESSAGE.ToClient}(). -- * To a @{Wrapper.Group} using @{#MESSAGE.ToGroup}() +-- * To a @{Wrapper.Unit} using @{#MESSAGE.ToUnit}() -- * To a coalition using @{#MESSAGE.ToCoalition}(). -- * To the red coalition using @{#MESSAGE.ToRed}(). -- * To the blue coalition using @{#MESSAGE.ToBlue}(). diff --git a/Moose Development/Moose/Sound/UserSound.lua b/Moose Development/Moose/Sound/UserSound.lua index ecc437c34..b5669770d 100644 --- a/Moose Development/Moose/Sound/UserSound.lua +++ b/Moose Development/Moose/Sound/UserSound.lua @@ -144,7 +144,7 @@ do -- UserSound -- @return #USERSOUND The usersound instance. -- @usage -- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" ) - -- local PlayerUnit = UNIT:FindByName( "PlayerUnit" ) -- Search for the active group named "PlayerUnit", that contains a human player. + -- local PlayerUnit = UNIT:FindByName( "PlayerUnit" ) -- Search for the active group named "PlayerUnit", a human player. -- BlueVictory:ToUnit( PlayerUnit ) -- Play the victory sound to the player unit. -- function USERSOUND:ToUnit( Unit, Delay ) diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index 8af77e1ac..8b37556e6 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -1279,7 +1279,7 @@ end -- @param #POSITIONABLE self -- @param #string Message The message text -- @param DCS#Duration Duration The duration of the message. --- @param Core.Set#SET_UNIT MessageSetGroup The SET_UNIT collection receiving the message. +-- @param Core.Set#SET_UNIT MessageSetUnit The SET_UNIT collection receiving the message. -- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. function POSITIONABLE:MessageToSetUnit( Message, Duration, MessageSetUnit, Name ) self:F2( { Message, Duration } ) From 749158c086ce03cd9751677cf394bfc48597fdb7 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 29 Apr 2022 12:09:57 +0200 Subject: [PATCH 127/200] Range added nil check --- Moose Development/Moose/Functional/Range.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index 01a095ca9..b59af16a8 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -3004,7 +3004,7 @@ function RANGE:_CheckInZone( _unitName ) Straferesult.rangename = self.rangename -- Save trap sheet. - if playerData.targeton and self.targetsheet then + if playerData and playerData.targeton and self.targetsheet then self:_SaveTargetSheet( _playername, result ) end -- RangeBoss edit for strafe data saved to file @@ -3524,7 +3524,7 @@ function RANGE:_TargetsheetOnOff( _unitname ) playerData.targeton = not playerData.targeton -- Inform player. - if playerData.targeton == true then + if playerData and playerData.targeton == true then text = string.format( "roger, your targetsheets are now SAVED." ) else text = string.format( "affirm, your targetsheets are NOT SAVED." ) From 5112c9598b0c27f0e26f12b415122abf4e0b4893 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 29 Apr 2022 12:18:26 +0200 Subject: [PATCH 128/200] COORDINATE - added bogey option to COORDINATE:ToStringBRAANATO(FromCoordinate,Bogey,Spades) --- Moose Development/Moose/Core/Point.lua | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 36f467284..81733b099 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -2768,9 +2768,10 @@ do -- COORDINATE --- Create a BRAA NATO call string to this COORDINATE from the FromCOORDINATE. Note - BRA delivered if no aspect can be obtained and "Merged" if range < 3nm -- @param #COORDINATE self -- @param #COORDINATE FromCoordinate The coordinate to measure the distance and the bearing from. + -- @param #boolean Bogey Add "Bogey" at the end if true (not yet declared hostile or friendly) -- @param #boolean Spades Add "Spades" at the end if true (no IFF/VID ID yet known) -- @return #string The BRAA text. - function COORDINATE:ToStringBRAANATO(FromCoordinate,Spades) + function COORDINATE:ToStringBRAANATO(FromCoordinate,Bogey,Spades) -- Thanks to @Pikey local BRAANATO = "Merged." @@ -2796,8 +2797,12 @@ do -- COORDINATE else BRAANATO = string.format("BRAA, %03d, %d miles, Angels %d, %s, Track %s",bearing, rangeNM, alt, aspect, track) end - if Spades then - BRAANATO = BRAANATO..", Spades." + if Bogey and Spades then + BRAANATO = BRAANATO..", Bogey, Spades." + elseif Bogey and (not Spades) then + BRAANATO = BRAANATO..", Bogey." + elseif (not Bogey) and Spades then + BRAANATO = BRAANATO..", Spades." else BRAANATO = BRAANATO.."." end From 6e8edd95ec3bb802740c5868d1d94df64ee2b940 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 29 Apr 2022 18:48:41 +0200 Subject: [PATCH 129/200] GROUP - making GetCoordinate() a bit more resilient POINT - slight changes to ToStringBRAANATO --- Moose Development/Moose/Core/Point.lua | 4 ++-- Moose Development/Moose/Wrapper/Group.lua | 25 +++++++++++++++-------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 81733b099..bfd7a9150 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -2799,9 +2799,9 @@ do -- COORDINATE end if Bogey and Spades then BRAANATO = BRAANATO..", Bogey, Spades." - elseif Bogey and (not Spades) then + elseif Bogey then BRAANATO = BRAANATO..", Bogey." - elseif (not Bogey) and Spades then + elseif Spades then BRAANATO = BRAANATO..", Spades." else BRAANATO = BRAANATO.."." diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 8127ffba9..850f256b6 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -1019,18 +1019,25 @@ end -- @return Core.Point#COORDINATE The COORDINATE of the GROUP. function GROUP:GetCoordinate() - local FirstUnit = self:GetUnit(1) + local Units = self:GetUnits() - if FirstUnit then - local FirstUnitCoordinate = FirstUnit:GetCoordinate() - local Heading = self:GetHeading() - FirstUnitCoordinate.Heading = Heading - return FirstUnitCoordinate + for _,_unit in pairs(Units) do + local FirstUnit = _unit -- Wrapper.Unit#UNIT + + if FirstUnit then + + local FirstUnitCoordinate = FirstUnit:GetCoordinate() + + if FirstUnitCoordinate then + local Heading = self:GetHeading() + FirstUnitCoordinate.Heading = Heading + return FirstUnitCoordinate + end + + end end - BASE:E( { "Cannot GetCoordinate", Group = self, Alive = self:IsAlive() } ) - - return nil + end From 8099847e29c6ce88be5ac44e283329e57af3cdce Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 4 May 2022 10:25:50 +0200 Subject: [PATCH 130/200] Fixes for DEAD event and extra nil checks --- Moose Development/Moose/Core/Base.lua | 3 +- Moose Development/Moose/Core/Event.lua | 321 +++++++++++---------- Moose Development/Moose/Core/Set.lua | 15 +- Moose Development/Moose/Core/Spawn.lua | 60 ++-- Moose Development/Moose/DCS.lua | 4 +- Moose Development/Moose/Utilities/FiFo.lua | 6 +- Moose Development/Moose/Wrapper/Group.lua | 39 ++- Moose Development/Moose/Wrapper/Unit.lua | 9 +- 8 files changed, 274 insertions(+), 183 deletions(-) diff --git a/Moose Development/Moose/Core/Base.lua b/Moose Development/Moose/Core/Base.lua index ccb8c8755..cde80ba1a 100644 --- a/Moose Development/Moose/Core/Base.lua +++ b/Moose Development/Moose/Core/Base.lua @@ -731,13 +731,14 @@ end -- @param #BASE self -- @param DCS#Time EventTime The time stamp of the event. -- @param DCS#Object Initiator The initiating object of the event. -function BASE:CreateEventCrash( EventTime, Initiator ) +function BASE:CreateEventCrash( EventTime, Initiator, IniObjectCategory ) self:F( { EventTime, Initiator } ) local Event = { id = world.event.S_EVENT_CRASH, time = EventTime, initiator = Initiator, + IniObjectCategory = IniObjectCategory, } world.onEvent( Event ) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 500e1871a..05d7ae41f 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -131,8 +131,6 @@ -- * Weapon data: Certain events populate weapon information. -- * Place data: Certain events populate place information. -- --- Example code snippet: --- -- --- This function is an Event Handling function that will be called when Tank1 is Dead. -- -- EventData is an EVENTDATA structure. -- -- We use the EventData.IniUnit to smoke the tank Green. @@ -143,6 +141,7 @@ -- EventData.IniUnit:SmokeGreen() -- end -- +-- -- Find below an overview which events populate which information categories: -- -- ![Objects](..\Presentations\EVENT\Dia14.JPG) @@ -152,7 +151,6 @@ -- In case a STATIC object is involved, the documentation indicates which fields will and won't not be populated. -- The fields **IniObjectCategory** and **TgtObjectCategory** contain the indicator which **kind of object is involved** in the event. -- You can use the enumerator **Object.Category.UNIT** and **Object.Category.STATIC** to check on IniObjectCategory and TgtObjectCategory. --- -- Example code snippet: -- -- if Event.IniObjectCategory == Object.Category.UNIT then @@ -174,6 +172,7 @@ -- @module Core.Event -- @image Core_Event.JPG + --- @type EVENT -- @field #EVENT.Events Events -- @extends Core.Base#BASE @@ -195,6 +194,7 @@ 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 + --- The different types of events supported by MOOSE. -- Use this structure to subscribe to events using the @{Core.Base#BASE.HandleEvent}() method. -- @type EVENTS @@ -227,13 +227,13 @@ EVENTS = { MarkChange = world.event.S_EVENT_MARK_CHANGE, MarkRemoved = world.event.S_EVENT_MARK_REMOVED, -- Moose Events - 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, + 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, -- Added with DCS 2.5.6 DetailedFailure = world.event.S_EVENT_DETAILED_FAILURE or -1, --We set this to -1 for backward compatibility to DCS 2.5.5 and earlier @@ -304,6 +304,8 @@ EVENTS = { -- @field Core.ZONE#ZONE Zone The zone object. -- @field #string ZoneName The name of the zone. + + local _EVENTMETA = { [world.event.S_EVENT_SHOT] = { Order = 1, @@ -560,6 +562,7 @@ local _EVENTMETA = { }, } + --- The Events structure -- @type EVENT.Events -- @field #number IniUnit @@ -573,11 +576,12 @@ function EVENT:New() local self = BASE:Inherit( self, BASE:New() ) -- Add world event handler. - self.EventHandler = world.addEventHandler( self ) + self.EventHandler = world.addEventHandler(self) return self end + --- Initializes the Events structure for the event. -- @param #EVENT self -- @param DCS#world.event EventID Event ID. @@ -591,7 +595,7 @@ function EVENT:Init( EventID, EventClass ) self.Events[EventID] = {} end - -- Each event has a sub-table of EventClasses, ordered by EventPriority. + -- Each event has a subtable of EventClasses, ordered by EventPriority. local EventPriority = EventClass:GetEventPriority() if not self.Events[EventID][EventPriority] then @@ -599,7 +603,7 @@ function EVENT:Init( EventID, EventClass ) end if not self.Events[EventID][EventPriority][EventClass] then - self.Events[EventID][EventPriority][EventClass] = {} + self.Events[EventID][EventPriority][EventClass] = {} end return self.Events[EventID][EventPriority][EventClass] @@ -610,7 +614,7 @@ end -- @param Core.Base#BASE EventClass The self instance of the class for which the event is. -- @param DCS#world.event EventID Event ID. -- @return #EVENT self -function EVENT:RemoveEvent( EventClass, EventID ) +function EVENT:RemoveEvent( EventClass, EventID ) -- Debug info. self:F2( { "Removing subscription for class: ", EventClass:GetClassNameAndID() } ) @@ -634,7 +638,7 @@ end -- @param Core.Base#BASE EventClass The self instance of the class for which the event is. -- @param DCS#world.event EventID Event ID. -- @return #EVENT.Events -function EVENT:Reset( EventObject ) -- R2.1 +function EVENT:Reset( EventObject ) --R2.1 self:F( { "Resetting subscriptions for class: ", EventObject:GetClassNameAndID() } ) @@ -653,11 +657,12 @@ function EVENT:Reset( EventObject ) -- R2.1 end end + --- Clears all event subscriptions for a @{Core.Base#BASE} derived object. -- @param #EVENT self -- @param Core.Base#BASE EventClass The self class object for which the events are removed. -- @return #EVENT self -function EVENT:RemoveAll( EventClass ) +function EVENT:RemoveAll(EventClass) local EventClassName = EventClass:GetClassNameAndID() @@ -671,6 +676,8 @@ function EVENT:RemoveAll( EventClass ) return self end + + --- Create an OnDead event handler for a group -- @param #EVENT self -- @param #table EventTemplate @@ -702,6 +709,7 @@ function EVENT:OnEventGeneric( EventFunction, EventClass, EventID ) return self end + --- Set a new listener for an `S_EVENT_X` event for a UNIT. -- @param #EVENT self -- @param #string UnitName The name of the UNIT. @@ -789,6 +797,7 @@ do -- OnDead end + do -- OnLand --- Create an OnLand event handler for a group @@ -855,7 +864,7 @@ do -- Event Creation id = EVENTS.NewCargo, time = timer.getTime(), cargo = Cargo, - } + } world.onEvent( Event ) end @@ -870,7 +879,7 @@ do -- Event Creation id = EVENTS.DeleteCargo, time = timer.getTime(), cargo = Cargo, - } + } world.onEvent( Event ) end @@ -885,7 +894,7 @@ do -- Event Creation id = EVENTS.NewZone, time = timer.getTime(), zone = Zone, - } + } world.onEvent( Event ) end @@ -900,7 +909,7 @@ do -- Event Creation id = EVENTS.DeleteZone, time = timer.getTime(), zone = Zone, - } + } world.onEvent( Event ) end @@ -915,11 +924,12 @@ do -- Event Creation id = EVENTS.NewZoneGoal, time = timer.getTime(), ZoneGoal = ZoneGoal, - } + } world.onEvent( Event ) end + --- Creation of a ZoneGoal Deletion Event. -- @param #EVENT self -- @param Core.ZoneGoal#ZONE_GOAL ZoneGoal The ZoneGoal created. @@ -930,11 +940,12 @@ do -- Event Creation id = EVENTS.DeleteZoneGoal, time = timer.getTime(), ZoneGoal = ZoneGoal, - } + } world.onEvent( Event ) end + --- Creation of a S_EVENT_PLAYER_ENTER_UNIT Event. -- @param #EVENT self -- @param Wrapper.Unit#UNIT PlayerUnit. @@ -944,8 +955,8 @@ do -- Event Creation local Event = { id = EVENTS.PlayerEnterUnit, time = timer.getTime(), - initiator = PlayerUnit:GetDCSObject(), - } + initiator = PlayerUnit:GetDCSObject() + } world.onEvent( Event ) end @@ -959,8 +970,8 @@ do -- Event Creation local Event = { id = EVENTS.PlayerEnterAircraft, time = timer.getTime(), - initiator = PlayerUnit:GetDCSObject(), - } + initiator = PlayerUnit:GetDCSObject() + } world.onEvent( Event ) end @@ -972,24 +983,25 @@ end -- @param #EVENTDATA Event Event data table. function EVENT:onEvent( Event ) + --- Function to handle errors. local ErrorHandler = function( errmsg ) - env.info( "Error in SCHEDULER function:" .. errmsg ) if BASE.Debug ~= nil then env.info( debug.traceback() ) end - return errmsg end + -- Get event meta data. local EventMeta = _EVENTMETA[Event.id] -- Check if this is a known event? 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 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 + -- Check if mission has ended. if Event.id and Event.id == EVENTS.MissionEnd then self.MissionEnd = true end @@ -997,50 +1009,27 @@ function EVENT:onEvent( Event ) 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 - -- Unit can be a CLIENT. Most likely this will be the case ... - 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 ) - -- if Event.IniGroup then - Event.IniGroupName = Event.IniDCSGroupName - -- end - 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 - + + if Event.IniObjectCategory == Object.Category.STATIC then + --- + -- Static + --- + if Event.id==31 then -- Event.initiator is a Static object representing the pilot. But getName() errors due to DCS bug. Event.IniDCSUnit = Event.initiator - local ID = Event.initiator.id_ - Event.IniDCSUnitName = string.format( "Ejected Pilot ID %s", tostring( ID ) ) + 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.IniCategory = 0 Event.IniTypeName = "Ejected Pilot" - elseif Event.id == 33 then -- ejection seat discarded + elseif Event.id == 33 then -- ejection seat discarded Event.IniDCSUnit = Event.initiator - local ID = Event.initiator.id_ - Event.IniDCSUnitName = string.format( "Ejection Seat ID %s", tostring( ID ) ) + 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.IniCategory = 0 Event.IniTypeName = "Ejection Seat" else Event.IniDCSUnit = Event.initiator @@ -1051,9 +1040,47 @@ function EVENT:onEvent( Event ) Event.IniCategory = Event.IniDCSUnit:getDesc().category Event.IniTypeName = Event.IniDCSUnit:getTypeName() end + + -- Dead events of units can be delayed and the initiator changed to a static. + -- Take care of that. + local Unit=UNIT:FindByName(Event.IniDCSUnitName) + if Unit then + Event.IniObjectCategory = Object.Category.UNIT + end + end + + if Event.IniObjectCategory == Object.Category.UNIT then + --- + -- Unit + --- + 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 + -- Unit can be a CLIENT. Most likely this will be the case ... + Event.IniUnit = CLIENT:FindByName( Event.IniDCSUnitName, '', true ) + end + + Event.IniDCSGroupName = Event.IniUnit and Event.IniUnit.GroupName or "" + if Event.IniDCSGroup and Event.IniDCSGroup:isExist() then + Event.IniDCSGroupName = Event.IniDCSGroup:getName() + Event.IniGroup = GROUP:FindByName( Event.IniDCSGroupName ) + Event.IniGroupName = Event.IniDCSGroupName + end + + Event.IniPlayerName = Event.IniDCSUnit:getPlayerName() + Event.IniCoalition = Event.IniDCSUnit:getCoalition() + Event.IniTypeName = Event.IniDCSUnit:getTypeName() + Event.IniCategory = Event.IniDCSUnit:getDesc().category end if Event.IniObjectCategory == Object.Category.CARGO then + --- + -- Cargo + --- Event.IniDCSUnit = Event.initiator Event.IniDCSUnitName = Event.IniDCSUnit:getName() Event.IniUnitName = Event.IniDCSUnitName @@ -1064,19 +1091,26 @@ function EVENT:onEvent( Event ) end if Event.IniObjectCategory == Object.Category.SCENERY then + --- + -- Scenery + --- + 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" -- TODO: Bug fix for 2.1! + Event.IniTypeName = Event.initiator:isExist() and Event.IniDCSUnit:getTypeName() or "SCENERY" end if Event.IniObjectCategory == Object.Category.BASE then + --- + -- Base Object + --- Event.IniDCSUnit = Event.initiator Event.IniDCSUnitName = Event.IniDCSUnit:getName() Event.IniUnitName = Event.IniDCSUnitName - Event.IniUnit = AIRBASE:FindByName( Event.IniDCSUnitName ) + Event.IniUnit = AIRBASE:FindByName(Event.IniDCSUnitName) Event.IniCoalition = Event.IniDCSUnit:getCoalition() Event.IniCategory = Event.IniDCSUnit:getDesc().category Event.IniTypeName = Event.IniDCSUnit:getTypeName() @@ -1084,7 +1118,12 @@ function EVENT:onEvent( Event ) end if Event.target then + + --- + -- TARGET + --- + -- Target category. Event.TgtObjectCategory = Event.target:getCategory() if Event.TgtObjectCategory == Object.Category.UNIT then @@ -1097,9 +1136,7 @@ function EVENT:onEvent( Event ) if Event.TgtDCSGroup and Event.TgtDCSGroup:isExist() then Event.TgtDCSGroupName = Event.TgtDCSGroup:getName() Event.TgtGroup = GROUP:FindByName( Event.TgtDCSGroupName ) - -- if Event.TgtGroup then Event.TgtGroupName = Event.TgtDCSGroupName - -- end end Event.TgtPlayerName = Event.TgtDCSUnit:getPlayerName() Event.TgtCoalition = Event.TgtDCSUnit:getCoalition() @@ -1118,18 +1155,18 @@ function EVENT:onEvent( Event ) 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.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.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.TgtDCSUnitName = string.format("Ejection Seat ID %s", tostring(Event.IniDCSUnitName)) Event.TgtUnitName = Event.TgtDCSUnitName else Event.TgtTypeName = "Static" @@ -1147,6 +1184,7 @@ function EVENT:onEvent( Event ) end end + -- Weapon. if Event.weapon then Event.Weapon = Event.weapon Event.WeaponName = Event.Weapon:getTypeName() @@ -1155,49 +1193,48 @@ function EVENT:onEvent( Event ) Event.WeaponCoalition = Event.WeaponUNIT and Event.Weapon:getCoalition() Event.WeaponCategory = Event.WeaponUNIT and Event.Weapon:getDesc().category Event.WeaponTypeName = Event.WeaponUNIT and Event.Weapon:getTypeName() - -- Event.WeaponTgtDCSUnit = Event.Weapon:getTarget() + --Event.WeaponTgtDCSUnit = Event.Weapon:getTarget() end -- Place should be given for takeoff and landing events as well as base captured. It should be a DCS airbase. if Event.place then - if Event.id == EVENTS.LandingAfterEjection then + if Event.id==EVENTS.LandingAfterEjection then -- Place is here the UNIT of which the pilot ejected. - -- local name=Event.place:getName() -- This returns a DCS error "Airbase doesn't exit" :( + --local name=Event.place:getName() -- This returns a DCS error "Airbase doesn't exit" :( -- However, this is not a big thing, as the aircraft the pilot ejected from is usually long crashed before the ejected pilot touches the ground. - -- Event.Place=UNIT:Find(Event.place) + --Event.Place=UNIT:Find(Event.place) else - Event.Place = AIRBASE:Find( Event.place ) - Event.PlaceName = Event.Place:GetName() + Event.Place=AIRBASE:Find(Event.place) + Event.PlaceName=Event.Place:GetName() end end -- Mark points. 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.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 + -- Cargo object. if Event.cargo then Event.Cargo = Event.cargo Event.CargoName = Event.cargo.Name end + -- Zone object. if Event.zone then Event.Zone = Event.zone Event.ZoneName = Event.zone.ZoneName end + -- Priority order. 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 + local PriorityEnd = PriorityOrder == -1 and 1 or 5 for EventPriority = PriorityBegin, PriorityEnd, PriorityOrder do @@ -1206,12 +1243,12 @@ function EVENT:onEvent( Event ) -- Okay, we got the event from DCS. Now loop the SORTED self.EventSorted[] table for the received Event.id, and for each EventData registered, check if a function needs to be called. for EventClass, EventData in pairs( self.Events[Event.id][EventPriority] ) do - -- if Event.IniObjectCategory ~= Object.Category.STATIC then + --if Event.IniObjectCategory ~= Object.Category.STATIC then -- self:E( { "Evaluating: ", EventClass:GetClassNameAndID() } ) - -- end + --end - Event.IniGroup = GROUP:FindByName( Event.IniDCSGroupName ) - Event.TgtGroup = GROUP:FindByName( Event.TgtDCSGroupName ) + Event.IniGroup = Event.IniGroup or GROUP:FindByName( Event.IniDCSGroupName ) + Event.TgtGroup = Event.TgtGroup or GROUP:FindByName( Event.TgtDCSGroupName ) -- If the EventData is for a UNIT, the call directly the EventClass EventFunction for that UNIT. if EventData.EventUnit then @@ -1221,39 +1258,35 @@ function EVENT:onEvent( Event ) Event.id == EVENTS.PlayerEnterUnit or Event.id == EVENTS.Crash or Event.id == EVENTS.Dead or - Event.id == EVENTS.RemoveUnit then + Event.id == EVENTS.RemoveUnit or + Event.id == EVENTS.UnitLost then local UnitName = EventClass:GetName() - if (EventMeta.Side == "I" and UnitName == Event.IniDCSUnitName) or - (EventMeta.Side == "T" and UnitName == Event.TgtDCSUnitName) then - + if ( EventMeta.Side == "I" and UnitName == Event.IniDCSUnitName ) or + ( EventMeta.Side == "T" and UnitName == Event.TgtDCSUnitName ) then + -- First test if a EventFunction is Set, otherwise search for the default function 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 ) + + local Result, Value = xpcall( + function() + return EventData.EventFunction( EventClass, Event ) + end, ErrorHandler ) else -- There is no EventFunction defined, so try to find if a default OnEvent function is defined on the object. - local EventFunction = EventClass[EventMeta.Event] + local EventFunction = EventClass[ EventMeta.Event ] if EventFunction and type( EventFunction ) == "function" then -- Now call the default event function. - 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 ) + local Result, Value = xpcall( + function() + return EventFunction( EventClass, Event ) + end, ErrorHandler ) end + end end else @@ -1271,48 +1304,43 @@ function EVENT:onEvent( Event ) Event.id == EVENTS.PlayerEnterUnit or Event.id == EVENTS.Crash or Event.id == EVENTS.Dead or - Event.id == EVENTS.RemoveUnit then + Event.id == EVENTS.RemoveUnit or + Event.id == EVENTS.UnitLost then -- We can get the name of the EventClass, which is now always a GROUP object. local GroupName = EventClass:GetName() - if (EventMeta.Side == "I" and GroupName == Event.IniDCSGroupName) or - (EventMeta.Side == "T" and GroupName == Event.TgtDCSGroupName) then + if ( EventMeta.Side == "I" and GroupName == Event.IniDCSGroupName ) or + ( EventMeta.Side == "T" and GroupName == Event.TgtDCSGroupName ) then -- First test if a EventFunction is Set, otherwise search for the default function 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 ) + local Result, Value = xpcall( + function() + return EventData.EventFunction( EventClass, Event, unpack( EventData.Params ) ) + end, ErrorHandler ) else -- There is no EventFunction defined, so try to find if a default OnEvent function is defined on the object. - local EventFunction = EventClass[EventMeta.Event] + local EventFunction = EventClass[ EventMeta.Event ] if EventFunction and type( EventFunction ) == "function" then -- Now call the default event function. - 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 ) + local Result, Value = xpcall( + function() + return EventFunction( EventClass, Event, unpack( EventData.Params ) ) + end, ErrorHandler ) end end end else -- The EventClass is not alive anymore, we remove it from the EventHandlers... - -- self:RemoveEvent( EventClass, Event.id ) + --self:RemoveEvent( EventClass, Event.id ) end else - + -- If the EventData is not bound to a specific unit, then call the EventClass EventFunction. -- Note that here the EventFunction will need to implement and determine the logic for the relevant source- or target unit, or weapon. if not EventData.EventUnit then @@ -1321,28 +1349,25 @@ function EVENT:onEvent( Event ) if EventData.EventFunction then -- There is an EventFunction defined, so call the EventFunction. - 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 ) + local Result, Value = xpcall( + function() + return EventData.EventFunction( EventClass, Event ) + end, ErrorHandler ) else -- There is no EventFunction defined, so try to find if a default OnEvent function is defined on the object. - local EventFunction = EventClass[EventMeta.Event] + local EventFunction = EventClass[ EventMeta.Event ] if EventFunction and type( EventFunction ) == "function" then -- Now call the default event function. - 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 ) + local Result, Value = xpcall( + function() + local Result, Value = EventFunction( EventClass, Event ) + return Result, Value + end, ErrorHandler ) + end + end end @@ -1365,7 +1390,7 @@ function EVENT:onEvent( Event ) 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 ) ) ) + 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 diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 800116360..f39a411db 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -1064,12 +1064,17 @@ do -- SET_GROUP function SET_GROUP:AddGroup( group ) self:Add( group:GetName(), group ) - - -- I set the default cargo bay weight limit each time a new group is added to the set. - for UnitID, UnitData in pairs( group:GetUnits() ) do - UnitData:SetCargoBayWeightLimit() + + if not DontSetCargoBayLimit then + -- I set the default cargo bay weight limit each time a new group is added to the set. + -- TODO Why is this here in the first place? + for UnitID, UnitData in pairs( group:GetUnits() ) do + if UnitData and UnitData:IsAlive() then + UnitData:SetCargoBayWeightLimit() + end + end end - + return self end diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index fc0749415..9d3f837e4 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -2827,21 +2827,40 @@ end -- The method will search for a #-mark, and will return the text before the #-mark. -- It will return nil of no prefix was found. -- @param #SPAWN self --- @param DCS#UNIT DCSUnit The @{DCSUnit} to be searched. --- @return #string The prefix --- @return #nil Nothing found +-- @param Wrapper.Group#GROUP SpawnGroup The GROUP object. +-- @return #string The prefix or #nil if nothing was found. 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 + + local SpawnPrefix=self:_GetPrefixFromGroupName(GroupName) + return SpawnPrefix end + + return nil +end +--- Return the prefix of a spawned group. +-- The method will search for a `#`-mark, and will return the text before the `#`-mark. It will return nil of no prefix was found. +-- @param #SPAWN self +-- @param #string SpawnGroupName The name of the spawned group. +-- @return #string The prefix or #nil if nothing was found. +function SPAWN:_GetPrefixFromGroupName(SpawnGroupName) + + if SpawnGroupName then + + local SpawnPrefix=string.match(SpawnGroupName, ".*#") + + if SpawnPrefix then + SpawnPrefix = SpawnPrefix:sub(1, -2) + end + + return SpawnPrefix + end + return nil end @@ -3235,24 +3254,27 @@ function SPAWN:_OnBirth( EventData ) end ---- Obscolete --- @todo Need to delete this... _DATABASE does this now ... - --- @param #SPAWN self -- @param Core.Event#EVENTDATA EventData function SPAWN:_OnDeadOrCrash( EventData ) self:F( self.SpawnTemplatePrefix ) - - local SpawnGroup = EventData.IniGroup - - if SpawnGroup then - local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup ) + + local unit=UNIT:FindByName(EventData.IniUnitName) + + if unit then + + local EventPrefix = self:_GetPrefixFromGroupName(unit.GroupName) + if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! self:T( { "Dead event: " .. EventPrefix } ) - if EventPrefix == self.SpawnTemplatePrefix or (self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix) then - self.AliveUnits = self.AliveUnits - 1 - self:T( "Alive Units: " .. self.AliveUnits ) + + if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then + + self.AliveUnits = self.AliveUnits - 1 + + self:T( "Alive Units: " .. self.AliveUnits ) end + end end end diff --git a/Moose Development/Moose/DCS.lua b/Moose Development/Moose/DCS.lua index 01661a678..909781eb5 100644 --- a/Moose Development/Moose/DCS.lua +++ b/Moose Development/Moose/DCS.lua @@ -487,8 +487,10 @@ do -- Object -- @field UNIT -- @field WEAPON -- @field STATIC - -- @field SCENERY -- @field BASE + -- @field SCENERY + -- @field CARGO + --- @type Object.Desc -- @extends #Desc diff --git a/Moose Development/Moose/Utilities/FiFo.lua b/Moose Development/Moose/Utilities/FiFo.lua index 38b318a2d..b004de791 100644 --- a/Moose Development/Moose/Utilities/FiFo.lua +++ b/Moose Development/Moose/Utilities/FiFo.lua @@ -249,7 +249,11 @@ end -- @return #boolean exists function FIFO:HasUniqueID(UniqueID) self:T(self.lid.."HasUniqueID") - return self.stackbyid[UniqueID] and true or false + if self.stackbyid[UniqueID] ~= nil then + return true + else + return false + end end --- FIFO Get the data stack by UniqueID diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 850f256b6..e393e348b 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -622,7 +622,7 @@ function GROUP:GetUnits() local DCSGroup = self:GetDCSObject() if DCSGroup then - local DCSUnits = DCSGroup:getUnits() + local DCSUnits = DCSGroup:getUnits() or {} local Units = {} for Index, UnitData in pairs( DCSUnits ) do Units[#Units+1] = UNIT:Find( UnitData ) @@ -680,6 +680,30 @@ function GROUP:GetUnit( UnitNumber ) return nil end +--- Check if an (air) group is a client or player slot. Information is retrieved from the group template. +-- @param #GROUP self +-- @return #boolean If true, group is associated with a client or player slot. +function GROUP:IsPlayer() + + -- Get group. + -- local group=self:GetGroup() + + -- Units of template group. + local units=self:GetTemplate().units + + -- Get numbers. + for _,unit in pairs(units) do + + -- Check if unit name matach and skill is Client or Player. + if unit.name==self:GetName() and (unit.skill=="Client" or unit.skill=="Player") then + return true + end + + end + + return false +end + --- Returns the DCS Unit with number UnitNumber. -- If the underlying DCS Unit does not exist, the method will return nil. . -- @param #GROUP self @@ -1019,7 +1043,7 @@ end -- @return Core.Point#COORDINATE The COORDINATE of the GROUP. function GROUP:GetCoordinate() - local Units = self:GetUnits() + local Units = self:GetUnits() or {} for _,_unit in pairs(Units) do local FirstUnit = _unit -- Wrapper.Unit#UNIT @@ -1213,13 +1237,14 @@ function GROUP:IsInZone( Zone ) for UnitID, UnitData in pairs(self:GetUnits()) do local Unit = UnitData -- Wrapper.Unit#UNIT - -- Get 2D vector. That's all we need for the zone check. - local vec2=Unit:GetVec2() + local vec2 = nil + if Unit then + -- Get 2D vector. That's all we need for the zone check. + vec2=Unit:GetVec2() + end - if Zone:IsVec2InZone(vec2) then + if vec2 and Zone:IsVec2InZone(vec2) then return true -- At least one unit is in the zone. That is enough. - else - -- This one is not but another could be. end end diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index bb6b61551..11f7c3d5e 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -90,6 +90,7 @@ UNIT = { ClassName="UNIT", UnitName=nil, + GroupName=nil, } @@ -110,11 +111,17 @@ UNIT = { function UNIT:Register( UnitName ) -- Inherit CONTROLLABLE. - local self = BASE:Inherit( self, CONTROLLABLE:New( UnitName ) ) + local self = BASE:Inherit( self, CONTROLLABLE:New( UnitName ) ) --#UNIT -- Set unit name. self.UnitName = UnitName + local unit=Unit.getByName(self.UnitName) + + if unit then + self.GroupName=unit:getGroup():getName() + end + -- Set event prio. self:SetEventPriority( 3 ) From 40bb181c7822abe9025d47f0dc6c520fc566a73b Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 4 May 2022 13:29:40 +0200 Subject: [PATCH 131/200] Another nil check... --- Moose Development/Moose/Core/Zone.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 5d88d833e..80c94bdf0 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -174,6 +174,7 @@ end -- @param DCS#Vec3 Vec3 The point to test. -- @return #boolean true if the Vec3 is within the zone. function ZONE_BASE:IsVec3InZone( Vec3 ) + if not Vec3 then return false end local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } ) return InZone end From 8a8b806362e65ac02d7650d559b75571d75ab6f3 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 4 May 2022 18:09:56 +0200 Subject: [PATCH 132/200] further event related stuff not working any more --- Moose Development/Moose/Core/Set.lua | 6 +++++- Moose Development/Moose/Core/Zone.lua | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index f39a411db..7ab8607b0 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -1332,7 +1332,11 @@ do -- SET_GROUP if Event.IniDCSUnit then local ObjectName, Object = self:FindInDatabase( Event ) if ObjectName then - if Event.IniDCSGroup:getSize() == 1 then -- Only remove if the last unit of the group was destroyed. + local size = 1 + if Event.IniDCSGroup then + size = Event.IniDCSGroup:getSize() + end + if size == 1 then -- Only remove if the last unit of the group was destroyed. self:Remove( ObjectName ) end end diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 80c94bdf0..09a148987 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1996,7 +1996,9 @@ end -- @return #boolean true if the point is within the zone. function ZONE_POLYGON_BASE:IsVec3InZone( Vec3 ) self:F2( Vec3 ) - + + if not Vec3 then return false end + local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } ) return InZone From 27902ee1078858815983c6a9c9be27903892c339 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 4 May 2022 22:36:39 +0200 Subject: [PATCH 133/200] Update Range.lua **RANGE** - Fixed a couple of bugs - Added new FSM events for strafing - Updated docs --- Moose Development/Moose/Functional/Range.lua | 311 +++++++++++-------- 1 file changed, 174 insertions(+), 137 deletions(-) diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index b59af16a8..6b4a3bc14 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -233,6 +233,16 @@ -- The next time you start the mission, these results are also automatically loaded. -- -- Strafing results are currently **not** saved. +-- +-- # FSM Events +-- +-- This class creates additional events that can be used by mission designers for custom reactions +-- +-- * `EnterRange` when a player enters a range zone. See @{#RANGE.OnAfterEnterRange} +-- * `ExitRange` when a player leaves a range zone. See @{#RANGE.OnAfterExitRange} +-- * `Impact` on impact of a player's weapon on a bombing target. See @{#RANGE.OnAfterImpact} +-- * `RollingIn` when a player rolls in on a strafing target. See @{#RANGE.OnAfterRollingIn} +-- * `StrafeResult` when a player finishes a strafing run. See @{#RANGE.OnAfterStrafeResult} -- -- # Examples -- @@ -366,14 +376,6 @@ RANGE.TargetType = { COORD = "Coordinate" } ---- Default range variables for RangeBoss/Hypeman tie in. -hypemanStrafeRollIn = "nil" -StrafeAircraftType = "strafeAircraftTypeNotSet" -Straferesult = {} -clientRollingIn = false -clientStrafed = false -invalidStrafe = false - --- Player settings. -- @type RANGE.PlayerData -- @field #boolean smokebombimpact Smoke bomb impact points. @@ -408,6 +410,14 @@ invalidStrafe = false -- @field #number smokepoints Number of smoke points. -- @field #number heading Heading of pit. +--- Strafe status for player. +-- @type RANGE.StrafeStatus +-- @field #number hits Number of hits on target. +-- @field #number time Number of times. +-- @field #number ammo Amount of ammo. +-- @field #boolean pastfoulline If `true`, player passed foul line. Invalid pass. +-- @field #RANGE.StrafeTarget zone Strafe target. + --- Bomb target result. -- @type RANGE.BombResult -- @field #string name Name of closest target. @@ -420,6 +430,13 @@ invalidStrafe = false -- @field #number time Time via timer.getAbsTime() in seconds of impact. -- @field #string date OS date. +--- Strafe result. +-- @type RANGE.StrafeResult +-- @field #string player Player name. +-- @field #string airframe Aircraft type of player. +-- @field #number time Time via timer.getAbsTime() in seconds of impact. +-- @field #string date OS date. + --- Sound file data. -- @type RANGE.Soundfile -- @field #string filename Name of the file @@ -532,7 +549,7 @@ RANGE.MenuF10Root = nil --- Range script version. -- @field #string version -RANGE.version = "2.3.0" +RANGE.version = "2.4.0" -- TODO list: -- TODO: Verbosity level for messages. @@ -583,6 +600,8 @@ function RANGE:New( rangename ) self:AddTransition("Stopped", "Start", "Running") -- Start RANGE script. self:AddTransition("*", "Status", "*") -- Status of RANGE script. self:AddTransition("*", "Impact", "*") -- Impact of bomb/rocket/missile. + self:AddTransition("*", "RollingIn", "*") -- Player rolling in on strafe target. + self:AddTransition("*", "StrafeResult", "*") -- Strafe result of player. self:AddTransition("*", "EnterRange", "*") -- Player enters the range. self:AddTransition("*", "ExitRange", "*") -- Player leaves the range. self:AddTransition("*", "Save", "*") -- Save player results. @@ -640,6 +659,37 @@ function RANGE:New( rangename ) -- @param #RANGE.BombResult result Data of the bombing run. -- @param #RANGE.PlayerData player Data of player settings etc. + + --- Triggers the FSM event "RollingIn". + -- @function [parent=#RANGE] RollingIn + -- @param #RANGE self + -- @param #RANGE.PlayerData player Data of player settings etc. + -- @param #RANGE.StrafeTarget target Strafe target. + + --- On after "RollingIn" event user function. Called when a player rolls in to a strafe taret. + -- @function [parent=#RANGE] OnAfterRollingIn + -- @param #RANGE self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #RANGE.PlayerData player Data of player settings etc. + -- @param #RANGE.StrafeTarget target Strafe target. + + --- Triggers the FSM event "StrafeResult". + -- @function [parent=#RANGE] StrafeResult + -- @param #RANGE self + -- @param #RANGE.PlayerData player Data of player settings etc. + -- @param #RANGE.StrafeResult result Data of the strafing run. + + --- On after "StrafeResult" event user function. Called when a player finished a strafing run. + -- @function [parent=#RANGE] OnAfterStrafeResult + -- @param #RANGE self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #RANGE.PlayerData player Data of player settings etc. + -- @param #RANGE.StrafeResult result Data of the strafing run. + --- Triggers the FSM event "EnterRange". -- @function [parent=#RANGE] EnterRange -- @param #RANGE self @@ -1594,7 +1644,6 @@ function RANGE:OnEventBirth( EventData ) self.strafeStatus[_uid] = nil -- Add Menu commands after a delay of 0.1 seconds. - -- SCHEDULER:New(nil, self._AddF10Commands, {self,_unitName}, 0.1) self:ScheduleOnce( 0.1, self._AddF10Commands, self, _unitName ) -- By default, some bomb impact points and do not flare each hit on target. @@ -1613,7 +1662,6 @@ function RANGE:OnEventBirth( EventData ) -- Start check in zone timer. if self.planes[_uid] ~= true then - -- SCHEDULER:New(nil, self._CheckInZone, {self, EventData.IniUnitName}, 1, 1) self.timerCheckZone = TIMER:New( self._CheckInZone, self, EventData.IniUnitName ):Start( 1, 1 ) self.planes[_uid] = true end @@ -1647,7 +1695,7 @@ function RANGE:OnEventHit( EventData ) local targetname = EventData.TgtUnitName -- Current strafe target of player. - local _currentTarget = self.strafeStatus[_unitID] + local _currentTarget = self.strafeStatus[_unitID] --#RANGE.StrafeStatus -- Player has rolled in on a strafing target. if _currentTarget and target:IsAlive() then @@ -1931,74 +1979,6 @@ end ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM Functions ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- -function RANGE:_SaveTargetSheet( _playername, result ) -- RangeBoss Specific Function - - --- Function that saves data to file - local function _savefile( filename, data ) - local f = io.open( filename, "wb" ) - if f then - f:write( data ) - f:close() - else - env.info( "RANGEBOSS EDIT - could not save target sheet to file" ) - -- self:E(self.lid..string.format("ERROR: could not save target sheet to file %s.\nFile may contain invalid characters.", tostring(filename))) - end - end - - -- Set path or default. - local path = self.targetpath - if lfs then - path = path or lfs.writedir() .. [[Logs\]] - end - - -- Create unused file name. - local filename = nil - for i = 1, 9999 do - - -- Create file name - if self.targetprefix then - filename = string.format( "%s_%s-%04d.csv", self.targetprefix, playerData.actype, i ) - else - local name = UTILS.ReplaceIllegalCharacters( _playername, "_" ) - filename = string.format( "RANGERESULTS-%s_Targetsheet-%s-%04d.csv", self.rangename, name, i ) - end - - -- Set path. - if path ~= nil then - filename = path .. "\\" .. filename - end - - -- Check if file exists. - local _exists = UTILS.FileExists( filename ) - if not _exists then - break - end - end - - -- Header line - local data = "Name,Target,Distance,Radial,Quality,Rounds Fired,Rounds Hit,Rounds Quality,Attack Heading,Weapon,Airframe,Mission Time,OS Time\n" - - -- local result=_result --#RANGE.BombResult - 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" - local roundsFired = result.roundsFired - local roundsHit = result.roundsHit - local strafeResult = result.roundsQuality - local attackHeading = result.heading - if os then - date = os.date() - end - data = data .. string.format( "%s,%s,%.2f,%03d,%s,%03d,%03d,%s,%03d,%s,%s,%s,%s", _playername, target, distance, radial, quality, roundsFired, roundsHit, strafeResult, attackHeading, weapon, airframe, time, date ) - - -- Save file. - _savefile( filename, data ) -end --- Check spawn queue and spawn aircraft if necessary. -- @param #RANGE self @@ -2299,6 +2279,73 @@ function RANGE:onafterLoad( From, Event, To ) end end +--- Save target sheet. +-- @param #RANGE self +-- @param #string _playername Player name. +-- @param #RANGE.StrafeResult result Results table. +function RANGE:_SaveTargetSheet( _playername, result ) -- RangeBoss Specific Function + + --- Function that saves data to file + local function _savefile( filename, data ) + local f = io.open( filename, "wb" ) + if f then + f:write( data ) + f:close() + else + env.info( "RANGEBOSS EDIT - could not save target sheet to file" ) + -- self:E(self.lid..string.format("ERROR: could not save target sheet to file %s.\nFile may contain invalid characters.", tostring(filename))) + end + end + + -- Set path or default. + local path = self.targetpath + if lfs then + path = path or lfs.writedir() .. [[Logs\]] + end + + -- Create unused file name. + local filename = nil + for i = 1, 9999 do + + -- Create file name + if self.targetprefix then + filename = string.format( "%s_%s-%04d.csv", self.targetprefix, result.airframe, i ) + else + local name = UTILS.ReplaceIllegalCharacters( _playername, "_" ) + filename = string.format( "RANGERESULTS-%s_Targetsheet-%s-%04d.csv", self.rangename, name, i ) + end + + -- Set path. + if path ~= nil then + filename = path .. "\\" .. filename + end + + -- Check if file exists. + local _exists = UTILS.FileExists( filename ) + if not _exists then + break + end + end + + -- Header line + local data = "Name,Target,Rounds Fired,Rounds Hit,Rounds Quality,Airframe,Mission Time,OS Time\n" + + local target = result.name + local airframe = result.airframe + local roundsFired = result.roundsFired + local roundsHit = result.roundsHit + local strafeResult = result.roundsQuality + local time = UTILS.SecondsToClock( result.time ) + local date = "n/a" + if os then + date = os.date() + end + data = data .. string.format( "%s,%s,%d,%d,%s,%s,%s,%s", _playername, target, roundsFired, roundsHit, strafeResult, airframe, time, date ) + + -- Save file. + _savefile( filename, data ) +end + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Display Messages ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -2324,7 +2371,7 @@ function RANGE:_DisplayMyStrafePitResults( _unitName ) local _message = string.format( "My Top %d Strafe Pit Results:\n", self.ndisplayresult ) -- Get player results. - local _results = self.strafePlayerResults[_playername] + local _results = self.strafePlayerResults[_playername] -- Create message. if _results == nil then @@ -2344,9 +2391,10 @@ function RANGE:_DisplayMyStrafePitResults( _unitName ) -- Loop over results for _, _result in pairs( _results ) do + local result=_result --#RANGE.StrafeResult -- Message text. - _message = _message .. string.format( "\n[%d] Hits %d - %s - %s", _count, _result.hits, _result.zone.name, _result.text ) + _message = _message .. string.format( "\n[%d] Hits %d - %s - %s", _count, result.roundsHit, result.name, result.roundsQuality ) -- Best result. if _bestMsg == "" then @@ -2835,14 +2883,16 @@ function RANGE:_CheckInZone( _unitName ) local unitheading = 0 -- RangeBoss if _unit and _playername then - + + -- Player data. + local playerData=self.PlayerSettings[_playername] -- #RANGE.PlayerData + --- Function to check if unit is in zone and facing in the right direction and is below the max alt. local function checkme( targetheading, _zone ) local zone = _zone -- Core.Zone#ZONE -- Heading check. local unitheading = _unit:GetHeading() - unitheadingStrafe = _unit:GetHeading() -- RangeBoss local pitheading = targetheading - 180 local deltaheading = unitheading - pitheading local towardspit = math.abs( deltaheading ) <= 90 or math.abs( deltaheading - 360 ) <= 90 @@ -2867,7 +2917,7 @@ function RANGE:_CheckInZone( _unitName ) local _unitID = _unit:GetID() -- Currently strafing? (strafeStatus is nil if not) - local _currentStrafeRun = self.strafeStatus[_unitID] + local _currentStrafeRun = self.strafeStatus[_unitID] --#RANGE.StrafeStatus if _currentStrafeRun then -- player has already registered for a strafing run. @@ -2879,7 +2929,6 @@ function RANGE:_CheckInZone( _unitName ) -- Check if player is in strafe zone and below max alt. if unitinzone then - StrafeAircraftType = _unit:GetTypeName() -- RangeBoss -- Still in zone, keep counting hits. Increase counter. _currentStrafeRun.time = _currentStrafeRun.time + 1 @@ -2909,8 +2958,10 @@ function RANGE:_CheckInZone( _unitName ) local _ammo = self:_GetAmmo( _unitName ) -- Result. - local _result = self.strafeStatus[_unitID] + local _result = self.strafeStatus[_unitID] --#RANGE.StrafeStatus + local _sound = nil -- #RANGE.Soundfile + --[[ --RangeBoss commented out in order to implement strafe quality based on accuracy percentage, not the number of rounds on target -- Judge this pass. Text is displayed on summary. if _result.hits >= _result.zone.goodPass*2 then @@ -2927,6 +2978,7 @@ function RANGE:_CheckInZone( _unitName ) _sound=RANGE.Sound.RCPoorPass end ]] + -- Calculate accuracy of run. Number of hits wrt number of rounds fired. local shots = _result.ammo - _ammo local accur = 0 @@ -2936,29 +2988,30 @@ function RANGE:_CheckInZone( _unitName ) accur = 100 end end - - if invalidStrafe == true then -- - _result.text = "* INVALID - PASSED FOUL LINE *" + + -- Results text and sound message. + local resulttext="" + if _result.pastfoulline == true then -- + resulttext = "* INVALID - PASSED FOUL LINE *" _sound = RANGE.Sound.RCPoorPass -- else if accur >= 90 then - _result.text = "DEADEYE PASS" + resulttext = "DEADEYE PASS" _sound = RANGE.Sound.RCExcellentPass elseif accur >= 75 then - _result.text = "EXCELLENT PASS" + resulttext = "EXCELLENT PASS" _sound = RANGE.Sound.RCExcellentPass elseif accur >= 50 then - _result.text = "GOOD PASS" + resulttext = "GOOD PASS" _sound = RANGE.Sound.RCGoodPass elseif accur >= 25 then - _result.text = "INEFFECTIVE PASS" + resulttext = "INEFFECTIVE PASS" _sound = RANGE.Sound.RCIneffectivePass else - _result.text = "POOR PASS" + resulttext = "POOR PASS" _sound = RANGE.Sound.RCPoorPass end end - clientStrafed = true -- RANGEBOSS -- Message text. local _text = string.format( "%s, hits on target %s: %d", self:_myname( _unitName ), _result.zone.name, _result.hits ) @@ -2969,45 +3022,27 @@ function RANGE:_CheckInZone( _unitName ) -- Send message. self:_DisplayMessageToGroup( _unit, _text ) - - -- RangeBoss Edit for strafe table insert - - -- Local results. - - local result = {} -- #RANGE.BombResult - result.name = _result.zone.name or "unknown" - result.distance = 0 - result.radial = 0 - result.weapon = "N/A" - result.quality = "N/A" - result.player = _playernamee + + -- Strafe result. + local result = {} -- #RANGE.StrafeResult + result.player=_playername + result.name=_result.zone.name or "unknown" result.time = timer.getAbsTime() - result.airframe = StrafeAircraftType - result.roundsFired = shots -- RANGEBOSS - result.roundsHit = _result.hits -- RANGEBOSS - result.roundsQuality = _result.text -- RANGEBOSS + result.roundsFired = shots + result.roundsHit = _result.hits + result.roundsQuality = resulttext result.strafeAccuracy = accur - result.heading = unitheadingStrafe -- RANGEBOSS - - Straferesult.name = _result.zone.name or "unknown" - Straferesult.distance = 0 - Straferesult.radial = 0 - Straferesult.weapon = "N/A" - Straferesult.quality = "N/A" - Straferesult.player = _playername - Straferesult.time = timer.getAbsTime() - Straferesult.airframe = StrafeAircraftType - Straferesult.roundsFired = shots - Straferesult.roundsHit = _result.hits - Straferesult.roundsQuality = _result.text - Straferesult.strafeAccuracy = accur - Straferesult.rangename = self.rangename - + result.rangename = self.rangename + result.airframe=playerData.airframe + result.invalid = _result.pastfoulline + + -- Griger Results. + self:StrafeResult(playerData, result) + -- Save trap sheet. if playerData and playerData.targeton and self.targetsheet then self:_SaveTargetSheet( _playername, result ) - end - -- RangeBoss edit for strafe data saved to file + end -- Voice over. if self.rangecontrol then @@ -3028,7 +3063,7 @@ function RANGE:_CheckInZone( _unitName ) -- Save stats so the player can retrieve them. local _stats = self.strafePlayerResults[_playername] or {} - table.insert( _stats, _result ) + table.insert( _stats, result ) self.strafePlayerResults[_playername] = _stats end @@ -3038,12 +3073,13 @@ function RANGE:_CheckInZone( _unitName ) -- Check to see if we're in any of the strafing zones (first time). for _, _targetZone in pairs( self.strafeTargets ) do + local target=_targetZone --#RANGE.StrafeTarget -- Get the current approach zone and check if player is inside. - local zone = _targetZone.polygon -- Core.Zone#ZONE_POLYGON_BASE + local zone = target.polygon -- Core.Zone#ZONE_POLYGON_BASE -- Check if unit in zone and facing the right direction. - local unitinzone = checkme( _targetZone.heading, zone ) + local unitinzone = checkme( target.heading, zone ) -- Player is inside zone. if unitinzone then @@ -3052,19 +3088,20 @@ function RANGE:_CheckInZone( _unitName ) local _ammo = self:_GetAmmo( _unitName ) -- Init strafe status for this player. - self.strafeStatus[_unitID] = { hits = 0, zone = _targetZone, time = 1, ammo = _ammo, pastfoulline = false } + self.strafeStatus[_unitID] = { hits = 0, zone = target, time = 1, ammo = _ammo, pastfoulline = false } -- Rolling in! - local _msg = string.format( "%s, rolling in on strafe pit %s.", self:_myname( _unitName ), _targetZone.name ) + local _msg = string.format( "%s, rolling in on strafe pit %s.", self:_myname( _unitName ), target.name ) if self.rangecontrol then self.rangecontrol:NewTransmission( RANGE.Sound.RCRollingInOnStrafeTarget.filename, RANGE.Sound.RCRollingInOnStrafeTarget.duration, self.soundpath ) end - clientRollingIn = true -- RANGEBOSS -- Send message. self:_DisplayMessageToGroup( _unit, _msg, 10, true ) - hypemanStrafeRollIn = _msg -- RANGEBOSS + + -- Trigger event that player is rolling in. + self:RollingIn(playerData, target) -- We found our player. Skip remaining checks. break From 466a18447c3fd0c87add515b2d953a126cb1b7fd Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 5 May 2022 08:57:23 +0200 Subject: [PATCH 134/200] SRS - adding volume setting and a test on OS and IO available --- Moose Development/Moose/Sound/SRS.lua | 29 +++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 3ec73c6f1..14ea4964a 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -131,7 +131,7 @@ MSRS = { --- MSRS class version. -- @field #string version -MSRS.version="0.0.4" +MSRS.version="0.0.5" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -150,8 +150,9 @@ MSRS.version="0.0.4" -- @param #string PathToSRS Path to the directory, where SRS is located. -- @param #number Frequency Radio frequency in MHz. Default 143.00 MHz. Can also be given as a #table of multiple frequencies. -- @param #number Modulation Radio modulation: 0=AM (default), 1=FM. See `radio.modulation.AM` and `radio.modulation.FM` enumerators. Can also be given as a #table of multiple modulations. +-- @param #number Volume Volume - 1.0 is max, 0.0 is silence -- @return #MSRS self -function MSRS:New(PathToSRS, Frequency, Modulation) +function MSRS:New(PathToSRS, Frequency, Modulation, Volume) -- Defaults. Frequency =Frequency or 143 @@ -167,6 +168,12 @@ function MSRS:New(PathToSRS, Frequency, Modulation) self:SetGender() self:SetCoalition() self:SetLabel() + self:SetVolume() + self.lid = string.format("%s-%s | ",self.name,self.version) + + if not io or not os then + self:E(self.lid.."***** ERROR - io or os NOT desanitized! MSRS will not work!") + end return self end @@ -209,6 +216,24 @@ function MSRS:GetPath() return self.path end +--- Set SRS volume. +-- @param #MSRS self +-- @param #number Volume Volume - 1.0 is max, 0.0 is silence +-- @return #MSRS self +function MSRS:SetVolume(Volume) + local volume = Volume or 1 + if volume > 1 then volume = 1 elseif volume < 0 then volume = 0 end + self.volume = volume + return self +end + +--- Get SRS volume. +-- @param #MSRS self +-- @return #number Volume Volume - 1.0 is max, 0.0 is silence +function MSRS:GetVolume() + return self.volume +end + --- Set label. -- @param #MSRS self -- @param #number Label. Default "ROBOT" From decc9d09f8be16d3cc56d9267de11c7d01ba7850 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 5 May 2022 11:34:14 +0200 Subject: [PATCH 135/200] docu update --- Moose Development/Moose/Sound/SRS.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 14ea4964a..e3d937a35 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -110,7 +110,11 @@ -- ## Set SRS Port -- -- Use @{#MSRS.SetPort} to define the SRS port. Defaults to 5002. --- +-- +-- ## Set SRS Volume +-- +-- Use @{#MSRS.SetVolume} to define the SRS volume. Defaults to 1.0. Allowed values are between 0.0 and 1.0, from silent to loudest. +-- -- @field #MSRS MSRS = { ClassName = "MSRS", From ca52585759250067e1a4763c0353268fcb0e83ff Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 5 May 2022 12:07:56 +0200 Subject: [PATCH 136/200] AI/ZONE - Some fixes for units unreachable --- Moose Development/Moose/AI/AI_BAI.lua | 4 ++-- Moose Development/Moose/AI/AI_CAP.lua | 4 ++-- Moose Development/Moose/AI/AI_CAS.lua | 2 +- Moose Development/Moose/AI/AI_Patrol.lua | 13 +++++++------ Moose Development/Moose/Core/Zone.lua | 6 +++--- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/Moose Development/Moose/AI/AI_BAI.lua b/Moose Development/Moose/AI/AI_BAI.lua index b71afd048..9da590e03 100644 --- a/Moose Development/Moose/AI/AI_BAI.lua +++ b/Moose Development/Moose/AI/AI_BAI.lua @@ -515,8 +515,8 @@ function AI_BAI_ZONE:onafterEngage( Controllable, From, Event, To, --- Calculate the current route point. local CurrentVec2 = self.Controllable:GetVec2() - --TODO: Create GetAltitude function for GROUP, and delete GetUnit(1). - local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude() + --DONE: Create GetAltitude function for GROUP, and delete GetUnit(1). + local CurrentAltitude = self.Controllable:GetAltitude() local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) local ToEngageZoneSpeed = self.PatrolMaxSpeed local CurrentRoutePoint = CurrentPointVec3:WaypointAir( diff --git a/Moose Development/Moose/AI/AI_CAP.lua b/Moose Development/Moose/AI/AI_CAP.lua index 611a27b54..6653387cc 100644 --- a/Moose Development/Moose/AI/AI_CAP.lua +++ b/Moose Development/Moose/AI/AI_CAP.lua @@ -428,8 +428,8 @@ function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To ) --- Calculate the current route point. local CurrentVec2 = self.Controllable:GetVec2() - --TODO: Create GetAltitude function for GROUP, and delete GetUnit(1). - local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude() + --DONE: Create GetAltitude function for GROUP, and delete GetUnit(1). + local CurrentAltitude = self.Controllable:GetAltitude() local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) local ToEngageZoneSpeed = self.PatrolMaxSpeed local CurrentRoutePoint = CurrentPointVec3:WaypointAir( diff --git a/Moose Development/Moose/AI/AI_CAS.lua b/Moose Development/Moose/AI/AI_CAS.lua index d8879f409..0a66f85da 100644 --- a/Moose Development/Moose/AI/AI_CAS.lua +++ b/Moose Development/Moose/AI/AI_CAS.lua @@ -460,7 +460,7 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To, local CurrentVec2 = self.Controllable:GetVec2() --TODO: Create GetAltitude function for GROUP, and delete GetUnit(1). - local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude() + local CurrentAltitude = self.Controllable:GetAltitude() local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) local ToEngageZoneSpeed = self.PatrolMaxSpeed local CurrentRoutePoint = CurrentPointVec3:WaypointAir( diff --git a/Moose Development/Moose/AI/AI_Patrol.lua b/Moose Development/Moose/AI/AI_Patrol.lua index a20f02ee4..d9b1e7f83 100644 --- a/Moose Development/Moose/AI/AI_Patrol.lua +++ b/Moose Development/Moose/AI/AI_Patrol.lua @@ -743,8 +743,8 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To ) if self.Controllable:InAir() == false then self:T( "Not in the air, finding route path within PatrolZone" ) local CurrentVec2 = self.Controllable:GetVec2() - --TODO: Create GetAltitude function for GROUP, and delete GetUnit(1). - local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude() + --DONE: Create GetAltitude function for GROUP, and delete GetUnit(1). + local CurrentAltitude = self.Controllable:GetAltitude() local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) local ToPatrolZoneSpeed = self.PatrolMaxSpeed local CurrentRoutePoint = CurrentPointVec3:WaypointAir( @@ -758,8 +758,8 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To ) else self:T( "In the air, finding route path within PatrolZone" ) local CurrentVec2 = self.Controllable:GetVec2() - --TODO: Create GetAltitude function for GROUP, and delete GetUnit(1). - local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude() + --DONE: Create GetAltitude function for GROUP, and delete GetUnit(1). + local CurrentAltitude = self.Controllable:GetAltitude() local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) local ToPatrolZoneSpeed = self.PatrolMaxSpeed local CurrentRoutePoint = CurrentPointVec3:WaypointAir( @@ -871,8 +871,9 @@ function AI_PATROL_ZONE:onafterRTB() --- Calculate the current route point. local CurrentVec2 = self.Controllable:GetVec2() - --TODO: Create GetAltitude function for GROUP, and delete GetUnit(1). - local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude() + --DONE: Create GetAltitude function for GROUP, and delete GetUnit(1). + --local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude() + local CurrentAltitude = self.Controllable:GetAltitude() local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) local ToPatrolZoneSpeed = self.PatrolMaxSpeed local CurrentRoutePoint = CurrentPointVec3:WaypointAir( diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 09a148987..58d03cf31 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1157,7 +1157,7 @@ end -- @return #boolean true if the location is within the zone. function ZONE_RADIUS:IsVec2InZone( Vec2 ) self:F2( Vec2 ) - + if not Vec2 then return false end local ZoneVec2 = self:GetVec2() if ZoneVec2 then @@ -1175,7 +1175,7 @@ end -- @return #boolean true if the point is within the zone. function ZONE_RADIUS:IsVec3InZone( Vec3 ) self:F2( Vec3 ) - + if not Vec3 then return false end local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } ) return InZone @@ -1966,7 +1966,7 @@ end -- @return #boolean true if the location is within the zone. function ZONE_POLYGON_BASE:IsVec2InZone( Vec2 ) self:F2( Vec2 ) - + if not Vec2 then return false end local Next local Prev local InPolygon = false From 07d761941ad8c8a7965777216813367d57ff61f4 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 5 May 2022 16:41:32 +0200 Subject: [PATCH 137/200] AI_AIR - restrict AB on RTB --- Moose Development/Moose/AI/AI_Air.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/AI/AI_Air.lua b/Moose Development/Moose/AI/AI_Air.lua index 9d5397073..809a4faef 100644 --- a/Moose Development/Moose/AI/AI_Air.lua +++ b/Moose Development/Moose/AI/AI_Air.lua @@ -589,7 +589,9 @@ function AI_AIR:onafterRTB( AIGroup, From, Event, To ) self:ClearTargetDistance() --AIGroup:ClearTasks() - + + AIGroup:OptionProhibitAfterburner(true) + local EngageRoute = {} --- Calculate the target route point. From 69eb920173a0a4b073eca2f4d0fb003e9359aa8d Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 5 May 2022 17:40:00 +0200 Subject: [PATCH 138/200] AI_CAP - more fallout from the dead units in the API --- Moose Development/Moose/AI/AI_CAP.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Moose Development/Moose/AI/AI_CAP.lua b/Moose Development/Moose/AI/AI_CAP.lua index 6653387cc..fa37513c2 100644 --- a/Moose Development/Moose/AI/AI_CAP.lua +++ b/Moose Development/Moose/AI/AI_CAP.lua @@ -428,6 +428,10 @@ function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To ) --- Calculate the current route point. local CurrentVec2 = self.Controllable:GetVec2() + if not CurrentVec2 then -- flight dead at this point + return self + end + --DONE: Create GetAltitude function for GROUP, and delete GetUnit(1). local CurrentAltitude = self.Controllable:GetAltitude() local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) From 7c5067a59a35bfbfe42da255fcbe360c36376b7f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 6 May 2022 08:01:31 +0200 Subject: [PATCH 139/200] UNIT Register - small fix for trains --- Moose Development/Moose/Wrapper/Unit.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 11f7c3d5e..091676403 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -119,7 +119,10 @@ function UNIT:Register( UnitName ) local unit=Unit.getByName(self.UnitName) if unit then - self.GroupName=unit:getGroup():getName() + local group = unit:getGroup() + if group then + self.GroupName=group:getName() + end end -- Set event prio. From ba4a8050ba527c45678b27418a665a5dc606f925 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 6 May 2022 10:36:39 +0200 Subject: [PATCH 140/200] AI Patrol - life check on route --- Moose Development/Moose/AI/AI_Patrol.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/AI/AI_Patrol.lua b/Moose Development/Moose/AI/AI_Patrol.lua index d9b1e7f83..3cde74e56 100644 --- a/Moose Development/Moose/AI/AI_Patrol.lua +++ b/Moose Development/Moose/AI/AI_Patrol.lua @@ -726,7 +726,8 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To ) end - if self.Controllable:IsAlive() then + local life = self.Controllable:GetLife() or 0 + if self.Controllable:IsAlive() and life > 1 then -- Determine if the AIControllable is within the PatrolZone. -- If not, make a waypoint within the to that the AIControllable will fly at maximum speed to that point. @@ -743,6 +744,7 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To ) if self.Controllable:InAir() == false then self:T( "Not in the air, finding route path within PatrolZone" ) local CurrentVec2 = self.Controllable:GetVec2() + if not CurrentVec2 then return end --DONE: Create GetAltitude function for GROUP, and delete GetUnit(1). local CurrentAltitude = self.Controllable:GetAltitude() local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) @@ -758,6 +760,7 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To ) else self:T( "In the air, finding route path within PatrolZone" ) local CurrentVec2 = self.Controllable:GetVec2() + if not CurrentVec2 then return end --DONE: Create GetAltitude function for GROUP, and delete GetUnit(1). local CurrentAltitude = self.Controllable:GetAltitude() local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) From cc497919975a1999c6eeef499c8ffbebb71aff7a Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 6 May 2022 11:45:11 +0200 Subject: [PATCH 141/200] Fallout fixes --- Moose Development/Moose/AI/AI_Air_Engage.lua | 4 ++++ Moose Development/Moose/AI/AI_Patrol.lua | 2 +- Moose Development/Moose/Core/Point.lua | 3 +-- Moose Development/Moose/Functional/Detection.lua | 4 ++-- Moose Development/Moose/Wrapper/Group.lua | 2 +- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Air_Engage.lua b/Moose Development/Moose/AI/AI_Air_Engage.lua index a80402a2f..31dbed41a 100644 --- a/Moose Development/Moose/AI/AI_Air_Engage.lua +++ b/Moose Development/Moose/AI/AI_Air_Engage.lua @@ -533,6 +533,10 @@ function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetU DefenderCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude. local TargetCoord = AttackSetUnit:GetFirst():GetPointVec3() + if not TargetCoord then + self:Return() + return + end TargetCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude. local TargetDistance = DefenderCoord:Get2DDistance( TargetCoord ) diff --git a/Moose Development/Moose/AI/AI_Patrol.lua b/Moose Development/Moose/AI/AI_Patrol.lua index 3cde74e56..e359a98ac 100644 --- a/Moose Development/Moose/AI/AI_Patrol.lua +++ b/Moose Development/Moose/AI/AI_Patrol.lua @@ -873,7 +873,7 @@ function AI_PATROL_ZONE:onafterRTB() --- Calculate the current route point. local CurrentVec2 = self.Controllable:GetVec2() - + if not CurrentVec2 then return end --DONE: Create GetAltitude function for GROUP, and delete GetUnit(1). --local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude() local CurrentAltitude = self.Controllable:GetAltitude() diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index bfd7a9150..277b97446 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -892,9 +892,8 @@ do -- COORDINATE -- @param #COORDINATE TargetCoordinate The target COORDINATE. Can also be a DCS#Vec3. -- @return DCS#Distance Distance The distance in meters. function COORDINATE:Get2DDistance(TargetCoordinate) - + if not TargetCoordinate then return 1000000 end local a={x=TargetCoordinate.x-self.x, y=0, z=TargetCoordinate.z-self.z} - local norm=UTILS.VecNorm(a) return norm end diff --git a/Moose Development/Moose/Functional/Detection.lua b/Moose Development/Moose/Functional/Detection.lua index 80465118a..c122e1c47 100644 --- a/Moose Development/Moose/Functional/Detection.lua +++ b/Moose Development/Moose/Functional/Detection.lua @@ -591,7 +591,7 @@ do -- DETECTION_BASE -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. - -- @param Wrapper.Group#GROUP DetectionGroup The Group detecting. + -- @param Wrapper.Group#GROUP Detection The Group detecting. -- @param #number DetectionTimeStamp Time stamp of detection event. function DETECTION_BASE:onafterDetection( From, Event, To, Detection, DetectionTimeStamp ) @@ -662,7 +662,7 @@ do -- DETECTION_BASE local DetectedObjectVec3 = DetectedObject:getPoint() local DetectedObjectVec2 = { x = DetectedObjectVec3.x, y = DetectedObjectVec3.z } - local DetectionGroupVec3 = Detection:GetVec3() + local DetectionGroupVec3 = Detection:GetVec3() or {x=0,y=0,z=0} local DetectionGroupVec2 = { x = DetectionGroupVec3.x, y = DetectionGroupVec3.z } local Distance = ((DetectedObjectVec3.x - DetectionGroupVec3.x) ^ 2 + diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index e393e348b..12b31f130 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -1160,7 +1160,7 @@ function GROUP:GetFuelAvg() local TotalFuel = 0 for UnitID, UnitData in pairs( self:GetUnits() ) do local Unit = UnitData -- Wrapper.Unit#UNIT - local UnitFuel = Unit:GetFuel() + local UnitFuel = Unit:GetFuel() or 0 self:F( { Fuel = UnitFuel } ) TotalFuel = TotalFuel + UnitFuel end From 41e8ddea8c5a51e97d1c4fb6012b28c4b69542e4 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 7 May 2022 11:54:33 +0200 Subject: [PATCH 142/200] GROUP - change to GetUnits(n) to make it more robust, now returns first alive unit,actually. Similar changes to GetHeading() --- Moose Development/Moose/Wrapper/Group.lua | 30 +++++++++++++++++------ 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 12b31f130..419c4a8a8 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -711,11 +711,25 @@ end -- @return DCS#Unit The DCS Unit. function GROUP:GetDCSUnit( UnitNumber ) - local DCSGroup=self:GetDCSObject() + local DCSGroup = self:GetDCSObject() if DCSGroup then - local DCSUnitFound=DCSGroup:getUnit( UnitNumber ) - return DCSUnitFound + + local UnitFound = nil + -- 2.7.1 dead event bug, return the first alive unit instead + local units = DCSGroup:getUnits() or {} + + for _,_unit in pairs(units) do + + local UnitFound = UNIT:Find(_unit) + + if UnitFound and UnitFound:IsAlive() then + + return UnitFound + + end + end + end return nil @@ -1093,19 +1107,21 @@ end function GROUP:GetHeading() self:F2(self.GroupName) + self:F2(self.GroupName) + local GroupSize = self:GetSize() local HeadingAccumulator = 0 - local n=0 + local Units = self:GetUnits() + if GroupSize then - for i = 1, GroupSize do - local unit=self:GetUnit(i) + for _,unit in pairs(Units) do if unit and unit:IsAlive() then HeadingAccumulator = HeadingAccumulator + unit:GetHeading() n=n+1 end end - return math.floor(HeadingAccumulator / n) + return math.floor(HeadingAccumulator / n) end BASE:E( { "Cannot GetHeading", Group = self, Alive = self:IsAlive() } ) From 04068d7117a3a63ccf208199be1aa7dde7b3fcae Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 7 May 2022 19:42:31 +0200 Subject: [PATCH 143/200] Group - small change --- Moose Development/Moose/Wrapper/Group.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 419c4a8a8..a801b5f3f 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -723,7 +723,7 @@ function GROUP:GetDCSUnit( UnitNumber ) local UnitFound = UNIT:Find(_unit) - if UnitFound and UnitFound:IsAlive() then + if UnitFound then return UnitFound From cdaef851a017c3ec8db8b1c1bdd2db7219e6db49 Mon Sep 17 00:00:00 2001 From: 476th-Scaley Date: Tue, 10 May 2022 09:10:41 +0100 Subject: [PATCH 144/200] Update AI_Air.lua (#1729) * Update AI_Air.lua Altered RTB airspeed (slower) and target altitude over the airfield being returned to (higher) to produce more realistic and fuel efficient descent profiles. Leads to aircraft arriving overhead the airfield quite high and generally flying one orbit to descend to land. Scaley * Create AI_Air.lua Co-authored-by: Applevangelist <72444570+Applevangelist@users.noreply.github.com> --- Moose Development/Moose/AI/AI_Air.lua | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Air.lua b/Moose Development/Moose/AI/AI_Air.lua index 809a4faef..62c1f4a00 100644 --- a/Moose Development/Moose/AI/AI_Air.lua +++ b/Moose Development/Moose/AI/AI_Air.lua @@ -253,6 +253,9 @@ function AI_AIR:New( AIGroup ) self.IdleCount = 0 + self.RTBSpeedMaxFactor = 0.6 + self.RTBSpeedMinFactor = 0.5 + return self end @@ -576,6 +579,19 @@ function AI_AIR.RTBHold( AIGroup, Fsm ) end +--- Set the min and max factors on RTB speed. Use this, if your planes are heading back to base too fast. Default values are 0.5 and 0.6. +-- The RTB speed is calculated as the max speed of the unit multiplied by MinFactor (lower bracket) and multiplied by MaxFactor (upper bracket). +-- A random value in this bracket is then applied in the waypoint routing generation. +-- @param #AI_AIR self +-- @param #number MinFactor Lower bracket factor. Defaults to 0.5. +-- @param #number MaxFactor Upper bracket factor. Defaults to 0.6. +-- @return #AI_AIR self +function AI_AIR:SetRTBSpeedFactors(MinFactor,MaxFactor) + self.RTBSpeedMaxFactor = MaxFactor or 0.6 + self.RTBSpeedMinFactor = MinFactor or 0.5 + return self +end + --- @param #AI_AIR self -- @param Wrapper.Group#GROUP AIGroup @@ -599,12 +615,14 @@ function AI_AIR:onafterRTB( AIGroup, From, Event, To ) local FromCoord = AIGroup:GetCoordinate() local ToTargetCoord = self.HomeAirbase:GetCoordinate() -- coordinate is on land height(!) local ToTargetVec3 = ToTargetCoord:GetVec3() - ToTargetVec3.y = ToTargetCoord:GetLandHeight()+1000 -- let's set this 1000m/3000 feet above ground + ToTargetVec3.y = ToTargetCoord:GetLandHeight()+3000 -- let's set this 1000m/3000 feet above ground 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 ) + local RTBSpeedMaxFactor = self.RTBSpeedMaxFactor or 0.6 + local RTBSpeedMinFactor = self.RTBSpeedMinFactor or 0.5 + self:SetRTBSpeed( RTBSpeedMax * RTBSpeedMinFactor, RTBSpeedMax * RTBSpeedMaxFactor) end local RTBSpeed = math.random( self.RTBMinSpeed, self.RTBMaxSpeed ) From 1483ffd7ff786266a9adb417cd2376dab0201b9d Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 10 May 2022 16:18:00 +0200 Subject: [PATCH 145/200] Correct link to demo missions --- Moose Development/Moose/AI/AI_Balancer.lua | 2 +- Moose Development/Moose/AI/AI_CAP.lua | 2 +- Moose Development/Moose/AI/AI_CAS.lua | 2 +- Moose Development/Moose/AI/AI_Patrol.lua | 2 +- Moose Development/Moose/Core/Scheduler.lua | 2 +- Moose Development/Moose/Core/Spawn.lua | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Balancer.lua b/Moose Development/Moose/AI/AI_Balancer.lua index 9c3be56e7..b855acf9c 100644 --- a/Moose Development/Moose/AI/AI_Balancer.lua +++ b/Moose Development/Moose/AI/AI_Balancer.lua @@ -9,7 +9,7 @@ -- -- === -- --- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/AIB%20-%20AI%20Balancing) +-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AIB%20-%20AI%20Balancing) -- -- === -- diff --git a/Moose Development/Moose/AI/AI_CAP.lua b/Moose Development/Moose/AI/AI_CAP.lua index fa37513c2..02dc40a49 100644 --- a/Moose Development/Moose/AI/AI_CAP.lua +++ b/Moose Development/Moose/AI/AI_CAP.lua @@ -10,7 +10,7 @@ -- -- === -- --- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/CAP%20-%20Combat%20Air%20Patrol) +-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/CAP%20-%20Combat%20Air%20Patrol) -- -- === -- diff --git a/Moose Development/Moose/AI/AI_CAS.lua b/Moose Development/Moose/AI/AI_CAS.lua index 0a66f85da..2829a7962 100644 --- a/Moose Development/Moose/AI/AI_CAS.lua +++ b/Moose Development/Moose/AI/AI_CAS.lua @@ -11,7 +11,7 @@ -- -- === -- --- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/CAS%20-%20Close%20Air%20Support) +-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/CAS%20-%20Close%20Air%20Support) -- -- === -- diff --git a/Moose Development/Moose/AI/AI_Patrol.lua b/Moose Development/Moose/AI/AI_Patrol.lua index e359a98ac..02b4744e0 100644 --- a/Moose Development/Moose/AI/AI_Patrol.lua +++ b/Moose Development/Moose/AI/AI_Patrol.lua @@ -16,7 +16,7 @@ -- -- === -- --- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/PAT%20-%20Patrolling) +-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/PAT%20-%20Patrolling) -- -- === -- diff --git a/Moose Development/Moose/Core/Scheduler.lua b/Moose Development/Moose/Core/Scheduler.lua index d0c200a1e..6ba33425e 100644 --- a/Moose Development/Moose/Core/Scheduler.lua +++ b/Moose Development/Moose/Core/Scheduler.lua @@ -14,7 +14,7 @@ -- -- # Demo Missions -- --- ### [SCHEDULER Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/SCH%20-%20Scheduler) +-- ### [SCHEDULER Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SCH%20-%20Scheduler) -- -- ### [SCHEDULER Demo Missions, only for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SCH%20-%20Scheduler) -- diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 9d3f837e4..d96eb0958 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -30,7 +30,7 @@ -- -- === -- --- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/SPA%20-%20Spawning) +-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SPA%20-%20Spawning) -- -- === -- From 58074f499ffdd6356d70e5c16faa0689803eb7fe Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 10 May 2022 19:40:32 +0200 Subject: [PATCH 146/200] AIRBASE - corrected ["Deir_ez_Zor"] = "Deir ez-Zor" (minus doesn't work in enum) --- Moose Development/Moose/Wrapper/Airbase.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index f24590551..9fcfe52ad 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -398,6 +398,7 @@ AIRBASE.TheChannel = { -- * AIRBASE.Syria.Ruwayshid -- * AIRBASE.Syria.Sanliurfa -- * AIRBASE.Syria.Tal_Siman +-- * AIRBASE.Syria.Deir_ez_Zor -- --@field Syria AIRBASE.Syria={ @@ -463,7 +464,7 @@ AIRBASE.Syria={ ["Ruwayshid"]="Ruwayshid", ["Sanliurfa"]="Sanliurfa", ["Tal_Siman"]="Tal Siman", - ["Deir_ez-Zor"] = "Deir ez-Zor", + ["Deir_ez_Zor"] = "Deir ez-Zor", } --- Airbases of the Mariana Islands map: From 646b113c55a3955800aef068e666a26ebb213534 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 11 May 2022 06:19:09 +0200 Subject: [PATCH 147/200] SRS - actually pass the volume to the command line --- Moose Development/Moose/Sound/SRS.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index e3d937a35..57beb40db 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -135,7 +135,7 @@ MSRS = { --- MSRS class version. -- @field #string version -MSRS.version="0.0.5" +MSRS.version="0.0.6" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -718,7 +718,7 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp --local command=string.format('start /b "" /d "%s" "%s" -f %s -m %s -c %s -p %s -n "%s" > bla.txt', path, exe, freqs, modus, coal, port, "ROBOT") -- Command. - local command=string.format('"%s\\%s" -f %s -m %s -c %s -p %s -n "%s"', path, exe, freqs, modus, coal, port, label) + local command=string.format('"%s\\%s" -f %s -m %s -c %s -p %s -n "%s" -v %.1f', path, exe, freqs, modus, coal, port, label,volume) -- Set voice or gender/culture. if voice then From f6aea13fae0193f9e1b8c280dcbf80c03834074f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 11 May 2022 07:30:26 +0200 Subject: [PATCH 148/200] SRS - put volume in "" - just in case --- Moose Development/Moose/Sound/SRS.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 57beb40db..eabaad197 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -718,7 +718,7 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp --local command=string.format('start /b "" /d "%s" "%s" -f %s -m %s -c %s -p %s -n "%s" > bla.txt', path, exe, freqs, modus, coal, port, "ROBOT") -- Command. - local command=string.format('"%s\\%s" -f %s -m %s -c %s -p %s -n "%s" -v %.1f', path, exe, freqs, modus, coal, port, label,volume) + local command=string.format('"%s\\%s" -f %s -m %s -c %s -p %s -n "%s" -v "%.1f"', path, exe, freqs, modus, coal, port, label,volume) -- Set voice or gender/culture. if voice then From e2b1276d7b6ae78cc63edeba2b6e511899e821a9 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 12 May 2022 14:49:30 +0200 Subject: [PATCH 149/200] Point - added option to add an SSML tag to ToStringBRAANATO --- Moose Development/Moose/Core/Point.lua | 46 ++++++++++++++++++-------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 277b97446..b9bc35664 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -2769,8 +2769,9 @@ do -- COORDINATE -- @param #COORDINATE FromCoordinate The coordinate to measure the distance and the bearing from. -- @param #boolean Bogey Add "Bogey" at the end if true (not yet declared hostile or friendly) -- @param #boolean Spades Add "Spades" at the end if true (no IFF/VID ID yet known) + -- @param #boolean SSML Add SSML tags speaking aspect as 0 1 2 and "brah" instead of BRAA -- @return #string The BRAA text. - function COORDINATE:ToStringBRAANATO(FromCoordinate,Bogey,Spades) + function COORDINATE:ToStringBRAANATO(FromCoordinate,Bogey,Spades,SSML) -- Thanks to @Pikey local BRAANATO = "Merged." @@ -2791,22 +2792,39 @@ do -- COORDINATE local track = UTILS.BearingToCardinal(bearing) or "North" if rangeNM > 3 then - if aspect == "" then - BRAANATO = string.format("BRA, %03d, %d miles, Angels %d, Track %s",bearing, rangeNM, alt, track) + if SSML then + if aspect == "" then + BRAANATO = string.format("brah %03d, %d miles, Angels %d, Track %s",bearing, rangeNM, alt, track) + else + BRAANATO = string.format("brah %03d, %d miles, Angels %d, %s, Track %s",bearing, rangeNM, alt, aspect, track) + end + if Bogey and Spades then + BRAANATO = BRAANATO..", Bogey, Spades." + elseif Bogey then + BRAANATO = BRAANATO..", Bogey." + elseif Spades then + BRAANATO = BRAANATO..", Spades." + else + BRAANATO = BRAANATO.."." + end else - BRAANATO = string.format("BRAA, %03d, %d miles, Angels %d, %s, Track %s",bearing, rangeNM, alt, aspect, track) - end - if Bogey and Spades then - BRAANATO = BRAANATO..", Bogey, Spades." - elseif Bogey then - BRAANATO = BRAANATO..", Bogey." - elseif Spades then - BRAANATO = BRAANATO..", Spades." - else - BRAANATO = BRAANATO.."." + if aspect == "" then + BRAANATO = string.format("BRA %03d, %d miles, Angels %d, Track %s",bearing, rangeNM, alt, track) + else + BRAANATO = string.format("BRAA %03d, %d miles, Angels %d, %s, Track %s",bearing, rangeNM, alt, aspect, track) + end + if Bogey and Spades then + BRAANATO = BRAANATO..", Bogey, Spades." + elseif Bogey then + BRAANATO = BRAANATO..", Bogey." + elseif Spades then + BRAANATO = BRAANATO..", Spades." + else + BRAANATO = BRAANATO.."." + end end end - + return BRAANATO end From c0b32a5584c12c2e736ff4cc0ab6003feeb57923 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 13 May 2022 16:37:41 +0200 Subject: [PATCH 150/200] SRS - added hints on using Google with TTS --- Moose Development/Moose/Sound/SRS.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index eabaad197..cafb49c7c 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -96,6 +96,14 @@ -- For more information on setting up a cloud account, visit: https://cloud.google.com/text-to-speech -- Google's supported SSML reference: https://cloud.google.com/text-to-speech/docs/ssml -- +-- **NOTE on using GOOGLE TTS with SRS:** You need to have the C# library installed in your SRS folder for Google to work. +-- You can obtain it e.g. here: [NuGet](https://www.nuget.org/packages/Grpc.Core) +-- +-- **Pro-Tipp** - use the command line with power shell to call DCS-SR-ExternalAudio.exe - it will tell you what is missing. +-- and also the Google Console error, in case you have missed a step in setting up your Google TTS. +-- E.g. `.\DCS-SR-ExternalAudio.exe -t "Text Message" -f 255 -m AM -c 2 -s 2 -z -G "Path_To_You_Google.Json"` +-- Plays a message on 255AM for the blue coalition in-game. +-- -- ## Set Voice -- -- Use a specifc voice with the @{#MSRS.SetVoice} function, e.g, `:SetVoice("Microsoft Hedda Desktop")`. From d5fb75fe432614f8811805e1e355b3fa3dd0cc18 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 14 May 2022 13:11:30 +0200 Subject: [PATCH 151/200] Positionable - added 6 passengers (cargo weight) to Toyota HL/LC new with 2.7.2 OB --- Moose Development/Moose/Wrapper/Positionable.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index 8b37556e6..d1ad2963e 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -1600,7 +1600,11 @@ do -- Cargo ["Ural-4320 APA-5D"] = 10, ["Ural-4320T"] = 14, ["ZBD04A"] = 7, -- new by kappa - ["VAB_Mephisto"] = 8 -- new by Apple + ["VAB_Mephisto"] = 8, -- new by Apple + ["tt_KORD"] = 6, -- 2.7.1 HL/TT + ["tt_DSHK"] = 6, + ["HL_KORD"] = 6, + ["HL_DSHK"] = 6, } local CargoBayWeightLimit = (Weights[Desc.typeName] or 0) * 95 From 7df3946189518ac1933e66ca1ea43e763c62c983 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 20 May 2022 20:16:45 +0200 Subject: [PATCH 152/200] Update Airboss.lua - Wind is calculated at 15 m (not 50 m) --- Moose Development/Moose/Ops/Airboss.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 8ebf78c15..8a6d82c5b 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -11210,7 +11210,7 @@ end --- Get wind direction and speed at carrier position. -- @param #AIRBOSS self --- @param #number alt Altitude ASL in meters. Default 50 m. +-- @param #number alt Altitude ASL in meters. Default 15 m. -- @param #boolean magnetic Direction including magnetic declination. -- @param Core.Point#COORDINATE coord (Optional) Coordinate at which to get the wind. Default is current carrier position. -- @return #number Direction the wind is blowing **from** in degrees. @@ -11220,8 +11220,8 @@ function AIRBOSS:GetWind( alt, magnetic, coord ) -- Current position of the carrier or input. local cv = coord or self:GetCoordinate() - -- Wind direction and speed. By default at 50 meters ASL. - local Wdir, Wspeed = cv:GetWind( alt or 50 ) + -- Wind direction and speed. By default at 15 meters ASL. + local Wdir, Wspeed = cv:GetWind( alt or 15 ) -- Include magnetic declination. if magnetic then From 4762793adcc8ff8ce7b57a49bf317d4a5427233a Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sun, 22 May 2022 11:16:16 +0200 Subject: [PATCH 153/200] Create README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5a1979522..69ab8403a 100644 --- a/README.md +++ b/README.md @@ -61,9 +61,10 @@ Documentation on the MOOSE class hierarchy, usage guides and background informat -## [MOOSE Youtube Channel](https://www.youtube.com/channel/UCjrA9j5LQoWsG4SpS8i79Qg) +## [MOOSE Youtube Tutorials](https://youtube.com/playlist?list=PLLkY2GByvtC2ME0Q9wrKRDE6qnXJYV3iT) -MOOSE has a [broadcast and training channel on YouTube](https://www.youtube.com/channel/UCjrA9j5LQoWsG4SpS8i79Qg) with various channels that you can watch. +Pene has kindly created a [tutorial series for MOOSE](https://youtube.com/playlist?list=PLLkY2GByvtC2ME0Q9wrKRDE6qnXJYV3iT) + with various videos that you can watch. From ed9c14e63d5a7d65b891cdb27744afdfd97be7e8 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 22 May 2022 12:06:45 +0200 Subject: [PATCH 154/200] GROUP - changes in GetDCSGroup --- Moose Development/Moose/Wrapper/Group.lua | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index a801b5f3f..fd2bfe86c 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -715,21 +715,20 @@ function GROUP:GetDCSUnit( UnitNumber ) if DCSGroup then - local UnitFound = nil - -- 2.7.1 dead event bug, return the first alive unit instead - local units = DCSGroup:getUnits() or {} + if DCSGroup.getUnit and DCSGroup:getUnit( UnitNumber ) then + return DCSGroup:getUnit( UnitNumber ) + else - for _,_unit in pairs(units) do - - local UnitFound = UNIT:Find(_unit) + local UnitFound = nil + -- 2.7.1 dead event bug, return the first alive unit instead + local units = DCSGroup:getUnits() or {} - if UnitFound then - - return UnitFound - + for _,_unit in pairs(units) do + if _unit and _unit:isExist() then + return _unit + end end end - end return nil From 3380ed9360c9d9eaa601f2137e8b04e8b9d08f77 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 7 Jun 2022 08:13:19 +0200 Subject: [PATCH 155/200] CSAR - added options to use Google TTS --- Moose Development/Moose/Ops/CSAR.lua | 102 ++++++++++++++++----------- 1 file changed, 59 insertions(+), 43 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index a213f5632..08bde94fc 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -30,7 +30,7 @@ -- @module Ops.CSAR -- @image OPS_CSAR.jpg --- Date: Feb 2022 +-- Date: June 2022 ------------------------------------------------------------------------- --- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM @@ -76,65 +76,69 @@ -- -- The following options are available (with their defaults). Only set the ones you want changed: -- --- self.allowDownedPilotCAcontrol = false -- Set to false if you don\'t want to allow control by Combined Arms. --- self.allowFARPRescue = true -- allows pilots to be rescued by landing at a FARP or Airbase. Else MASH only! --- self.FARPRescueDistance = 1000 -- you need to be this close to a FARP or Airport for the pilot to be rescued. --- self.autosmoke = false -- automatically smoke a downed pilot\'s location when a heli is near. --- self.autosmokedistance = 1000 -- distance for autosmoke --- self.coordtype = 1 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. --- self.csarOncrash = false -- (WIP) If set to true, will generate a downed pilot when a plane crashes as well. --- self.enableForAI = false -- set to false to disable AI pilots from being rescued. --- self.pilotRuntoExtractPoint = true -- Downed pilot will run to the rescue helicopter up to self.extractDistance in meters. --- self.extractDistance = 500 -- Distance the downed pilot will start to run to the rescue helicopter. --- self.immortalcrew = true -- Set to true to make wounded crew immortal. --- self.invisiblecrew = false -- Set to true to make wounded crew insvisible. --- self.loadDistance = 75 -- configure distance for pilots to get into helicopter in meters. --- self.mashprefix = {"MASH"} -- prefixes of #GROUP objects used as MASHes. --- self.max_units = 6 -- max number of pilots that can be carried if #CSAR.AircraftType is undefined. --- self.messageTime = 15 -- Time to show messages for in seconds. Doubled for long messages. --- self.radioSound = "beacon.ogg" -- the name of the sound file to use for the pilots\' radio beacons. --- self.smokecolor = 4 -- Color of smokemarker, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue. --- self.useprefix = true -- Requires CSAR helicopter #GROUP names to have the prefix(es) defined below. --- self.csarPrefix = { "helicargo", "MEDEVAC"} -- #GROUP name prefixes used for useprefix=true - DO NOT use # in helicopter names in the Mission Editor! --- self.verbose = 0 -- set to > 1 for stats output for debugging. +-- mycsar.allowDownedPilotCAcontrol = false -- Set to false if you don\'t want to allow control by Combined Arms. +-- mycsar.allowFARPRescue = true -- allows pilots to be rescued by landing at a FARP or Airbase. Else MASH only! +-- mycsar.FARPRescueDistance = 1000 -- you need to be this close to a FARP or Airport for the pilot to be rescued. +-- mycsar.autosmoke = false -- automatically smoke a downed pilot\'s location when a heli is near. +-- mycsar.autosmokedistance = 1000 -- distance for autosmoke +-- mycsar.coordtype = 1 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. +-- mycsar.csarOncrash = false -- (WIP) If set to true, will generate a downed pilot when a plane crashes as well. +-- mycsar.enableForAI = false -- set to false to disable AI pilots from being rescued. +-- mycsar.pilotRuntoExtractPoint = true -- Downed pilot will run to the rescue helicopter up to mycsar.extractDistance in meters. +-- mycsar.extractDistance = 500 -- Distance the downed pilot will start to run to the rescue helicopter. +-- mycsar.immortalcrew = true -- Set to true to make wounded crew immortal. +-- mycsar.invisiblecrew = false -- Set to true to make wounded crew insvisible. +-- mycsar.loadDistance = 75 -- configure distance for pilots to get into helicopter in meters. +-- mycsar.mashprefix = {"MASH"} -- prefixes of #GROUP objects used as MASHes. +-- mycsar.max_units = 6 -- max number of pilots that can be carried if #CSAR.AircraftType is undefined. +-- mycsar.messageTime = 15 -- Time to show messages for in seconds. Doubled for long messages. +-- mycsar.radioSound = "beacon.ogg" -- the name of the sound file to use for the pilots\' radio beacons. +-- mycsar.smokecolor = 4 -- Color of smokemarker, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue. +-- mycsar.useprefix = true -- Requires CSAR helicopter #GROUP names to have the prefix(es) defined below. +-- mycsar.csarPrefix = { "helicargo", "MEDEVAC"} -- #GROUP name prefixes used for useprefix=true - DO NOT use # in helicopter names in the Mission Editor! +-- mycsar.verbose = 0 -- set to > 1 for stats output for debugging. -- -- (added 0.1.4) limit amount of downed pilots spawned by **ejection** events --- self.limitmaxdownedpilots = true --- self.maxdownedpilots = 10 +-- mycsar.limitmaxdownedpilots = true +-- mycsar.maxdownedpilots = 10 -- -- (added 0.1.8) - allow to set far/near distance for approach and optionally pilot must open doors --- self.approachdist_far = 5000 -- switch do 10 sec interval approach mode, meters --- self.approachdist_near = 3000 -- switch to 5 sec interval approach mode, meters --- self.pilotmustopendoors = false -- switch to true to enable check of open doors +-- mycsar.approachdist_far = 5000 -- switch do 10 sec interval approach mode, meters +-- mycsar.approachdist_near = 3000 -- switch to 5 sec interval approach mode, meters +-- mycsar.pilotmustopendoors = false -- switch to true to enable check of open doors -- -- (added 0.1.9) --- self.suppressmessages = false -- switch off all messaging if you want to do your own +-- mycsar.suppressmessages = false -- switch off all messaging if you want to do your own -- -- (added 0.1.11) --- self.rescuehoverheight = 20 -- max height for a hovering rescue in meters --- self.rescuehoverdistance = 10 -- max distance for a hovering rescue in meters +-- mycsar.rescuehoverheight = 20 -- max height for a hovering rescue in meters +-- mycsar.rescuehoverdistance = 10 -- max distance for a hovering rescue in meters -- -- (added 0.1.12) -- -- Country codes for spawned pilots --- self.countryblue= country.id.USA --- self.countryred = country.id.RUSSIA --- self.countryneutral = country.id.UN_PEACEKEEPERS +-- mycsar.countryblue= country.id.USA +-- mycsar.countryred = country.id.RUSSIA +-- mycsar.countryneutral = country.id.UN_PEACEKEEPERS -- -- ## 2.1 Experimental Features -- -- WARNING - Here\'ll be dragons! -- DANGER - For this to work you need to de-sanitize your mission environment (all three entries) in \Scripts\MissionScripting.lua -- Needs SRS => 1.9.6 to work (works on the **server** side of SRS) --- self.useSRS = false -- Set true to use FF\'s SRS integration --- self.SRSPath = "E:\\Progra~1\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your SRS installation -- server(!) --- self.SRSchannel = 300 -- radio channel --- self.SRSModulation = radio.modulation.AM -- modulation --- self.SRSport = 5002 -- and SRS port +-- mycsar.useSRS = false -- Set true to use FF\'s SRS integration +-- mycsar.SRSPath = "C:\\Progra~1\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your SRS installation -- server(!) +-- mycsar.SRSchannel = 300 -- radio channel +-- mycsar.SRSModulation = radio.modulation.AM -- modulation +-- mycsar.SRSport = 5002 -- and SRS Server port +-- mycsar.SRSCulture = "en-GB" -- SRS voice culture +-- mycsar.SRSVoice = nil -- SRS voice, relevant for Google TTS +-- mycsar.SRSGPathToCredentials = nil -- Path to your Google credentials json file, set this if you want to use Google TTS +-- mycsar.SRSVolume = 1 -- Volume, between 0 and 1 -- -- --- self.csarUsePara = false -- If set to true, will use the LandingAfterEjection Event instead of Ejection --shagrat --- self.wetfeettemplate = "man in floating thingy" -- if you use a mod to have a pilot in a rescue float, put the template name in here for wet feet spawns. Note: in conjunction with csarUsePara this might create dual ejected pilots in edge cases. +-- mycsar.csarUsePara = false -- If set to true, will use the LandingAfterEjection Event instead of Ejection --shagrat +-- mycsar.wetfeettemplate = "man in floating thingy" -- if you use a mod to have a pilot in a rescue float, put the template name in here for wet feet spawns. Note: in conjunction with csarUsePara this might create dual ejected pilots in edge cases. -- -- ## 3. Results -- -- Number of successful landings with save pilots and aggregated number of saved pilots is stored in these variables in the object: -- --- self.rescues -- number of successful landings *with* saved pilots --- self.rescuedpilots -- aggregated number of pilots rescued from the field (of *all* players) +-- mycsar.rescues -- number of successful landings *with* saved pilots +-- mycsar.rescuedpilots -- aggregated number of pilots rescued from the field (of *all* players) -- -- ## 4. Events -- @@ -260,7 +264,7 @@ CSAR.AircraftType["AH-64D_BLK_II"] = 2 --- CSAR class version. -- @field #string version -CSAR.version="1.0.4e" +CSAR.version="1.0.5" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -415,6 +419,10 @@ function CSAR:New(Coalition, Template, Alias) self.SRSchannel = 300 -- radio channel self.SRSModulation = radio.modulation.AM -- modulation self.SRSport = 5002 -- port + self.SRSCulture = "en-GB" + self.SRSVoice = nil + self.SRSGPathToCredentials = nil + self.SRSVolume = 1 ------------------------ --- Pseudo Functions --- @@ -1526,6 +1534,14 @@ function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear, _speak, _overrid local channel = self.SRSchannel local msrs = MSRS:New(path,channel,modulation) msrs:SetPort(self.SRSport) + msrs:SetLabel("CSAR") + msrs:SetCulture(self.SRSCulture) + msrs:SetCoalition(self.coalition) + msrs:SetVoice(self.SRSVoice) + if self.SRSGPathToCredentials then + msrs:SetGoogle(self.SRSGPathToCredentials) + end + msrs:SetVolume(self.SRSVolume) msrs:PlaySoundText(srstext, 2) end return self From 1dcccdc43409e1c582a9b60a9f108517456ece95 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 7 Jun 2022 08:56:13 +0200 Subject: [PATCH 156/200] CSAR - added a couple of more lines to go out via TTS --- Moose Development/Moose/Ops/CSAR.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 08bde94fc..e1c719363 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -1248,7 +1248,7 @@ function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupNam _maxUnits = self.max_units end if _unitsInHelicopter + 1 > _maxUnits then - self:_DisplayMessageToSAR(_heliUnit, string.format("%s, %s. We\'re already crammed with %d guys! Sorry!", _pilotName, _heliName, _unitsInHelicopter, _unitsInHelicopter), self.messageTime) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s, %s. We\'re already crammed with %d guys! Sorry!", _pilotName, _heliName, _unitsInHelicopter, _unitsInHelicopter), self.messageTime,false,false,true) return true end @@ -1346,7 +1346,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG --if _time <= 0 or _distance < self.loadDistance then if _distance < self.loadDistance + 5 or _distance <= 13 then if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then - self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true) + self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true, true) return true else self.landedStatus[_lookupKeyHeli] = nil @@ -1358,7 +1358,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG else if (_distance < self.loadDistance) then if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then - self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true) + self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true, true) return true else self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) @@ -1400,7 +1400,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG self:_DisplayMessageToSAR(_heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you\'re too far away!", self.messageTime, true) else if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then - self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true) + self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true, true) return true else self.hoverStatus[_lookupKeyHeli] = nil @@ -1459,7 +1459,7 @@ function CSAR:_ScheduledSARFlight(heliname,groupname, isairport) if ( _dist < self.FARPRescueDistance or isairport ) and _heliUnit:InAir() == false then if self.pilotmustopendoors and self:_IsLoadingDoorOpen(heliname) == false then - self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me out!", self.messageTime, true) + self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me out!", self.messageTime, true, true) else self:_RescuePilots(_heliUnit) return From 4011bc3fe66de16ccc70899692e1b808b858c38c Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 8 Jun 2022 20:24:45 +0200 Subject: [PATCH 157/200] AIRBASE added South Atlantic --- Moose Development/Moose/Wrapper/Airbase.lua | 23 +++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 9fcfe52ad..6c2bde5d7 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -486,6 +486,29 @@ AIRBASE.MarianaIslands = { ["Olf_Orote"] = "Olf Orote", } +--- Airbases of the South Atlantic map: +-- +-- * AIRBASE.SouthAtlantic.Port_Stanley +-- * AIRBASE.SouthAtlantic.Mount_Pleasant +-- * AIRBASE.SouthAtlantic.San_Carlos_FOB +-- * AIRBASE.SouthAtlantic.Rio_Grande +-- * AIRBASE.SouthAtlantic.Rio_Gallegos +-- * AIRBASE.SouthAtlantic.Ushuaia +-- * AIRBASE.SouthAtlantic.Ushuaia_Helo_Port +-- * AIRBASE.SouthAtlantic.Punta_Arenas +-- +--@field MarianaIslands +AIRBASE.SouthAtlantic={ + ["Port_Stanley"]="Port Stanley", + ["Mount_Pleasant"]="Mount Pleasant", + ["San_Carlos_FOB"]="San Carlos FOB", + ["Rio_Grande"]="Rio Grande", + ["Rio_Gallegos"]="Rio Gallegos", + ["Ushuaia"]="Ushuaia", + ["Ushuaia_Helo_Port"]="Ushuaia Helo Port", + ["Punta_Arenas"]="Punta Arenas", +} + --- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy". -- @type AIRBASE.ParkingSpot -- @field Core.Point#COORDINATE Coordinate Coordinate of the parking spot. From 932015668b0c54ac193302e07222a3cf692dc84d Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 9 Jun 2022 10:56:20 +0200 Subject: [PATCH 158/200] Added documentation for CTLD_HERCULES --- Moose Development/Moose/Ops/CTLD.lua | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 4a7a5e66c..5176f24bc 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -4792,10 +4792,11 @@ end end -- end do do ---- Hercules Cargo Drop Events by Anubis Yinepu +--- **Hercules Cargo AIR Drop Events** by Anubis Yinepu -- Moose CTLD OO refactoring by Applevangelist -- --- This script will only work for the Herculus mod by Anubis +-- This script will only work for the Herculus mod by Anubis, and only for **Air Dropping** cargo from the Hercules. +-- Use the standard Moose CTLD if you want to unload on the ground. -- Payloads carried by pylons 11, 12 and 13 need to be declared in the Herculus_Loadout.lua file -- Except for Ammo pallets, this script will spawn whatever payload gets launched from pylons 11, 12 and 13 -- Pylons 11, 12 and 13 are moveable within the Herculus cargobay area @@ -4883,7 +4884,7 @@ CTLD_HERCULES.Types = { ["ART GVOZDIKA [34720lb]"] = {['name'] = "SAU Gvozdika", ['container'] = false}, ["APC MTLB Air [26400lb]"] = {['name'] = "MTLB", ['container'] = true}, ["APC MTLB Skid [26290lb]"] = {['name'] = "MTLB", ['container'] = false}, - ["Generic Crate [20000lb]"] = {['name'] = "Hercules_Container_Parachute", ['container'] = true} --nothing generic in Moose CTLD + --["Generic Crate [20000lb]"] = {['name'] = "Hercules_Container_Parachute", ['container'] = true} --nothing generic in Moose CTLD } --- Cargo Object @@ -4922,6 +4923,14 @@ CTLD_HERCULES.Types = { -- ...Checking template for Transport Tigr Air [15900lb] (Tigr_233036) ... OK) -- -- Expected template names are the ones in the rounded brackets. +-- +-- HINTS +-- +-- The script works on the EVENTS.Shot trigger, which is used by the mod when you **drop cargo from the Hercules while flying**. Unloading on the ground does +-- not achieve anything here. If you just want to unload on the ground, use the normal Moose CTLD. +-- There are two ways of airdropping: +-- 1) Very low and very slow (>5m and <10m AGL) - here you can drop stuff which has "Skid" at the end of the cargo name (loaded via F8 Ground Crew menu) +-- 2) Higher up and slow (>100m AGL) - here you can drop paratroopers and cargo which has "Air" at the end of the cargo name (loaded via F8 Ground Crew menu) function CTLD_HERCULES:New(Coalition, Alias, CtldObject) -- Inherit everything from FSM class. local self=BASE:Inherit(self, FSM:New()) -- #CTLD_HERCULES From e45f5e1122b289c52a11860d64e7397e5aa8a2d9 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 9 Jun 2022 11:46:29 +0200 Subject: [PATCH 159/200] CTLD - further documentation --- Moose Development/Moose/Ops/CTLD.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 5176f24bc..32f7ed106 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -4909,7 +4909,8 @@ CTLD_HERCULES.Types = { -- @return #CTLD_HERCULES self -- @usage -- Integrate to your CTLD instance like so, where `my_ctld` is a previously created CTLD instance: --- +-- +-- my_ctld.enableHercules = false -- avoid dual loading via CTLD F10 and F8 ground crew -- local herccargo = CTLD_HERCULES:New("blue", "Hercules Test", my_ctld) -- -- You also need: From f0e0b918af98877cf83ca4d79f998b694928ab9a Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 9 Jun 2022 12:12:29 +0200 Subject: [PATCH 160/200] CTLD - More docu --- Moose Development/Moose/Ops/CTLD.lua | 48 ++++++++++++++++++---------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 32f7ed106..522dd1efe 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -722,7 +722,8 @@ do -- ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18, cargoweightlimit = 700}, -- ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18, cargoweightlimit = 700}, -- ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25, cargoweightlimit = 19000}, --- ["UH-60L"] = {type="UH-60L", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, +-- ["UH-60L"] = {type="UH-60L", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, +-- ["AH-64D_BLK_II"] = {type="AH-64D_BLK_II", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 17, cargoweightlimit = 200}, -- -- ### 2.1.2 Activate and deactivate zones -- @@ -870,12 +871,13 @@ do -- -- ## 5. Support for Hercules mod by Anubis -- --- Basic support for the Hercules mod By Anubis has been build into CTLD - that is you can load/drop/build the same objects as the helicopters. --- To also cover objects and troops which can be loaded from the groud crew Rearm/Refuel menu, you need to use @{#CTLD_HERCULES.New}() and link --- this object to your CTLD setup. In this case, do **not** use the `Hercules_Cargo.lua` or `Hercules_Cargo_CTLD.lua` which are part of the mod +-- Basic support for the Hercules mod By Anubis has been build into CTLD - that is you can load/drop/build the same way and for the same objects as +-- the helicopters (main method). +-- To cover objects and troops which can be loaded from the groud crew Rearm/Refuel menu (F8), you need to use @{#CTLD_HERCULES.New}() and link +-- this object to your CTLD setup (alternative method). In this case, do **not** use the `Hercules_Cargo.lua` or `Hercules_Cargo_CTLD.lua` which are part of the mod -- in your mission! -- --- ### 5.1 Create an own CTLD instance and allow the usage of the Hercules mod: +-- ### 5.1 Create an own CTLD instance and allow the usage of the Hercules mod (main method) -- -- local my_ctld = CTLD:New(coalition.side.BLUE,{"Helicargo", "Hercules"},"Lufttransportbrigade I") -- @@ -893,26 +895,38 @@ do -- -- my_ctld.useprefix = true -- this is true by default and MUST BE ON. -- --- ### 5.2 Integrate Hercules ground crew loadable objects --- --- Add ground crew loadable objects to your CTLD instance like so, where `my_ctld` is the previously created CTLD instance: +-- ### 5.2 Integrate Hercules ground crew (F8 Menu) loadable objects (alternative method) -- +-- Integrate to your CTLD instance like so, where `my_ctld` is a previously created CTLD instance: +-- +-- my_ctld.enableHercules = false -- avoid dual loading via CTLD F10 and F8 ground crew -- local herccargo = CTLD_HERCULES:New("blue", "Hercules Test", my_ctld) -- --- You also need: --- --- * A template called "Infantry" for 10 Paratroopers (as set via herccargo.infantrytemplate). --- * Depending on what you are loading with the help of the ground crew, there are 42 more templates for the various vehicles that are loadable. +-- You also need: +-- +-- * A template called "Infantry" for 10 Paratroopers (as set via herccargo.infantrytemplate). +-- * Depending on what you are loading with the help of the ground crew, there are 42 more templates for the various vehicles that are loadable. -- -- There's a **quick check output in the `dcs.log`** which tells you what's there and what not. --- E.g.: --- ...Checking template for APC BTR-82A Air [24998lb] (BTR-82A) ... MISSING) --- ...Checking template for ART 2S9 NONA Skid [19030lb] (SAU 2-C9) ... MISSING) --- ...Checking template for EWR SBORKA Air [21624lb] (Dog Ear radar) ... MISSING) --- ...Checking template for Transport Tigr Air [15900lb] (Tigr_233036) ... OK) +-- E.g.: +-- +-- ...Checking template for APC BTR-82A Air [24998lb] (BTR-82A) ... MISSING) +-- ...Checking template for ART 2S9 NONA Skid [19030lb] (SAU 2-C9) ... MISSING) +-- ...Checking template for EWR SBORKA Air [21624lb] (Dog Ear radar) ... MISSING) +-- ...Checking template for Transport Tigr Air [15900lb] (Tigr_233036) ... OK) -- -- Expected template names are the ones in the rounded brackets. -- +-- ### 5.2.1 Hints +-- +-- The script works on the EVENTS.Shot trigger, which is used by the mod when you **drop cargo from the Hercules while flying**. Unloading on the ground does +-- not achieve anything here. If you just want to unload on the ground, use the normal Moose CTLD (see 5.1). +-- +-- There are two ways of airdropping: +-- +-- 1) Very low and very slow (>5m and <10m AGL) - here you can drop stuff which has "Skid" at the end of the cargo name (loaded via F8 Ground Crew menu) +-- 2) Higher up and slow (>100m AGL) - here you can drop paratroopers and cargo which has "Air" at the end of the cargo name (loaded via F8 Ground Crew menu) +-- -- Standard transport capabilities as per the real Hercules are: -- -- ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64}, -- 19t cargo, 64 paratroopers From 708c076885f0c881c4daa2f55d41ae02e6f843c2 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 12 Jun 2022 12:47:23 +0200 Subject: [PATCH 161/200] CSAR - Put wounded group back into status green, so they run to the chopper --- Moose Development/Moose/Ops/CSAR.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index e1c719363..e33248b31 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -1336,7 +1336,8 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG local _time = self.landedStatus[_lookupKeyHeli] if _time == nil then self.landedStatus[_lookupKeyHeli] = math.floor( (_distance - self.loadDistance) / 3.6 ) - _time = self.landedStatus[_lookupKeyHeli] + _time = self.landedStatus[_lookupKeyHeli] + _woundedGroup:OptionAlarmStateGreen() self:_OrderGroupToMoveToPoint(_woundedGroup, _heliUnit:GetCoordinate()) self:_DisplayMessageToSAR(_heliUnit, "Wait till " .. _pilotName .. " gets in. \nETA " .. _time .. " more seconds.", self.messageTime, false) else From 2f34b0a5ed0a70633bb96f4b90c6927eb5c8c1b8 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 13 Jun 2022 15:34:01 +0200 Subject: [PATCH 162/200] Update Beacon.lua (#1734) --- Moose Development/Moose/Core/Beacon.lua | 327 ++++++++++++------------ 1 file changed, 169 insertions(+), 158 deletions(-) diff --git a/Moose Development/Moose/Core/Beacon.lua b/Moose Development/Moose/Core/Beacon.lua index c5203a6b3..5c466e583 100644 --- a/Moose Development/Moose/Core/Beacon.lua +++ b/Moose Development/Moose/Core/Beacon.lua @@ -1,9 +1,9 @@ --- **Core** - TACAN and other beacons. --- +-- -- === --- +-- -- ## Features: --- +-- -- * Provide beacon functionality to assist pilots. -- -- === @@ -14,34 +14,34 @@ -- @image Core_Radio.JPG --- *In order for the light to shine so brightly, the darkness must be present.* -- Francis Bacon --- +-- -- After attaching a @{#BEACON} to your @{Wrapper.Positionable#POSITIONABLE}, you need to select the right function to activate the kind of beacon you want. --- There are two types of BEACONs available : the AA TACAN Beacon and the general purpose Radio Beacon. +-- There are two types of BEACONs available : the (aircraft) TACAN Beacon and the general purpose Radio Beacon. -- Note that in both case, you can set an optional parameter : the `BeaconDuration`. This can be very usefull to simulate the battery time if your BEACON is --- attach to a cargo crate, for exemple. --- --- ## AA TACAN Beacon usage --- --- This beacon only works with airborne @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}. Use @{#BEACON:AATACAN}() to set the beacon parameters and start the beacon. --- Use @#BEACON:StopAATACAN}() to stop it. --- +-- attach to a cargo crate, for exemple. +-- +-- ## Aircraft TACAN Beacon usage +-- +-- This beacon only works with airborne @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}. Use @{#BEACON:ActivateTACAN}() to set the beacon parameters and start the beacon. +-- Use @#BEACON:StopRadioBeacon}() to stop it. +-- -- ## General Purpose Radio Beacon usage --- +-- -- This beacon will work with any @{Wrapper.Positionable#POSITIONABLE}, but **it won't follow the @{Wrapper.Positionable#POSITIONABLE}** ! This means that you should only use it with -- @{Wrapper.Positionable#POSITIONABLE} that don't move, or move very slowly. Use @{#BEACON:RadioBeacon}() to set the beacon parameters and start the beacon. -- Use @{#BEACON:StopRadioBeacon}() to stop it. --- +-- -- @type BEACON -- @field #string ClassName Name of the class "BEACON". -- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will receive radio capabilities. -- @extends Core.Base#BASE BEACON = { - ClassName = "BEACON", + ClassName = "BEACON", Positionable = nil, - name = nil, + name = nil, } ---- Beacon types supported by DCS. +--- Beacon types supported by DCS. -- @type BEACON.Type -- @field #number NULL -- @field #number VOR @@ -65,19 +65,19 @@ BEACON = { -- @field #number ICLS_GLIDESLOPE -- @field #number NAUTICAL_HOMER BEACON.Type={ - NULL = 0, + NULL = 0, VOR = 1, DME = 2, - VOR_DME = 3, + VOR_DME = 3, TACAN = 4, - VORTAC = 5, + VORTAC = 5, RSBN = 128, - BROADCAST_STATION = 1024, + BROADCAST_STATION = 1024, HOMER = 8, - AIRPORT_HOMER = 4104, - AIRPORT_HOMER_WITH_MARKER = 4136, + AIRPORT_HOMER = 4104, + AIRPORT_HOMER_WITH_MARKER = 4136, ILS_FAR_HOMER = 16408, - ILS_NEAR_HOMER = 16424, + ILS_NEAR_HOMER = 16424, ILS_LOCALIZER = 16640, ILS_GLIDESLOPE = 16896, PRMG_LOCALIZER = 33024, @@ -95,26 +95,26 @@ BEACON.Type={ -- @field #number TACAN TACtical Air Navigation system on ground. -- @field #number TACAN_TANKER_X TACtical Air Navigation system for tankers on X band. -- @field #number TACAN_TANKER_Y TACtical Air Navigation system for tankers on Y band. --- @field #number VOR Very High Frequency Omnidirectional Range +-- @field #number VOR Very High Frequency Omni-Directional Range -- @field #number ILS_LOCALIZER ILS localizer -- @field #number ILS_GLIDESLOPE ILS glide slope. -- @field #number PRGM_LOCALIZER PRGM localizer. -- @field #number PRGM_GLIDESLOPE PRGM glide slope. -- @field #number BROADCAST_STATION Broadcast station. --- @field #number VORTAC Radio-based navigational aid for aircraft pilots consisting of a co-located VHF omnidirectional range (VOR) beacon and a tactical air navigation system (TACAN) beacon. +-- @field #number VORTAC Radio-based navigational aid for aircraft pilots consisting of a co-located VHF omni-directional range (VOR) beacon and a tactical air navigation system (TACAN) beacon. -- @field #number TACAN_AA_MODE_X TACtical Air Navigation for aircraft on X band. -- @field #number TACAN_AA_MODE_Y TACtical Air Navigation for aircraft on Y band. -- @field #number VORDME Radio beacon that combines a VHF omnidirectional range (VOR) with a distance measuring equipment (DME). -- @field #number ICLS_LOCALIZER Carrier landing system. -- @field #number ICLS_GLIDESLOPE Carrier landing system. BEACON.System={ - PAR_10 = 1, - RSBN_5 = 2, - TACAN = 3, + PAR_10 = 1, + RSBN_5 = 2, + TACAN = 3, TACAN_TANKER_X = 4, TACAN_TANKER_Y = 5, - VOR = 6, - ILS_LOCALIZER = 7, + VOR = 6, + ILS_LOCALIZER = 7, ILS_GLIDESLOPE = 8, PRMG_LOCALIZER = 9, PRMG_GLIDESLOPE = 10, @@ -131,27 +131,28 @@ BEACON.System={ -- If you want to create a BEACON, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetBeacon}() instead. -- @param #BEACON self -- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities. --- @return #BEACON Beacon object or #nil if the POSITIONABLE is invalid. -function BEACON:New( Positionable ) +-- @return #BEACON Beacon object or #nil if the positionable is invalid. +function BEACON:New(Positionable) -- Inherit BASE. - local self = BASE:Inherit( self, BASE:New() ) -- #BEACON - + local self=BASE:Inherit(self, BASE:New()) --#BEACON + -- Debug. - self:F( Positionable ) - + self:F(Positionable) + -- Set positionable. - if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure POSITIONABLE is valid + if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid self.Positionable = Positionable - self.name = Positionable:GetName() - self:I( string.format( "New BEACON %s", tostring( self.name ) ) ) + 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 } ) + + self:E({"The passed positionable is invalid, no BEACON created", Positionable}) return nil end + --- Activates a TACAN BEACON. -- @param #BEACON self -- @param #number Channel TACAN channel, i.e. the "10" part in "10Y". @@ -161,55 +162,60 @@ end -- @param #number Duration How long will the beacon last in seconds. Omit for forever. -- @return #BEACON self -- @usage --- -- -- Let's create a TACAN Beacon for a tanker --- local myUnit = UNIT:FindByName("MyUnit") +-- local myUnit = UNIT:FindByName("MyUnit") -- local myBeacon = myUnit:GetBeacon() -- Creates the beacon --- +-- -- myBeacon:ActivateTACAN(20, "Y", "TEXACO", true) -- Activate the beacon --- -function BEACON:ActivateTACAN( Channel, Mode, Message, Bearing, Duration ) - self:T( { channel = Channel, mode = Mode, callsign = Message, bearing = Bearing, duration = Duration } ) - +function BEACON:ActivateTACAN(Channel, Mode, Message, Bearing, Duration) + self:T({channel=Channel, mode=Mode, callsign=Message, bearing=Bearing, duration=Duration}) + + Mode=Mode or "Y" + -- Get frequency. - local Frequency = UTILS.TACANToFrequency( Channel, Mode ) - + local Frequency=UTILS.TACANToFrequency(Channel, Mode) + -- Check. - if not Frequency then - self:E( { "The passed TACAN channel is invalid, the BEACON is not emitting" } ) + if not Frequency then + self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"}) return self end - + -- Beacon type. - local Type = BEACON.Type.TACAN - + local Type=BEACON.Type.TACAN + -- Beacon system. - local System = BEACON.System.TACAN - + local System=BEACON.System.TACAN + -- Check if unit is an aircraft and set system accordingly. - local AA = self.Positionable:IsAir() + local AA=self.Positionable:IsAir() + + if AA then - System = 5 -- NOTE: 5 is how you cat the correct tanker behaviour! --BEACON.System.TACAN_TANKER + System=5 --NOTE: 5 is how you cat the correct tanker behaviour! --BEACON.System.TACAN_TANKER -- Check if "Y" mode is selected for aircraft. - 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 } ) + if Mode=="X" then + --self:E({"WARNING: The POSITIONABLE you want to attach the AA Tacan Beacon is an aircraft: Mode should Y!", self.Positionable}) + System=BEACON.System.TACAN_TANKER_X + else + System=BEACON.System.TACAN_TANKER_Y end end - + -- Attached unit. - local UnitID = self.Positionable:GetID() - + local UnitID=self.Positionable:GetID() + -- Debug. - 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: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))}) + -- Start beacon. - self.Positionable:CommandActivateBeacon( Type, System, Frequency, UnitID, Channel, Mode, AA, Message, Bearing ) - + self.Positionable:CommandActivateBeacon(Type, System, Frequency, UnitID, Channel, Mode, AA, Message, Bearing) + -- Stop scheduler. if Duration then - self.Positionable:DeactivateBeacon( Duration ) + self.Positionable:DeactivateBeacon(Duration) end - + return self end @@ -219,27 +225,28 @@ end -- @param #string Callsign The Message that is going to be coded in Morse and broadcasted by the beacon. -- @param #number Duration How long will the beacon last in seconds. Omit for forever. -- @return #BEACON self -function BEACON:ActivateICLS( Channel, Callsign, Duration ) - self:F( { Channel = Channel, Callsign = Callsign, Duration = Duration } ) - +function BEACON:ActivateICLS(Channel, Callsign, Duration) + self:F({Channel=Channel, Callsign=Callsign, Duration=Duration}) + -- Attached unit. - local UnitID = self.Positionable:GetID() - + local UnitID=self.Positionable:GetID() + -- Debug - self:T2( { "ICLS BEACON started!" } ) - + self:T2({"ICLS BEACON started!"}) + -- Start beacon. - self.Positionable:CommandActivateICLS( Channel, UnitID, Callsign ) - + self.Positionable:CommandActivateICLS(Channel, UnitID, Callsign) + -- Stop scheduler if Duration then -- Schedule the stop of the BEACON if asked by the MD - self.Positionable:DeactivateBeacon( Duration ) + self.Positionable:DeactivateBeacon(Duration) end - + return self end ---- Activates a TACAN BEACON on an Aircraft. +--- DEPRECATED: Please use @{BEACON:ActivateTACAN}() instead. +-- Activates a TACAN BEACON on an Aircraft. -- @param #BEACON self -- @param #number TACANChannel (the "10" part in "10Y"). Note that AA TACAN are only available on Y Channels -- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon @@ -247,57 +254,58 @@ end -- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever. -- @return #BEACON self -- @usage --- -- -- Let's create a TACAN Beacon for a tanker --- local myUnit = UNIT:FindByName("MyUnit") +-- local myUnit = UNIT:FindByName("MyUnit") -- local myBeacon = myUnit:GetBeacon() -- Creates the beacon --- +-- -- myBeacon:AATACAN(20, "TEXACO", true) -- Activate the beacon --- -function BEACON:AATACAN( TACANChannel, Message, Bearing, BeaconDuration ) - self:F( { TACANChannel, Message, Bearing, BeaconDuration } ) - +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 } ) + 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" } ) + + 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 - - -- I'm using the beacon type 4 (BEACON_TYPE_TACAN). For System, I'm using 5 (TACAN_TANKER_MODE_Y) if the bearing shows its bearing - -- or 14 (TACAN_AA_MODE_Y) if it does not + + -- I'm using the beacon type 4 (BEACON_TYPE_TACAN). For System, I'm using 5 (TACAN_TANKER_MODE_Y) if the bearing shows its bearing or 14 (TACAN_AA_MODE_Y) if it does not local System if Bearing then - System = 5 + System = BEACON.System.TACAN_TANKER_Y else - System = 14 + System = BEACON.System.TACAN_AA_MODE_Y end - + if IsValid then -- Starts the BEACON - self:T2( { "AA TACAN BEACON started !" } ) - self.Positionable:SetCommand( { + self:T2({"AA TACAN BEACON started !"}) + self.Positionable:SetCommand({ id = "ActivateBeacon", params = { - type = 4, + type = BEACON.Type.TACAN, system = System, callsign = Message, + AA = true, frequency = Frequency, - }, - } ) - + bearing = Bearing, + modeChannel = "Y", + } + }) + if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD - SCHEDULER:New( nil, function() + SCHEDULER:New(nil, + function() self:StopAATACAN() - end, {}, BeaconDuration ) + end, {}, BeaconDuration) end end - + return self end @@ -307,19 +315,21 @@ end function BEACON:StopAATACAN() self:F() if not self.Positionable then - self:E( { "Start the beacon first before stopping it!" } ) + self:E({"Start the beacon first before stoping it !"}) else - self.Positionable:SetCommand( { - id = 'DeactivateBeacon', - params = {}, - } ) + self.Positionable:SetCommand({ + id = 'DeactivateBeacon', + params = { + } + }) end end + --- Activates a general purpose Radio Beacon -- This uses the very generic singleton function "trigger.action.radioTransmission()" provided by DCS to broadcast a sound file on a specific frequency. --- Although any frequency could be used, only 2 DCS Modules can home on radio beacons at the time of writing : the Huey and the Mi-8. --- They can home in on these specific frequencies : +-- Although any frequency could be used, only a few DCS Modules can home on radio beacons at the time of writing, i.e. the Mi-8, Huey, Gazelle etc. +-- The following e.g. can home in on these specific frequencies : -- * **Mi8** -- * R-828 -> 20-60MHz -- * ARKUD -> 100-150MHz (canal 1 : 114166, canal 2 : 114333, canal 3 : 114583, canal 4 : 121500, canal 5 : 123100, canal 6 : 124100) AM @@ -342,63 +352,64 @@ end -- -- -- Set the beacon and start it -- UnitBeacon:RadioBeacon("MySoundFileSOS.ogg", 40, radio.modulation.FM, 20, 5*60) -function BEACON:RadioBeacon( FileName, Frequency, Modulation, Power, BeaconDuration ) - self:F( { FileName, Frequency, Modulation, Power, BeaconDuration } ) +function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDuration) + self:F({FileName, Frequency, Modulation, Power, BeaconDuration}) local IsValid = false - + -- Check the filename - if type( FileName ) == "string" then - if FileName:find( ".ogg" ) or FileName:find( ".wav" ) then - if not FileName:find( "l10n/DEFAULT/" ) then + 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 } ) + self:E({"File name invalid. Maybe something wrong with the extension ? ", FileName}) end - + -- Check the Frequency - if type( Frequency ) ~= "number" and IsValid then - self:E( { "Frequency invalid. ", Frequency } ) + if type(Frequency) ~= "number" and IsValid then + self:E({"Frequency invalid. ", Frequency}) IsValid = false end Frequency = Frequency * 1000000 -- Conversion to Hz - + -- Check the modulation - if Modulation ~= radio.modulation.AM and Modulation ~= radio.modulation.FM and IsValid then -- TODO: Maybe make this future proof if ED decides to add an other modulation ? - self:E( { "Modulation is invalid. Use DCS's enum radio.modulation.", Modulation } ) + if Modulation ~= radio.modulation.AM and Modulation ~= radio.modulation.FM and IsValid then --TODO Maybe make this future proof if ED decides to add an other modulation ? + self:E({"Modulation is invalid. Use DCS's enum radio.modulation.", Modulation}) IsValid = false end - + -- Check the Power - if type( Power ) ~= "number" and IsValid then - self:E( { "Power is invalid. ", Power } ) + if type(Power) ~= "number" and IsValid then + self:E({"Power is invalid. ", Power}) IsValid = false end - Power = math.floor( math.abs( Power ) ) -- TODO: Find what is the maximum power allowed by DCS and limit power to that - + Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that + if IsValid then - self:T2( { "Activating Beacon on ", Frequency, Modulation } ) + self:T2({"Activating Beacon on ", Frequency, Modulation}) -- Note that this is looped. I have to give this transmission a unique name, I use the class ID - trigger.action.radioTransmission( FileName, self.Positionable:GetPositionVec3(), Modulation, true, Frequency, Power, tostring( self.ID ) ) - - if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD - SCHEDULER:New( nil, function() - self:StopRadioBeacon() - end, {}, BeaconDuration ) - end - end + trigger.action.radioTransmission(FileName, self.Positionable:GetPositionVec3(), Modulation, true, Frequency, Power, tostring(self.ID)) + + if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD + SCHEDULER:New( nil, + function() + self:StopRadioBeacon() + end, {}, BeaconDuration) + end + end end ---- Stops the AA TACAN BEACON +--- Stop the Radio Beacon -- @param #BEACON self -- @return #BEACON self function BEACON:StopRadioBeacon() self:F() -- The unique name of the transmission is the class ID - trigger.action.stopRadioTransmission( tostring( self.ID ) ) + trigger.action.stopRadioTransmission(tostring(self.ID)) return self end @@ -406,26 +417,26 @@ end -- @param #BEACON self -- @param #number TACANChannel -- @param #string TACANMode --- @return #number Frequency +-- @return #number Frequecy -- @return #nil if parameters are invalid -function BEACON:_TACANToFrequency( TACANChannel, TACANMode ) - self:F3( { TACANChannel, TACANMode } ) +function BEACON:_TACANToFrequency(TACANChannel, TACANMode) + self:F3({TACANChannel, TACANMode}) - if type( TACANChannel ) ~= "number" then + if type(TACANChannel) ~= "number" then if TACANMode ~= "X" and TACANMode ~= "Y" then return nil -- error in arguments end end - - -- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137. - -- I have no idea what it does but it seems to work + +-- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137. +-- I have no idea what it does but it seems to work local A = 1151 -- 'X', channel >= 64 - local B = 64 -- channel >= 64 - + local B = 64 -- channel >= 64 + if TACANChannel < 64 then B = 1 end - + if TACANMode == 'Y' then A = 1025 if TACANChannel < 64 then @@ -436,6 +447,6 @@ function BEACON:_TACANToFrequency( TACANChannel, TACANMode ) A = 962 end end - + return (A + TACANChannel - B) * 1000000 end From 514e568e04265a13cb8bb338df57b4c434b88c40 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 13 Jun 2022 15:39:39 +0200 Subject: [PATCH 163/200] Update Beacon.lua --- Moose Development/Moose/Core/Beacon.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Beacon.lua b/Moose Development/Moose/Core/Beacon.lua index 5c466e583..7a819f7cf 100644 --- a/Moose Development/Moose/Core/Beacon.lua +++ b/Moose Development/Moose/Core/Beacon.lua @@ -23,7 +23,7 @@ -- ## Aircraft TACAN Beacon usage -- -- This beacon only works with airborne @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}. Use @{#BEACON:ActivateTACAN}() to set the beacon parameters and start the beacon. --- Use @#BEACON:StopRadioBeacon}() to stop it. +-- Use @{#BEACON:StopRadioBeacon}() to stop it. -- -- ## General Purpose Radio Beacon usage -- From 40c6cc59d3336553a5f95e400f2fef05c1a6be39 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 13 Jun 2022 15:43:10 +0200 Subject: [PATCH 164/200] Update Beacon.lua --- Moose Development/Moose/Core/Beacon.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Core/Beacon.lua b/Moose Development/Moose/Core/Beacon.lua index 7a819f7cf..98e2032af 100644 --- a/Moose Development/Moose/Core/Beacon.lua +++ b/Moose Development/Moose/Core/Beacon.lua @@ -17,19 +17,19 @@ -- -- After attaching a @{#BEACON} to your @{Wrapper.Positionable#POSITIONABLE}, you need to select the right function to activate the kind of beacon you want. -- There are two types of BEACONs available : the (aircraft) TACAN Beacon and the general purpose Radio Beacon. --- Note that in both case, you can set an optional parameter : the `BeaconDuration`. This can be very usefull to simulate the battery time if your BEACON is +-- Note that in both case, you can set an optional parameter : the `BeaconDuration`. This can be very useful to simulate the battery time if your BEACON is -- attach to a cargo crate, for exemple. -- -- ## Aircraft TACAN Beacon usage -- --- This beacon only works with airborne @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}. Use @{#BEACON:ActivateTACAN}() to set the beacon parameters and start the beacon. --- Use @{#BEACON:StopRadioBeacon}() to stop it. +-- This beacon only works with airborne @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}. Use @{#BEACON.ActivateTACAN}() to set the beacon parameters and start the beacon. +-- Use @{#BEACON.StopRadioBeacon}() to stop it. -- -- ## General Purpose Radio Beacon usage -- -- This beacon will work with any @{Wrapper.Positionable#POSITIONABLE}, but **it won't follow the @{Wrapper.Positionable#POSITIONABLE}** ! This means that you should only use it with --- @{Wrapper.Positionable#POSITIONABLE} that don't move, or move very slowly. Use @{#BEACON:RadioBeacon}() to set the beacon parameters and start the beacon. --- Use @{#BEACON:StopRadioBeacon}() to stop it. +-- @{Wrapper.Positionable#POSITIONABLE} that don't move, or move very slowly. Use @{#BEACON.RadioBeacon}() to set the beacon parameters and start the beacon. +-- Use @{#BEACON.StopRadioBeacon}() to stop it. -- -- @type BEACON -- @field #string ClassName Name of the class "BEACON". From 6025339b464739d20e4e732629cac6705cf457c1 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 14 Jun 2022 12:39:17 +0200 Subject: [PATCH 165/200] CSAR - some fixes for latest open beta --- Moose Development/Moose/Ops/CSAR.lua | 122 +++++++++++++++++---------- 1 file changed, 77 insertions(+), 45 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index e33248b31..10f36e91a 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -130,7 +130,7 @@ -- mycsar.SRSGPathToCredentials = nil -- Path to your Google credentials json file, set this if you want to use Google TTS -- mycsar.SRSVolume = 1 -- Volume, between 0 and 1 -- -- --- mycsar.csarUsePara = false -- If set to true, will use the LandingAfterEjection Event instead of Ejection --shagrat +-- mycsar.csarUsePara = false -- If set to true, will use the LandingAfterEjection Event instead of Ejection. Requires mycsar.enableForAI to be set to true. --shagrat -- mycsar.wetfeettemplate = "man in floating thingy" -- if you use a mod to have a pilot in a rescue float, put the template name in here for wet feet spawns. Note: in conjunction with csarUsePara this might create dual ejected pilots in edge cases. -- -- ## 3. Results @@ -869,12 +869,12 @@ function CSAR:_EventHandler(EventData) -- no Player if self.enableForAI == false and _event.IniPlayerName == nil then - return + return self end -- no event if _event == nil or _event.initiator == nil then - return false + return self -- take off elseif _event.id == EVENTS.Takeoff then -- taken off @@ -882,35 +882,43 @@ function CSAR:_EventHandler(EventData) local _coalition = _event.IniCoalition if _coalition ~= self.coalition then - return --ignore! + return self --ignore! end if _event.IniGroupName then self.takenOff[_event.IniUnitName] = true end - return true + return self -- player enter unit elseif _event.id == EVENTS.PlayerEnterAircraft or _event.id == EVENTS.PlayerEnterUnit then --player entered unit self:T(self.lid .. " Event unit - Player Enter") local _coalition = _event.IniCoalition + self:T("Coalition = "..UTILS.GetCoalitionName(_coalition)) if _coalition ~= self.coalition then - return --ignore! + return self --ignore! end if _event.IniPlayerName then self.takenOff[_event.IniPlayerName] = nil end + -- jumped into flying plane? + self:T("Taken Off: "..tostring(_event.IniUnit:InAir(true))) + + if _event.IniUnit:InAir(true) then + self.takenOff[_event.IniPlayerName] = true + end + local _unit = _event.IniUnit local _group = _event.IniGroup if _unit:IsHelicopter() or _group:IsHelicopter() then self:_AddMedevacMenuItem() end - return true + return self elseif (_event.id == EVENTS.PilotDead and self.csarOncrash == false) then -- Pilot dead @@ -922,57 +930,68 @@ function CSAR:_EventHandler(EventData) local _group = _event.IniGroup if _unit == nil then - return -- error! + return self -- error! end local _coalition = _event.IniCoalition if _coalition ~= self.coalition then - return --ignore! + return self --ignore! end -- Catch multiple events here? if self.takenOff[_event.IniUnitName] == true or _group:IsAirborne() then if self:_DoubleEjection(_unitname) then - return + return self end else self:T(self.lid .. " Pilot has not taken off, ignore") end - return + return self elseif _event.id == EVENTS.PilotDead or _event.id == EVENTS.Ejection then if _event.id == EVENTS.PilotDead and self.csarOncrash == false then - return + return self end self:T(self.lid .. " Event unit - Pilot Ejected") local _unit = _event.IniUnit local _unitname = _event.IniUnitName local _group = _event.IniGroup + + self:T({_unit.UnitName, _unitname, _group.GroupName}) if _unit == nil then - return -- error! + self:T("Unit NIL!") + return self -- error! end - local _coalition = _unit:GetCoalition() + --local _coalition = _unit:GetCoalition() -- nil now for some reason + local _coalition = _group:GetCoalition() if _coalition ~= self.coalition then - return --ignore! + self:T("Wrong coalition! Coalition = "..UTILS.GetCoalitionName(_coalition)) + return self --ignore! end - + + + self:T("Airborne: "..tostring(_group:IsAirborne())) + self:T("Taken Off: "..tostring(self.takenOff[_event.IniUnitName])) + if not self.takenOff[_event.IniUnitName] and not _group:IsAirborne() then self:T(self.lid .. " Pilot has not taken off, ignore") - return -- give up, pilot hasnt taken off + -- return self -- give up, pilot hasnt taken off end if self:_DoubleEjection(_unitname) then - return + self:T("Double Ejection!") + return self end -- limit no of pilots in the field. if self.limitmaxdownedpilots and self:_ReachedPilotLimit() then - return + self:T("Maxed Downed Pilot!") + return self end @@ -981,33 +1000,27 @@ function CSAR:_EventHandler(EventData) local wetfeet = false - local surface = _unit:GetCoordinate():GetSurfaceType() + local initdcscoord = nil + local initcoord = nil + --if _event.id == EVENTS.Ejection then + initdcscoord = _event.TgtDCSUnit:getPoint() + initcoord = COORDINATE:NewFromVec3(initdcscoord) + self:T({initdcscoord}) + --end + + --local surface = _unit:GetCoordinate():GetSurfaceType() + local surface = initcoord:GetSurfaceType() + if surface == land.SurfaceType.WATER then + self:T("Wet feet!") wetfeet = true end -- all checks passed, get going. if self.csarUsePara == false or (self.csarUsePara and wetfeet ) then --shagrat check parameter LandingAfterEjection, if true don't spawn a Pilot from EJECTION event, wait for the Chute to land - local _freq = self:_GenerateADFFrequency() - self:_AddCsar(_coalition, _unit:GetCountry(), _unit:GetCoordinate() , _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, false, "none") - return true - end - - ---- shagrat on event LANDING_AFTER_EJECTION spawn pilot at parachute location - elseif (_event.id == EVENTS.LandingAfterEjection and self.csarUsePara == true) then - self:I({EVENT=_event}) - local _LandingPos = COORDINATE:NewFromVec3(_event.initiator:getPosition().p) - local _unitname = "Aircraft" --_event.initiator:getName() or "Aircraft" --shagrat Optional use of Object name which is unfortunately 'f15_Pilot_Parachute' - local _typename = "Ejected Pilot" --_event.Initiator.getTypeName() or "Ejected Pilot" - local _country = _event.initiator:getCountry() - local _coalition = coalition.getCountryCoalition( _country ) - if _coalition == self.coalition then local _freq = self:_GenerateADFFrequency() - self:I({coalition=_coalition,country= _country, coord=_LandingPos, name=_unitname, player=_event.IniPlayerName, freq=_freq}) - self:_AddCsar(_coalition, _country, _LandingPos, nil, _unitname, _event.IniPlayerName, _freq, false, "none")--shagrat add CSAR at Parachute location. - - Unit.destroy(_event.initiator) -- shagrat remove static Pilot model - end - return true + self:_AddCsar(_coalition, _unit:GetCountry(), initcoord , _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, false, "none") + return self + end elseif _event.id == EVENTS.Land then self:T(self.lid .. " Landing") @@ -1022,12 +1035,12 @@ function CSAR:_EventHandler(EventData) if _unit == nil then self:T(self.lid .. " Unit nil on landing") - return -- error! + return self -- error! end local _coalition = _event.IniCoalition if _coalition ~= self.coalition then - return --ignore! + return self --ignore! end self.takenOff[_event.IniUnitName] = nil @@ -1036,13 +1049,13 @@ function CSAR:_EventHandler(EventData) if _place == nil then self:T(self.lid .. " Landing Place Nil") - return -- error! + return self -- error! end -- anyone on board? if self.inTransitGroups[_event.IniUnitName] == nil then -- ignore - return + return self end if _place:GetCoalition() == self.coalition or _place:GetCoalition() == coalition.side.NEUTRAL then @@ -1052,8 +1065,27 @@ function CSAR:_EventHandler(EventData) end end - return true + return self end + + ---- shagrat on event LANDING_AFTER_EJECTION spawn pilot at parachute location + if (_event.id == EVENTS.LandingAfterEjection and self.csarUsePara == true) then + self:T("LANDING_AFTER_EJECTION") + local _LandingPos = COORDINATE:NewFromVec3(_event.initiator:getPosition().p) + local _unitname = "Aircraft" --_event.initiator:getName() or "Aircraft" --shagrat Optional use of Object name which is unfortunately 'f15_Pilot_Parachute' + local _typename = "Ejected Pilot" --_event.Initiator.getTypeName() or "Ejected Pilot" + local _country = _event.initiator:getCountry() + local _coalition = coalition.getCountryCoalition( _country ) + self:T("Country = ".._country.." Coalition = ".._coalition) + if _coalition == self.coalition then + local _freq = self:_GenerateADFFrequency() + self:I({coalition=_coalition,country= _country, coord=_LandingPos, name=_unitname, player=_event.IniPlayerName, freq=_freq}) + self:_AddCsar(_coalition, _country, _LandingPos, nil, _unitname, _event.IniPlayerName, _freq, false, "none")--shagrat add CSAR at Parachute location. + + Unit.destroy(_event.initiator) -- shagrat remove static Pilot model + end + end + return self end From afec1c3a5bc66e3c9cf487e24d2de5ab764f8517 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 14 Jun 2022 13:06:55 +0200 Subject: [PATCH 166/200] COORDINATE - additions to BRAANATO --- Moose Development/Moose/Core/Point.lua | 38 +++++++++++++++++++++----- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index b9bc35664..3981f0625 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -2770,8 +2770,10 @@ do -- COORDINATE -- @param #boolean Bogey Add "Bogey" at the end if true (not yet declared hostile or friendly) -- @param #boolean Spades Add "Spades" at the end if true (no IFF/VID ID yet known) -- @param #boolean SSML Add SSML tags speaking aspect as 0 1 2 and "brah" instead of BRAA + -- @param #boolean Angels If true, altitude is e.g. "Angels 25" (i.e., a friendly plane), else "25 thousand" + -- @param #boolean Zeros If using SSML, be aware that Google TTS will say "oh" and not "zero" for "0"; if Zeros is set to true, "0" will be replaced with "zero" -- @return #string The BRAA text. - function COORDINATE:ToStringBRAANATO(FromCoordinate,Bogey,Spades,SSML) + function COORDINATE:ToStringBRAANATO(FromCoordinate,Bogey,Spades,SSML,Angels,Zeros) -- Thanks to @Pikey local BRAANATO = "Merged." @@ -2789,14 +2791,36 @@ do -- COORDINATE local alt = UTILS.Round(UTILS.MetersToFeet(self.y)/1000,0)--*1000 + local alttext = string.format("%d thousand",alt) + + if Angels then + alttext = string.format("Angels %d",alt) + end + + if alt < 1 then + alttext = "very low" + end + local track = UTILS.BearingToCardinal(bearing) or "North" if rangeNM > 3 then - if SSML then - if aspect == "" then - BRAANATO = string.format("brah %03d, %d miles, Angels %d, Track %s",bearing, rangeNM, alt, track) + if SSML then -- google says "oh" instead of zero, be aware + if Zeros then + bearing = string.format("%03d",bearing) + local AngleDegText = string.gsub(bearing,"%d","%1 ") -- "0 5 1 " + AngleDegText = string.gsub(AngleDegText," $","") -- "0 5 1" + AngleDegText = string.gsub(AngleDegText,"0","zero") + if aspect == "" then + BRAANATO = string.format("brah %s, %d miles, %s, Track %s", AngleDegText, rangeNM, alttext, track) + else + BRAANATO = string.format("brah %s, %d miles, %s, %s, Track %s", AngleDegText, rangeNM, alttext, aspect, track) + end else - BRAANATO = string.format("brah %03d, %d miles, Angels %d, %s, Track %s",bearing, rangeNM, alt, aspect, track) + if aspect == "" then + BRAANATO = string.format("brah %03d, %d miles, %s, Track %s", bearing, rangeNM, alttext, track) + else + BRAANATO = string.format("brah %03d, %d miles, %s, %s, Track %s", bearing, rangeNM, alttext, aspect, track) + end end if Bogey and Spades then BRAANATO = BRAANATO..", Bogey, Spades." @@ -2809,9 +2833,9 @@ do -- COORDINATE end else if aspect == "" then - BRAANATO = string.format("BRA %03d, %d miles, Angels %d, Track %s",bearing, rangeNM, alt, track) + BRAANATO = string.format("BRA %03d, %d miles, %s, Track %s",bearing, rangeNM, alttext, track) else - BRAANATO = string.format("BRAA %03d, %d miles, Angels %d, %s, Track %s",bearing, rangeNM, alt, aspect, track) + BRAANATO = string.format("BRAA %03d, %d miles, %s, %s, Track %s",bearing, rangeNM, alttext, aspect, track) end if Bogey and Spades then BRAANATO = BRAANATO..", Bogey, Spades." From 196bcf39cf76ade9edf081408fbca404efb36252 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 14 Jun 2022 16:56:33 +0200 Subject: [PATCH 167/200] Added Falklands map stuff --- Moose Development/Moose/Utilities/Utils.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 5e7a411e5..c6932047b 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -52,6 +52,7 @@ BIGSMOKEPRESET = { -- @field #string TheChannel The Channel map. -- @field #string Syria Syria map. -- @field #string MarianaIslands Mariana Islands map. +-- @field #string Falklands South Atlantic map. DCSMAP = { Caucasus="Caucasus", NTTR="Nevada", @@ -59,7 +60,8 @@ DCSMAP = { PersianGulf="PersianGulf", TheChannel="TheChannel", Syria="Syria", - MarianaIslands="MarianaIslands" + MarianaIslands="MarianaIslands", + Falklands="Falklands", } @@ -1347,6 +1349,7 @@ end -- * The Cannel Map -10 (West) -- * Syria +5 (East) -- * Mariana Islands +2 (East) +-- * Falklands +12 (East) - note there's a LOT of deviation across the map, as we're closer to the South Pole -- @param #string map (Optional) Map for which the declination is returned. Default is from env.mission.theatre -- @return #number Declination in degrees. function UTILS.GetMagneticDeclination(map) @@ -1369,6 +1372,8 @@ function UTILS.GetMagneticDeclination(map) declination=5 elseif map==DCSMAP.MarianaIslands then declination=2 + elseif map==DCSMAP.Falklands then + declination=12 else declination=0 end From d5636f4a1902f271ffbc1fb3fec6120b747399a5 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 16 Jun 2022 13:41:44 +0200 Subject: [PATCH 168/200] CSAR - added event "Landed" (at a friendly/neutral AFB), fix for AFB rescue --- Moose Development/Moose/Ops/CSAR.lua | 60 ++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 10f36e91a..cfc9d1342 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -264,7 +264,7 @@ CSAR.AircraftType["AH-64D_BLK_II"] = 2 --- CSAR class version. -- @field #string version -CSAR.version="1.0.5" +CSAR.version="1.0.6" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -333,6 +333,7 @@ function CSAR:New(Coalition, Template, Alias) self:AddTransition("*", "Status", "*") -- CSAR status update. self:AddTransition("*", "PilotDown", "*") -- Downed Pilot added self:AddTransition("*", "Approach", "*") -- CSAR heli closing in. + self:AddTransition("*", "Landed", "*") -- CSAR heli landed self:AddTransition("*", "Boarded", "*") -- Pilot boarded. self:AddTransition("*", "Returning", "*") -- CSAR able to return to base. self:AddTransition("*", "Rescued", "*") -- Pilot at MASH. @@ -474,7 +475,16 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string Heliname Name of the helicopter group. -- @param #string Woundedgroupname Name of the downed pilot\'s group. - --- On After "Boarded" event. Downed pilot boarded heli. + --- On After "Landed" event. Heli landed at an airbase. + -- @function [parent=#CSAR] OnAfterLanded + -- @param #CSAR self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string HeliName Name of the #UNIT which has landed. + -- @param Wrapper.Airbase#AIRBASE Airbase Airbase where the heli landed. + + --- On After "Boarded" event. Downed pilot boarded heli. -- @function [parent=#CSAR] OnAfterBoarded -- @param #CSAR self -- @param #string From From state. @@ -1038,15 +1048,17 @@ function CSAR:_EventHandler(EventData) return self -- error! end - local _coalition = _event.IniCoalition + --local _coalition = _event.IniCoalition + local _coalition = _event.IniGroup:GetCoalition() if _coalition ~= self.coalition then + self:T(self.lid .. " Wrong coalition") return self --ignore! end self.takenOff[_event.IniUnitName] = nil local _place = _event.Place -- Wrapper.Airbase#AIRBASE - + if _place == nil then self:T(self.lid .. " Landing Place Nil") return self -- error! @@ -1059,6 +1071,7 @@ function CSAR:_EventHandler(EventData) end if _place:GetCoalition() == self.coalition or _place:GetCoalition() == coalition.side.NEUTRAL then + self:__Landed(2,_event.IniUnitName, _place) self:_ScheduledSARFlight(_event.IniUnitName,_event.IniGroupName,true) else self:T(string.format("Airfield %d, Unit %d", _place:GetCoalition(), _unit:GetCoalition())) @@ -1185,7 +1198,7 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) self.heliVisibleMessage[_lookupKeyHeli] = nil self.heliCloseMessage[_lookupKeyHeli] = nil self.landedStatus[_lookupKeyHeli] = nil - self:T("...helinunit nil!") + self:T("...heliunit nil!") return end @@ -1281,7 +1294,7 @@ function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupNam end if _unitsInHelicopter + 1 > _maxUnits then self:_DisplayMessageToSAR(_heliUnit, string.format("%s, %s. We\'re already crammed with %d guys! Sorry!", _pilotName, _heliName, _unitsInHelicopter, _unitsInHelicopter), self.messageTime,false,false,true) - return true + return self end local found,downedgrouptable = self:_CheckNameInDownedPilots(_woundedGroupName) @@ -1302,7 +1315,7 @@ function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupNam self:__Boarded(5,_heliName,_woundedGroupName) - return true + return self end --- (Internal) Move group to destination. @@ -1378,24 +1391,24 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG end --if _time <= 0 or _distance < self.loadDistance then if _distance < self.loadDistance + 5 or _distance <= 13 then - if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then + if self.pilotmustopendoors and (self:_IsLoadingDoorOpen(_heliName) == false) then self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true, true) - return true + return false else self.landedStatus[_lookupKeyHeli] = nil self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) - return false + return true end end end else if (_distance < self.loadDistance) then - if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then + if self.pilotmustopendoors and (self:_IsLoadingDoorOpen(_heliName) == false) then self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true, true) - return true + return false else self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) - return false + return true end end end @@ -1432,18 +1445,19 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG if _time > 0 then self:_DisplayMessageToSAR(_heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you\'re too far away!", self.messageTime, true) else - if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then + if self.pilotmustopendoors and (self:_IsLoadingDoorOpen(_heliName) == false) then self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true, true) - return true + return false else self.hoverStatus[_lookupKeyHeli] = nil self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) - return false + return true end end _reset = false else self:_DisplayMessageToSAR(_heliUnit, "Too high to winch " .. _pilotName .. " \nReduce height and hover for 10 seconds!", self.messageTime, true,true) + return false end end @@ -2075,7 +2089,7 @@ end -- @param #string To To state. function CSAR:onafterStart(From, Event, To) self:T({From, Event, To}) - self:I(self.lid .. "Started.") + self:I(self.lid .. "Started ("..self.version..")") -- event handler self:HandleEvent(EVENTS.Takeoff, self._EventHandler) self:HandleEvent(EVENTS.Land, self._EventHandler) @@ -2275,6 +2289,18 @@ function CSAR:onbeforePilotDown(From, Event, To, Group, Frequency, Leadername, C self:T({From, Event, To, Group, Frequency, Leadername, CoordinatesText}) return self end + +--- (Internal) Function called before Landed() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +-- @param #string HeliName Name of the #UNIT which has landed. +-- @param Wrapper.Airbase#AIRBASE Airbase Airbase where the heli landed. +function CSAR:onbeforeLanded(From, Event, To, HeliName, Airbase) + self:T({From, Event, To, HeliName, Airbase}) + return self +end -------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- End Ops.CSAR -------------------------------------------------------------------------------------------------------------------------------------------------------------------- From b83f4782943fbf1bb6516362d21e32fac5e5f2b9 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 16 Jun 2022 15:42:02 +0200 Subject: [PATCH 169/200] CSAR - fix for oncrash --- Moose Development/Moose/Ops/CSAR.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index cfc9d1342..76281a3f1 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -1012,11 +1012,15 @@ function CSAR:_EventHandler(EventData) local initdcscoord = nil local initcoord = nil - --if _event.id == EVENTS.Ejection then + if _event.id == EVENTS.Ejection then initdcscoord = _event.TgtDCSUnit:getPoint() initcoord = COORDINATE:NewFromVec3(initdcscoord) self:T({initdcscoord}) - --end + else + initdcscoord = _event.IniDCSUnit:getPoint() + initcoord = COORDINATE:NewFromVec3(initdcscoord) + self:T({initdcscoord}) + end --local surface = _unit:GetCoordinate():GetSurfaceType() local surface = initcoord:GetSurfaceType() From d59fc331f6cd0c7c0768d15f0bc7e384343f2071 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 25 Jun 2022 14:27:51 +0200 Subject: [PATCH 170/200] UTILS - Fix for Gazelle Door Check --- Moose Development/Moose/Utilities/Utils.lua | 53 ++++++++++----------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index c6932047b..31f868fbc 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1782,71 +1782,68 @@ end --@return #boolean Outcome - true if a (loading door) is open, false if not, nil if none exists. function UTILS.IsLoadingDoorOpen( unit_name ) - local ret_val = false local unit = Unit.getByName(unit_name) + if unit ~= nil then local type_name = unit:getTypeName() + BASE:T("TypeName = ".. type_name) - if type_name == "Mi-8MT" and unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(86) == 1 or unit:getDrawArgumentValue(250) < 0 then + if type_name == "Mi-8MT" and (unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(86) == 1 or unit:getDrawArgumentValue(250) < 0) then BASE:T(unit_name .. " Cargo doors are open or cargo door not present") - ret_val = true + return true end - if type_name == "Mi-24P" and unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(86) == 1 then + if type_name == "Mi-24P" and (unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(86) == 1) then BASE:T(unit_name .. " a side door is open") - ret_val = true + return true end - if type_name == "UH-1H" and unit:getDrawArgumentValue(43) == 1 or unit:getDrawArgumentValue(44) == 1 then + if type_name == "UH-1H" and (unit:getDrawArgumentValue(43) == 1 or unit:getDrawArgumentValue(44) == 1) then BASE:T(unit_name .. " a side door is open ") - ret_val = true + return true + end + + if string.find(type_name, "SA342" ) and (unit:getDrawArgumentValue(34) == 1) then + BASE:T(unit_name .. " front door(s) are open or doors removed") + return true end - if string.find(type_name, "SA342" ) and unit:getDrawArgumentValue(34) == 1 or unit:getDrawArgumentValue(38) == 1 then - BASE:T(unit_name .. " front door(s) are open") - ret_val = true - end - - if string.find(type_name, "Hercules") and unit:getDrawArgumentValue(1215) == 1 and unit:getDrawArgumentValue(1216) == 1 then + if string.find(type_name, "Hercules") and (unit:getDrawArgumentValue(1215) == 1 and unit:getDrawArgumentValue(1216) == 1) then BASE:T(unit_name .. " rear doors are open") - ret_val = true + return true end if string.find(type_name, "Hercules") and (unit:getDrawArgumentValue(1220) == 1 or unit:getDrawArgumentValue(1221) == 1) then BASE:T(unit_name .. " para doors are open") - ret_val = true + return true end - if string.find(type_name, "Hercules") and unit:getDrawArgumentValue(1217) == 1 then + if string.find(type_name, "Hercules") and (unit:getDrawArgumentValue(1217) == 1) then BASE:T(unit_name .. " side door is open") - ret_val = true + return true end if string.find(type_name, "Bell-47") then -- bell aint got no doors so always ready to load injured soldiers BASE:T(unit_name .. " door is open") - ret_val = true + return true end - if string.find(type_name, "UH-60L") and (unit:getDrawArgumentValue(401) == 1) or (unit:getDrawArgumentValue(402) == 1) then + if string.find(type_name, "UH-60L") and (unit:getDrawArgumentValue(401) == 1 or unit:getDrawArgumentValue(402) == 1) then BASE:T(unit_name .. " cargo door is open") - ret_val = true + return true end - if string.find(type_name, "UH-60L" ) and unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(400) == 1 then + if string.find(type_name, "UH-60L" ) and (unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(400) == 1 ) then BASE:T(unit_name .. " front door(s) are open") - ret_val = true + return true end if type_name == "AH-64D_BLK_II" then BASE:T(unit_name .. " front door(s) are open") - ret_val = true -- no doors on this one ;) + return true -- no doors on this one ;) end - if ret_val == false then - BASE:T(unit_name .. " all doors are closed") - end - - return ret_val + return false end -- nil From f50c374d04cb5164bbae55c58fe11eff56e484ef Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 25 Jun 2022 17:24:56 +0200 Subject: [PATCH 171/200] CSAR - hand back descriptive name as 3rd parameter on event Boarded() --- Moose Development/Moose/Ops/CSAR.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 76281a3f1..06cd10ae0 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -165,7 +165,7 @@ -- -- The pilot has been boarded to the helicopter. Use e.g. `function my_csar:OnAfterBoarded(...)` to link into this event: -- --- function my_csar:OnAfterBoarded(from, event, to, heliname, groupname) +-- function my_csar:OnAfterBoarded(from, event, to, heliname, groupname, description) -- ... your code here ... -- end -- @@ -492,6 +492,7 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. -- @param #string Woundedgroupname Name of the downed pilot\'s group. + -- @param #string Description Descriptive name of the group. --- On After "Returning" event. Heli can return home with downed pilot(s). -- @function [parent=#CSAR] OnAfterReturning @@ -1317,7 +1318,7 @@ function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupNam self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s I\'m in! Get to the MASH ASAP! ", _heliName, _pilotName), self.messageTime,true,true) - self:__Boarded(5,_heliName,_woundedGroupName) + self:__Boarded(5,_heliName,_woundedGroupName,grouptable.desc) return self end From 1fdf4f371d39285bf144292bb613a50e0199621e Mon Sep 17 00:00:00 2001 From: Chump Date: Sun, 26 Jun 2022 15:11:49 -0500 Subject: [PATCH 172/200] Fix for issues #1735 & #1736 (#1737) * Update Database.lua Remove duplicate function * Update PseudoATC.lua Added nil check --- Moose Development/Moose/Core/Database.lua | 10 ---------- Moose Development/Moose/Functional/PseudoATC.lua | 11 +++++++---- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 002b43c6e..1cc49fde7 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -241,16 +241,6 @@ function DATABASE:DeleteAirbase( AirbaseName ) self.AIRBASES[AirbaseName] = nil end ---- Finds an AIRBASE based on the AirbaseName. --- @param #DATABASE self --- @param #string AirbaseName --- @return Wrapper.Airbase#AIRBASE The found AIRBASE. -function DATABASE:FindAirbase( AirbaseName ) - - local AirbaseFound = self.AIRBASES[AirbaseName] - return AirbaseFound -end - do -- Zones --- Finds a @{Zone} based on the zone name. diff --git a/Moose Development/Moose/Functional/PseudoATC.lua b/Moose Development/Moose/Functional/PseudoATC.lua index 7358dda2c..7e26f74e7 100644 --- a/Moose Development/Moose/Functional/PseudoATC.lua +++ b/Moose Development/Moose/Functional/PseudoATC.lua @@ -949,11 +949,14 @@ function PSEUDOATC:LocalAirports(GID, UID) for _,airbase in pairs(airports) do local name=airbase:getName() - local q=AIRBASE:FindByName(name):GetCoordinate() - local d=q:Get2DDistance(pos) + local a=AIRBASE:FindByName(name) + if a then + local q=a:GetCoordinate() + local d=q:Get2DDistance(pos) - -- Add to table. - table.insert(self.group[GID].player[UID].airports, {distance=d, name=name}) + -- Add to table. + table.insert(self.group[GID].player[UID].airports, {distance=d, name=name}) + end end end From 183a60159c6069df05069815b40fc506502db896 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 1 Jul 2022 23:05:26 +0200 Subject: [PATCH 173/200] Update Range.lua - Fixed bug for strafing --- Moose Development/Moose/Functional/Range.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index 6b4a3bc14..e7aa20889 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -3018,7 +3018,7 @@ function RANGE:_CheckInZone( _unitName ) 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 ) + _text = _text .. string.format( "\n%s", resulttext ) -- Send message. self:_DisplayMessageToGroup( _unit, _text ) From f6e673c2bbe80951c0eb1c83546da7e169120e1a Mon Sep 17 00:00:00 2001 From: rfdazzle <70452098+rfdazzle@users.noreply.github.com> Date: Thu, 7 Jul 2022 02:41:56 -0400 Subject: [PATCH 174/200] Fixed ATIS TTS readouts for wind direction & TACAN (#1739) Added a substitution that takes effect when self.useSRS which converts wind direction into aviation-speak, e.g. "Zero Seven One" instead of the previous behaviour which was "Zero Seventy-One". Updated TACAN TTS string to include the 'Ray' in 'X-Ray' when SRS is in use --- Moose Development/Moose/Ops/ATIS.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 535d7fd03..780e1f450 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -1740,6 +1740,10 @@ function ATIS:onafterBroadcast( From, Event, To ) end -- Wind + -- Adding a space after each digit of WINDFROM to convert this to aviation-speak for TTS via SRS + if self.useSRS then + WINDFROM = string.gsub(WINDFROM,".", "%1 ") + end if self.metric then subtitle = string.format( "Wind from %s at %s m/s", WINDFROM, WINDSPEED ) else @@ -2197,7 +2201,7 @@ function ATIS:onafterBroadcast( From, Event, To ) -- TACAN if self.tacan then - subtitle = string.format( "TACAN channel %dX", self.tacan ) + subtitle = string.format( "TACAN channel %dX Ray", self.tacan ) if not self.useSRS then self:Transmission( ATIS.Sound.TACANChannel, 1.0, subtitle ) self.radioqueue:Number2Transmission( tostring( self.tacan ), nil, 0.2 ) From 4ae586ebaa19c7ee40fc4eb8f52087149fc91865 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 19 Jul 2022 08:10:54 +0200 Subject: [PATCH 175/200] Controllable/Beacon - added function to switch on Link4 A2A - typo in documentation --- .../Moose/AI/AI_A2A_Dispatcher.lua | 2 +- Moose Development/Moose/Core/Beacon.lua | 26 +++++++++++++++++ .../Moose/Wrapper/Controllable.lua | 28 +++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua index fe53bfe28..0ba77d4ea 100644 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua @@ -425,7 +425,7 @@ do -- AI_A2A_DISPATCHER -- * @{#AI_A2A_DISPATCHER.SetSquadronTakeoffFromParkingHot}() will spawn new aircraft in with running engines at a parking spot at the airfield. -- * @{#AI_A2A_DISPATCHER.SetSquadronTakeoffFromRunway}() will spawn new aircraft at the runway at the airfield. -- - -- **The default landing method is to spawn new aircraft directly in the air.** + -- **The default take-off method is to spawn new aircraft directly in the air.** -- -- Use these methods to fine-tune for specific airfields that are known to create bottlenecks, or have reduced airbase efficiency. -- The more and the longer aircraft need to taxi at an airfield, the more risk there is that: diff --git a/Moose Development/Moose/Core/Beacon.lua b/Moose Development/Moose/Core/Beacon.lua index 98e2032af..23094ea88 100644 --- a/Moose Development/Moose/Core/Beacon.lua +++ b/Moose Development/Moose/Core/Beacon.lua @@ -245,6 +245,32 @@ function BEACON:ActivateICLS(Channel, Callsign, Duration) return self end +--- Activates a LINK4 BEACON. The unit the BEACON is attached to should be an aircraft carrier supporting this system. +-- @param #BEACON self +-- @param #number Frequency LINK4 FRequency in MHz, eg 336. +-- @param #string Morse The ID that is going to be coded in Morse and broadcasted by the beacon. +-- @param #number Duration How long will the beacon last in seconds. Omit for forever. +-- @return #BEACON self +function BEACON:ActivateLink4(Frequency, Morse, Duration) + self:F({Frequency=Frequency, Morse=Morse, Duration=Duration}) + + -- Attached unit. + local UnitID=self.Positionable:GetID() + + -- Debug + self:T2({"LINK4 BEACON started!"}) + + -- Start beacon. + self.Positionable:CommandActivateLink4(Frequency,UnitID,Morse) + + -- Stop sheduler + if Duration then -- Schedule the stop of the BEACON if asked by the MD + self.Positionable:DeactivateBeacon(Duration) + end + + return self +end + --- DEPRECATED: Please use @{BEACON:ActivateTACAN}() instead. -- Activates a TACAN BEACON on an Aircraft. -- @param #BEACON self diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 3e280a09d..cd7d812eb 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -697,6 +697,34 @@ function CONTROLLABLE:CommandActivateICLS( Channel, UnitID, Callsign, Delay ) return self end +--- Activate LINK4 system of the CONTROLLABLE. The controllable should be an aircraft carrier! +-- @param #CONTROLLABLE self +-- @param #number Frequency Link4 Frequency in MHz, e.g. 336 +-- @param #number UnitID The ID of the unit the ICLS system is attached to. Useful if more units are in one group. +-- @param #string Callsign Morse code identification callsign. +-- @param #number Delay (Optional) Delay in seconds before the ICLS is deactivated. +-- @return #CONTROLLABLE self +function CONTROLLABLE:CommandActivateLink4(Frequency, UnitID, Callsign, Delay) + + -- Command to activate Link4 system. + local CommandActivateLink4= { + id = "ActivateLink4", + params= { + ["frequency "] = Frequency*1000, + ["unitId"] = UnitID, + ["name"] = Callsign, + } + } + + if Delay and Delay>0 then + SCHEDULER:New(nil, self.CommandActivateLink4, {self}, Delay) + else + self:SetCommand(CommandActivateLink4) + end + + return self +end + --- Deactivate the active beacon of the CONTROLLABLE. -- @param #CONTROLLABLE self -- @param #number Delay (Optional) Delay in seconds before the beacon is deactivated. From eef8b362d218b006d00eb6991af458c655d8da04 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 19 Jul 2022 08:29:43 +0200 Subject: [PATCH 176/200] Beacon - added deactivate Link4 --- Moose Development/Moose/Core/Beacon.lua | 3 ++- .../Moose/Wrapper/Controllable.lua | 24 ++++++++++++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Core/Beacon.lua b/Moose Development/Moose/Core/Beacon.lua index 23094ea88..d4c3c2ebd 100644 --- a/Moose Development/Moose/Core/Beacon.lua +++ b/Moose Development/Moose/Core/Beacon.lua @@ -265,12 +265,13 @@ function BEACON:ActivateLink4(Frequency, Morse, Duration) -- Stop sheduler if Duration then -- Schedule the stop of the BEACON if asked by the MD - self.Positionable:DeactivateBeacon(Duration) + self.Positionable:DeactivateLink4(Duration) end return self end + --- DEPRECATED: Please use @{BEACON:ActivateTACAN}() instead. -- Activates a TACAN BEACON on an Aircraft. -- @param #BEACON self diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index cd7d812eb..5a59a28e2 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -700,9 +700,9 @@ end --- Activate LINK4 system of the CONTROLLABLE. The controllable should be an aircraft carrier! -- @param #CONTROLLABLE self -- @param #number Frequency Link4 Frequency in MHz, e.g. 336 --- @param #number UnitID The ID of the unit the ICLS system is attached to. Useful if more units are in one group. +-- @param #number UnitID The DCS UNIT ID of the unit the LINK4 system is attached to. Useful if more units are in one group. -- @param #string Callsign Morse code identification callsign. --- @param #number Delay (Optional) Delay in seconds before the ICLS is deactivated. +-- @param #number Delay (Optional) Delay in seconds before the LINK4 is deactivated. -- @return #CONTROLLABLE self function CONTROLLABLE:CommandActivateLink4(Frequency, UnitID, Callsign, Delay) @@ -735,7 +735,7 @@ function CONTROLLABLE:CommandDeactivateBeacon( Delay ) local CommandDeactivateBeacon = { id = 'DeactivateBeacon', params = {} } if Delay and Delay > 0 then - SCHEDULER:New( nil, self.CommandActivateBeacon, { self }, Delay ) + SCHEDULER:New( nil, self.CommandDeactivateBeacon, { self }, Delay ) else self:SetCommand( CommandDeactivateBeacon ) end @@ -761,6 +761,24 @@ function CONTROLLABLE:CommandDeactivateICLS( Delay ) return self end +--- Deactivate the active Link4 of the CONTROLLABLE. +-- @param #CONTROLLABLE self +-- @param #number Delay (Optional) Delay in seconds before the Link4 is deactivated. +-- @return #CONTROLLABLE self +function CONTROLLABLE:CommandDeactivateLink4(Delay) + + -- Command to deactivate + local CommandDeactivateLink4={id='DeactivateLink4', params={}} + + if Delay and Delay>0 then + SCHEDULER:New(nil, self.CommandDeactivateLink4, {self}, Delay) + else + self:SetCommand(CommandDeactivateLink4) + end + + return self +end + --- Set callsign of the CONTROLLABLE. See [DCS command setCallsign](https://wiki.hoggitworld.com/view/DCS_command_setCallsign) -- @param #CONTROLLABLE self -- @param DCS#CALLSIGN CallName Number corresponding the the callsign identifier you wish this group to be called. From 636d6ce32475bf91e08a820ce2fbd61edb19d6b4 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Fri, 22 Jul 2022 11:02:55 +0200 Subject: [PATCH 177/200] AIRBASE - Added 2 AFB on the Falklands Map (#1748) --@field MarianaIslands AIRBASE.SouthAtlantic={ ["Port_Stanley"]="Port Stanley", ["Mount_Pleasant"]="Mount Pleasant", ["San_Carlos_FOB"]="San Carlos FOB", ["Rio_Grande"]="Rio Grande", ["Rio_Gallegos"]="Rio Gallegos", ["Ushuaia"]="Ushuaia", ["Ushuaia_Helo_Port"]="Ushuaia Helo Port", ["Punta_Arenas"]="Punta Arenas", ["Pampa_Guanaco"]="Pampa Guanaco", ["San_Julian"]="San Julian", } --- Moose Development/Moose/Wrapper/Airbase.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 6c2bde5d7..941477860 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -496,6 +496,8 @@ AIRBASE.MarianaIslands = { -- * AIRBASE.SouthAtlantic.Ushuaia -- * AIRBASE.SouthAtlantic.Ushuaia_Helo_Port -- * AIRBASE.SouthAtlantic.Punta_Arenas +-- * AIRBASE.SouthAtlantic.Pampa_Guanaco +-- * AIRBASE.SouthAtlantic.San_Julian -- --@field MarianaIslands AIRBASE.SouthAtlantic={ @@ -507,6 +509,8 @@ AIRBASE.SouthAtlantic={ ["Ushuaia"]="Ushuaia", ["Ushuaia_Helo_Port"]="Ushuaia Helo Port", ["Punta_Arenas"]="Punta Arenas", + ["Pampa_Guanaco"]="Pampa Guanaco", + ["San_Julian"]="San Julian", } --- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy". From a37d4214c072b83cec40dd685f12c0acc6dbb15c Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 22 Jul 2022 11:06:55 +0200 Subject: [PATCH 178/200] SET - fix for left over self:I() --- Moose Development/Moose/Core/Set.lua | 2 +- Moose Development/Moose/Wrapper/Airbase.lua | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 7ab8607b0..f9c5349b8 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -5627,7 +5627,7 @@ do -- SET_ZONE if self.Filter.Prefixes then local MZonePrefix = false for ZonePrefixId, ZonePrefix in pairs( self.Filter.Prefixes ) do - self:T3( { "Prefix:", string.find( MZoneName, ZonePrefix, 1 ), ZonePrefix } ) + self:T2( { "Prefix:", string.find( MZoneName, ZonePrefix, 1 ), ZonePrefix } ) if string.find( MZoneName, ZonePrefix, 1 ) then MZonePrefix = true end diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 941477860..38149634e 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -513,6 +513,7 @@ AIRBASE.SouthAtlantic={ ["San_Julian"]="San Julian", } + --- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy". -- @type AIRBASE.ParkingSpot -- @field Core.Point#COORDINATE Coordinate Coordinate of the parking spot. From 3c5f3d6c37a5cb4b846b769fe3e2d9f31a655020 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 25 Jul 2022 08:12:28 +0200 Subject: [PATCH 179/200] UNIT - added get altitude function with AGL option --- .../Moose/Wrapper/Positionable.lua | 4 ++-- Moose Development/Moose/Wrapper/Unit.lua | 20 +++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index d1ad2963e..60f3687a6 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -537,7 +537,7 @@ function POSITIONABLE:GetBoundingRadius( MinDist ) return nil end ---- Returns the altitude of the POSITIONABLE. +--- Returns the altitude above sea level of the POSITIONABLE. -- @param Wrapper.Positionable#POSITIONABLE self -- @return DCS#Distance The altitude of the POSITIONABLE. -- @return #nil The POSITIONABLE is not existing or alive. @@ -779,7 +779,7 @@ function POSITIONABLE:GetRelativeVelocity( Positionable ) return UTILS.VecNorm( vtot ) end ---- Returns the POSITIONABLE height in meters. +--- Returns the POSITIONABLE height above sea level in meters. -- @param Wrapper.Positionable#POSITIONABLE self -- @return DCS#Vec3 The height of the POSITIONABLE in meters. -- @return #nil The POSITIONABLE is not existing or alive. diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 091676403..f11e84906 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -178,8 +178,28 @@ function UNIT:GetDCSObject() return nil end +--- Returns the unit altitude above sea level in meters. +-- @param Wrapper.Unit#UNIT self +-- @param #boolean FromGround Measure from the ground or from sea level (ASL). Provide **true** for measuring from the ground (AGL). **false** or **nil** if you measure from sea level. +-- @return #number The height of the group or nil if is not existing or alive. +function UNIT:GetAltitude(FromGround) + + local DCSUnit = Unit.getByName( self.UnitName ) + if DCSUnit then + local altitude = 0 + local point = DCSUnit.getPoint() --DCS#Vec3 + altitude = point.y + if FromGround then + local land = land.getHeight( { x = point.x, y = point.z } ) or 0 + altitude = altitude - land + end + return altitude + end + return nil + +end --- Respawn the @{Wrapper.Unit} using a (tweaked) template of the parent Group. -- From 562a3f6208a42d80dfc513b28e6698f263898711 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 29 Jul 2022 08:51:23 +0200 Subject: [PATCH 180/200] SET - fix for dead units --- Moose Development/Moose/Core/Set.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index f9c5349b8..be1432348 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -1127,8 +1127,10 @@ do -- SET_GROUP local NearestGroup = nil -- Wrapper.Group#GROUP local ClosestDistance = nil - - for ObjectID, ObjectData in pairs( self.Set ) do + + local Set = self:GetAliveSet() + + for ObjectID, ObjectData in pairs( Set ) do if NearestGroup == nil then NearestGroup = ObjectData ClosestDistance = PointVec2:DistanceFromPointVec2( ObjectData:GetCoordinate() ) From 73493c3a237bfa30d6eb62a0d33080156882e206 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sun, 31 Jul 2022 09:17:36 +0200 Subject: [PATCH 181/200] Update Unit.lua (#1751) --- Moose Development/Moose/Wrapper/Unit.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index f11e84906..b9db49851 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -188,7 +188,7 @@ function UNIT:GetAltitude(FromGround) if DCSUnit then local altitude = 0 - local point = DCSUnit.getPoint() --DCS#Vec3 + local point = DCSUnit:getPoint() --DCS#Vec3 altitude = point.y if FromGround then local land = land.getHeight( { x = point.x, y = point.z } ) or 0 From e3c03287b7a059805387cd61a6ef9fc7036396b7 Mon Sep 17 00:00:00 2001 From: Gavin Edwards Date: Sat, 13 Aug 2022 12:40:53 -0700 Subject: [PATCH 182/200] Engines invincible (#1758) * Quick update to Airboss.lua to inluce Invincible. Not yet tested. * Initial quick test and calibration of the landing spot; looks good. More testing required. * Recompiled the Moose.lua based off the last commit. * Removing surpurflous Moose.lua for pull request. --- Moose Development/Moose/Ops/Airboss.lua | 95 +++++++++++++++++++------ 1 file changed, 74 insertions(+), 21 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 8a6d82c5b..ffab16ed2 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -33,6 +33,7 @@ -- * [USS Harry S. Truman](https://en.wikipedia.org/wiki/USS_Harry_S._Truman) (CVN-75) [Super Carrier Module] -- * [USS Forrestal](https://en.wikipedia.org/wiki/USS_Forrestal_(CV-59)) (CV-59) [Heatblur Carrier Module] -- * [HMS Hermes](https://en.wikipedia.org/wiki/HMS_Hermes_(R12)) (R12) [**WIP**] +-- * [HMS Invincible](https://en.wikipedia.org/wiki/HMS_Invincible_(R05) (R05) [**WIP**] -- * [USS Tarawa](https://en.wikipedia.org/wiki/USS_Tarawa_(LHA-1)) (LHA-1) [**WIP**] -- * [USS America](https://en.wikipedia.org/wiki/USS_America_(LHA-6)) (LHA-6) [**WIP**] -- * [Juan Carlos I](https://en.wikipedia.org/wiki/Spanish_amphibious_assault_ship_Juan_Carlos_I) (L61) [**WIP**] @@ -53,7 +54,7 @@ -- -- At the moment, optimized parameters are available for the F/A-18C Hornet (Lot 20) and A-4E community mod as aircraft and the USS John C. Stennis as carrier. -- --- The AV-8B Harrier, HMS Hermes, the USS Tarawa, USS America, HMAS Canberra, and Juan Carlos I are WIP. The AV-8B harrier and the LHA's and LHD can only be used together, i.e. these ships are the only carriers the harrier is supposed to land on and +-- The AV-8B Harrier, HMS Hermes, HMS Invincible, the USS Tarawa, USS America, HMAS Canberra, and Juan Carlos I are WIP. The AV-8B harrier and the LHA's and LHD can only be used together, i.e. these ships are the only carriers the harrier is supposed to land on and -- no other fixed wing aircraft (human or AI controlled) are supposed to land on these ships. Currently only Case I is supported. Case II/III take slightly different steps from the CVN carrier. -- However, if no offset is used for the holding radial this provides a very close representation of the V/STOL Case III, allowing for an approach to over the deck and a vertical landing. -- @@ -1263,7 +1264,7 @@ AIRBOSS = { --- Aircraft types capable of landing on carrier (human+AI). -- @type AIRBOSS.AircraftCarrier --- @field #string AV8B AV-8B Night Harrier. Works only with the HMS Hermes, USS Tarawa, USS America, and Juan Carlos I. +-- @field #string AV8B AV-8B Night Harrier. Works only with the HMS Hermes, HMS Invincible, USS Tarawa, USS America, and Juan Carlos I. -- @field #string A4EC A-4E Community mod. -- @field #string HORNET F/A-18C Lot 20 Hornet by Eagle Dynamics. -- @field #string F14A F-14A by Heatblur. @@ -1300,6 +1301,7 @@ AIRBOSS.AircraftCarrier={ -- @field #string FORRESTAL USS Forrestal (CV-59) [Heatblur Carrier Module] -- @field #string VINSON USS Carl Vinson (CVN-70) [Obsolete] -- @field #string HERMES HMS Hermes (R12) [V/STOL Carrier] +-- @field #string INVINCIBLE HMS Invincible (R05) [V/STOL Carrier] -- @field #string TARAWA USS Tarawa (LHA-1) [V/STOL Carrier] -- @field #string AMERICA USS America (LHA-6) [V/STOL Carrier] -- @field #string JCARLOS Juan Carlos I (L61) [V/STOL Carrier] @@ -1314,6 +1316,7 @@ AIRBOSS.CarrierType = { FORRESTAL = "Forrestal", VINSON = "VINSON", HERMES = "HERMES81", + INVINCIBLE = "hms_invincible", TARAWA = "LHA_Tarawa", AMERICA = "USS America LHA-6", JCARLOS = "L61", @@ -1988,6 +1991,9 @@ function AIRBOSS:New( carriername, alias ) elseif self.carriertype == AIRBOSS.CarrierType.HERMES then -- Hermes parameters. self:_InitHermes() + elseif self.carriertype == AIRBOSS.CarrierType.INVINCIBLE then + -- Invincible parameters. + self:_InitInvincible() elseif self.carriertype == AIRBOSS.CarrierType.TARAWA then -- Tarawa parameters. self:_InitTarawa() @@ -2090,7 +2096,7 @@ function AIRBOSS:New( carriername, alias ) -- cL:FlareYellow() -- Carrier specific. - if self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.HERMES or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.TARAWA or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.AMERICA or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.JCARLOS or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.CANBERRA then + if self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.INVINCIBLE or sself.carrier:GetTypeName() ~= AIRBOSS.CarrierType.HERMES or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.TARAWA or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.AMERICA or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.JCARLOS or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.CANBERRA then -- Flare wires. local w1 = stern:Translate( self.carrierparam.wire1, FB, true ) @@ -2823,7 +2829,7 @@ end function AIRBOSS:SetGlideslopeErrorThresholds(_max,_min, High, HIGH, Low, LOW) --Check if V/STOL Carrier - if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or sself.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then -- allow a larger GSE for V/STOL operations --Pene Testing self.gle._max=_max or 0.7 @@ -2860,7 +2866,7 @@ end function AIRBOSS:SetLineupErrorThresholds(_max,_min, Left, LeftMed, LEFT, Right, RightMed, RIGHT) --Check if V/STOL Carrier -- Pene testing - if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or sself.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then -- V/STOL Values -- allow a larger LUE for V/STOL operations self.lue._max=_max or 1.8 @@ -4466,6 +4472,46 @@ function AIRBOSS:_InitHermes() end +--- Init parameters for R05 HMS Invincible carrier. +-- @param #AIRBOSS self +function AIRBOSS:_InitInvincible() + + -- Init Stennis as default. + self:_InitStennis() + + -- Carrier Parameters. + self.carrierparam.sterndist = -105 + self.carrierparam.deckheight = 12 -- From model viewer WL0. + + -- Total size of the carrier (approx as rectangle). + self.carrierparam.totlength = 228.19 + self.carrierparam.totwidthport = 20.5 + self.carrierparam.totwidthstarboard = 24.5 + + -- Landing runway. + self.carrierparam.rwyangle = 0 + self.carrierparam.rwylength = 215 + self.carrierparam.rwywidth = 13 + + -- Wires. + self.carrierparam.wire1 = nil + self.carrierparam.wire2 = nil + self.carrierparam.wire3 = nil + self.carrierparam.wire4 = nil + + -- Late break. + self.BreakLate.name = "Late Break" + self.BreakLate.Xmin = -UTILS.NMToMeters( 1 ) -- Not more than 1 NM behind the boat. Last check was at 0. + self.BreakLate.Xmax = UTILS.NMToMeters( 5 ) -- Not more than 5 NM in front of the boat. Enough for late breaks? + self.BreakLate.Zmin = -UTILS.NMToMeters( 0.25 ) -- Not more than 0.25 NM port. + self.BreakLate.Zmax = UTILS.NMToMeters( 0.5 ) -- Not more than 0.5 NM starboard. + self.BreakLate.LimitXmin = 0 -- Check and next step 0.8 NM port and in front of boat. + self.BreakLate.LimitXmax = nil + self.BreakLate.LimitZmin = -UTILS.NMToMeters( 0.5 ) -- 926 m port, closer than the stennis as abeam is 0.8-1.0 rather than 1.2 + self.BreakLate.LimitZmax = nil + +end + --- Init parameters for LHA-1 Tarawa carrier. -- @param #AIRBOSS self function AIRBOSS:_InitTarawa() @@ -6293,7 +6339,7 @@ function AIRBOSS:_GetMarshalAltitude( stack, case ) p2 = Carrier:Translate( UTILS.NMToMeters( 1.5 ), hdg ) -- Tarawa,LHA,LHD Delta patterns. - if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or sself.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then -- Pattern is directly overhead the carrier. p1 = Carrier:Translate( UTILS.NMToMeters( 1.0 ), hdg + 90 ) @@ -8132,7 +8178,7 @@ function AIRBOSS:OnEventLand( EventData ) self:T( self.lid .. text ) -- Check carrier type. - if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or sself.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then -- Power "Idle". self:RadioTransmission( self.LSORadio, self.LSOCall.IDLE, false, 1, nil, true ) @@ -8167,7 +8213,7 @@ function AIRBOSS:OnEventLand( EventData ) -- AI unit landed -- -------------------- - if self.carriertype ~= AIRBOSS.CarrierType.HERMES or self.carriertype ~= AIRBOSS.CarrierType.TARAWA or self.carriertype ~= AIRBOSS.CarrierType.AMERICA or self.carriertype ~= AIRBOSS.CarrierType.JCARLOS or self.carriertype ~= AIRBOSS.CarrierType.CANBERRA then + if self.carriertype ~= AIRBOSS.CarrierType.INVINCIBLE or sself.carriertype ~= AIRBOSS.CarrierType.HERMES or self.carriertype ~= AIRBOSS.CarrierType.TARAWA or self.carriertype ~= AIRBOSS.CarrierType.AMERICA or self.carriertype ~= AIRBOSS.CarrierType.JCARLOS or self.carriertype ~= AIRBOSS.CarrierType.CANBERRA then -- Coordinate at landing event local coord = EventData.IniUnit:GetCoordinate() @@ -9205,7 +9251,7 @@ function AIRBOSS:_CheckForLongDownwind( playerData ) local limit = UTILS.NMToMeters( -1.6 ) -- For the tarawa, other LHA and LHD we give a bit more space. - if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or sself.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then limit = UTILS.NMToMeters( -2.0 ) end @@ -9292,7 +9338,7 @@ function AIRBOSS:_Ninety( playerData ) self:_PlayerHint( playerData ) -- Next step: wake. - if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or sself.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then -- Harrier has no wake stop. It stays port of the boat. self:_SetPlayerStep( playerData, AIRBOSS.PatternStep.FINAL ) else @@ -9987,7 +10033,7 @@ function AIRBOSS:_GetSternCoord() -- local stern=self:GetCoordinate() -- Stern coordinate (sterndist<0). --Pene testing Case III - if self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then + if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or sself.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then if case==3 then -- CASE III V/STOL translation Due over deck approach if needed. self.sterncoord:Translate(self.carrierparam.sterndist, hdg, true, true):Translate(8, FB-90, true, true) @@ -10628,7 +10674,7 @@ function AIRBOSS:_GetZoneRunwayBox() return self.zoneRunwaybox end ---- Get zone of primary abeam landing position of HMS Hermes, USS Tarawa, USS America and Juan Carlos. Box length 50 meters and width 30 meters. +--- Get zone of primary abeam landing position of HMS Hermes, HMS Invincible, USS Tarawa, USS America and Juan Carlos. Box length 50 meters and width 30 meters. --- Allow for Clear to land call from LSO approaching abeam the landing spot if stable as per NATOPS 00-80T -- @param #AIRBOSS self @@ -10733,7 +10779,7 @@ function AIRBOSS:_GetZoneHolding( case, stack ) self.zoneHolding = ZONE_RADIUS:New( "CASE I Holding Zone", Post:GetVec2(), self.marshalradius ) -- Delta pattern. - if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or sself.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then self.zoneHolding = ZONE_RADIUS:New( "CASE I Holding Zone", self.carrier:GetVec2(), UTILS.NMToMeters( 5 ) ) end @@ -10785,7 +10831,7 @@ function AIRBOSS:_GetZoneCommence( case, stack ) -- Three position local Three = self:GetCoordinate():Translate( D, hdg + 275 ) - if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or sself.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then local Dx = UTILS.NMToMeters( 2.25 ) @@ -11076,7 +11122,7 @@ function AIRBOSS:_GetAltCarrier( unit ) return h end ---- Get optimal landing position of the aircraft. Usually between second and third wire. In case of Tarawa, Canberrra, Juan Carlos and America we take the abeam landing spot 120 ft above and 21 ft abeam the 7.5 position, for the Juan Carlos I and HMS Hermes it is 120 ft above and 21 ft abeam the 5 position. For CASE III it is 120ft directly above the landing spot. +--- Get optimal landing position of the aircraft. Usually between second and third wire. In case of Tarawa, Canberrra, Juan Carlos and America we take the abeam landing spot 120 ft above and 21 ft abeam the 7.5 position, for the Juan Carlos I, HMS Invincible, and HMS Hermes and Invincible it is 120 ft above and 21 ft abeam the 5 position. For CASE III it is 120ft directly above the landing spot. -- @param #AIRBOSS self -- @return Core.Point#COORDINATE Optimal landing coordinate. function AIRBOSS:_GetOptLandingCoordinate() @@ -11091,7 +11137,7 @@ function AIRBOSS:_GetOptLandingCoordinate() local FB=self:GetFinalBearing(false) local case=self.case -- set Case III V/STOL abeam landing spot over deck -- Pene Testing - if self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then + if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or sself.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then if case==3 then self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate()) @@ -11104,7 +11150,7 @@ function AIRBOSS:_GetOptLandingCoordinate() self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate()):Translate(35, FB-90, true, true) --stern=self:_GetLandingSpotCoordinate():Translate(35, FB-90) - -- Alitude 120 ft. + -- Atlitude 120 ft. self.landingcoord:SetAltitude(UTILS.FeetToMeters(120)) end @@ -11142,6 +11188,13 @@ function AIRBOSS:_GetLandingSpotCoordinate() -- Primary landing spot 5 self.landingspotcoord:Translate( 69, hdg, true, true ):SetAltitude( self.carrierparam.deckheight ) + elseif self.carriertype == AIRBOSS.CarrierType.INVINCIBLE then + + -- Using spot 3 as the default + local hdg = self:GetHeading() + + self.landingspotcoord:Translate( 69, hdg, true, true ):SetAltitude( self.carrierparam.deckheight ) + -- This location looks good. elseif self.carriertype == AIRBOSS.CarrierType.TARAWA then -- Landing 100 ft abeam, 120 alt. @@ -12083,7 +12136,7 @@ function AIRBOSS:_GS( step, n ) if n == -1 then gp = AIRBOSS.GroovePos.IC elseif n == 1 then - if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or sself.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then gp = AIRBOSS.GroovePos.AL else gp = AIRBOSS.GroovePos.IW @@ -13962,7 +14015,7 @@ function AIRBOSS:_IsCarrierAircraft( unit ) -- Special case for Harrier which can only land on Tarawa, LHA and LHD. if aircrafttype == AIRBOSS.AircraftCarrier.AV8B then - if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or sself.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then return true else return false @@ -13970,7 +14023,7 @@ function AIRBOSS:_IsCarrierAircraft( unit ) end -- Also only Harriers can land on the Tarawa, LHA and LHD. - if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then if aircrafttype ~= AIRBOSS.AircraftCarrier.AV8B then return false end @@ -17327,7 +17380,7 @@ function AIRBOSS:_MarkCaseZones( _unitName, flare ) end -- Tarawa, LHA and LHD landing spots. - if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or sself.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then text = text .. "\n* abeam landing stop with RED flares" -- Abeam landing spot zone. local ALSPT = self:_GetZoneAbeamLandingSpot() From 003e865ff7566a44cbbf0c0ba366a1f12afc2e82 Mon Sep 17 00:00:00 2001 From: Engines Date: Sun, 14 Aug 2022 22:15:15 -0700 Subject: [PATCH 183/200] Late break bug (#1762) * Quick update to Airboss.lua to inluce Invincible. Not yet tested. * Initial quick test and calibration of the landing spot; looks good. More testing required. * Recompiled the Moose.lua based off the last commit. * Removing surpurflous Moose.lua for pull request. * There was an error with the late break paratemers on the Brit carriers causing pilots to be thrown off the pattern on the break. Fixed, tested on Invicible, working as anticipated now. * Removing the test Moose.lua (again). * Fixing merge confilict * Trying again. --- Moose Development/Moose/Ops/Airboss.lua | 38 ++++++++++++------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index ffab16ed2..1dfe90d83 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -2096,7 +2096,7 @@ function AIRBOSS:New( carriername, alias ) -- cL:FlareYellow() -- Carrier specific. - if self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.INVINCIBLE or sself.carrier:GetTypeName() ~= AIRBOSS.CarrierType.HERMES or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.TARAWA or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.AMERICA or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.JCARLOS or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.CANBERRA then + if self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.INVINCIBLE or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.HERMES or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.TARAWA or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.AMERICA or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.JCARLOS or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.CANBERRA then -- Flare wires. local w1 = stern:Translate( self.carrierparam.wire1, FB, true ) @@ -2829,7 +2829,7 @@ end function AIRBOSS:SetGlideslopeErrorThresholds(_max,_min, High, HIGH, Low, LOW) --Check if V/STOL Carrier - if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or sself.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then -- allow a larger GSE for V/STOL operations --Pene Testing self.gle._max=_max or 0.7 @@ -2866,7 +2866,7 @@ end function AIRBOSS:SetLineupErrorThresholds(_max,_min, Left, LeftMed, LEFT, Right, RightMed, RIGHT) --Check if V/STOL Carrier -- Pene testing - if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or sself.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then -- V/STOL Values -- allow a larger LUE for V/STOL operations self.lue._max=_max or 1.8 @@ -4463,8 +4463,8 @@ function AIRBOSS:_InitHermes() self.BreakLate.name = "Late Break" self.BreakLate.Xmin = -UTILS.NMToMeters( 1 ) -- Not more than 1 NM behind the boat. Last check was at 0. self.BreakLate.Xmax = UTILS.NMToMeters( 5 ) -- Not more than 5 NM in front of the boat. Enough for late breaks? - self.BreakLate.Zmin = -UTILS.NMToMeters( 0.25 ) -- Not more than 0.25 NM port. - self.BreakLate.Zmax = UTILS.NMToMeters( 0.5 ) -- Not more than 0.5 NM starboard. + self.BreakLate.Zmin = -UTILS.NMToMeters( 1.6 ) -- Not more than 1.6 NM port. + self.BreakLate.Zmax = UTILS.NMToMeters( 1 ) -- Not more than 1 NM starboard. self.BreakLate.LimitXmin = 0 -- Check and next step 0.8 NM port and in front of boat. self.BreakLate.LimitXmax = nil self.BreakLate.LimitZmin = -UTILS.NMToMeters( 0.5 ) -- 926 m port, closer than the stennis as abeam is 0.8-1.0 rather than 1.2 @@ -4503,8 +4503,8 @@ function AIRBOSS:_InitInvincible() self.BreakLate.name = "Late Break" self.BreakLate.Xmin = -UTILS.NMToMeters( 1 ) -- Not more than 1 NM behind the boat. Last check was at 0. self.BreakLate.Xmax = UTILS.NMToMeters( 5 ) -- Not more than 5 NM in front of the boat. Enough for late breaks? - self.BreakLate.Zmin = -UTILS.NMToMeters( 0.25 ) -- Not more than 0.25 NM port. - self.BreakLate.Zmax = UTILS.NMToMeters( 0.5 ) -- Not more than 0.5 NM starboard. + self.BreakLate.Zmin = -UTILS.NMToMeters( 1.6 ) -- Not more than 1.6 NM port. + self.BreakLate.Zmax = UTILS.NMToMeters( 1 ) -- Not more than 1 NM starboard. self.BreakLate.LimitXmin = 0 -- Check and next step 0.8 NM port and in front of boat. self.BreakLate.LimitXmax = nil self.BreakLate.LimitZmin = -UTILS.NMToMeters( 0.5 ) -- 926 m port, closer than the stennis as abeam is 0.8-1.0 rather than 1.2 @@ -6339,7 +6339,7 @@ function AIRBOSS:_GetMarshalAltitude( stack, case ) p2 = Carrier:Translate( UTILS.NMToMeters( 1.5 ), hdg ) -- Tarawa,LHA,LHD Delta patterns. - if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or sself.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then -- Pattern is directly overhead the carrier. p1 = Carrier:Translate( UTILS.NMToMeters( 1.0 ), hdg + 90 ) @@ -8178,7 +8178,7 @@ function AIRBOSS:OnEventLand( EventData ) self:T( self.lid .. text ) -- Check carrier type. - if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or sself.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then -- Power "Idle". self:RadioTransmission( self.LSORadio, self.LSOCall.IDLE, false, 1, nil, true ) @@ -8213,7 +8213,7 @@ function AIRBOSS:OnEventLand( EventData ) -- AI unit landed -- -------------------- - if self.carriertype ~= AIRBOSS.CarrierType.INVINCIBLE or sself.carriertype ~= AIRBOSS.CarrierType.HERMES or self.carriertype ~= AIRBOSS.CarrierType.TARAWA or self.carriertype ~= AIRBOSS.CarrierType.AMERICA or self.carriertype ~= AIRBOSS.CarrierType.JCARLOS or self.carriertype ~= AIRBOSS.CarrierType.CANBERRA then + if self.carriertype ~= AIRBOSS.CarrierType.INVINCIBLE or self.carriertype ~= AIRBOSS.CarrierType.HERMES or self.carriertype ~= AIRBOSS.CarrierType.TARAWA or self.carriertype ~= AIRBOSS.CarrierType.AMERICA or self.carriertype ~= AIRBOSS.CarrierType.JCARLOS or self.carriertype ~= AIRBOSS.CarrierType.CANBERRA then -- Coordinate at landing event local coord = EventData.IniUnit:GetCoordinate() @@ -9251,7 +9251,7 @@ function AIRBOSS:_CheckForLongDownwind( playerData ) local limit = UTILS.NMToMeters( -1.6 ) -- For the tarawa, other LHA and LHD we give a bit more space. - if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or sself.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then limit = UTILS.NMToMeters( -2.0 ) end @@ -9338,7 +9338,7 @@ function AIRBOSS:_Ninety( playerData ) self:_PlayerHint( playerData ) -- Next step: wake. - if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or sself.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then -- Harrier has no wake stop. It stays port of the boat. self:_SetPlayerStep( playerData, AIRBOSS.PatternStep.FINAL ) else @@ -10033,7 +10033,7 @@ function AIRBOSS:_GetSternCoord() -- local stern=self:GetCoordinate() -- Stern coordinate (sterndist<0). --Pene testing Case III - if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or sself.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then + if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then if case==3 then -- CASE III V/STOL translation Due over deck approach if needed. self.sterncoord:Translate(self.carrierparam.sterndist, hdg, true, true):Translate(8, FB-90, true, true) @@ -10779,7 +10779,7 @@ function AIRBOSS:_GetZoneHolding( case, stack ) self.zoneHolding = ZONE_RADIUS:New( "CASE I Holding Zone", Post:GetVec2(), self.marshalradius ) -- Delta pattern. - if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or sself.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then self.zoneHolding = ZONE_RADIUS:New( "CASE I Holding Zone", self.carrier:GetVec2(), UTILS.NMToMeters( 5 ) ) end @@ -10831,7 +10831,7 @@ function AIRBOSS:_GetZoneCommence( case, stack ) -- Three position local Three = self:GetCoordinate():Translate( D, hdg + 275 ) - if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or sself.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then local Dx = UTILS.NMToMeters( 2.25 ) @@ -11137,7 +11137,7 @@ function AIRBOSS:_GetOptLandingCoordinate() local FB=self:GetFinalBearing(false) local case=self.case -- set Case III V/STOL abeam landing spot over deck -- Pene Testing - if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or sself.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then + if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then if case==3 then self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate()) @@ -12136,7 +12136,7 @@ function AIRBOSS:_GS( step, n ) if n == -1 then gp = AIRBOSS.GroovePos.IC elseif n == 1 then - if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or sself.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then gp = AIRBOSS.GroovePos.AL else gp = AIRBOSS.GroovePos.IW @@ -14015,7 +14015,7 @@ function AIRBOSS:_IsCarrierAircraft( unit ) -- Special case for Harrier which can only land on Tarawa, LHA and LHD. if aircrafttype == AIRBOSS.AircraftCarrier.AV8B then - if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or sself.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then return true else return false @@ -17380,7 +17380,7 @@ function AIRBOSS:_MarkCaseZones( _unitName, flare ) end -- Tarawa, LHA and LHD landing spots. - if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or sself.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then text = text .. "\n* abeam landing stop with RED flares" -- Abeam landing spot zone. local ALSPT = self:_GetZoneAbeamLandingSpot() From 63b0dae794f3e9c6e5633e517220e3ca236a81f9 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 22 Aug 2022 17:03:02 +0200 Subject: [PATCH 184/200] Update ATC_Ground.lua (#1766) * Make ATC_Ground work on any map --- .../Moose/Functional/ATC_Ground.lua | 2890 ++++------------- 1 file changed, 585 insertions(+), 2305 deletions(-) diff --git a/Moose Development/Moose/Functional/ATC_Ground.lua b/Moose Development/Moose/Functional/ATC_Ground.lua index 822c1ace3..2e72dcea3 100644 --- a/Moose Development/Moose/Functional/ATC_Ground.lua +++ b/Moose Development/Moose/Functional/ATC_Ground.lua @@ -18,6 +18,8 @@ -- -- ### Contributions: Dutch Baron - Concept & Testing -- ### Author: FlightControl - Framework Design & Programming +-- ### Refactoring to use the Runway auto-detection: Applevangelist +-- @date August 2022 -- -- === -- @@ -28,21 +30,20 @@ -- @field Core.Set#SET_CLIENT SetClient -- @extends Core.Base#BASE ---- Base class for ATC\_GROUND implementations. +--- [DEPRECATED, use ATC_GROUND_UNIVERSAL] Base class for ATC\_GROUND implementations. -- @field #ATC_GROUND ATC_GROUND = { ClassName = "ATC_GROUND", SetClient = nil, Airbases = nil, AirbaseNames = nil, - --KickSpeed = nil, -- The maximum speed in meters per second for all airbases until a player gets kicked. This is overridden at each derived class. } --- @type ATC_GROUND.AirbaseNames -- @list <#string> ---- Creates a new ATC\_GROUND object. +--- [DEPRECATED, use ATC_GROUND_UNIVERSAL] Creates a new ATC\_GROUND object. -- @param #ATC_GROUND self -- @param Airbases A table of Airbase Names. -- @return #ATC_GROUND self @@ -59,10 +60,18 @@ function ATC_GROUND:New( Airbases, AirbaseList ) for AirbaseID, Airbase in pairs( self.Airbases ) do - Airbase.ZoneBoundary = _DATABASE:FindAirbase( AirbaseID ):GetZone() + -- Specified ZoneBoundary is used if set or Airbase radius by default + if Airbase.ZoneBoundary then + Airbase.ZoneBoundary = ZONE_POLYGON_BASE:New( "Boundary " .. AirbaseID, Airbase.ZoneBoundary ) + else + Airbase.ZoneBoundary = _DATABASE:FindAirbase( AirbaseID ):GetZone() + end + Airbase.ZoneRunways = {} - for PointsRunwayID, PointsRunway in pairs( Airbase.PointsRunways ) do - Airbase.ZoneRunways[PointsRunwayID] = ZONE_POLYGON_BASE:New( "Runway " .. PointsRunwayID, PointsRunway ) + if Airbase.PointsRunways then + for PointsRunwayID, PointsRunway in pairs( Airbase.PointsRunways ) do + Airbase.ZoneRunways[PointsRunwayID] = ZONE_POLYGON_BASE:New( "Runway " .. PointsRunwayID, PointsRunway ) + end end Airbase.Monitor = self.AirbaseList and false or true -- When AirbaseList is not given, monitor every Airbase, otherwise don't monitor any (yet). end @@ -72,13 +81,6 @@ function ATC_GROUND:New( Airbases, AirbaseList ) self.Airbases[AirbaseName].Monitor = true end --- -- Template --- local TemplateBoundary = GROUP:FindByName( "Template Boundary" ) --- self.Airbases.Template.ZoneBoundary = ZONE_POLYGON:New( "Template Boundary", TemplateBoundary ):SmokeZone(SMOKECOLOR.White):Flush() --- --- local TemplateRunway1 = GROUP:FindByName( "Template Runway 1" ) --- self.Airbases.Template.ZoneRunways[1] = ZONE_POLYGON:New( "Template Runway 1", TemplateRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - self.SetClient:ForEachClient( --- @param Wrapper.Client#CLIENT Client function( Client ) @@ -97,7 +99,6 @@ function ATC_GROUND:New( Airbases, AirbaseList ) return self end - --- Smoke the airbases runways. -- @param #ATC_GROUND self -- @param Utilities.Utils#SMOKECOLOR SmokeColor The color of the smoke around the runways. @@ -111,7 +112,6 @@ function ATC_GROUND:SmokeRunways( SmokeColor ) end end - --- Set the maximum speed in meters per second (Mps) until the player gets kicked. -- An airbase can be specified to set the kick speed for. -- @param #ATC_GROUND self @@ -246,7 +246,6 @@ function ATC_GROUND:SetMaximumKickSpeedMiph( MaximumKickSpeedMiph, Airbase ) return self end - --- @param #ATC_GROUND self function ATC_GROUND:_AirbaseMonitor() @@ -402,11 +401,455 @@ function ATC_GROUND:_AirbaseMonitor() return true end +--- +-- @type ATC_GROUND_UNIVERSAL +-- @field Core.Set#SET_CLIENT SetClient +-- @field #string Version +-- @field #string ClassName +-- @field #table Airbases +-- @field #table AirbaseList +-- @field #number KickSpeed +-- @extends Core.Base#BASE + +--- Base class for ATC\_GROUND\_UNIVERSAL implementations. +-- @field #ATC_GROUND_UNIVERSAL +ATC_GROUND_UNIVERSAL = { + ClassName = "ATC_GROUND_UNIVERSAL", + Version = "0.0.1", + SetClient = nil, + Airbases = nil, + AirbaseList = nil, + KickSpeed = nil, -- The maximum speed in meters per second for all airbases until a player gets kicked. This is overridden at each derived class. +} + +--- Creates a new ATC\_GROUND\_UNIVERSAL object. This works on any map. +-- @param #ATC_GROUND_UNIVERSAL self +-- @param AirbaseList (Optional) A table of Airbase Names. +-- @return #ATC_GROUND_UNIVERSAL self +function ATC_GROUND_UNIVERSAL:New(AirbaseList) + + -- Inherits from BASE + local self = BASE:Inherit( self, BASE:New() ) -- #ATC_GROUND + self:E( { self.ClassName } ) + + self.Airbases = {} + + for _name,_ in pairs(_DATABASE.AIRBASES) do + self.Airbases[_name]={} + end + + self.AirbaseList = AirbaseList + + self.SetClient = SET_CLIENT:New():FilterCategories( "plane" ):FilterStart() + + + for AirbaseID, Airbase in pairs( self.Airbases ) do + -- Specified ZoneBoundary is used if set or Airbase radius by default + if Airbase.ZoneBoundary then + Airbase.ZoneBoundary = ZONE_POLYGON_BASE:New( "Boundary " .. AirbaseID, Airbase.ZoneBoundary ) + else + Airbase.ZoneBoundary = _DATABASE:FindAirbase( AirbaseID ):GetZone() + end + + Airbase.ZoneRunways = AIRBASE:FindByName(AirbaseID):GetRunways() + Airbase.Monitor = self.AirbaseList and false or true -- When AirbaseList is not given, monitor every Airbase, otherwise don't monitor any (yet). + end + + -- Now activate the monitoring for the airbases that need to be monitored. + for AirbaseID, AirbaseName in pairs( self.AirbaseList or {} ) do + self.Airbases[AirbaseName].Monitor = true + end + + self.SetClient:ForEachClient( + --- @param Wrapper.Client#CLIENT Client + 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 + ) + + -- This is simple slot blocker is used on the server. + SSB = USERFLAG:New( "SSB" ) + SSB:Set( 100 ) + + -- Kickspeed + self.KickSpeed = UTILS.KnotsToMps(10) + self:SetMaximumKickSpeedMiph(30) + + return self +end + + +--- Add a specific Airbase Boundary if you don't want to use the round zone that is auto-created. +-- @param #ATC_GROUND_UNIVERSAL self +-- @param #string Airbase The name of the Airbase +-- @param Core.Zone#ZONE Zone The ZONE object to be used, e.g. a ZONE_POLYGON +-- @return #ATC_GROUND_UNIVERSAL self +function ATC_GROUND_UNIVERSAL:SetAirbaseBoundaries(Airbase, Zone) + self.Airbases[Airbase].ZoneBoundary = Zone + return self +end + +--- Smoke the airbases runways. +-- @param #ATC_GROUND_UNIVERSAL self +-- @param Utilities.Utils#SMOKECOLOR SmokeColor The color of the smoke around the runways. +-- @return #ATC_GROUND_UNIVERSAL self +function ATC_GROUND_UNIVERSAL:SmokeRunways( SmokeColor ) + + local SmokeColor = SmokeColor or SMOKECOLOR.Red + for AirbaseID, Airbase in pairs( self.Airbases ) do + if Airbase.ZoneRunways then + for _,_runwaydata in pairs (Airbase.ZoneRunways) do + local runwaydata = _runwaydata -- Wrapper.Airbase#AIRBASE.Runway + runwaydata.zone:SmokeZone(SmokeColor) + end + end + end + + return self +end + +--- Draw the airbases runways. +-- @param #ATC_GROUND_UNIVERSAL self +-- @param #table Color The color of the line around the runways, in RGB, e.g `{1,0,0}` for red. +-- @return #ATC_GROUND_UNIVERSAL self +function ATC_GROUND_UNIVERSAL:DrawRunways( Color ) + + local Color = Color or {1,0,0} + for AirbaseID, Airbase in pairs( self.Airbases ) do + if Airbase.ZoneRunways then + for _,_runwaydata in pairs (Airbase.ZoneRunways) do + local runwaydata = _runwaydata -- Wrapper.Airbase#AIRBASE.Runway + runwaydata.zone:DrawZone(-1,Color) + end + end + end + + return self +end + +--- Draw the airbases boundaries. +-- @param #ATC_GROUND_UNIVERSAL self +-- @param #table Color The color of the line around the runways, in RGB, e.g `{1,0,0}` for red. +-- @return #ATC_GROUND_UNIVERSAL self +function ATC_GROUND_UNIVERSAL:DrawBoundaries( Color ) + + local Color = Color or {1,0,0} + for AirbaseID, Airbase in pairs( self.Airbases ) do + if Airbase.ZoneBoundary then + Airbase.ZoneBoundary:DrawZone(-1, Color) + end + end + + return self +end + +--- Set the maximum speed in meters per second (Mps) until the player gets kicked. +-- An airbase can be specified to set the kick speed for. +-- @param #ATC_GROUND_UNIVERSAL self +-- @param #number KickSpeed The speed in Mps. +-- @param #string Airbase (optional) The airbase name to set the kick speed for. +-- @return #ATC_GROUND_UNIVERSAL self +-- @usage +-- +-- -- Declare Atc_Ground +-- +-- Atc_Ground = ATC_GROUND_UNIVERSAL:New() +-- +-- -- Then use one of these methods... +-- +-- Atc_Ground:SetKickSpeed( UTILS.KmphToMps( 80 ) ) -- Kick the players at 80 kilometers per hour +-- +-- Atc_Ground:SetKickSpeed( UTILS.MiphToMps( 100 ) ) -- Kick the players at 100 miles per hour +-- +-- Atc_Ground:SetKickSpeed( 24 ) -- Kick the players at 24 meters per second ( 24 * 3.6 = 86.4 kilometers per hour ) +-- +function ATC_GROUND_UNIVERSAL:SetKickSpeed( KickSpeed, Airbase ) + + if not Airbase then + self.KickSpeed = KickSpeed + else + self.Airbases[Airbase].KickSpeed = KickSpeed + end + + return self +end + +--- Set the maximum speed in Kmph until the player gets kicked. +-- @param #ATC_GROUND_UNIVERSAL self +-- @param #number KickSpeed Set the speed in Kmph. +-- @param #string Airbase (optional) The airbase name to set the kick speed for. +-- @return #ATC_GROUND_UNIVERSAL self +-- +-- Atc_Ground:SetKickSpeedKmph( 80 ) -- Kick the players at 80 kilometers per hour +-- +function ATC_GROUND_UNIVERSAL:SetKickSpeedKmph( KickSpeed, Airbase ) + + self:SetKickSpeed( UTILS.KmphToMps( KickSpeed ), Airbase ) + + return self +end + +--- Set the maximum speed in Miph until the player gets kicked. +-- @param #ATC_GROUND_UNIVERSAL self +-- @param #number KickSpeedMiph Set the speed in Mph. +-- @param #string Airbase (optional) The airbase name to set the kick speed for. +-- @return #ATC_GROUND_UNIVERSAL self +-- +-- Atc_Ground:SetKickSpeedMiph( 100 ) -- Kick the players at 100 miles per hour +-- +function ATC_GROUND_UNIVERSAL:SetKickSpeedMiph( KickSpeedMiph, Airbase ) + + self:SetKickSpeed( UTILS.MiphToMps( KickSpeedMiph ), Airbase ) + + return self +end + + +--- Set the maximum kick speed in meters per second (Mps) until the player gets kicked. +-- There are no warnings given if this speed is reached, and is to prevent players to take off from the airbase! +-- An airbase can be specified to set the maximum kick speed for. +-- @param #ATC_GROUND_UNIVERSAL self +-- @param #number MaximumKickSpeed The speed in Mps. +-- @param #string Airbase (optional) The airbase name to set the kick speed for. +-- @return #ATC_GROUND_UNIVERSAL self +-- @usage +-- +-- -- Declare Atc_Ground +-- +-- Atc_Ground = ATC_GROUND_UNIVERSAL:New() +-- +-- -- Then use one of these methods... +-- +-- Atc_Ground:SetMaximumKickSpeed( UTILS.KmphToMps( 80 ) ) -- Kick the players at 80 kilometers per hour +-- +-- Atc_Ground:SetMaximumKickSpeed( UTILS.MiphToMps( 100 ) ) -- Kick the players at 100 miles per hour +-- +-- Atc_Ground:SetMaximumKickSpeed( 24 ) -- Kick the players at 24 meters per second ( 24 * 3.6 = 86.4 kilometers per hour ) +-- +function ATC_GROUND_UNIVERSAL:SetMaximumKickSpeed( MaximumKickSpeed, Airbase ) + + if not Airbase then + self.MaximumKickSpeed = MaximumKickSpeed + else + self.Airbases[Airbase].MaximumKickSpeed = MaximumKickSpeed + end + + return self +end + +--- Set the maximum kick speed in kilometers per hour (Kmph) until the player gets kicked. +-- There are no warnings given if this speed is reached, and is to prevent players to take off from the airbase! +-- An airbase can be specified to set the maximum kick speed for. +-- @param #ATC_GROUND_UNIVERSAL self +-- @param #number MaximumKickSpeed Set the speed in Kmph. +-- @param #string Airbase (optional) The airbase name to set the kick speed for. +-- @return #ATC_GROUND_UNIVERSAL self +-- +-- Atc_Ground:SetMaximumKickSpeedKmph( 150 ) -- Kick the players at 150 kilometers per hour +-- +function ATC_GROUND_UNIVERSAL:SetMaximumKickSpeedKmph( MaximumKickSpeed, Airbase ) + + self:SetMaximumKickSpeed( UTILS.KmphToMps( MaximumKickSpeed ), Airbase ) + + return self +end + +--- Set the maximum kick speed in miles per hour (Miph) until the player gets kicked. +-- There are no warnings given if this speed is reached, and is to prevent players to take off from the airbase! +-- An airbase can be specified to set the maximum kick speed for. +-- @param #ATC_GROUND_UNIVERSAL self +-- @param #number MaximumKickSpeedMiph Set the speed in Mph. +-- @param #string Airbase (optional) The airbase name to set the kick speed for. +-- @return #ATC_GROUND_UNIVERSAL self +-- +-- Atc_Ground:SetMaximumKickSpeedMiph( 100 ) -- Kick the players at 100 miles per hour +-- +function ATC_GROUND_UNIVERSAL:SetMaximumKickSpeedMiph( MaximumKickSpeedMiph, Airbase ) + + self:SetMaximumKickSpeed( UTILS.MiphToMps( MaximumKickSpeedMiph ), Airbase ) + + return self +end + +--- [Internal] Monitoring function +-- @param #ATC_GROUND_UNIVERSAL self +-- @return #ATC_GROUND_UNIVERSAL self +function ATC_GROUND_UNIVERSAL:_AirbaseMonitor() + + self.SetClient:ForEachClient( + --- @param Wrapper.Client#CLIENT Client + 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 + + if AirbaseMeta.ZoneRunways then + for _,_runwaydata in pairs (AirbaseMeta.ZoneRunways) do + local runwaydata = _runwaydata -- Wrapper.Airbase#AIRBASE.Runway + NotInRunwayZone = ( Client:IsNotInZone( _runwaydata.zone ) == true ) and NotInRunwayZone or false + end + end + + if NotInRunwayZone then + + if IsOnGround then + local Taxi = Client:GetState( self, "Taxi" ) + self: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 + + -- TODO: GetVelocityKMH function usage + local Velocity = VELOCITY_POSITIONABLE:New( Client ) + --MESSAGE:New( "Velocity = " .. Velocity:ToString(), 1 ):ToAll() + local IsAboveRunway = Client:IsAboveRunway() + self:T( {IsAboveRunway, IsOnGround, Velocity:Get() }) + + if IsOnGround then + local Speeding = false + if AirbaseMeta.MaximumKickSpeed then + if Velocity:Get() > AirbaseMeta.MaximumKickSpeed then + Speeding = true + end + else + if Velocity:Get() > self.MaximumKickSpeed then + Speeding = true + end + end + if Speeding == true then + MESSAGE:New( "Penalty! Player " .. Client:GetPlayerName() .. + " has been kicked, due to a severe airbase traffic rule violation ...", 10, "ATC" ):ToAll() + Client:Destroy() + Client:SetState( self, "Speeding", false ) + Client:SetState( self, "Warnings", 0 ) + end + end + + + if IsOnGround then + + local Speeding = false + if AirbaseMeta.KickSpeed then -- If there is a speed defined for the airbase, use that only. + 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() + --- @param Wrapper.Client#CLIENT Client + 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() + --- @param Wrapper.Client#CLIENT Client + 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 + +--- Start SCHEDULER for ATC_GROUND_UNIVERSAL object. +-- @param #ATC_GROUND_UNIVERSAL self +-- @param RepeatScanSeconds Time in second for defining occurency of alerts. +-- @return #ATC_GROUND_UNIVERSAL self +function ATC_GROUND_UNIVERSAL:Start( RepeatScanSeconds ) + RepeatScanSeconds = RepeatScanSeconds or 0.05 + self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, 2, RepeatScanSeconds ) + return self +end --- @type ATC_GROUND_CAUCASUS -- @extends #ATC_GROUND ---- # ATC\_GROUND\_CAUCASUS, extends @{#ATC_GROUND} +--- # ATC\_GROUND\_CAUCASUS, extends @{#ATC_GROUND_UNIVERSAL} -- -- The ATC\_GROUND\_CAUCASUS class monitors the speed of the airplanes at the airbase during taxi. -- The pilots may not drive faster than the maximum speed for the airbase, or they will be despawned. @@ -514,269 +957,6 @@ end -- @field #ATC_GROUND_CAUCASUS 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,}, - }, - }, - }, - }, } --- Creates a new ATC_GROUND_CAUCASUS object. @@ -786,216 +966,11 @@ ATC_GROUND_CAUCASUS = { function ATC_GROUND_CAUCASUS:New( AirbaseNames ) -- Inherits from BASE - local self = BASE:Inherit( self, ATC_GROUND:New( self.Airbases, AirbaseNames ) ) + local self = BASE:Inherit( self, ATC_GROUND_UNIVERSAL:New(AirbaseNames) ) self:SetKickSpeedKmph( 50 ) self:SetMaximumKickSpeedKmph( 150 ) - -- -- AnapaVityazevo - -- local AnapaVityazevoBoundary = GROUP:FindByName( "AnapaVityazevo Boundary" ) - -- self.Airbases.AnapaVityazevo.ZoneBoundary = ZONE_POLYGON:New( "AnapaVityazevo Boundary", AnapaVityazevoBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local AnapaVityazevoRunway1 = GROUP:FindByName( "AnapaVityazevo Runway 1" ) - -- self.Airbases.AnapaVityazevo.ZoneRunways[1] = ZONE_POLYGON:New( "AnapaVityazevo Runway 1", AnapaVityazevoRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Batumi - -- local BatumiBoundary = GROUP:FindByName( "Batumi Boundary" ) - -- self.Airbases.Batumi.ZoneBoundary = ZONE_POLYGON:New( "Batumi Boundary", BatumiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local BatumiRunway1 = GROUP:FindByName( "Batumi Runway 1" ) - -- self.Airbases.Batumi.ZoneRunways[1] = ZONE_POLYGON:New( "Batumi Runway 1", BatumiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Beslan - -- local BeslanBoundary = GROUP:FindByName( "Beslan Boundary" ) - -- self.Airbases.Beslan.ZoneBoundary = ZONE_POLYGON:New( "Beslan Boundary", BeslanBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local BeslanRunway1 = GROUP:FindByName( "Beslan Runway 1" ) - -- self.Airbases.Beslan.ZoneRunways[1] = ZONE_POLYGON:New( "Beslan Runway 1", BeslanRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Gelendzhik - -- local GelendzhikBoundary = GROUP:FindByName( "Gelendzhik Boundary" ) - -- self.Airbases.Gelendzhik.ZoneBoundary = ZONE_POLYGON:New( "Gelendzhik Boundary", GelendzhikBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local GelendzhikRunway1 = GROUP:FindByName( "Gelendzhik Runway 1" ) - -- self.Airbases.Gelendzhik.ZoneRunways[1] = ZONE_POLYGON:New( "Gelendzhik Runway 1", GelendzhikRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Gudauta - -- local GudautaBoundary = GROUP:FindByName( "Gudauta Boundary" ) - -- self.Airbases.Gudauta.ZoneBoundary = ZONE_POLYGON:New( "Gudauta Boundary", GudautaBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local GudautaRunway1 = GROUP:FindByName( "Gudauta Runway 1" ) - -- self.Airbases.Gudauta.ZoneRunways[1] = ZONE_POLYGON:New( "Gudauta Runway 1", GudautaRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Kobuleti - -- local KobuletiBoundary = GROUP:FindByName( "Kobuleti Boundary" ) - -- self.Airbases.Kobuleti.ZoneBoundary = ZONE_POLYGON:New( "Kobuleti Boundary", KobuletiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local KobuletiRunway1 = GROUP:FindByName( "Kobuleti Runway 1" ) - -- self.Airbases.Kobuleti.ZoneRunways[1] = ZONE_POLYGON:New( "Kobuleti Runway 1", KobuletiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- KrasnodarCenter - -- local KrasnodarCenterBoundary = GROUP:FindByName( "KrasnodarCenter Boundary" ) - -- self.Airbases.KrasnodarCenter.ZoneBoundary = ZONE_POLYGON:New( "KrasnodarCenter Boundary", KrasnodarCenterBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local KrasnodarCenterRunway1 = GROUP:FindByName( "KrasnodarCenter Runway 1" ) - -- self.Airbases.KrasnodarCenter.ZoneRunways[1] = ZONE_POLYGON:New( "KrasnodarCenter Runway 1", KrasnodarCenterRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- KrasnodarPashkovsky - -- local KrasnodarPashkovskyBoundary = GROUP:FindByName( "KrasnodarPashkovsky Boundary" ) - -- self.Airbases.KrasnodarPashkovsky.ZoneBoundary = ZONE_POLYGON:New( "KrasnodarPashkovsky Boundary", KrasnodarPashkovskyBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local KrasnodarPashkovskyRunway1 = GROUP:FindByName( "KrasnodarPashkovsky Runway 1" ) - -- self.Airbases.KrasnodarPashkovsky.ZoneRunways[1] = ZONE_POLYGON:New( "KrasnodarPashkovsky Runway 1", KrasnodarPashkovskyRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- local KrasnodarPashkovskyRunway2 = GROUP:FindByName( "KrasnodarPashkovsky Runway 2" ) - -- self.Airbases.KrasnodarPashkovsky.ZoneRunways[2] = ZONE_POLYGON:New( "KrasnodarPashkovsky Runway 2", KrasnodarPashkovskyRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Krymsk - -- local KrymskBoundary = GROUP:FindByName( "Krymsk Boundary" ) - -- self.Airbases.Krymsk.ZoneBoundary = ZONE_POLYGON:New( "Krymsk Boundary", KrymskBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local KrymskRunway1 = GROUP:FindByName( "Krymsk Runway 1" ) - -- self.Airbases.Krymsk.ZoneRunways[1] = ZONE_POLYGON:New( "Krymsk Runway 1", KrymskRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Kutaisi - -- local KutaisiBoundary = GROUP:FindByName( "Kutaisi Boundary" ) - -- self.Airbases.Kutaisi.ZoneBoundary = ZONE_POLYGON:New( "Kutaisi Boundary", KutaisiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local KutaisiRunway1 = GROUP:FindByName( "Kutaisi Runway 1" ) - -- self.Airbases.Kutaisi.ZoneRunways[1] = ZONE_POLYGON:New( "Kutaisi Runway 1", KutaisiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- MaykopKhanskaya - -- local MaykopKhanskayaBoundary = GROUP:FindByName( "MaykopKhanskaya Boundary" ) - -- self.Airbases.MaykopKhanskaya.ZoneBoundary = ZONE_POLYGON:New( "MaykopKhanskaya Boundary", MaykopKhanskayaBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local MaykopKhanskayaRunway1 = GROUP:FindByName( "MaykopKhanskaya Runway 1" ) - -- self.Airbases.MaykopKhanskaya.ZoneRunways[1] = ZONE_POLYGON:New( "MaykopKhanskaya Runway 1", MaykopKhanskayaRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- MineralnyeVody - -- local MineralnyeVodyBoundary = GROUP:FindByName( "MineralnyeVody Boundary" ) - -- self.Airbases.MineralnyeVody.ZoneBoundary = ZONE_POLYGON:New( "MineralnyeVody Boundary", MineralnyeVodyBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local MineralnyeVodyRunway1 = GROUP:FindByName( "MineralnyeVody Runway 1" ) - -- self.Airbases.MineralnyeVody.ZoneRunways[1] = ZONE_POLYGON:New( "MineralnyeVody Runway 1", MineralnyeVodyRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Mozdok - -- local MozdokBoundary = GROUP:FindByName( "Mozdok Boundary" ) - -- self.Airbases.Mozdok.ZoneBoundary = ZONE_POLYGON:New( "Mozdok Boundary", MozdokBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local MozdokRunway1 = GROUP:FindByName( "Mozdok Runway 1" ) - -- self.Airbases.Mozdok.ZoneRunways[1] = ZONE_POLYGON:New( "Mozdok Runway 1", MozdokRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Nalchik - -- local NalchikBoundary = GROUP:FindByName( "Nalchik Boundary" ) - -- self.Airbases.Nalchik.ZoneBoundary = ZONE_POLYGON:New( "Nalchik Boundary", NalchikBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local NalchikRunway1 = GROUP:FindByName( "Nalchik Runway 1" ) - -- self.Airbases.Nalchik.ZoneRunways[1] = ZONE_POLYGON:New( "Nalchik Runway 1", NalchikRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Novorossiysk - -- local NovorossiyskBoundary = GROUP:FindByName( "Novorossiysk Boundary" ) - -- self.Airbases.Novorossiysk.ZoneBoundary = ZONE_POLYGON:New( "Novorossiysk Boundary", NovorossiyskBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local NovorossiyskRunway1 = GROUP:FindByName( "Novorossiysk Runway 1" ) - -- self.Airbases.Novorossiysk.ZoneRunways[1] = ZONE_POLYGON:New( "Novorossiysk Runway 1", NovorossiyskRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- SenakiKolkhi - -- local SenakiKolkhiBoundary = GROUP:FindByName( "SenakiKolkhi Boundary" ) - -- self.Airbases.SenakiKolkhi.ZoneBoundary = ZONE_POLYGON:New( "SenakiKolkhi Boundary", SenakiKolkhiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local SenakiKolkhiRunway1 = GROUP:FindByName( "SenakiKolkhi Runway 1" ) - -- self.Airbases.SenakiKolkhi.ZoneRunways[1] = ZONE_POLYGON:New( "SenakiKolkhi Runway 1", SenakiKolkhiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- SochiAdler - -- local SochiAdlerBoundary = GROUP:FindByName( "SochiAdler Boundary" ) - -- self.Airbases.SochiAdler.ZoneBoundary = ZONE_POLYGON:New( "SochiAdler Boundary", SochiAdlerBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local SochiAdlerRunway1 = GROUP:FindByName( "SochiAdler Runway 1" ) - -- self.Airbases.SochiAdler.ZoneRunways[1] = ZONE_POLYGON:New( "SochiAdler Runway 1", SochiAdlerRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- local SochiAdlerRunway2 = GROUP:FindByName( "SochiAdler Runway 2" ) - -- self.Airbases.SochiAdler.ZoneRunways[2] = ZONE_POLYGON:New( "SochiAdler Runway 2", SochiAdlerRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Soganlug - -- local SoganlugBoundary = GROUP:FindByName( "Soganlug Boundary" ) - -- self.Airbases.Soganlug.ZoneBoundary = ZONE_POLYGON:New( "Soganlug Boundary", SoganlugBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local SoganlugRunway1 = GROUP:FindByName( "Soganlug Runway 1" ) - -- self.Airbases.Soganlug.ZoneRunways[1] = ZONE_POLYGON:New( "Soganlug Runway 1", SoganlugRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- SukhumiBabushara - -- local SukhumiBabusharaBoundary = GROUP:FindByName( "SukhumiBabushara Boundary" ) - -- self.Airbases.SukhumiBabushara.ZoneBoundary = ZONE_POLYGON:New( "SukhumiBabushara Boundary", SukhumiBabusharaBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local SukhumiBabusharaRunway1 = GROUP:FindByName( "SukhumiBabushara Runway 1" ) - -- self.Airbases.SukhumiBabushara.ZoneRunways[1] = ZONE_POLYGON:New( "SukhumiBabushara Runway 1", SukhumiBabusharaRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- TbilisiLochini - -- local TbilisiLochiniBoundary = GROUP:FindByName( "TbilisiLochini Boundary" ) - -- self.Airbases.TbilisiLochini.ZoneBoundary = ZONE_POLYGON:New( "TbilisiLochini Boundary", TbilisiLochiniBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local TbilisiLochiniRunway1 = GROUP:FindByName( "TbilisiLochini Runway 1" ) - -- self.Airbases.TbilisiLochini.ZoneRunways[1] = ZONE_POLYGON:New( "TbilisiLochini Runway 1", TbilisiLochiniRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- local TbilisiLochiniRunway2 = GROUP:FindByName( "TbilisiLochini Runway 2" ) - -- self.Airbases.TbilisiLochini.ZoneRunways[2] = ZONE_POLYGON:New( "TbilisiLochini Runway 2", TbilisiLochiniRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Vaziani - -- local VazianiBoundary = GROUP:FindByName( "Vaziani Boundary" ) - -- self.Airbases.Vaziani.ZoneBoundary = ZONE_POLYGON:New( "Vaziani Boundary", VazianiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local VazianiRunway1 = GROUP:FindByName( "Vaziani Runway 1" ) - -- self.Airbases.Vaziani.ZoneRunways[1] = ZONE_POLYGON:New( "Vaziani Runway 1", VazianiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - - - -- Template - -- local TemplateBoundary = GROUP:FindByName( "Template Boundary" ) - -- self.Airbases.Template.ZoneBoundary = ZONE_POLYGON:New( "Template Boundary", TemplateBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local TemplateRunway1 = GROUP:FindByName( "Template Runway 1" ) - -- self.Airbases.Template.ZoneRunways[1] = ZONE_POLYGON:New( "Template Runway 1", TemplateRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - return self end @@ -1122,257 +1097,6 @@ end -- @field #ATC_GROUND_NEVADA 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] = { - 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,}, - }, - }, - }, - }, } --- Creates a new ATC_GROUND_NEVADA object. @@ -1382,168 +1106,11 @@ ATC_GROUND_NEVADA = { function ATC_GROUND_NEVADA:New( AirbaseNames ) -- Inherits from BASE - local self = BASE:Inherit( self, ATC_GROUND:New( self.Airbases, AirbaseNames ) ) + local self = BASE:Inherit( self, ATC_GROUND_UNIVERSAL:New( AirbaseNames ) ) self:SetKickSpeedKmph( 50 ) self:SetMaximumKickSpeedKmph( 150 ) - -- These lines here are for the demonstration mission. - -- They create in the dcs.log the coordinates of the runway polygons, that are then - -- taken by the moose designer from the dcs.log and reworked to define the - -- Airbases structure, which is part of the class. - -- When new airbases are added or airbases are changed on the map, - -- the MOOSE designer willde-comment this section and apply the changes in the demo - -- mission, and do a re-run to create a new dcs.log, and then add the changed coordinates - -- in the Airbases structure. - -- So, this needs to stay commented normally once a map has been finished. - - --[[ - - -- Beatty - do - local VillagePrefix = "Beatty" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - -- Boulder - do - local VillagePrefix = "Boulder" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) - local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - -- Creech - do - local VillagePrefix = "Creech" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) - local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - -- Echo - do - local VillagePrefix = "Echo" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - -- Groom Lake - do - local VillagePrefix = "GroomLake" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) - local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - -- Henderson - do - local VillagePrefix = "Henderson" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) - local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - -- Jean - do - local VillagePrefix = "Jean" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) - local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - -- Laughlin - do - local VillagePrefix = "Laughlin" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - -- Lincoln - do - local VillagePrefix = "Lincoln" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - -- McCarran - do - local VillagePrefix = "McCarran" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) - local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - local Runway3 = GROUP:FindByName( VillagePrefix .. " 3" ) - local Zone3 = ZONE_POLYGON:New( VillagePrefix .. " 3", Runway3 ):SmokeZone(SMOKECOLOR.Red):Flush() - local Runway4 = GROUP:FindByName( VillagePrefix .. " 4" ) - local Zone4 = ZONE_POLYGON:New( VillagePrefix .. " 4", Runway4 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - -- Mesquite - do - local VillagePrefix = "Mesquite" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - -- Mina - do - local VillagePrefix = "Mina" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - -- Nellis - do - local VillagePrefix = "Nellis" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) - local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - -- Pahute - do - local VillagePrefix = "Pahute" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - -- TonopahTR - do - local VillagePrefix = "TonopahTR" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - -- Tonopah - do - local VillagePrefix = "Tonopah" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) - local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - -- Vegas - do - local VillagePrefix = "Vegas" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) - local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - local Runway3 = GROUP:FindByName( VillagePrefix .. " 3" ) - local Zone3 = ZONE_POLYGON:New( VillagePrefix .. " 3", Runway3 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - --]] - return self end @@ -1684,440 +1251,7 @@ end -- -- @field #ATC_GROUND_NORMANDY 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,}, - }, - }, - }, - }, + ClassName = "ATC_GROUND_NORMANDY", } @@ -2128,285 +1262,10 @@ ATC_GROUND_NORMANDY = { function ATC_GROUND_NORMANDY:New( AirbaseNames ) -- Inherits from BASE - local self = BASE:Inherit( self, ATC_GROUND:New( self.Airbases, AirbaseNames ) ) -- #ATC_GROUND_NORMANDY + local self = BASE:Inherit( self, ATC_GROUND_UNIVERSAL:New( AirbaseNames ) ) -- #ATC_GROUND_NORMANDY self:SetKickSpeedKmph( 40 ) self:SetMaximumKickSpeedKmph( 100 ) - - -- These lines here are for the demonstration mission. - -- They create in the dcs.log the coordinates of the runway polygons, that are then - -- taken by the moose designer from the dcs.log and reworked to define the - -- Airbases structure, which is part of the class. - -- When new airbases are added or airbases are changed on the map, - -- the MOOSE designer willde-comment this section and apply the changes in the demo - -- mission, and do a re-run to create a new dcs.log, and then add the changed coordinates - -- in the Airbases structure. - -- So, this needs to stay commented normally once a map has been finished. - - --[[ - - -- Azeville - do - local VillagePrefix = "Azeville" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Bazenville - do - local VillagePrefix = "Bazenville" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Beny - do - local VillagePrefix = "Beny" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Beuzeville - do - local VillagePrefix = "Beuzeville" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Biniville - do - local VillagePrefix = "Biniville" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Brucheville - do - local VillagePrefix = "Brucheville" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Cardonville - do - local VillagePrefix = "Cardonville" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Carpiquet - do - local VillagePrefix = "Carpiquet" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Chailey - do - local VillagePrefix = "Chailey" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) - local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Chippelle - do - local VillagePrefix = "Chippelle" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Cretteville - do - local VillagePrefix = "Cretteville" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Cricqueville - do - local VillagePrefix = "Cricqueville" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Deux - do - local VillagePrefix = "Deux" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Evreux - do - local VillagePrefix = "Evreux" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) - local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Ford - do - local VillagePrefix = "Ford" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) - local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Funtington - do - local VillagePrefix = "Funtington" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) - local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Lantheuil - do - local VillagePrefix = "Lantheuil" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Lessay - do - local VillagePrefix = "Lessay" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) - local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Lignerolles - do - local VillagePrefix = "Lignerolles" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Longues - do - local VillagePrefix = "Longues" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Maupertus - do - local VillagePrefix = "Maupertus" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Meautis - do - local VillagePrefix = "Meautis" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Molay - do - local VillagePrefix = "Molay" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Oar - do - local VillagePrefix = "Oar" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) - local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Picauville - do - local VillagePrefix = "Picauville" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Rucqueville - do - local VillagePrefix = "Rucqueville" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- SaintPierre - do - local VillagePrefix = "SaintPierre" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- SainteCroix - do - local VillagePrefix = "SainteCroix" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - --SainteLaurent - do - local VillagePrefix = "SainteLaurent" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Sommervieu - do - local VillagePrefix = "Sommervieu" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Tangmere - do - local VillagePrefix = "Tangmere" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) - local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - --]] return self end @@ -2538,452 +1397,8 @@ end -- @field #ATC_GROUND_PERSIANGULF 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,}, - }, - }, - }, - }, } - --- Creates a new ATC_GROUND_PERSIANGULF object. -- @param #ATC_GROUND_PERSIANGULF self -- @param AirbaseNames A list {} of airbase names (Use AIRBASE.PersianGulf enumerator). @@ -2991,266 +1406,11 @@ ATC_GROUND_PERSIANGULF = { function ATC_GROUND_PERSIANGULF:New( AirbaseNames ) -- Inherits from BASE - local self = BASE:Inherit( self, ATC_GROUND:New( self.Airbases, AirbaseNames ) ) -- #ATC_GROUND_PERSIANGULF + local self = BASE:Inherit( self, ATC_GROUND_UNIVERSAL:New( AirbaseNames ) ) -- #ATC_GROUND_PERSIANGULF self:SetKickSpeedKmph( 50 ) self:SetMaximumKickSpeedKmph( 150 ) - - -- These lines here are for the demonstration mission. - -- They create in the dcs.log the coordinates of the runway polygons, that are then - -- taken by the moose designer from the dcs.log and reworked to define the - -- Airbases structure, which is part of the class. - -- When new airbases are added or airbases are changed on the map, - -- the MOOSE designer willde-comment this section and apply the changes in the demo - -- mission, and do a re-run to create a new dcs.log, and then add the changed coordinates - -- in the Airbases structure. - -- So, this needs to stay commented normally once a map has been finished. - - --[[ - - -- Abu_Musa_Island_Airport - do - local VillagePrefix = "Abu_Musa_Island_Airport" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Al_Dhafra_AB - do - local VillagePrefix = "Al_Dhafra_AB" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) - local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Al_Maktoum_Intl - do - local VillagePrefix = "Al_Maktoum_Intl" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Al_Minhad_AB - do - local VillagePrefix = "Al_Minhad_AB" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Bandar_Abbas_Intl - do - local VillagePrefix = "Bandar_Abbas_Intl" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) - local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Bandar_Lengeh - do - local VillagePrefix = "Bandar_Lengeh" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Dubai_Intl - do - local VillagePrefix = "Dubai_Intl" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) - local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Fujairah_Intl - do - local VillagePrefix = "Fujairah_Intl" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Havadarya - do - local VillagePrefix = "Havadarya" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Kerman_Airport - do - local VillagePrefix = "Kerman_Airport" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Khasab - do - local VillagePrefix = "Khasab" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Lar_Airbase - do - local VillagePrefix = "Lar_Airbase" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Qeshm_Island - do - local VillagePrefix = "Qeshm_Island" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Sharjah_Intl - do - local VillagePrefix = "Sharjah_Intl" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) - local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Shiraz_International_Airport - do - local VillagePrefix = "Shiraz_International_Airport" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) - local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Sir_Abu_Nuayr - do - local VillagePrefix = "Sir_Abu_Nuayr" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Sirri_Island - do - local VillagePrefix = "Sirri_Island" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Tunb_Island_AFB - do - local VillagePrefix = "Tunb_Island_AFB" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Tunb_Kochak - do - local VillagePrefix = "Tunb_Kochak" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Sas_Al_Nakheel_Airport - do - local VillagePrefix = "Sas_Al_Nakheel_Airport" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Bandar_e_Jask_airfield - do - local VillagePrefix = "Bandar_e_Jask_airfield" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Abu_Dhabi_International_Airport - do - local VillagePrefix = "Abu_Dhabi_International_Airport" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) - local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Al_Bateen_Airport - do - local VillagePrefix = "Al_Bateen_Airport" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Kish_International_Airport - do - local VillagePrefix = "Kish_International_Airport" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) - local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Al_Ain_International_Airport - do - local VillagePrefix = "Al_Ain_International_Airport" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Lavan_Island_Airport - do - local VillagePrefix = "Lavan_Island_Airport" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Jiroft_Airport - do - local VillagePrefix = "Jiroft_Airport" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - - -- Bandar_Abbas_Intl - do - local VillagePrefix = "Bandar_Abbas_Intl" - local Runway1 = GROUP:FindByName( VillagePrefix .. " 1" ) - local Zone1 = ZONE_POLYGON:New( VillagePrefix .. " 1", Runway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - local Runway2 = GROUP:FindByName( VillagePrefix .. " 2" ) - local Zone2 = ZONE_POLYGON:New( VillagePrefix .. " 2", Runway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - end - - --]] - - return self end --- Start SCHEDULER for ATC_GROUND_PERSIANGULF object. @@ -3263,5 +1423,125 @@ function ATC_GROUND_PERSIANGULF:Start( RepeatScanSeconds ) end + --- @type ATC_GROUND_MARIANAISLANDS +-- @extends #ATC_GROUND + +--- # ATC\_GROUND\_MARIANA, extends @{#ATC_GROUND} +-- +-- The ATC\_GROUND\_MARIANA class monitors the speed of the airplanes at the airbase during taxi. +-- The pilots may not drive faster than the maximum speed for the airbase, or they will be despawned. +-- +-- --- +-- +-- ![Banner Image](..\Presentations\ATC_GROUND\Dia1.JPG) +-- +-- --- +-- +-- The default maximum speed for the airbases at Persian Gulf is **50 km/h**. Warnings are given if this speed limit is trespassed. +-- Players will be immediately kicked when driving faster than **150 km/h** on the taxi way. +-- +-- The ATC\_GROUND\_MARIANA class monitors the speed of the airplanes at the airbase during taxi. +-- The pilots may not drive faster than the maximum speed for the airbase, or they will be despawned. +-- +-- The pilot will receive 3 times a warning during speeding. After the 3rd warning, if the pilot is still driving +-- faster than the maximum allowed speed, the pilot will be kicked. +-- +-- Different airbases have different maximum speeds, according safety regulations. +-- +-- # Airbases monitored +-- +-- The following airbases are monitored at the Mariana Island region. +-- Use the @{Wrapper.Airbase#AIRBASE.MarianaIslands} enumeration to select the airbases to be monitored. +-- +-- * AIRBASE.MarianaIslands.Rota_Intl +-- * AIRBASE.MarianaIslands.Andersen_AFB +-- * AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl +-- * AIRBASE.MarianaIslands.Saipan_Intl +-- * AIRBASE.MarianaIslands.Tinian_Intl +-- * AIRBASE.MarianaIslands.Olf_Orote +-- +-- # Installation +-- +-- ## In Single Player Missions +-- +-- ATC\_GROUND is fully functional in single player. +-- +-- ## In Multi Player Missions +-- +-- ATC\_GROUND is functional in multi player, however ... +-- +-- Due to a bug in DCS since release 1.5, the despawning of clients are not anymore working in multi player. +-- To **work around this problem**, a much better solution has been made, using the **slot blocker** script designed +-- by Ciribob. +-- +-- With the help of __Ciribob__, this script has been extended to also kick client players while in flight. +-- ATC\_GROUND is communicating with this modified script to kick players! +-- +-- Install the file **SimpleSlotBlockGameGUI.lua** on the server, following the installation instructions described by Ciribob. +-- +-- [Simple Slot Blocker from Ciribob & FlightControl](https://github.com/ciribob/DCS-SimpleSlotBlock) +-- +-- # Script it! +-- +-- ## 1. ATC_GROUND_MARIANAISLANDS Constructor +-- +-- Creates a new ATC_GROUND_MARIANAISLANDS object that will monitor pilots taxiing behaviour. +-- +-- -- This creates a new ATC_GROUND_MARIANAISLANDS object. +-- +-- -- Monitor for these clients the airbases. +-- AirbasePoliceCaucasus = ATC_GROUND_MARIANAISLANDS:New() +-- +-- ATC_Ground = ATC_GROUND_MARIANAISLANDS:New( +-- { AIRBASE.MarianaIslands.Andersen_AFB, +-- AIRBASE.MarianaIslands.Saipan_Intl +-- } +-- ) +-- +-- +-- ## 2. Set various options +-- +-- There are various methods that you can use to tweak the behaviour of the ATC\_GROUND classes. +-- +-- ### 2.1 Speed limit at an airbase. +-- +-- * @{#ATC_GROUND.SetKickSpeed}(): Set the speed limit allowed at an airbase in meters per second. +-- * @{#ATC_GROUND.SetKickSpeedKmph}(): Set the speed limit allowed at an airbase in kilometers per hour. +-- * @{#ATC_GROUND.SetKickSpeedMiph}(): Set the speed limit allowed at an airbase in miles per hour. +-- +-- ### 2.2 Prevent Takeoff at an airbase. Players will be kicked immediately. +-- +-- * @{#ATC_GROUND.SetMaximumKickSpeed}(): Set the maximum speed allowed at an airbase in meters per second. +-- * @{#ATC_GROUND.SetMaximumKickSpeedKmph}(): Set the maximum speed allowed at an airbase in kilometers per hour. +-- * @{#ATC_GROUND.SetMaximumKickSpeedMiph}(): Set the maximum speed allowed at an airbase in miles per hour. +-- +---- @field #ATC_GROUND_MARIANAISLANDS +ATC_GROUND_MARIANAISLANDS = { + ClassName = "ATC_GROUND_MARIANAISLANDS", +} + +--- Creates a new ATC_GROUND_MARIANAISLANDS object. +-- @param #ATC_GROUND_MARIANAISLANDS self +-- @param AirbaseNames A list {} of airbase names (Use AIRBASE.MarianaIslands enumerator). +-- @return #ATC_GROUND_MARIANAISLANDS self +function ATC_GROUND_MARIANAISLANDS:New( AirbaseNames ) + + -- Inherits from BASE + local self = BASE:Inherit( self, ATC_GROUND_UNIVERSAL:New( self.Airbases, AirbaseNames ) ) + + self:SetKickSpeedKmph( 50 ) + self:SetMaximumKickSpeedKmph( 150 ) + + return self +end + +--- Start SCHEDULER for ATC_GROUND_MARIANAISLANDS object. +-- @param #ATC_GROUND_MARIANAISLANDS self +-- @param RepeatScanSeconds Time in second for defining occurency of alerts. +-- @return nothing +function ATC_GROUND_MARIANAISLANDS:Start( RepeatScanSeconds ) + RepeatScanSeconds = RepeatScanSeconds or 0.05 + self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, 2, RepeatScanSeconds ) +end From 73994914eb5d227dc4c4c417d3ac4fdcfb71c75e Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 22 Aug 2022 17:03:21 +0200 Subject: [PATCH 185/200] Fix for #1763 UTILS.GetOSTime() (#1765) * Fix GetOSTime fixes #1763 --- Moose Development/Moose/Utilities/Utils.lua | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 31f868fbc..60e07f9dc 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1724,11 +1724,17 @@ end --- Get OS time. Needs os to be desanitized! -- @return #number Os time in seconds. function UTILS.GetOSTime() - if os then - return os.clock() - end - - return nil + if os then + local ts = 0 + local t = os.date("*t") + local s = t.sec + local m = t.min * 60 + local h = t.hour * 3600 + ts = s+m+h + return ts + else + return nil + end end --- Shuffle a table accoring to Fisher Yeates algorithm From d15c2be2d0054fff5f776ca132bc9618ac26ad56 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 22 Aug 2022 17:03:33 +0200 Subject: [PATCH 186/200] Range fixes (#1764) * Fixed some typos in self.PlayerSettings * Fixed result messages * Thanks for [JFG] Caponi --- Moose Development/Moose/Functional/Range.lua | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index e7aa20889..938f7e891 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -2381,7 +2381,7 @@ function RANGE:_DisplayMyStrafePitResults( _unitName ) -- Sort results table wrt number of hits. local _sort = function( a, b ) - return a.hits > b.hits + return a.roundsHit > b.roundsHit end table.sort( _results, _sort ) @@ -2398,7 +2398,7 @@ function RANGE:_DisplayMyStrafePitResults( _unitName ) -- Best result. if _bestMsg == "" then - _bestMsg = string.format( "Hits %d - %s - %s", _result.hits, _result.zone.name, _result.text ) + _bestMsg = string.format( "Hits %d - %s - %s", result.roundsHit, result.name, result.roundsQuality) end -- 10 runs @@ -2443,15 +2443,15 @@ function RANGE:_DisplayStrafePitResults( _unitName ) -- Get the best result of the player. local _best = nil for _, _result in pairs( _results ) do - if _best == nil or _result.hits > _best.hits then + if _best == nil or _result.roundsHit > _best.roundsHit then _best = _result end end -- Add best result to table. 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 } ) + local text = string.format( "%s: Hits %i - %s - %s", _playerName, _best.roundsHit, _best.name, _best.roundsQuality ) + table.insert( _playerResults, { msg = text, hits = _best.roundsHit } ) end end @@ -3487,7 +3487,7 @@ function RANGE:_SmokeBombImpactOnOff( unitname ) 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 + self.PlayerSettings[playername].smokebombimpact = true text = string.format( "%s, %s, smoking impact points of bombs is now ON.", self.rangename, playername ) end self:_DisplayMessageToGroup( unit, text, 5, false, true ) @@ -3508,7 +3508,7 @@ function RANGE:_SmokeBombDelayOnOff( unitname ) 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 + self.PlayerSettings[playername].delaysmoke = true text = string.format( "%s, %s, delayed smoke of bombs is now ON.", self.rangename, playername ) end self:_DisplayMessageToGroup( unit, text, 5, false, true ) From bdbbdfe60ec1268c275aa2f443e0159f9e269c79 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 23 Aug 2022 09:58:50 +0200 Subject: [PATCH 187/200] #RANGE * Added Instructor and RangeControl radio info on F10 --- Moose Development/Moose/Functional/Range.lua | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index 938f7e891..1999747a2 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -2667,6 +2667,26 @@ function RANGE:_DisplayRangeInfo( _unitname ) text = text .. string.format( "Max strafing alt AGL: %s\n", tstrafemaxalt ) text = text .. string.format( "# of strafe targets: %d\n", self.nstrafetargets ) text = text .. string.format( "# of bomb targets: %d\n", self.nbombtargets ) + if self.instructor then + local alive = "N/A" + if self.instructorrelayname then + local relay = UNIT:FindByName( self.instructorrelayname ) + if relay then + alive = tostring( relay:IsAlive() ) + end + end + text = text .. string.format( "Instructor %.3f MHz (Relay=%s)\n", self.instructorfreq, alive ) + end + if self.rangecontrol then + local alive = "N/A" + if self.rangecontrolrelayname then + local relay = UNIT:FindByName( self.rangecontrolrelayname ) + if relay then + alive = tostring( relay:IsAlive() ) + end + end + text = text .. string.format( "Control %.3f MHz (Relay=%s)\n", self.rangecontrolfreq, alive ) + end text = text .. texthit text = text .. textbomb text = text .. textdelay From 3f488cc091371e7f0308f3daa6af79258f407f6e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 25 Aug 2022 10:38:44 +0200 Subject: [PATCH 188/200] #AIRBASE * Added 3 new airports to the enumerator SouthAtlantic --- Moose Development/Moose/Wrapper/Airbase.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 38149634e..dcf5b598b 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -498,6 +498,9 @@ AIRBASE.MarianaIslands = { -- * AIRBASE.SouthAtlantic.Punta_Arenas -- * AIRBASE.SouthAtlantic.Pampa_Guanaco -- * AIRBASE.SouthAtlantic.San_Julian +-- * AIRBASE.SouthAtlantic.Puerto_Williams +-- * AIRBASE.SouthAtlantic.Puerto_Natales +-- * AIRBASE.SouthAtlantic.El_Calafate -- --@field MarianaIslands AIRBASE.SouthAtlantic={ @@ -511,6 +514,9 @@ AIRBASE.SouthAtlantic={ ["Punta_Arenas"]="Punta Arenas", ["Pampa_Guanaco"]="Pampa Guanaco", ["San_Julian"]="San Julian", + ["Puerto_Williams"]="Puerto Williams", + ["Puerto_Natales"]="Puerto Natales", + ["El_Calafate"]="El Calafate", } From aaf77815ca7ec911694e8da281ed81c3bd46a57d Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 25 Aug 2022 10:50:45 +0200 Subject: [PATCH 189/200] #SRS * Added MSRSQUEUE message queue --- Moose Development/Moose/Sound/SRS.lua | 443 ++++++++++++++++++++++---- 1 file changed, 389 insertions(+), 54 deletions(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index cafb49c7c..1c66e30ea 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -52,8 +52,6 @@ -- -- === -- --- ![Banner Image](..\Presentations\ATIS\ATIS_Main.png) --- -- # The MSRS Concept -- -- This class allows to broadcast sound files or text via Simple Radio Standalone (SRS). @@ -143,7 +141,7 @@ MSRS = { --- MSRS class version. -- @field #string version -MSRS.version="0.0.6" +MSRS.version="0.1.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -181,7 +179,7 @@ function MSRS:New(PathToSRS, Frequency, Modulation, Volume) self:SetCoalition() self:SetLabel() self:SetVolume() - self.lid = string.format("%s-%s | ",self.name,self.version) + self.lid = string.format("%s-%s | ", self.name, self.version) if not io or not os then self:E(self.lid.."***** ERROR - io or os NOT desanitized! MSRS will not work!") @@ -455,19 +453,8 @@ function MSRS:PlaySoundFile(Soundfile, Delay) -- Append file. command=command..' --file="'..tostring(soundfile)..'"' + -- Execute command. self:_ExecCommand(command) - - --[[ - - command=command.." > bla.txt" - - -- Debug output. - self:I(string.format("MSRS PlaySoundfile command=%s", command)) - - -- Execute SRS command. - local x=os.execute(command) - - ]] end @@ -493,16 +480,6 @@ function MSRS:PlaySoundText(SoundText, Delay) -- Execute command. self:_ExecCommand(command) - - --[[ - command=command.." > bla.txt" - - -- Debug putput. - self:I(string.format("MSRS PlaySoundfile command=%s", command)) - - -- Execute SRS command. - local x=os.execute(command) - ]] end @@ -529,37 +506,48 @@ function MSRS:PlayText(Text, Delay) -- Execute command. self:_ExecCommand(command) - --[[ - - -- Check that length of command is max 255 chars or os.execute() will not work! - if string.len(command)>255 then - - -- Create a tmp file. - local filename = os.getenv('TMP') .. "\\MSRS-"..STTS.uuid()..".bat" - - local script = io.open(filename, "w+") - script:write(command.." && exit") - script:close() - - -- Play command. - command=string.format("\"%s\"", filename) - - -- Play file in 0.05 seconds - timer.scheduleFunction(os.execute, command, timer.getTime()+0.05) - - -- Remove file in 1 second. - timer.scheduleFunction(os.remove, filename, timer.getTime()+1) - else - - -- Debug output. - self:I(string.format("MSRS Text command=%s", command)) + end - -- Execute SRS command. - local x=os.execute(command) + return self +end + +--- Play text message via STTS with explicitly specified options. +-- @param #MSRS self +-- @param #string Text Text message. +-- @param #number Delay Delay in seconds, before the message is played. +-- @param #table Frequencies Radio frequencies. +-- @param #table Modulations Radio modulations. +-- @param #string Gender Gender. +-- @param #string Culture Culture. +-- @param #string Voice Voice. +-- @param #number Volume Volume. +-- @param #string Label Label. +-- @return #MSRS self +function MSRS:PlayTextExt(Text, Delay, Frequencies, Modulations, Gender, Culture, Voice, Volume, Label) + + if Delay and Delay>0 then + self:ScheduleOnce(Delay, MSRS.PlayTextExt, self, Text, 0, Frequencies, Modulations, Gender, Culture, Voice, Volume, Label) + else + + -- Ensure table. + if Frequencies and type(Frequencies)~="table" then + Frequencies={Frequencies} + end + + -- Ensure table. + if Modulations and type(Modulations)~="table" then + Modulations={Modulations} + end + + -- Get command line. + local command=self:_GetCommand(Frequencies, Modulations, nil, Gender, Voice, Culture, Volume, nil, nil, Label) + + -- Append text. + command=command..string.format(" --text=\"%s\"", tostring(Text)) - end + -- Execute command. + self:_ExecCommand(command) - ]] end return self @@ -760,6 +748,353 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp return command end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Manages radio transmissions. +-- +-- The purpose of the MSRSQUEUE class is to manage SRS text-to-speech (TTS) messages using the MSRS class. +-- This can be used to submit multiple TTS messages and the class takes care that they are transmitted one after the other (and not overlapping). +-- +-- @type MSRSQUEUE +-- @field #string ClassName Name of the class "MSRSQUEUE". +-- @field #string lid ID for dcs.log. +-- @field #table queue The queue of transmissions. +-- @field #string alias Name of the radio queue. +-- @field #number dt Time interval in seconds for checking the radio queue. +-- @field #number Tlast Time (abs) when the last transmission finished. +-- @field #boolean checking If `true`, the queue update function is scheduled to be called again. +-- @extends Core.Base#BASE +MSRSQUEUE = { + ClassName = "MSRSQUEUE", + Debugmode = nil, + lid = nil, + queue = {}, + alias = nil, + dt = nil, + Tlast = nil, + checking = nil, +} + +--- Radio queue transmission data. +-- @type MSRSQUEUE.Transmission +-- @field #string text Text to be transmitted. +-- @field Sound.SRS#MSRS msrs MOOSE SRS object. +-- @field #number duration Duration in seconds. +-- @field #table subgroups Groups to send subtitle to. +-- @field #string subtitle Subtitle of the transmission. +-- @field #number subduration Duration of the subtitle being displayed. +-- @field #number frequency Frequency. +-- @field #number modulation Modulation. +-- @field #number Tstarted Mission time (abs) in seconds when the transmission started. +-- @field #boolean isplaying If true, transmission is currently playing. +-- @field #number Tplay Mission time (abs) in seconds when the transmission should be played. +-- @field #number interval Interval in seconds before next transmission. + +--- Create a new MSRSQUEUE object for a given radio frequency/modulation. +-- @param #MSRSQUEUE self +-- @param #string alias (Optional) Name of the radio queue. +-- @return #MSRSQUEUE self The MSRSQUEUE object. +function MSRSQUEUE:New(alias) + + -- Inherit base + local self=BASE:Inherit(self, BASE:New()) --#MSRSQUEUE + + self.alias=alias or "My Radio" + + self.dt=1.0 + + self.lid=string.format("MSRSQUEUE %s | ", self.alias) + + return self +end + +--- Clear the radio queue. +-- @param #MSRSQUEUE self +-- @return #MSRSQUEUE self The MSRSQUEUE object. +function MSRSQUEUE:Clear() + self:I(self.lid.."Clearning MSRSQUEUE") + self.queue={} + return self +end + + +--- Add a transmission to the radio queue. +-- @param #MSRSQUEUE self +-- @param #MSRSQUEUE.Transmission transmission The transmission data table. +-- @return #MSRSQUEUE self +function MSRSQUEUE:AddTransmission(transmission) + + -- Init. + transmission.isplaying=false + transmission.Tstarted=nil + + -- Add to queue. + table.insert(self.queue, transmission) + + -- Start checking. + if not self.checking then + self:_CheckRadioQueue() + end + + return self +end + +--- Create a new transmission and add it to the radio queue. +-- @param #MSRSQUEUE self +-- @param #string text Text to play. +-- @param #number duration Duration in seconds the file lasts. Default is determined by number of characters of the text message. +-- @param Sound.SRS#MSRS msrs MOOSE SRS object. +-- @param #number tstart Start time (abs) seconds. Default now. +-- @param #number interval Interval in seconds after the last transmission finished. +-- @param #table subgroups Groups that should receive the subtiltle. +-- @param #string subtitle Subtitle displayed when the message is played. +-- @param #number subduration Duration [sec] of the subtitle being displayed. Default 5 sec. +-- @param #number frequency Radio frequency if other than MSRS default. +-- @param #number modulation Radio modulation if other then MSRS default. +-- @return #MSRSQUEUE.Transmission Radio transmission table. +function MSRSQUEUE:NewTransmission(text, duration, msrs, tstart, interval, subgroups, subtitle, subduration, frequency, modulation) + + -- Sanity checks. + if not text then + self:E(self.lid.."ERROR: No text specified.") + return nil + end + if type(text)~="string" then + self:E(self.lid.."ERROR: Text specified is NOT a string.") + return nil + end + + + -- Create a new transmission object. + local transmission={} --#MSRSQUEUE.Transmission + transmission.text=text + transmission.duration=duration or STTS.getSpeechTime(text) + transmission.msrs=msrs + transmission.Tplay=tstart or timer.getAbsTime() + transmission.subtitle=subtitle + transmission.interval=interval or 0 + transmission.frequency=frequency + transmission.modulation=modulation + transmission.subgroups=subgroups + if transmission.subtitle then + transmission.subduration=subduration or transmission.duration + else + transmission.subduration=0 --nil + end + + -- Add transmission to queue. + self:AddTransmission(transmission) + + return transmission +end + +--- Broadcast radio message. +-- @param #MSRSQUEUE self +-- @param #MSRSQUEUE.Transmission transmission The transmission. +function MSRSQUEUE:Broadcast(transmission) + + if transmission.frequency then + transmission.msrs:PlayTextExt(transmission.text, nil, transmission.frequency, transmission.modulation, Gender, Culture, Voice, Volume, Label) + else + transmission.msrs:PlayText(transmission.text) + end + + local function texttogroup(gid) + -- Text to group. + trigger.action.outTextForGroup(gid, transmission.subtitle, transmission.subduration, true) + end + + if transmission.subgroups and #transmission.subgroups>0 then + + for _,_group in pairs(transmission.subgroups) do + local group=_group --Wrapper.Group#GROUP + + if group and group:IsAlive() then + local gid=group:GetID() + + self:ScheduleOnce(4, texttogroup, gid) + end + + end + + end + +end + +--- Calculate total transmission duration of all transmission in the queue. +-- @param #MSRSQUEUE self +-- @return #number Total transmission duration. +function MSRSQUEUE:CalcTransmisstionDuration() + + local Tnow=timer.getAbsTime() + + local T=0 + for _,_transmission in pairs(self.queue) do + local transmission=_transmission --#MSRSQUEUE.Transmission + + if transmission.isplaying then + + -- Playing for dt seconds. + local dt=Tnow-transmission.Tstarted + + T=T+transmission.duration-dt + + else + T=T+transmission.duration + end + + end + + return T +end + +--- Check radio queue for transmissions to be broadcasted. +-- @param #MSRSQUEUE self +-- @param #number delay Delay in seconds before checking. +function MSRSQUEUE:_CheckRadioQueue(delay) + + -- Transmissions in queue. + local N=#self.queue + + -- Debug info. + self:T2(self.lid..string.format("Check radio queue %s: delay=%.3f sec, N=%d, checking=%s", self.alias, delay or 0, N, tostring(self.checking))) + + if delay and delay>0 then + + -- Delayed call. + self:ScheduleOnce(delay, MSRSQUEUE._CheckRadioQueue, self) + + -- Checking on. + self.checking=true + + else + + -- Check if queue is empty. + if N==0 then + + -- Debug info. + self:T(self.lid..string.format("Check radio queue %s empty ==> disable checking", self.alias)) + + -- Queue is now empty. Nothing to else to do. We start checking again, if a transmission is added. + self.checking=false + + return + end + + -- Get current abs time. + local time=timer.getAbsTime() + + -- Checking on. + self.checking=true + + -- Set dt. + local dt=self.dt + + + local playing=false + local next=nil --#MSRSQUEUE.Transmission + local remove=nil + for i,_transmission in ipairs(self.queue) do + local transmission=_transmission --#MSRSQUEUE.Transmission + + -- Check if transmission time has passed. + if time>=transmission.Tplay then + + -- Check if transmission is currently playing. + if transmission.isplaying then + + -- Check if transmission is finished. + if time>=transmission.Tstarted+transmission.duration then + + -- Transmission over. + transmission.isplaying=false + + -- Remove ith element in queue. + remove=i + + -- Store time last transmission finished. + self.Tlast=time + + else -- still playing + + -- Transmission is still playing. + playing=true + + dt=transmission.duration-(time-transmission.Tstarted) + + end + + else -- not playing yet + + local Tlast=self.Tlast + + if transmission.interval==nil then + + -- Not playing ==> this will be next. + if next==nil then + next=transmission + end + + else + + if Tlast==nil or time-Tlast>=transmission.interval then + next=transmission + else + + end + end + + -- We got a transmission or one with an interval that is not due yet. No need for anything else. + if next or Tlast then + break + end + + end + + else + + -- Transmission not due yet. + + end + end + + -- Found a new transmission. + if next~=nil and not playing then + -- Debug info. + self:T(self.lid..string.format("Broadcasting text=\"%s\" at T=%.3f", next.text, time)) + + -- Call SRS. + self:Broadcast(next) + + next.isplaying=true + next.Tstarted=time + dt=next.duration + end + + -- Remove completed call from queue. + if remove then + -- Remove from queue. + table.remove(self.queue, remove) + N=N-1 + + -- Check if queue is empty. + if #self.queue==0 then + -- Debug info. + self:T(self.lid..string.format("Check radio queue %s empty ==> disable checking", self.alias)) + + self.checking=false + + return + end + end + + -- Check queue. + self:_CheckRadioQueue(dt) + + end + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 5277cca4e1455db98114ff569a9467d57eaa9400 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Thu, 25 Aug 2022 16:00:47 +0200 Subject: [PATCH 190/200] Range fix issue #1753 (#1767) Issue #1753 - when using `AddBombingTargets` the randommove flag was not passed to `AddBombingTargetUnit` for a group object --- Moose Development/Moose/Functional/Range.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index 1999747a2..948b23cc4 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -1388,7 +1388,7 @@ function RANGE:AddBombingTargets( targetnames, goodhitrange, randommove ) 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 ) + self:AddBombingTargetUnit( _unit, goodhitrange, randommove ) else self:E( self.id .. string.format( "ERROR! Could not find bombing target %s.", name ) ) end From 7ef69208d4919b28cbc4eb341c8cbbba802ea730 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 30 Aug 2022 14:36:18 +0200 Subject: [PATCH 191/200] #ZONE_RADIUS * Added `ZONE_RADIUS:GetRandomCoordinateWithoutBuildings(...)` #MARKEROPS_BASE * Added Class --- .../Moose/Core/MarkerOps_Base.lua | 251 ++++++++++++++++++ Moose Development/Moose/Core/Zone.lua | 81 +++++- Moose Development/Moose/Modules.lua | 1 + 3 files changed, 332 insertions(+), 1 deletion(-) create mode 100644 Moose Development/Moose/Core/MarkerOps_Base.lua diff --git a/Moose Development/Moose/Core/MarkerOps_Base.lua b/Moose Development/Moose/Core/MarkerOps_Base.lua new file mode 100644 index 000000000..8e318a7a7 --- /dev/null +++ b/Moose Development/Moose/Core/MarkerOps_Base.lua @@ -0,0 +1,251 @@ +--- **Core** - MarkerOps_Base. +-- +-- **Main Features:** +-- +-- * Create an easy way to tap into markers added to the F10 map by users. +-- * Recognize own tag and list of keywords. +-- * Matched keywords are handed down to functions. +-- +-- === +-- +-- ### Author: **Applevangelist** +-- +-- Date: 5 May 2021 +-- +-- === +--- +-- @module Core.MarkerOps_Base +-- @image MOOSE_Core.JPG + +-------------------------------------------------------------------------- +-- MARKEROPS_BASE Class Definition. +-------------------------------------------------------------------------- + +--- MARKEROPS_BASE class. +-- @type MARKEROPS_BASE +-- @field #string ClassName Name of the class. +-- @field #string Tag Tag to identify commands. +-- @field #table Keywords Table of keywords to recognize. +-- @field #string version Version of #MARKEROPS_BASE. +-- @extends Core.Fsm#FSM + +--- *Fiat lux.* -- Latin proverb. +-- +-- === +-- +-- # The MARKEROPS_BASE Concept +-- +-- This class enable scripting text-based actions from markers. +-- +-- @field #MARKEROPS_BASE +MARKEROPS_BASE = { + ClassName = "MARKEROPS", + Tag = "mytag", + Keywords = {}, + version = "0.0.1", + debug = false, +} + +--- Function to instantiate a new #MARKEROPS_BASE object. +-- @param #MARKEROPS_BASE self +-- @param #string Tagname Name to identify us from the event text. +-- @param #table Keywords Table of keywords recognized from the event text. +-- @return #MARKEROPS_BASE self +function MARKEROPS_BASE:New(Tagname,Keywords) + -- Inherit FSM + local self=BASE:Inherit(self, FSM:New()) -- #MARKEROPS_BASE + + -- Set some string id for output to DCS.log file. + self.lid=string.format("MARKEROPS_BASE %s | ", tostring(self.version)) + + self.Tag = Tagname or "mytag"-- #string + self.Keywords = Keywords or {} -- #table - might want to use lua regex here, too + self.debug = false + + ----------------------- + --- FSM Transitions --- + ----------------------- + + -- Start State. + self:SetStartState("Stopped") + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("Stopped", "Start", "Running") -- Start the FSM. + self:AddTransition("*", "MarkAdded", "*") -- Start the FSM. + self:AddTransition("*", "MarkChanged", "*") -- Start the FSM. + self:AddTransition("*", "MarkDeleted", "*") -- Start the FSM. + self:AddTransition("Running", "Stop", "Stopped") -- Stop the FSM. + + self:HandleEvent(EVENTS.MarkAdded, self.OnEventMark) + self:HandleEvent(EVENTS.MarkChange, self.OnEventMark) + self:HandleEvent(EVENTS.MarkRemoved, self.OnEventMark) + + -- start + self:I(self.lid..string.format("started for %s",self.Tag)) + self:__Start(1) + return self + + ------------------- + -- PSEUDO Functions + ------------------- + + --- On after "MarkAdded" event. Triggered when a Marker is added to the F10 map. + -- @function [parent=#MARKEROPS_BASE] OnAfterMarkAdded + -- @param #MARKEROPS_BASE self + -- @param #string From The From state + -- @param #string Event The Event called + -- @param #string To The To state + -- @param #string Text The text on the marker + -- @param #table Keywords Table of matching keywords found in the Event text + -- @param Core.Point#COORDINATE Coord Coordinate of the marker. + + --- On after "MarkChanged" event. Triggered when a Marker is changed on the F10 map. + -- @function [parent=#MARKEROPS_BASE] OnAfterMarkChanged + -- @param #MARKEROPS_BASE self + -- @param #string From The From state + -- @param #string Event The Event called + -- @param #string To The To state + -- @param #string Text The text on the marker + -- @param #table Keywords Table of matching keywords found in the Event text + -- @param Core.Point#COORDINATE Coord Coordinate of the marker. + + --- On after "MarkDeleted" event. Triggered when a Marker is deleted from the F10 map. + -- @function [parent=#MARKEROPS_BASE] OnAfterMarkDeleted + -- @param #MARKEROPS_BASE self + -- @param #string From The From state + -- @param #string Event The Event called + -- @param #string To The To state + + --- "Stop" trigger. Used to stop the function an unhandle events + -- @function [parent=#MARKEROPS_BASE] Stop + +end + +--- (internal) Handle events. +-- @param #MARKEROPS_BASE self +-- @param Core.Event#EVENTDATA Event +function MARKEROPS_BASE:OnEventMark(Event) + self:T({Event}) + if Event == nil or Event.idx == nil then + self:E("Skipping onEvent. Event or Event.idx unknown.") + return true + end + --position + local vec3={y=Event.pos.y, x=Event.pos.x, z=Event.pos.z} + local coord=COORDINATE:NewFromVec3(vec3) + if self.debug then + local coordtext = coord:ToStringLLDDM() + local text = tostring(Event.text) + local m = MESSAGE:New(string.format("Mark added at %s with text: %s",coordtext,text),10,"Info",false):ToAll() + end + -- decision + if Event.id==world.event.S_EVENT_MARK_ADDED then + self:T({event="S_EVENT_MARK_ADDED", carrier=self.groupname, vec3=Event.pos}) + -- Handle event + local Eventtext = tostring(Event.text) + if Eventtext~=nil then + if self:_MatchTag(Eventtext) then + local matchtable = self:_MatchKeywords(Eventtext) + self:MarkAdded(Eventtext,matchtable,coord) + end + end + elseif Event.id==world.event.S_EVENT_MARK_CHANGE then + self:T({event="S_EVENT_MARK_CHANGE", carrier=self.groupname, vec3=Event.pos}) + -- Handle event. + local Eventtext = tostring(Event.text) + if Eventtext~=nil then + if self:_MatchTag(Eventtext) then + local matchtable = self:_MatchKeywords(Eventtext) + self:MarkChanged(Eventtext,matchtable,coord) + end + end + elseif Event.id==world.event.S_EVENT_MARK_REMOVED then + self:T({event="S_EVENT_MARK_REMOVED", carrier=self.groupname, vec3=Event.pos}) + -- Hande event. + local Eventtext = tostring(Event.text) + if Eventtext~=nil then + if self:_MatchTag(Eventtext) then + self:MarkDeleted() + end + end + end +end + +--- (internal) Match tag. +-- @param #MARKEROPS_BASE self +-- @param #string Eventtext Text added to the marker. +-- @return #boolean +function MARKEROPS_BASE:_MatchTag(Eventtext) + local matches = false + local type = string.lower(self.Tag) -- #string + if string.find(string.lower(Eventtext),type) then + matches = true --event text contains tag + end + return matches +end + +--- (internal) Match keywords table. +-- @param #MARKEROPS_BASE self +-- @param #string Eventtext Text added to the marker. +-- @return #table +function MARKEROPS_BASE:_MatchKeywords(Eventtext) + local matchtable = {} + local keytable = self.Keywords + for _index,_word in pairs (keytable) do + if string.find(string.lower(Eventtext),string.lower(_word))then + table.insert(matchtable,_word) + end + end + return matchtable +end + +--- On before "MarkAdded" event. Triggered when a Marker is added to the F10 map. + -- @param #MARKEROPS_BASE self + -- @param #string From The From state + -- @param #string Event The Event called + -- @param #string To The To state + -- @param #string Text The text on the marker + -- @param #table Keywords Table of matching keywords found in the Event text + -- @param Core.Point#COORDINATE Coord Coordinate of the marker. +function MARKEROPS_BASE:onbeforeMarkAdded(From,Event,To,Text,Keywords,Coord) + self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()}) +end + +--- On before "MarkChanged" event. Triggered when a Marker is changed on the F10 map. + -- @param #MARKEROPS_BASE self + -- @param #string From The From state + -- @param #string Event The Event called + -- @param #string To The To state + -- @param #string Text The text on the marker + -- @param #table Keywords Table of matching keywords found in the Event text + -- @param Core.Point#COORDINATE Coord Coordinate of the marker. +function MARKEROPS_BASE:onbeforeMarkChanged(From,Event,To,Text,Keywords,Coord) + self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()}) +end + +--- On before "MarkDeleted" event. Triggered when a Marker is removed from the F10 map. + -- @param #MARKEROPS_BASE self + -- @param #string From The From state + -- @param #string Event The Event called + -- @param #string To The To state +function MARKEROPS_BASE:onbeforeMarkDeleted(From,Event,To) + self:T({self.lid,From,Event,To}) +end + +--- On enter "Stopped" event. Unsubscribe events. + -- @param #MARKEROPS_BASE self + -- @param #string From The From state + -- @param #string Event The Event called + -- @param #string To The To state +function MARKEROPS_BASE:onenterStopped(From,Event,To) + self:T({self.lid,From,Event,To}) + -- unsubscribe from events + self:UnHandleEvent(EVENTS.MarkAdded) + self:UnHandleEvent(EVENTS.MarkChange) + self:UnHandleEvent(EVENTS.MarkRemoved) +end + +-------------------------------------------------------------------------- +-- MARKEROPS_BASE Class Definition End. +-------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 58d03cf31..2f9d3ee39 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1295,7 +1295,86 @@ function ZONE_RADIUS:GetRandomCoordinate(inner, outer, surfacetypes) return Coordinate end - +--- Returns a @{Core.Point#COORDINATE} object reflecting a random location within the zone where there are no **map objects** of type "Building". +-- Does not find statics you might have placed there. **Note** This might be quite CPU intensive, use with care. +-- @param #ZONE_RADIUS self +-- @param #number inner (Optional) Minimal distance from the center of the zone in meters. Default is 0m. +-- @param #number outer (Optional) Maximal distance from the outer edge of the zone in meters. Default is the radius of the zone. +-- @param #number distance (Optional) Minumum distance from any building coordinate. Defaults to 100m. +-- @param #boolean markbuildings (Optional) Place markers on found buildings (if any). +-- @param #boolean markfinal (Optional) Place marker on the final coordinate (if any). +-- @return Core.Point#COORDINATE The random coordinate or `nil` if cannot be found in 1000 iterations. +function ZONE_RADIUS:GetRandomCoordinateWithoutBuildings(inner,outer,distance,markbuildings,markfinal) + + local dist = distance or 100 + + local objects = {} + + if self.ScanData and self.ScanData.Scenery then + objects = self:GetScannedScenery() + else + self:Scan({Object.Category.SCENERY}) + objects = self:GetScannedScenery() + end + + local T0 = timer.getTime() + local T1 = timer.getTime() + + + local buildings = {} + if self.ScanData and self.ScanData.BuildingCoordinates then + buildings = self.ScanData.BuildingCoordinates + else + -- build table of buildings coordinates + for _,_object in pairs (objects) do + for _,_scen in pairs (_object) do + local scenery = _scen -- Wrapper.Scenery#SCENERY + local description=scenery:GetDesc() + if description and description.attributes and description.attributes.Buildings then + if markbuildings then + MARKER:New(scenery:GetCoordinate(),"Building"):ToAll() + end + buildings[#buildings+1] = scenery:GetCoordinate() + end + end + end + self.ScanData.BuildingCoordinates = buildings + end + + -- max 1000 tries + local rcoord = nil + local found = false + local iterations = 0 + + for i=1,1000 do + iterations = iterations + 1 + rcoord = self:GetRandomCoordinate(inner,outer) + found = false + for _,_coord in pairs (buildings) do + local coord = _coord -- Core.Point#COORDINATE + -- keep >50m dist from buildings + if coord:Get2DDistance(rcoord) > dist then + found = true + else + found = false + end + end + if found then + -- we have a winner! + if markfinal then + MARKER:New(rcoord,"FREE"):ToAll() + end + break + end + end + + T1=timer.getTime() + + self:T(string.format("Found a coordinate: %s | Iterations: %d | Time: %d",tostring(found),iterations,T1-T0)) + + if found then return rcoord else return nil end + +end --- @type ZONE -- @extends #ZONE_RADIUS diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 29c10ad0c..f56995e1b 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -28,6 +28,7 @@ __Moose.Include( 'Scripts/Moose/Core/SpawnStatic.lua' ) __Moose.Include( 'Scripts/Moose/Core/Timer.lua' ) __Moose.Include( 'Scripts/Moose/Core/Goal.lua' ) __Moose.Include( 'Scripts/Moose/Core/Spot.lua' ) +__Moose.Include( 'Scripts/Moose/Core/MarkerOps_Base.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Object.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Identifiable.lua' ) From 1255f48645468dc1caf81274c3c4ca3bd2e136b8 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 30 Aug 2022 14:38:36 +0200 Subject: [PATCH 192/200] Update Moose.files --- Moose Setup/Moose.files | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index e820e19ef..27d7addf7 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -29,6 +29,7 @@ Core/SpawnStatic.lua Core/Timer.lua Core/Goal.lua Core/Spot.lua +Core/MarkerOps_Base.lua Wrapper/Object.lua Wrapper/Identifiable.lua From 1060ff16d86c01af728bd885ff2f2d68c55d890d Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 31 Aug 2022 17:40:44 +0200 Subject: [PATCH 193/200] GROUP - Attributes aligned to dev --- Moose Development/Moose/Wrapper/Group.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index fd2bfe86c..e282f080f 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -213,6 +213,7 @@ GROUP.Attribute = { GROUND_APC="Ground_APC", GROUND_TRUCK="Ground_Truck", GROUND_INFANTRY="Ground_Infantry", + GROUND_IFV="Ground_IFV", GROUND_ARTILLERY="Ground_Artillery", GROUND_TANK="Ground_Tank", GROUND_TRAIN="Ground_Train", @@ -2421,7 +2422,9 @@ function GROUP:GetAttribute() elseif artillery then attribute=GROUP.Attribute.GROUND_ARTILLERY elseif tank then - attribute=GROUP.Attribute.GROUND_TANK + attribute=GROUP.Attribute.GROUND_TANK + elseif ifv then + attribute=GROUP.Attribute.GROUND_IFV elseif apc then attribute=GROUP.Attribute.GROUND_APC elseif infantry then From a0c189a3495b6b56a9022c5e8ab92a0d1f6e9807 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 31 Aug 2022 18:11:55 +0200 Subject: [PATCH 194/200] UTILS - added NDB 305kHz in frequency generator --- Moose Development/Moose/Utilities/Utils.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 60e07f9dc..e2606b296 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1878,7 +1878,7 @@ function UTILS.GenerateVHFrequencies() -- known and sorted map-wise NDBs in kHz local _skipFrequencies = { 214,274,291.5,295,297.5, - 300.5,304,307,309.5,311,312,312.5,316, + 300.5,304,305,307,309.5,311,312,312.5,316, 320,324,328,329,330,332,336,337, 342,343,348,351,352,353,358, 363,365,368,372.5,374, From 6820774266773a20f0735bd3b398a6c14543693c Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 1 Sep 2022 08:03:53 +0200 Subject: [PATCH 195/200] GROUP - added optical tracker to ID SAMs --- Moose Development/Moose/Wrapper/Group.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index e282f080f..ebb8d9228 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -2373,14 +2373,15 @@ function GROUP:GetAttribute() --- Ground --- -------------- -- Ground - local apc=self:HasAttribute("Infantry carriers") + local apc=self:HasAttribute("APC") local truck=self:HasAttribute("Trucks") and self:GetCategory()==Group.Category.GROUND local infantry=self:HasAttribute("Infantry") local artillery=self:HasAttribute("Artillery") local tank=self:HasAttribute("Old Tanks") or self:HasAttribute("Modern Tanks") - local aaa=self:HasAttribute("AAA") + local aaa=self:HasAttribute("AAA") and (not self:HasAttribute("SAM elements")) local ewr=self:HasAttribute("EWR") - local sam=self:HasAttribute("SAM elements") and (not self:HasAttribute("AAA")) + local ifv=self:HasAttribute("IFV") + local sam=self:HasAttribute("SAM elements") or self:HasAttribute("Optical Tracker") -- Train local train=self:GetCategory()==Group.Category.TRAIN From 372cc021bda19891fe7c6fe8eee46fd08f049ede Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 1 Sep 2022 08:36:40 +0200 Subject: [PATCH 196/200] UNIT - added optical tracker to `HasSEAD()` --- Moose Development/Moose/Wrapper/Unit.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index b9db49851..f18a07d7e 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -829,7 +829,9 @@ function UNIT:HasSEAD() 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 + UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] and UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] == true or + UnitSEADAttributes["Optical Tracker"] and UnitSEADAttributes["Optical Tracker"] == true + then HasSEAD = true end return HasSEAD From a9fad53255f32fa1eb0030d0ea62f16e60e7fedd Mon Sep 17 00:00:00 2001 From: grandpaSam Date: Thu, 1 Sep 2022 11:49:46 -0700 Subject: [PATCH 197/200] Updated CARGO tag documentation The documentation for the CARGO tag on static objects omitted a required C= parameter. Without this parameter the database will not register the static as a CARGO object --- Moose Development/Moose/Cargo/Cargo.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Cargo/Cargo.lua b/Moose Development/Moose/Cargo/Cargo.lua index c17c4affb..e23e43199 100644 --- a/Moose Development/Moose/Cargo/Cargo.lua +++ b/Moose Development/Moose/Cargo/Cargo.lua @@ -206,27 +206,29 @@ -- * **NR=** Provide the maximum range in meters when the cargo units will be boarded within the carrier during boarding. -- Note that this option is optional, so can be omitted. The default value of the RR is 10 meters. -- --- ## 5.2) The \#CARGO tag to create CARGO_CRATE objects: +-- ## 5.2) The \#CARGO tag to create CARGO_CRATE or CARGO_SLINGLOAD objects: -- -- You can also use the \#CARGO tag on **static** objects, including **static cargo** objects of the mission editor. -- -- For example, the following #CARGO naming in the **static name** of the object, will create a CARGO_CRATE object when the mission starts. -- --- `Static #CARGO(T=Workmaterials,RR=500,NR=25)` +-- `Static #CARGO(T=Workmaterials,C=CRATE,RR=500,NR=25)` -- -- This will create a CARGO_CRATE object: -- -- * with the group name `Static #CARGO` -- * is of type `Workmaterials` +-- * is of category `CRATE` (as opposed to `SLING`) -- * will report when a carrier is within 500 meters -- * will board to carriers when the carrier is within 500 meters from the cargo object -- * will disappear when the cargo is within 25 meters from the carrier during boarding -- -- So the overall syntax of the #CARGO naming tag and arguments are: -- --- `StaticName #CARGO(T=CargoTypeName,RR=Range,NR=Range)` +-- `StaticName #CARGO(T=CargoTypeName,C=Category,RR=Range,NR=Range)` -- -- * **T=** Provide a text that contains the type name of the cargo object. This type name can be used to filter cargo within a SET_CARGO object. +-- * **C=** Provide either `CRATE` or `SLING` to have this static created as a CARGO_CRATE or CARGO_SLINGLOAD respectivly. -- * **RR=** Provide the minimal range in meters when the report to the carrier, and board to the carrier. -- Note that this option is optional, so can be omitted. The default value of the RR is 250 meters. -- * **NR=** Provide the maximum range in meters when the cargo units will be boarded within the carrier during boarding. From 1207417894a369fa4a353ca0a1f6120c2fc32666 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 5 Sep 2022 17:17:05 +0200 Subject: [PATCH 198/200] #ZONE * ZONE_ELASTIC * Typo --- Moose Development/Moose/Core/Zone.lua | 404 +++++++++++++++++++++++--- 1 file changed, 362 insertions(+), 42 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 2f9d3ee39..bcc585a64 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -59,7 +59,11 @@ -- @field #number ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability. -- @field #number DrawID Unique ID of the drawn zone on the F10 map. -- @field #table Color Table with four entries, e.g. {1, 0, 0, 0.15}. First three are RGB color code. Fourth is the transparency Alpha value. +-- @field #table FillColor Table with four entries, e.g. {1, 0, 0, 0.15}. First three are RGB color code. Fourth is the transparency Alpha value. +-- @field #number drawCoalition Draw coalition. -- @field #number ZoneID ID of zone. Only zones defined in the ME have an ID! +-- @field #table Table of any trigger zone properties from the ME. The key is the Name of the property, and the value is the property's Value. +-- @field #number Surface Type of surface. Only determined at the center of the zone! -- @extends Core.Fsm#FSM @@ -103,6 +107,11 @@ -- * @{#ZONE_BASE.SmokeZone}(): Smokes the zone boundaries in a color. -- * @{#ZONE_BASE.FlareZone}(): Flares the zone boundaries in a color. -- +-- ## A zone might have additional Properties created in the DCS Mission Editor, which can be accessed: +-- +-- *@{#ZONE_BASE.GetProperty}(): Returns the Value of the zone with the given PropertyName, or nil if no matching property exists. +-- *@{#ZONE_BASE.GetAllProperties}(): Returns the zone Properties table. +-- -- @field #ZONE_BASE ZONE_BASE = { ClassName = "ZONE_BASE", @@ -111,6 +120,8 @@ ZONE_BASE = { DrawID=nil, Color={}, ZoneID=nil, + Properties={}, + Surface=nil, } @@ -336,18 +347,41 @@ end -- @param #ZONE_BASE self -- @return #nil The bounding square. function ZONE_BASE:GetBoundingSquare() - --return { x1 = 0, y1 = 0, x2 = 0, y2 = 0 } return nil end +--- Get surface type of the zone. +-- @param #ZONE_BASE self +-- @return DCS#SurfaceType Type of surface. +function ZONE_BASE:GetSurfaceType() + local coord=self:GetCoordinate() + local surface=coord:GetSurfaceType() + return surface +end + --- Bound the zone boundaries with a tires. -- @param #ZONE_BASE self function ZONE_BASE:BoundZone() self:F2() - end +--- Set draw coalition of zone. +-- @param #ZONE_BASE self +-- @param #number Coalition Coalition. Default -1. +-- @return #ZONE_BASE self +function ZONE_BASE:SetDrawCoalition(Coalition) + self.drawCoalition=Coalition or -1 + return self +end + +--- Get draw coalition of zone. +-- @param #ZONE_BASE self +-- @return #number Draw coaliton. +function ZONE_BASE:GetDrawCoalition() + return self.drawCoalition or -1 +end + --- Set color of zone. -- @param #ZONE_BASE self -- @param #table RGBcolor RGB color table. Default `{1, 0, 0}`. @@ -371,7 +405,7 @@ end -- @param #ZONE_BASE self -- @return #table Table with four entries, e.g. {1, 0, 0, 0.15}. First three are RGB color code. Fourth is the transparency Alpha value. function ZONE_BASE:GetColor() - return self.Color + return self.Color or {1, 0, 0, 0.15} end --- Get RGB color of zone. @@ -379,9 +413,10 @@ end -- @return #table Table with three entries, e.g. {1, 0, 0}, which is the RGB color code. function ZONE_BASE:GetColorRGB() local rgb={} - rgb[1]=self.Color[1] - rgb[2]=self.Color[2] - rgb[3]=self.Color[3] + local Color=self:GetColor() + rgb[1]=Color[1] + rgb[2]=Color[2] + rgb[3]=Color[3] return rgb end @@ -389,7 +424,55 @@ end -- @param #ZONE_BASE self -- @return #number Alpha value. function ZONE_BASE:GetColorAlpha() - local alpha=self.Color[4] + local Color=self:GetColor() + local alpha=Color[4] + return alpha +end + +--- Set fill color of zone. +-- @param #ZONE_BASE self +-- @param #table RGBcolor RGB color table. Default `{1, 0, 0}`. +-- @param #number Alpha Transparacy between 0 and 1. Default 0.15. +-- @return #ZONE_BASE self +function ZONE_BASE:SetFillColor(RGBcolor, Alpha) + + RGBcolor=RGBcolor or {1, 0, 0} + Alpha=Alpha or 0.15 + + self.FillColor={} + self.FillColor[1]=RGBcolor[1] + self.FillColor[2]=RGBcolor[2] + self.FillColor[3]=RGBcolor[3] + self.FillColor[4]=Alpha + + return self +end + +--- Get fill color table of the zone. +-- @param #ZONE_BASE self +-- @return #table Table with four entries, e.g. {1, 0, 0, 0.15}. First three are RGB color code. Fourth is the transparency Alpha value. +function ZONE_BASE:GetFillColor() + return self.FillColor or {1, 0, 0, 0.15} +end + +--- Get RGB fill color of zone. +-- @param #ZONE_BASE self +-- @return #table Table with three entries, e.g. {1, 0, 0}, which is the RGB color code. +function ZONE_BASE:GetFillColorRGB() + local rgb={} + local FillColor=self:GetFillColor() + rgb[1]=FillColor[1] + rgb[2]=FillColor[2] + rgb[3]=FillColor[3] + return rgb +end + +--- Get transperency Alpha fill value of zone. +-- @param #ZONE_BASE self +-- @return #number Alpha value. +function ZONE_BASE:GetFillColorAlpha() + local FillColor=self:GetFillColor() + local alpha=FillColor[4] return alpha end @@ -481,6 +564,26 @@ function ZONE_BASE:GetZoneMaybe() end end +-- Returns the Value of the zone with the given PropertyName, or nil if no matching property exists. +-- @param #ZONE_BASE self +-- @param #string PropertyName The name of a the TriggerZone Property to be retrieved. +-- @return #string The Value of the TriggerZone Property with the given PropertyName, or nil if absent. +-- @usage +-- +-- local PropertiesZone = ZONE:FindByName("Properties Zone") +-- local Property = "ExampleProperty" +-- local PropertyValue = PropertiesZone:GetProperty(Property) +-- +function ZONE_BASE:GetProperty(PropertyName) + return self.Properties[PropertyName] +end + +-- Returns the zone Properties table. +-- @param #ZONE_BASE self +-- @return #table The Key:Value table of TriggerZone properties of the zone. +function ZONE_BASE:GetAllProperties() + return self.Properties +end --- The ZONE_RADIUS class, defined by a zone name, a location and a radius. -- @type ZONE_RADIUS @@ -823,8 +926,8 @@ end -- @param ObjectCategories An array of categories of the objects to find in the zone. E.g. `{Object.Category.UNIT}` -- @param UnitCategories An array of unit categories of the objects to find in the zone. E.g. `{Unit.Category.GROUND_UNIT,Unit.Category.SHIP}` -- @usage --- self.Zone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT}) --- local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition ) +-- myzone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT}) +-- local IsAttacked = myzone:IsSomeInZoneOfCoalition( self.Coalition ) function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) self.ScanData = {} @@ -894,7 +997,7 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) 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] } ) + self:T( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } ) end end @@ -1157,7 +1260,9 @@ end -- @return #boolean true if the location is within the zone. function ZONE_RADIUS:IsVec2InZone( Vec2 ) self:F2( Vec2 ) + if not Vec2 then return false end + local ZoneVec2 = self:GetVec2() if ZoneVec2 then @@ -1175,7 +1280,7 @@ end -- @return #boolean true if the point is within the zone. function ZONE_RADIUS:IsVec3InZone( Vec3 ) self:F2( Vec3 ) - if not Vec3 then return false end + if not Vec3 then return false end local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } ) return InZone @@ -1282,8 +1387,8 @@ end --- Returns a @{Core.Point#COORDINATE} object reflecting a random 3D location within the zone. -- @param #ZONE_RADIUS self --- @param #number inner (Optional) Minimal distance from the center of the zone. Default is 0. --- @param #number outer (Optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. +-- @param #number inner (Optional) Minimal distance from the center of the zone in meters. Default is 0 m. +-- @param #number outer (Optional) Maximal distance from the outer edge of the zone in meters. Default is the radius of the zone. -- @param #table surfacetypes (Optional) Table of surface types. Can also be a single surface type. We will try max 1000 times to find the right type! -- @return Core.Point#COORDINATE The random coordinate. function ZONE_RADIUS:GetRandomCoordinate(inner, outer, surfacetypes) @@ -1450,9 +1555,9 @@ function ZONE:New( ZoneName ) end --- Find a zone in the _DATABASE using the name of the zone. --- @param #ZONE_BASE self +-- @param #ZONE self -- @param #string ZoneName The name of the zone. --- @return #ZONE_BASE self +-- @return #ZONE self function ZONE:FindByName( ZoneName ) local ZoneFound = _DATABASE:FindZone( ZoneName ) @@ -1688,7 +1793,7 @@ end --- @type ZONE_POLYGON_BASE --- --@field #ZONE_POLYGON_BASE.ListVec2 Polygon The polygon defined by an array of @{DCS#Vec2}. +-- @field #ZONE_POLYGON_BASE.ListVec2 Polygon The polygon defined by an array of @{DCS#Vec2}. -- @extends #ZONE_BASE @@ -1926,7 +2031,7 @@ function ZONE_POLYGON_BASE:BoundZone( UnBound ) end ---- Draw the zone on the F10 map. **NOTE** Currently, only polygons with **exactly four points** are supported! +--- Draw the zone on the F10 map. **NOTE** Currently, only polygons **up to ten points** are supported! -- @param #ZONE_POLYGON_BASE self -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red. @@ -1938,33 +2043,48 @@ end -- @return #ZONE_POLYGON_BASE self function ZONE_POLYGON_BASE:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) - local coordinate=COORDINATE:NewFromVec2(self._.Polygon[1]) + if self._.Polygon and #self._.Polygon>=3 then - Color=Color or self:GetColorRGB() - Alpha=Alpha or 1 + local coordinate=COORDINATE:NewFromVec2(self._.Polygon[1]) + + Coalition=Coalition or self:GetDrawCoalition() + + -- Set draw coalition. + self:SetDrawCoalition(Coalition) - FillColor=FillColor or UTILS.DeepCopy(Color) - FillAlpha=FillAlpha or self:GetColorAlpha() - - - if #self._.Polygon==4 then - - local Coord2=COORDINATE:NewFromVec2(self._.Polygon[2]) - local Coord3=COORDINATE:NewFromVec2(self._.Polygon[3]) - local Coord4=COORDINATE:NewFromVec2(self._.Polygon[4]) - - self.DrawID=coordinate:QuadToAll(Coord2, Coord3, Coord4, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) - - else - - local Coordinates=self:GetVerticiesCoordinates() - table.remove(Coordinates, 1) - - self.DrawID=coordinate:MarkupToAllFreeForm(Coordinates, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) - + Color=Color or self:GetColorRGB() + Alpha=Alpha or 1 + + -- Set color. + self:SetColor(Color, Alpha) + + FillColor=FillColor or self:GetFillColorRGB() + if not FillColor then UTILS.DeepCopy(Color) end + FillAlpha=FillAlpha or self:GetFillColorAlpha() + if not FillAlpha then FillAlpha=0.15 end + + -- Set fill color. + self:SetFillColor(FillColor, FillAlpha) + + if #self._.Polygon==4 then + + local Coord2=COORDINATE:NewFromVec2(self._.Polygon[2]) + local Coord3=COORDINATE:NewFromVec2(self._.Polygon[3]) + local Coord4=COORDINATE:NewFromVec2(self._.Polygon[4]) + + self.DrawID=coordinate:QuadToAll(Coord2, Coord3, Coord4, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) + + else + + local Coordinates=self:GetVerticiesCoordinates() + table.remove(Coordinates, 1) + + self.DrawID=coordinate:MarkupToAllFreeForm(Coordinates, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) + + end + end - return self end @@ -2045,7 +2165,7 @@ end -- @return #boolean true if the location is within the zone. function ZONE_POLYGON_BASE:IsVec2InZone( Vec2 ) self:F2( Vec2 ) - if not Vec2 then return false end + if not Vec2 then return false end local Next local Prev local InPolygon = false @@ -2077,7 +2197,7 @@ function ZONE_POLYGON_BASE:IsVec3InZone( Vec3 ) self:F2( Vec3 ) if not Vec3 then return false end - + local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } ) return InZone @@ -2297,6 +2417,206 @@ function ZONE_POLYGON:FindByName( ZoneName ) return ZoneFound end +do -- ZONE_ELASTIC + + --- @type ZONE_ELASTIC + -- @field #table points Points in 2D. + -- @field #table setGroups Set of GROUPs. + -- @field #table setOpsGroups Set of OPSGROUPS. + -- @field #table setUnits Set of UNITs. + -- @field #number updateID Scheduler ID for updating. + -- @extends #ZONE_POLYGON_BASE + + --- The ZONE_ELASTIC class defines a dynamic polygon zone, where only the convex hull is used. + -- + -- @field #ZONE_ELASTIC + ZONE_ELASTIC = { + ClassName="ZONE_ELASTIC", + points={}, + setGroups={} + } + + --- Constructor to create a ZONE_ELASTIC instance. + -- @param #ZONE_ELASTIC self + -- @param #string ZoneName Name of the zone. + -- @param DCS#Vec2 Points (Optional) Fixed points. + -- @return #ZONE_ELASTIC self + function ZONE_ELASTIC:New(ZoneName, Points) + + local self=BASE:Inherit(self, ZONE_POLYGON_BASE:New(ZoneName, Points)) --#ZONE_ELASTIC + + -- Zone objects are added to the _DATABASE and SET_ZONE objects. + _EVENTDISPATCHER:CreateEventNewZone( self ) + + if Points then + self.points=Points + end + + return self + end + + --- Add a vertex (point) to the polygon. + -- @param #ZONE_ELASTIC self + -- @param DCS#Vec2 Vec2 Point in 2D (with x and y coordinates). + -- @return #ZONE_ELASTIC self + function ZONE_ELASTIC:AddVertex2D(Vec2) + + -- Add vec2 to points. + table.insert(self.points, Vec2) + + return self + end + + + --- Add a vertex (point) to the polygon. + -- @param #ZONE_ELASTIC self + -- @param DCS#Vec3 Vec3 Point in 3D (with x, y and z coordinates). Only the x and z coordinates are used. + -- @return #ZONE_ELASTIC self + function ZONE_ELASTIC:AddVertex3D(Vec3) + + -- Add vec2 from vec3 to points. + table.insert(self.points, {x=Vec3.x, y=Vec3.z}) + + return self + end + + + --- Add a set of groups. Positions of the group will be considered as polygon vertices when contructing the convex hull. + -- @param #ZONE_ELASTIC self + -- @param Core.Set#SET_GROUP SetGroup Set of groups. + -- @return #ZONE_ELASTIC self + function ZONE_ELASTIC:AddSetGroup(GroupSet) + + -- Add set to table. + table.insert(self.setGroups, GroupSet) + + return self + end + + + --- Update the convex hull of the polygon. + -- This uses the [Graham scan](https://en.wikipedia.org/wiki/Graham_scan). + -- @param #ZONE_ELASTIC self + -- @param #number Delay Delay in seconds before the zone is updated. Default 0. + -- @param #boolean Draw Draw the zone. Default `nil`. + -- @return #ZONE_ELASTIC self + function ZONE_ELASTIC:Update(Delay, Draw) + + -- Debug info. + self:T(string.format("Updating ZONE_ELASTIC %s", tostring(self.ZoneName))) + + -- Copy all points. + local points=UTILS.DeepCopy(self.points or {}) + + if self.setGroups then + for _,_setGroup in pairs(self.setGroups) do + local setGroup=_setGroup --Core.Set#SET_GROUP + for _,_group in pairs(setGroup.Set) do + local group=_group --Wrapper.Group#GROUP + if group and group:IsAlive() then + table.insert(points, group:GetVec2()) + end + end + end + end + + -- Update polygon verticies from points. + self._.Polygon=self:_ConvexHull(points) + + if Draw~=false then + if self.DrawID or Draw==true then + self:UndrawZone() + self:DrawZone() + end + end + + return self + end + + --- Start the updating scheduler. + -- @param #ZONE_ELASTIC self + -- @param #number Tstart Time in seconds before the updating starts. + -- @param #number dT Time interval in seconds between updates. Default 60 sec. + -- @param #number Tstop Time in seconds after which the updating stops. Default `nil`. + -- @param #boolean Draw Draw the zone. Default `nil`. + -- @return #ZONE_ELASTIC self + function ZONE_ELASTIC:StartUpdate(Tstart, dT, Tstop, Draw) + + self.updateID=self:ScheduleRepeat(Tstart, dT, 0, Tstop, ZONE_ELASTIC.Update, self, 0, Draw) + + return self + end + + --- Stop the updating scheduler. + -- @param #ZONE_ELASTIC self + -- @param #number Delay Delay in seconds before the scheduler will be stopped. Default 0. + -- @return #ZONE_ELASTIC self + function ZONE_ELASTIC:StopUpdate(Delay) + + if Delay and Delay>0 then + self:ScheduleOnce(Delay, ZONE_ELASTIC.StopUpdate, self) + else + + if self.updateID then + + self:ScheduleStop(self.updateID) + + self.updateID=nil + + end + + end + + return self + end + + + --- Create a convec hull. + -- @param #ZONE_ELASTIC self + -- @param #table pl Points + -- @return #table Points + function ZONE_ELASTIC:_ConvexHull(pl) + + if #pl == 0 then + return {} + end + + table.sort(pl, function(left,right) + return left.x < right.x + end) + + local h = {} + + -- Function: ccw > 0 if three points make a counter-clockwise turn, clockwise if ccw < 0, and collinear if ccw = 0. + local function ccw(a,b,c) + return (b.x - a.x) * (c.y - a.y) > (b.y - a.y) * (c.x - a.x) + end + + -- lower hull + for i,pt in pairs(pl) do + while #h >= 2 and not ccw(h[#h-1], h[#h], pt) do + table.remove(h,#h) + end + table.insert(h,pt) + end + + -- upper hull + local t = #h + 1 + for i=#pl, 1, -1 do + local pt = pl[i] + while #h >= t and not ccw(h[#h-1], h[#h], pt) do + table.remove(h, #h) + end + table.insert(h, pt) + end + + table.remove(h, #h) + + return h + end + +end + do -- ZONE_AIRBASE --- @type ZONE_AIRBASE From 6c46dafc599eedb638c8298d688ff9ff6362fea2 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 6 Sep 2022 13:16:30 +0200 Subject: [PATCH 199/200] Revert "Auxiliary commit to revert individual files from 1207417894a369fa4a353ca0a1f6120c2fc32666" This reverts commit 551d4a9c85c89e4aaad2c59ed058c84ff8c7ad6c. --- Moose Development/Moose/.vscode/settings.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 Moose Development/Moose/.vscode/settings.json diff --git a/Moose Development/Moose/.vscode/settings.json b/Moose Development/Moose/.vscode/settings.json new file mode 100644 index 000000000..13211d027 --- /dev/null +++ b/Moose Development/Moose/.vscode/settings.json @@ -0,0 +1,17 @@ +{ + "Lua.workspace.preloadFileSize": 1000, + "Lua.diagnostics.disable": [ + "undefined-doc-name" + ], + "Lua.diagnostics.globals": [ + "BASE", + "lfs", + "__Moose", + "trigger", + "coord", + "missionCommands" + ], + "Lua.completion.displayContext": 5, + "Lua.runtime.version": "Lua 5.1", + "Lua.completion.callSnippet": "Both" +} \ No newline at end of file From c137a4b06dd968656211df21ddcf2d732168cccc Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 6 Sep 2022 13:18:22 +0200 Subject: [PATCH 200/200] Revert "#ZONE" This reverts commit 1207417894a369fa4a353ca0a1f6120c2fc32666. --- Moose Development/Moose/Core/Zone.lua | 404 +++----------------------- 1 file changed, 42 insertions(+), 362 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index bcc585a64..2f9d3ee39 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -59,11 +59,7 @@ -- @field #number ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability. -- @field #number DrawID Unique ID of the drawn zone on the F10 map. -- @field #table Color Table with four entries, e.g. {1, 0, 0, 0.15}. First three are RGB color code. Fourth is the transparency Alpha value. --- @field #table FillColor Table with four entries, e.g. {1, 0, 0, 0.15}. First three are RGB color code. Fourth is the transparency Alpha value. --- @field #number drawCoalition Draw coalition. -- @field #number ZoneID ID of zone. Only zones defined in the ME have an ID! --- @field #table Table of any trigger zone properties from the ME. The key is the Name of the property, and the value is the property's Value. --- @field #number Surface Type of surface. Only determined at the center of the zone! -- @extends Core.Fsm#FSM @@ -107,11 +103,6 @@ -- * @{#ZONE_BASE.SmokeZone}(): Smokes the zone boundaries in a color. -- * @{#ZONE_BASE.FlareZone}(): Flares the zone boundaries in a color. -- --- ## A zone might have additional Properties created in the DCS Mission Editor, which can be accessed: --- --- *@{#ZONE_BASE.GetProperty}(): Returns the Value of the zone with the given PropertyName, or nil if no matching property exists. --- *@{#ZONE_BASE.GetAllProperties}(): Returns the zone Properties table. --- -- @field #ZONE_BASE ZONE_BASE = { ClassName = "ZONE_BASE", @@ -120,8 +111,6 @@ ZONE_BASE = { DrawID=nil, Color={}, ZoneID=nil, - Properties={}, - Surface=nil, } @@ -347,41 +336,18 @@ end -- @param #ZONE_BASE self -- @return #nil The bounding square. function ZONE_BASE:GetBoundingSquare() + --return { x1 = 0, y1 = 0, x2 = 0, y2 = 0 } return nil end ---- Get surface type of the zone. --- @param #ZONE_BASE self --- @return DCS#SurfaceType Type of surface. -function ZONE_BASE:GetSurfaceType() - local coord=self:GetCoordinate() - local surface=coord:GetSurfaceType() - return surface -end - --- Bound the zone boundaries with a tires. -- @param #ZONE_BASE self function ZONE_BASE:BoundZone() self:F2() + end ---- Set draw coalition of zone. --- @param #ZONE_BASE self --- @param #number Coalition Coalition. Default -1. --- @return #ZONE_BASE self -function ZONE_BASE:SetDrawCoalition(Coalition) - self.drawCoalition=Coalition or -1 - return self -end - ---- Get draw coalition of zone. --- @param #ZONE_BASE self --- @return #number Draw coaliton. -function ZONE_BASE:GetDrawCoalition() - return self.drawCoalition or -1 -end - --- Set color of zone. -- @param #ZONE_BASE self -- @param #table RGBcolor RGB color table. Default `{1, 0, 0}`. @@ -405,7 +371,7 @@ end -- @param #ZONE_BASE self -- @return #table Table with four entries, e.g. {1, 0, 0, 0.15}. First three are RGB color code. Fourth is the transparency Alpha value. function ZONE_BASE:GetColor() - return self.Color or {1, 0, 0, 0.15} + return self.Color end --- Get RGB color of zone. @@ -413,10 +379,9 @@ end -- @return #table Table with three entries, e.g. {1, 0, 0}, which is the RGB color code. function ZONE_BASE:GetColorRGB() local rgb={} - local Color=self:GetColor() - rgb[1]=Color[1] - rgb[2]=Color[2] - rgb[3]=Color[3] + rgb[1]=self.Color[1] + rgb[2]=self.Color[2] + rgb[3]=self.Color[3] return rgb end @@ -424,55 +389,7 @@ end -- @param #ZONE_BASE self -- @return #number Alpha value. function ZONE_BASE:GetColorAlpha() - local Color=self:GetColor() - local alpha=Color[4] - return alpha -end - ---- Set fill color of zone. --- @param #ZONE_BASE self --- @param #table RGBcolor RGB color table. Default `{1, 0, 0}`. --- @param #number Alpha Transparacy between 0 and 1. Default 0.15. --- @return #ZONE_BASE self -function ZONE_BASE:SetFillColor(RGBcolor, Alpha) - - RGBcolor=RGBcolor or {1, 0, 0} - Alpha=Alpha or 0.15 - - self.FillColor={} - self.FillColor[1]=RGBcolor[1] - self.FillColor[2]=RGBcolor[2] - self.FillColor[3]=RGBcolor[3] - self.FillColor[4]=Alpha - - return self -end - ---- Get fill color table of the zone. --- @param #ZONE_BASE self --- @return #table Table with four entries, e.g. {1, 0, 0, 0.15}. First three are RGB color code. Fourth is the transparency Alpha value. -function ZONE_BASE:GetFillColor() - return self.FillColor or {1, 0, 0, 0.15} -end - ---- Get RGB fill color of zone. --- @param #ZONE_BASE self --- @return #table Table with three entries, e.g. {1, 0, 0}, which is the RGB color code. -function ZONE_BASE:GetFillColorRGB() - local rgb={} - local FillColor=self:GetFillColor() - rgb[1]=FillColor[1] - rgb[2]=FillColor[2] - rgb[3]=FillColor[3] - return rgb -end - ---- Get transperency Alpha fill value of zone. --- @param #ZONE_BASE self --- @return #number Alpha value. -function ZONE_BASE:GetFillColorAlpha() - local FillColor=self:GetFillColor() - local alpha=FillColor[4] + local alpha=self.Color[4] return alpha end @@ -564,26 +481,6 @@ function ZONE_BASE:GetZoneMaybe() end end --- Returns the Value of the zone with the given PropertyName, or nil if no matching property exists. --- @param #ZONE_BASE self --- @param #string PropertyName The name of a the TriggerZone Property to be retrieved. --- @return #string The Value of the TriggerZone Property with the given PropertyName, or nil if absent. --- @usage --- --- local PropertiesZone = ZONE:FindByName("Properties Zone") --- local Property = "ExampleProperty" --- local PropertyValue = PropertiesZone:GetProperty(Property) --- -function ZONE_BASE:GetProperty(PropertyName) - return self.Properties[PropertyName] -end - --- Returns the zone Properties table. --- @param #ZONE_BASE self --- @return #table The Key:Value table of TriggerZone properties of the zone. -function ZONE_BASE:GetAllProperties() - return self.Properties -end --- The ZONE_RADIUS class, defined by a zone name, a location and a radius. -- @type ZONE_RADIUS @@ -926,8 +823,8 @@ end -- @param ObjectCategories An array of categories of the objects to find in the zone. E.g. `{Object.Category.UNIT}` -- @param UnitCategories An array of unit categories of the objects to find in the zone. E.g. `{Unit.Category.GROUND_UNIT,Unit.Category.SHIP}` -- @usage --- myzone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT}) --- local IsAttacked = myzone:IsSomeInZoneOfCoalition( self.Coalition ) +-- self.Zone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT}) +-- local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition ) function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) self.ScanData = {} @@ -997,7 +894,7 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) local SceneryName = ZoneObject:getName() self.ScanData.Scenery[SceneryType] = self.ScanData.Scenery[SceneryType] or {} self.ScanData.Scenery[SceneryType][SceneryName] = SCENERY:Register( SceneryName, ZoneObject ) - self:T( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } ) + self:F2( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } ) end end @@ -1260,9 +1157,7 @@ end -- @return #boolean true if the location is within the zone. function ZONE_RADIUS:IsVec2InZone( Vec2 ) self:F2( Vec2 ) - if not Vec2 then return false end - local ZoneVec2 = self:GetVec2() if ZoneVec2 then @@ -1280,7 +1175,7 @@ end -- @return #boolean true if the point is within the zone. function ZONE_RADIUS:IsVec3InZone( Vec3 ) self:F2( Vec3 ) - if not Vec3 then return false end + if not Vec3 then return false end local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } ) return InZone @@ -1387,8 +1282,8 @@ end --- Returns a @{Core.Point#COORDINATE} object reflecting a random 3D location within the zone. -- @param #ZONE_RADIUS self --- @param #number inner (Optional) Minimal distance from the center of the zone in meters. Default is 0 m. --- @param #number outer (Optional) Maximal distance from the outer edge of the zone in meters. Default is the radius of the zone. +-- @param #number inner (Optional) Minimal distance from the center of the zone. Default is 0. +-- @param #number outer (Optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. -- @param #table surfacetypes (Optional) Table of surface types. Can also be a single surface type. We will try max 1000 times to find the right type! -- @return Core.Point#COORDINATE The random coordinate. function ZONE_RADIUS:GetRandomCoordinate(inner, outer, surfacetypes) @@ -1555,9 +1450,9 @@ function ZONE:New( ZoneName ) end --- Find a zone in the _DATABASE using the name of the zone. --- @param #ZONE self +-- @param #ZONE_BASE self -- @param #string ZoneName The name of the zone. --- @return #ZONE self +-- @return #ZONE_BASE self function ZONE:FindByName( ZoneName ) local ZoneFound = _DATABASE:FindZone( ZoneName ) @@ -1793,7 +1688,7 @@ end --- @type ZONE_POLYGON_BASE --- @field #ZONE_POLYGON_BASE.ListVec2 Polygon The polygon defined by an array of @{DCS#Vec2}. +-- --@field #ZONE_POLYGON_BASE.ListVec2 Polygon The polygon defined by an array of @{DCS#Vec2}. -- @extends #ZONE_BASE @@ -2031,7 +1926,7 @@ function ZONE_POLYGON_BASE:BoundZone( UnBound ) end ---- Draw the zone on the F10 map. **NOTE** Currently, only polygons **up to ten points** are supported! +--- Draw the zone on the F10 map. **NOTE** Currently, only polygons with **exactly four points** are supported! -- @param #ZONE_POLYGON_BASE self -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red. @@ -2043,48 +1938,33 @@ end -- @return #ZONE_POLYGON_BASE self function ZONE_POLYGON_BASE:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) - if self._.Polygon and #self._.Polygon>=3 then + local coordinate=COORDINATE:NewFromVec2(self._.Polygon[1]) - local coordinate=COORDINATE:NewFromVec2(self._.Polygon[1]) - - Coalition=Coalition or self:GetDrawCoalition() - - -- Set draw coalition. - self:SetDrawCoalition(Coalition) + Color=Color or self:GetColorRGB() + Alpha=Alpha or 1 - Color=Color or self:GetColorRGB() - Alpha=Alpha or 1 - - -- Set color. - self:SetColor(Color, Alpha) - - FillColor=FillColor or self:GetFillColorRGB() - if not FillColor then UTILS.DeepCopy(Color) end - FillAlpha=FillAlpha or self:GetFillColorAlpha() - if not FillAlpha then FillAlpha=0.15 end - - -- Set fill color. - self:SetFillColor(FillColor, FillAlpha) - - if #self._.Polygon==4 then - - local Coord2=COORDINATE:NewFromVec2(self._.Polygon[2]) - local Coord3=COORDINATE:NewFromVec2(self._.Polygon[3]) - local Coord4=COORDINATE:NewFromVec2(self._.Polygon[4]) - - self.DrawID=coordinate:QuadToAll(Coord2, Coord3, Coord4, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) - - else - - local Coordinates=self:GetVerticiesCoordinates() - table.remove(Coordinates, 1) - - self.DrawID=coordinate:MarkupToAllFreeForm(Coordinates, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) - - end - + FillColor=FillColor or UTILS.DeepCopy(Color) + FillAlpha=FillAlpha or self:GetColorAlpha() + + + if #self._.Polygon==4 then + + local Coord2=COORDINATE:NewFromVec2(self._.Polygon[2]) + local Coord3=COORDINATE:NewFromVec2(self._.Polygon[3]) + local Coord4=COORDINATE:NewFromVec2(self._.Polygon[4]) + + self.DrawID=coordinate:QuadToAll(Coord2, Coord3, Coord4, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) + + else + + local Coordinates=self:GetVerticiesCoordinates() + table.remove(Coordinates, 1) + + self.DrawID=coordinate:MarkupToAllFreeForm(Coordinates, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) + end + return self end @@ -2165,7 +2045,7 @@ end -- @return #boolean true if the location is within the zone. function ZONE_POLYGON_BASE:IsVec2InZone( Vec2 ) self:F2( Vec2 ) - if not Vec2 then return false end + if not Vec2 then return false end local Next local Prev local InPolygon = false @@ -2197,7 +2077,7 @@ function ZONE_POLYGON_BASE:IsVec3InZone( Vec3 ) self:F2( Vec3 ) if not Vec3 then return false end - + local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } ) return InZone @@ -2417,206 +2297,6 @@ function ZONE_POLYGON:FindByName( ZoneName ) return ZoneFound end -do -- ZONE_ELASTIC - - --- @type ZONE_ELASTIC - -- @field #table points Points in 2D. - -- @field #table setGroups Set of GROUPs. - -- @field #table setOpsGroups Set of OPSGROUPS. - -- @field #table setUnits Set of UNITs. - -- @field #number updateID Scheduler ID for updating. - -- @extends #ZONE_POLYGON_BASE - - --- The ZONE_ELASTIC class defines a dynamic polygon zone, where only the convex hull is used. - -- - -- @field #ZONE_ELASTIC - ZONE_ELASTIC = { - ClassName="ZONE_ELASTIC", - points={}, - setGroups={} - } - - --- Constructor to create a ZONE_ELASTIC instance. - -- @param #ZONE_ELASTIC self - -- @param #string ZoneName Name of the zone. - -- @param DCS#Vec2 Points (Optional) Fixed points. - -- @return #ZONE_ELASTIC self - function ZONE_ELASTIC:New(ZoneName, Points) - - local self=BASE:Inherit(self, ZONE_POLYGON_BASE:New(ZoneName, Points)) --#ZONE_ELASTIC - - -- Zone objects are added to the _DATABASE and SET_ZONE objects. - _EVENTDISPATCHER:CreateEventNewZone( self ) - - if Points then - self.points=Points - end - - return self - end - - --- Add a vertex (point) to the polygon. - -- @param #ZONE_ELASTIC self - -- @param DCS#Vec2 Vec2 Point in 2D (with x and y coordinates). - -- @return #ZONE_ELASTIC self - function ZONE_ELASTIC:AddVertex2D(Vec2) - - -- Add vec2 to points. - table.insert(self.points, Vec2) - - return self - end - - - --- Add a vertex (point) to the polygon. - -- @param #ZONE_ELASTIC self - -- @param DCS#Vec3 Vec3 Point in 3D (with x, y and z coordinates). Only the x and z coordinates are used. - -- @return #ZONE_ELASTIC self - function ZONE_ELASTIC:AddVertex3D(Vec3) - - -- Add vec2 from vec3 to points. - table.insert(self.points, {x=Vec3.x, y=Vec3.z}) - - return self - end - - - --- Add a set of groups. Positions of the group will be considered as polygon vertices when contructing the convex hull. - -- @param #ZONE_ELASTIC self - -- @param Core.Set#SET_GROUP SetGroup Set of groups. - -- @return #ZONE_ELASTIC self - function ZONE_ELASTIC:AddSetGroup(GroupSet) - - -- Add set to table. - table.insert(self.setGroups, GroupSet) - - return self - end - - - --- Update the convex hull of the polygon. - -- This uses the [Graham scan](https://en.wikipedia.org/wiki/Graham_scan). - -- @param #ZONE_ELASTIC self - -- @param #number Delay Delay in seconds before the zone is updated. Default 0. - -- @param #boolean Draw Draw the zone. Default `nil`. - -- @return #ZONE_ELASTIC self - function ZONE_ELASTIC:Update(Delay, Draw) - - -- Debug info. - self:T(string.format("Updating ZONE_ELASTIC %s", tostring(self.ZoneName))) - - -- Copy all points. - local points=UTILS.DeepCopy(self.points or {}) - - if self.setGroups then - for _,_setGroup in pairs(self.setGroups) do - local setGroup=_setGroup --Core.Set#SET_GROUP - for _,_group in pairs(setGroup.Set) do - local group=_group --Wrapper.Group#GROUP - if group and group:IsAlive() then - table.insert(points, group:GetVec2()) - end - end - end - end - - -- Update polygon verticies from points. - self._.Polygon=self:_ConvexHull(points) - - if Draw~=false then - if self.DrawID or Draw==true then - self:UndrawZone() - self:DrawZone() - end - end - - return self - end - - --- Start the updating scheduler. - -- @param #ZONE_ELASTIC self - -- @param #number Tstart Time in seconds before the updating starts. - -- @param #number dT Time interval in seconds between updates. Default 60 sec. - -- @param #number Tstop Time in seconds after which the updating stops. Default `nil`. - -- @param #boolean Draw Draw the zone. Default `nil`. - -- @return #ZONE_ELASTIC self - function ZONE_ELASTIC:StartUpdate(Tstart, dT, Tstop, Draw) - - self.updateID=self:ScheduleRepeat(Tstart, dT, 0, Tstop, ZONE_ELASTIC.Update, self, 0, Draw) - - return self - end - - --- Stop the updating scheduler. - -- @param #ZONE_ELASTIC self - -- @param #number Delay Delay in seconds before the scheduler will be stopped. Default 0. - -- @return #ZONE_ELASTIC self - function ZONE_ELASTIC:StopUpdate(Delay) - - if Delay and Delay>0 then - self:ScheduleOnce(Delay, ZONE_ELASTIC.StopUpdate, self) - else - - if self.updateID then - - self:ScheduleStop(self.updateID) - - self.updateID=nil - - end - - end - - return self - end - - - --- Create a convec hull. - -- @param #ZONE_ELASTIC self - -- @param #table pl Points - -- @return #table Points - function ZONE_ELASTIC:_ConvexHull(pl) - - if #pl == 0 then - return {} - end - - table.sort(pl, function(left,right) - return left.x < right.x - end) - - local h = {} - - -- Function: ccw > 0 if three points make a counter-clockwise turn, clockwise if ccw < 0, and collinear if ccw = 0. - local function ccw(a,b,c) - return (b.x - a.x) * (c.y - a.y) > (b.y - a.y) * (c.x - a.x) - end - - -- lower hull - for i,pt in pairs(pl) do - while #h >= 2 and not ccw(h[#h-1], h[#h], pt) do - table.remove(h,#h) - end - table.insert(h,pt) - end - - -- upper hull - local t = #h + 1 - for i=#pl, 1, -1 do - local pt = pl[i] - while #h >= t and not ccw(h[#h-1], h[#h], pt) do - table.remove(h, #h) - end - table.insert(h, pt) - end - - table.remove(h, #h) - - return h - end - -end - do -- ZONE_AIRBASE --- @type ZONE_AIRBASE