From 7ca7caea7501002ad297db0caa023c50f33fba32 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 24 Jul 2021 15:50:10 +0200 Subject: [PATCH] Updates with changes from develop --- Moose Development/Moose/Ops/ATIS.lua | 84 ++-- Moose Development/Moose/Ops/Airboss.lua | 597 +++++++++++++++-------- Moose Development/Moose/Ops/CSAR.lua | 91 ++-- Moose Development/Moose/Ops/CTLD.lua | 389 +++++++++++++-- Moose Development/Moose/Wrapper/Unit.lua | 12 + 5 files changed, 873 insertions(+), 300 deletions(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index e8b475195..8d5223cd0 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -383,13 +383,13 @@ ATIS.Alphabet = { --- Runway correction for converting true to magnetic heading. -- @type ATIS.RunwayM2T --- @field #number Caucasus 0° (East). --- @field #number Nevada +12° (East). --- @field #number Normandy -10° (West). --- @field #number PersianGulf +2° (East). --- @field #number TheChannel -10° (West). --- @field #number Syria +5° (East). --- @field #number MarianaIslands +2° (East). +-- @field #number Caucasus 0° (East). +-- @field #number Nevada +12° (East). +-- @field #number Normandy -10° (West). +-- @field #number PersianGulf +2° (East). +-- @field #number TheChannel -10° (West). +-- @field #number Syria +5° (East). +-- @field #number MarianaIslands +2° (East). ATIS.RunwayM2T={ Caucasus=0, Nevada=12, @@ -839,9 +839,9 @@ function ATIS:SetMapMarks(switch) return self end ---- Set magnetic runway headings as depicted on the runway, *e.g.* "13" for 130° or "25L" for the left runway with magnetic heading 250°. +--- Set magnetic runway headings as depicted on the runway, *e.g.* "13" for 130° or "25L" for the left runway with magnetic heading 250°. -- @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. +-- @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) @@ -974,12 +974,12 @@ end -- -- To get *true* from *magnetic* heading one has to add easterly or substract westerly variation, e.g -- --- A magnetic heading of 180° corresponds to a true heading of +-- A magnetic heading of 180° corresponds to a true heading of -- --- * 186° on the Caucaus map --- * 192° on the Nevada map --- * 170° on the Normany map --- * 182° on the Persian Gulf map +-- * 186° on the Caucaus map +-- * 192° on the Nevada map +-- * 170° on the Normany map +-- * 182° on the Persian Gulf map -- -- Likewise, to convert *true* into *magnetic* heading, one has to substract easterly and add westerly variation. -- @@ -1314,7 +1314,7 @@ function ATIS:onafterBroadcast(From, Event, To) 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 TS=288.15 -- Standard Temperature assumed by Altimeter is 15°C local q=qnh*100 -- Calculate Pressure. @@ -1451,13 +1451,13 @@ function ATIS:onafterBroadcast(From, Event, To) --- Temperature and Dew Point --- --------------------------------- - -- Temperature in °C. + -- Temperature in °C. local temperature=coord:GetTemperature(height+5) - -- Dew point in °C. + -- Dew point in °C. local dewpoint=temperature-(100-self.relHumidity)/5 - -- Convert to °F. + -- Convert to °F. if self.TDegF then temperature=UTILS.CelciusToFarenheit(temperature) dewpoint=UTILS.CelciusToFarenheit(dewpoint) @@ -1617,6 +1617,30 @@ function ATIS:onafterBroadcast(From, Event, To) -- 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 -- Overcast + Rain clouddens=9 if temperature>5 then @@ -1877,15 +1901,15 @@ function ATIS:onafterBroadcast(From, Event, To) -- Temperature if self.TDegF then if temperature<0 then - subtitle=string.format("Temperature -%s °F", TEMPERATURE) + 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) + 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 @@ -1906,15 +1930,15 @@ function ATIS:onafterBroadcast(From, Event, To) -- Dew point if self.TDegF then if dewpoint<0 then - subtitle=string.format("Dew point -%s °F", DEWPOINT) + 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) + 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 @@ -2252,8 +2276,8 @@ function ATIS:onafterReport(From, Event, To, Text) -- 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, "°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", "hecto Pascals") @@ -2373,7 +2397,7 @@ end --- Get runway from user supplied magnetic heading. -- @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°. +-- @return #string Runway magnetic heading divided by ten (and rounded). Eg, "13" for 130°. function ATIS:GetMagneticRunway(windfrom) local diffmin=nil @@ -2416,7 +2440,7 @@ function ATIS:GetNavPoint(navpoints, runway, left) local navL=self:GetRunwayLR(nav.runway) local hdgD=UTILS.HdgDiff(navy,rwyy) - if hdgD<=15 then --We allow an error of +-15° here. + 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 diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index f2ad2df53..6468751be 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -39,6 +39,7 @@ -- * [F-14A/B Tomcat](https://forums.eagle.ru/forumdisplay.php?f=395) (Player & AI) -- * [A-4E Skyhawk Community Mod](https://forums.eagle.ru/showthread.php?t=224989) (Player & AI) -- * [AV-8B N/A Harrier](https://forums.eagle.ru/forumdisplay.php?f=555) (Player & AI) [**WIP**] +-- * [T-45C Goshawk](https://www.vnao-cvw-7.com/t-45-goshawk) (VNAO)(Player & AI) [**WIP**] -- * F/A-18C Hornet (AI) -- * F-14A Tomcat (AI) -- * E-2D Hawkeye (AI) @@ -101,7 +102,7 @@ -- ### Wags DCS Hornet Videos: -- -- * [DCS: F/A-18C Hornet - Episode 9: CASE I Carrier Landing](https://www.youtube.com/watch?v=TuigBLhtAH8) --- * [DCS: F/A-18C Hornet – Episode 16: CASE III Introduction](https://www.youtube.com/watch?v=DvlMHnLjbDQ) +-- * [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 @@ -266,7 +267,7 @@ -- -- That being said, this script allows you to use any of the three cases to be used at any time. Or, in other words, *you* need to specify when which case is safe and appropriate. -- --- This is a lot of responsability. *You* are the boss, but *you* need to make the right decisions or things will go terribly wrong! +-- This is a lot of responsibility. *You* are the boss, but *you* need to make the right decisions or things will go terribly wrong! -- -- Recovery windows can be set up via the @{#AIRBOSS.AddRecoveryWindow} function as explained below. With this it is possible to seamlessly (within reason!) switch recovery cases in the same mission. -- @@ -293,14 +294,14 @@ -- -- ![Banner Image](..\Presentations\AIRBOSS\Airboss_Case1_Landing.png) -- --- Once the aircraft reaches the Inital, the landing pattern begins. The important steps of the pattern are shown in the image above. +-- Once the aircraft reaches the Initial, the landing pattern begins. The important steps of the pattern are shown in the image above. -- -- -- ## CASE III -- -- ![Banner Image](..\Presentations\AIRBOSS\Airboss_Case3.png) -- --- A Case III recovery is conducted during nighttime. The holding position and the landing pattern are rather different from a Case I recovery as can be seen in the image above. +-- A Case III recovery is conducted during nighttime or when the visibility is below CASE II minima during the day. The holding position and the landing pattern are rather different from a Case I recovery as can be seen in the image above. -- -- The first holding zone starts 21 NM astern the carrier at angels 6. The separation between the stacks is 1000 ft just like in Case I. However, the distance to the boat -- increases by 1 NM with each stack. The general form can be written as D=15+6+(N-1), where D is the distance to the boat in NM and N the number of the stack starting at N=1. @@ -572,7 +573,10 @@ -- * **L**ined **U**p **L**eft or **R**ight: LUL, LUR -- * Too **H**igh or too **LO**w: H, LO -- * Too **F**ast or too **SLO**w: F, SLO --- * **Fly through** glideslope **down** or **up**: \\ , / +-- * **O**ver**S**hoot: OS, only referenced during **X** +-- * **Fly through** glideslope **down** or **up**: \\ , /, advisory only +-- * **D**rift **L**eft or **R**ight:DL, DR, advisory only +-- * **A**ngled **A**pproach: Angled approach (wings level and LUL): AA, advisory only -- -- Each grading, x, is subdivided by -- @@ -633,7 +637,7 @@ -- -- ## Foul Deck Waveoff -- --- A foul deck waveoff is called by the LSO if an aircraft is detected within the landing area when an approaching aircraft is crossing the ship's wake during Case I/II operations, +-- A foul deck waveoff is called by the LSO if an aircraft is detected within the landing area when an approaching aircraft is at position IM-IC during Case I/II operations, -- or with an aircraft approaching the 3/4 NM during Case III operations. -- -- The approaching aircraft will be notified via LSO radio comms and is supposed to overfly the landing area to enter the Bolter pattern. **This pass is not graded**. @@ -721,7 +725,7 @@ -- -- The same holds true after the recovery window closes. The carrier will head back to the place where he left its assigned route and resume the path to the next waypoint defined in the mission editor. -- --- Note that the carrier will only head into the wind, if the wind direction is different by more than 5° from the current heading of the carrier (the angled runway, if any, fis taken into account here). +-- Note that the carrier will only head into the wind, if the wind direction is different by more than 5° from the current heading of the carrier (the angled runway, if any, fis taken into account here). -- -- === -- @@ -860,10 +864,10 @@ -- -- The graph displays the lineup error (LUE) as a function of the distance to the carrier. -- --- The pilot approaches the carrier from the port side, LUE>0°, at a distance of ~1 NM. --- At the beginning of the groove (X), he significantly overshoots to the starboard side (LUE<5°). +-- The pilot approaches the carrier from the port side, LUE>0°, at a distance of ~1 NM. +-- At the beginning of the groove (X), he significantly overshoots to the starboard side (LUE<5°). -- In the middle (IM), he performs good corrections and smoothly reduces the lineup error. --- Finally, at a distance of ~0.3 NM (IC) he has corrected his lineup with the runway to a reasonable level, |LUE|<0.5°. +-- Finally, at a distance of ~0.3 NM (IC) he has corrected his lineup with the runway to a reasonable level, |LUE|<0.5°. -- -- ## Glideslope Error -- @@ -872,7 +876,7 @@ -- The graph displays the glideslope error (GSE) as a function of the distance to the carrier. -- -- In this case the pilot already enters the groove (X) below the optimal glideslope. He is not able to correct his height in the IM part and --- stays significantly too low. In close, he performs a harsh correction to gain altitude and ends up even slightly too high (GSE>0.5°). +-- stays significantly too low. In close, he performs a harsh correction to gain altitude and ends up even slightly too high (GSE>0.5°). -- At his point further corrections are necessary. -- -- ## Angle of Attack @@ -1263,6 +1267,7 @@ AIRBOSS = { -- @field #string S3BTANKER Lockheed S-3B Viking tanker. -- @field #string E2D Grumman E-2D Hawkeye AWACS. -- @field #string C2A Grumman C-2A Greyhound from Military Aircraft Mod. +-- @field #string T45C T-45C by VNAO AIRBOSS.AircraftCarrier={ AV8B="AV8BNA", HORNET="FA-18C_hornet", @@ -1271,6 +1276,7 @@ AIRBOSS.AircraftCarrier={ F14B="F-14B", F14A_AI="F-14A", FA18C="F/A-18C", + T45C="T-45", S3B="S-3B", S3BTANKER="S-3B Tanker", E2D="E-2C", @@ -1338,6 +1344,8 @@ AIRBOSS.CarrierType={ -- @field #number _min Min _OK_ value. Default -0.5 deg. -- @field #number Left (LUR) threshold. Default -1.0 deg. -- @field #number Right (LUL) threshold. Default 1.0 deg. +-- @field #number LeftMed threshold for AA/OS measuring. Default -2.0 deg. +-- @field #number RightMed threshold for AA/OS measuring. Default 2.0 deg. -- @field #number LEFT LUR threshold. Default -3.0 deg. -- @field #number RIGHT LUL threshold. Default 3.0 deg. @@ -1940,17 +1948,22 @@ function AIRBOSS:New(carriername, alias) -- Welcome players. 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 + -- Init carrier parameters. if self.carriertype==AIRBOSS.CarrierType.STENNIS then self:_InitStennis() elseif self.carriertype==AIRBOSS.CarrierType.ROOSEVELT then self:_InitNimitz() elseif self.carriertype==AIRBOSS.CarrierType.LINCOLN then - self:_InitNimitz() - elseif self.carriertype==AIRBOSS.CarrierType.WASHINGTON then self:_InitNimitz() - elseif self.carriertype==AIRBOSS.CarrierType.TRUMAN then - self:_InitNimitz() + elseif self.carriertype==AIRBOSS.CarrierType.WASHINGTON then + self:_InitNimitz() + elseif self.carriertype==AIRBOSS.CarrierType.TRUMAN then + self:_InitNimitz() elseif self.carriertype==AIRBOSS.CarrierType.VINSON then -- TODO: Carl Vinson parameters. self:_InitStennis() @@ -1988,7 +2001,7 @@ function AIRBOSS:New(carriername, alias) 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:_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) @@ -2028,7 +2041,7 @@ function AIRBOSS:New(carriername, alias) -- Bow bow:FlareYellow() - + -- Runway half width = 10 m. local r1=stern:Translate(self.carrierparam.rwywidth*0.5, FB+90) local r2=stern:Translate(self.carrierparam.rwywidth*0.5, FB-90) @@ -2425,7 +2438,7 @@ end -- @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 offset Relative Marshal radial in degrees for Case II/III recoveries. Default 30°. -- @return #AIRBOSS self function AIRBOSS:SetMenuRecovery(duration, windondeck, uturn, offset) @@ -2643,10 +2656,10 @@ end --- Set multiplayer environment wire correction. -- @param #AIRBOSS self --- @param #number Dcorr Correction distance in meters. Default 8.7 m. +-- @param #number Dcorr Correction distance in meters. Default 12 m. -- @return #AIRBOSS self function AIRBOSS:SetMPWireCorrection(Dcorr) - self.mpWireCorrection=Dcorr or 8.7 + self.mpWireCorrection=Dcorr or 12 return self end @@ -2808,16 +2821,20 @@ end -- @param #number _max -- @param #number _min -- @param #number Left +-- @param #number LeftMed -- @param #number LEFT -- @param #number Right +-- @param #number RightMed -- @param #number RIGHT -- @return #AIRBOSS self -function AIRBOSS:SetLineupErrorThresholds(_max,_min, Left, LEFT, Right, RIGHT) +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 @@ -3288,7 +3305,7 @@ function AIRBOSS:GetNextRecoveryTime(InSeconds) 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 @@ -3398,6 +3415,12 @@ function AIRBOSS:onafterStart(From, Event, To) 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.StatusTimer=TIMER:New(self._Status, self):Start(2, 0.5) -- Start status check in 1 second. self:__Status(1) @@ -3410,19 +3433,12 @@ end -- @param #string To To state. function AIRBOSS:onafterStatus(From, Event, To) - if true then - --env.info("FF Status ==> return") - --return - end - -- Get current time. local time=timer.getTime() -- Update marshal and pattern queue every 30 seconds. if time-self.Tqueue>self.dTqueue then - --collectgarbage() - -- Get time. local clock=UTILS.SecondsToClock(timer.getAbsTime()) local eta=UTILS.SecondsToClock(self:_GetETAatNextWP()) @@ -3433,7 +3449,7 @@ function AIRBOSS:onafterStatus(From, Event, To) local speed=self.carrier:GetVelocityKNOTS() -- Check water is ahead. - local collision=self:_CheckCollisionCoord(pos:Translate(self.collisiondist, hdg)) + local collision=false --self:_CheckCollisionCoord(pos:Translate(self.collisiondist, hdg)) local holdtime=0 if self.holdtimestamp then @@ -3490,14 +3506,8 @@ function AIRBOSS:onafterStatus(From, Event, To) self.recoverywindow.WIND=false end - else - - -- Find path around the obstacle. - if not self.detour then - --self:_Pathfinder() - end - end + end @@ -3528,14 +3538,21 @@ function AIRBOSS:onafterStatus(From, Event, To) self:_ActivateBeacons() end + -- Call status every ~0.5 seconds. + self:__Status(-30) + +end + +--- Check AI status. Pattern queue AI in the groove? Marshal queue AI arrived in holding zone? +-- @param #AIRBOSS self +function AIRBOSS:_Status() + -- Check player status. self:_CheckPlayerStatus() -- Check AI landing pattern status self:_CheckAIStatus() - -- Call status every ~0.5 seconds. - self:__Status(-self.dTstatus) end --- Check AI status. Pattern queue AI in the groove? Marshal queue AI arrived in holding zone? @@ -3587,11 +3604,15 @@ function AIRBOSS:_CheckAIStatus() -- Get lineup and distance to carrier. local lineup=self:_Lineup(unit, true) + local unitcoord=unit:GetCoord() + + local dist=unitcoord:Get2DDistance(self:GetCoord()) + -- Distance in NM. - local distance=UTILS.MetersToNM(unit:GetCoordinate():Get2DDistance(self:GetCoordinate())) + local distance=UTILS.MetersToNM(dist) -- Altitude in ft. - local alt=UTILS.MetersToFeet(unit:GetAltitude()) + local alt=UTILS.MetersToFeet(unitcoord.y) -- Check if parameters are right and flight is in the groove. if lineup<2 and distance<=0.75 and alt<500 and not element.ballcall then @@ -3857,7 +3878,7 @@ function AIRBOSS:_CheckRecoveryTimes() -- Check if time is less than 5 minutes. if nextwindow.WIND and nextwindow.START-time 5° different from the current heading. + -- Check that wind is blowing from a direction > 5° different from the current heading. local hdg=self:GetHeading() local wind=self:GetHeadingIntoWind() local delta=self:_GetDeltaHeading(hdg, wind) @@ -3875,7 +3896,7 @@ function AIRBOSS:_CheckRecoveryTimes() 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))) + 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) @@ -4145,7 +4166,7 @@ end -- @param #string To To state. 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) @@ -5471,6 +5492,7 @@ 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 @@ -5497,6 +5519,15 @@ function AIRBOSS:_GetAircraftAoA(playerData) 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 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! @@ -5681,6 +5712,9 @@ function AIRBOSS:_GetAircraftParameters(playerData, step) elseif skyhawk then alt=UTILS.FeetToMeters(600) speed=UTILS.KnotsToMps(250) + elseif goshawk then + alt=UTILS.FeetToMeters(800) + speed=UTILS.KnotsToMps(300) end elseif step==AIRBOSS.PatternStep.BREAKENTRY then @@ -5691,11 +5725,14 @@ function AIRBOSS:_GetAircraftParameters(playerData, step) elseif skyhawk then alt=UTILS.FeetToMeters(600) speed=UTILS.KnotsToMps(250) + elseif goshawk then + alt=UTILS.FeetToMeters(800) + speed=UTILS.KnotsToMps(300) end elseif step==AIRBOSS.PatternStep.EARLYBREAK then - if hornet or tomcat or harrier then + if hornet or tomcat or harrier or goshawk then alt=UTILS.FeetToMeters(800) elseif skyhawk then alt=UTILS.FeetToMeters(600) @@ -5703,7 +5740,7 @@ function AIRBOSS:_GetAircraftParameters(playerData, step) elseif step==AIRBOSS.PatternStep.LATEBREAK then - if hornet or tomcat or harrier then + if hornet or tomcat or harrier or goshawk then alt=UTILS.FeetToMeters(800) elseif skyhawk then alt=UTILS.FeetToMeters(600) @@ -5711,7 +5748,7 @@ function AIRBOSS:_GetAircraftParameters(playerData, step) elseif step==AIRBOSS.PatternStep.ABEAM then - if hornet or tomcat or harrier then + if hornet or tomcat or harrier or goshawk then alt=UTILS.FeetToMeters(600) elseif skyhawk then alt=UTILS.FeetToMeters(500) @@ -5726,10 +5763,19 @@ function AIRBOSS:_GetAircraftParameters(playerData, step) dist=UTILS.NMToMeters(1.2) end + if goshawk then + -- 0.9 to 1.1 NM per natops ch.4 page 48 + dist=UTILS.NMToMeters(0.9) + else + dist=UTILS.NMToMeters(1.1) + end + elseif step==AIRBOSS.PatternStep.NINETY then if hornet or tomcat then alt=UTILS.FeetToMeters(500) + elseif goshawk then + alt=UTILS.FeetToMeters(450) elseif skyhawk then alt=UTILS.FeetToMeters(500) elseif harrier then @@ -5740,7 +5786,7 @@ function AIRBOSS:_GetAircraftParameters(playerData, step) elseif step==AIRBOSS.PatternStep.WAKE then - if hornet then + if hornet or goshawk then 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. @@ -5753,7 +5799,7 @@ function AIRBOSS:_GetAircraftParameters(playerData, step) elseif step==AIRBOSS.PatternStep.FINAL then - if hornet then + if hornet or goshawk then alt=UTILS.FeetToMeters(300) elseif tomcat then alt=UTILS.FeetToMeters(360) @@ -6079,66 +6125,32 @@ function AIRBOSS:_ScanCarrierZone() -- Create a new flight group if knownflight then - -- Debug output. - self:T2(self.lid..string.format("Known flight group %s of type %s in CCA.", groupname, actype)) - -- Check if flight is AI and if we want to handle it at all. - if knownflight.ai and self.handleai then + if knownflight.ai and knownflight.flag==-100 and self.handleai then - -- Defines if AI group should be handled by the airboss. - local iscarriersquad=true + local putintomarshal=false - -- Check if AI group is part of the group set if a set was defined. - if self.squadsetAI then - local group=self.squadsetAI:FindGroup(groupname) - if group then - iscarriersquad=true + -- Get flight group. + local flight=_DATABASE:GetFlightGroup(groupname) + + if flight and flight:IsInbound() and flight.destbase:GetName()==self.carrier:GetName() then + if flight.ishelo then else - iscarriersquad=false - end - end - - -- Check if group was explicitly excluded. - if self.excludesetAI then - local group=self.excludesetAI:FindGroup(groupname) - if group then - iscarriersquad=false + putintomarshal=true end + flight.airboss=self end - -- Get distance to carrier. - local dist=knownflight.group:GetCoordinate():Get2DDistance(self:GetCoordinate()) - -- Close in distance. Is >0 if AC comes closer wrt to first detected distance d0. - local closein=knownflight.dist0-dist - - -- Debug info. - self:T3(self.lid..string.format("Known AI flight group %s closed in by %.1f NM", knownflight.groupname, UTILS.MetersToNM(closein))) - - -- Is this group the tanker? - local istanker=self.tanker and self.tanker.tanker:GetName()==groupname - - -- Is this group the AWACS? - local isawacs=self.awacs and self.awacs.tanker:GetName()==groupname - - -- Send tanker to marshal stack? - local tanker2marshal = istanker and self.tanker:IsReturning() and self.tanker.airbase:GetName()==self.airbase:GetName() and knownflight.flag==-100 and self.tanker.recovery==true - - -- Send AWACS to marhsal stack? - local awacs2marshal = isawacs and self.awacs:IsReturning() and self.awacs.airbase:GetName()==self.airbase:GetName() and knownflight.flag==-100 and self.awacs.recovery==true - - -- Put flight into Marshal. - local putintomarshal=closein>UTILS.NMToMeters(5) and knownflight.flag==-100 and iscarriersquad and (not istanker) and (not isawacs) - - -- Send AI flight to marshal stack if group closes in more than 5 and has initial flag value. - if putintomarshal or tanker2marshal or awacs2marshal then + -- Send AI flight to marshal stack. + if putintomarshal then -- Get the next free stack for current recovery case. local stack=self:_GetFreeStack(knownflight.ai) -- Repawn. - local respawn=self.respawnAI --or tanker2marshal + local respawn=self.respawnAI if stack then @@ -6158,7 +6170,8 @@ function AIRBOSS:_ScanCarrierZone() break end -- Closed in or tanker/AWACS - end -- AI + + end else @@ -6262,7 +6275,7 @@ function AIRBOSS:_MarshalPlayer(playerData, stack) -- Set stack flag. flight.flag=stack - + -- Trigger Marshal event. self:Marshal(flight) end @@ -6521,7 +6534,7 @@ function AIRBOSS:_MarshalAI(flight, nstack, respawn) -- Route group. flight.group:Route(wp, 1) - + -- Trigger Marshal event. self:Marshal(flight) @@ -6920,7 +6933,7 @@ function AIRBOSS:_AddMarshalGroup(flight, stack) -- For case 1 we want the BRC but above routine return FB. radial=self:GetBRC() end - local text=string.format("Select TACAN %03d°, channel %d%s (%s)", radial, self.TACANchannel,self.TACANmode, self.TACANmorse) + local text=string.format("Select TACAN %03d°, channel %d%s (%s)", radial, self.TACANchannel,self.TACANmode, self.TACANmorse) self:MessageToPlayer(flight, text, nil, "") end @@ -7057,7 +7070,7 @@ function AIRBOSS:_GetFreeStack(ai, case, empty) -- Recovery case. case=case or self.case - + if case==1 then return self:_GetFreeStack_Old(ai, case, empty) end @@ -7073,7 +7086,7 @@ function AIRBOSS:_GetFreeStack(ai, case, empty) for i=1,nmaxstacks do stack[i]=self.NmaxStack -- Number of human flights per stack. end - + local nmax=1 -- Loop over all flights in marshal stack. @@ -7085,7 +7098,7 @@ function AIRBOSS:_GetFreeStack(ai, case, empty) -- Get stack of flight. local n=flight.flag - + if n>nmax then nmax=n end @@ -7102,7 +7115,7 @@ function AIRBOSS:_GetFreeStack(ai, case, empty) end end - + local nfree=nil if stack[nmax]==0 then -- Max occupied stack is completely full! @@ -7118,7 +7131,7 @@ function AIRBOSS:_GetFreeStack(ai, case, empty) -- Case II/III return next stack nfree=nmax+1 end - + 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])) @@ -7130,7 +7143,7 @@ function AIRBOSS:_GetFreeStack(ai, case, empty) else nfree=nmax end - + end self:I(self.lid..string.format("Returning free stack %s", tostring(nfree))) @@ -8877,6 +8890,58 @@ function AIRBOSS:OnEventEjection(EventData) end +--- Airboss event handler for event REMOVEUNIT. +-- @param #AIRBOSS self +-- @param Core.Event#EVENTDATA 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) + return + end + if EventData.IniUnit==nil then + self:E(self.lid.."ERROR: EventData.IniUnit=nil in event REMOVEUNIT!") + self:E(EventData) + return + end + + + local _unitName=EventData.IniUnitName + local _unit, _playername=self:_GetPlayerUnitAndName(_unitName) + + self:T3(self.lid.."EJECT: unit = "..tostring(EventData.IniUnitName)) + self:T3(self.lid.."EJECT: group = "..tostring(EventData.IniGroupName)) + self:T3(self.lid.."EJECT: player = "..tostring(_playername)) + + if _unit and _playername then + self:T(self.lid..string.format("Player %s removed!",_playername)) + + -- Get player flight. + local flight=self.players[_playername] + + -- Remove flight completely from all queues and collapse marshal if necessary. + if flight then + self:_RemoveFlight(flight, true) + end + + else + -- Debug message. + 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) + + -- 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) + end + +end + --- Airboss event handler for event player leave unit. -- @param #AIRBOSS self -- @param Core.Event#EVENTDATA EventData @@ -9281,7 +9346,7 @@ function AIRBOSS:_Initial(playerData) -- 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°!" + hint=hint.." - Hook down, SAS on, Wing Sweep 68°!" else hint=hint.." - Hook down!" end @@ -10041,6 +10106,27 @@ function AIRBOSS:_Groove(playerData) -- Distance in NM. 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 + 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)) @@ -10339,24 +10425,25 @@ function AIRBOSS:_GetSternCoord() local FB=self:GetFinalBearing() -- Stern coordinate (sterndist<0). Also translate 10 meters starboard wrt Final bearing. - local stern=self:GetCoordinate() + self.sterncoord:UpdateFromCoordinate(self:GetCoordinate()) + --local stern=self:GetCoordinate() -- Stern coordinate (sterndist<0). if self.carriertype==AIRBOSS.CarrierType.TARAWA then -- Tarawa: Translate 8 meters port. - stern=stern:Translate(self.carrierparam.sterndist, hdg):Translate(8, FB-90) + 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. - stern=stern:Translate(self.carrierparam.sterndist, hdg):Translate(7, FB+90) + self.sterncoord:Translate(self.carrierparam.sterndist, hdg, true, true):Translate(7, FB+90, true, true) else -- Nimitz SC: translate 8 meters starboard wrt Final bearing. - stern=stern:Translate(self.carrierparam.sterndist, hdg):Translate(9.5, FB+90) + self.sterncoord:Translate(self.carrierparam.sterndist, hdg, true, true):Translate(9.5, FB+90, true, true) end -- Set altitude. - stern:SetAltitude(self.carrierparam.deckheight) + self.sterncoord:SetAltitude(self.carrierparam.deckheight) - return stern + return self.sterncoord end --- Get wire from landing position. @@ -10476,6 +10563,9 @@ function AIRBOSS:_Trapped(playerData) 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 end -- Get wire. @@ -10561,6 +10651,8 @@ end -- @return Core.Zone#ZONE_POLYGON_BASE Initial zone. function AIRBOSS:_GetZoneInitial(case) + 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) @@ -10568,7 +10660,7 @@ function AIRBOSS:_GetZoneInitial(case) local cv=self:GetCoordinate() -- Vec2 array. - local vec2 + local vec2={} if case==1 then -- Case I @@ -10599,9 +10691,12 @@ function AIRBOSS:_GetZoneInitial(case) 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) - return zone + self.zoneInitial:UpdateFromVec2(vec2) + + --return zone + return self.zoneInitial end --- Get lineup groove zone. @@ -10609,6 +10704,8 @@ end -- @return Core.Zone#ZONE_POLYGON_BASE Lineup zone. function AIRBOSS:_GetZoneLineup() + 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) @@ -10625,10 +10722,13 @@ function AIRBOSS:_GetZoneLineup() -- Vec2 array. local vec2={c1:GetVec2(), c2:GetVec2(), c3:GetVec2(), c4:GetVec2(), c5:GetVec2()} - -- Polygon zone. - local zone=ZONE_POLYGON_BASE:New("Zone Lineup", vec2) + self.zoneLineup:UpdateFromVec2(vec2) - return zone + -- Polygon zone. + --local zone=ZONE_POLYGON_BASE:New("Zone Lineup", vec2) + --return zone + + return self.zoneLineup end @@ -10640,6 +10740,8 @@ end -- @return Core.Zone#ZONE_POLYGON_BASE Groove zone. function AIRBOSS:_GetZoneGroove(l, w, b) + self.zoneGroove=self.zoneGroove or ZONE_POLYGON_BASE:New("Zone Groove") + l=l or 1.50 w=w or 0.25 b=b or 0.10 @@ -10661,10 +10763,13 @@ function AIRBOSS:_GetZoneGroove(l, w, b) -- Vec2 array. local vec2={c1:GetVec2(), c2:GetVec2(), c3:GetVec2(), c4:GetVec2(), c5:GetVec2(), c6:GetVec2()} - -- Polygon zone. - local zone=ZONE_POLYGON_BASE:New("Zone Groove", vec2) + self.zoneGroove:UpdateFromVec2(vec2) - return zone + -- Polygon zone. + --local zone=ZONE_POLYGON_BASE:New("Zone Groove", vec2) + --return zone + + return self.zoneGroove end --- Get Bullseye zone with radius 1 NM and DME 3 NM from the carrier. Radial depends on recovery case. @@ -10688,8 +10793,9 @@ function AIRBOSS:_GetZoneBullseye(case) -- Create zone. local zone=ZONE_RADIUS:New("Zone Bullseye", vec2, radius) - return zone + + --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. @@ -10885,6 +10991,8 @@ end -- @return Core.Zone#ZONE Zone surrounding the carrier. function AIRBOSS:_GetZoneCarrierBox() + self.zoneCarrierbox=self.zoneCarrierbox or ZONE_POLYGON_BASE:New("Carrier Box Zone") + -- Stern coordinate. local S=self:_GetSternCoord() @@ -10913,9 +11021,12 @@ function AIRBOSS:_GetZoneCarrierBox() end -- Create polygon zone. - local zone=ZONE_POLYGON_BASE:New("Carrier Box Zone", vec2) + --local zone=ZONE_POLYGON_BASE:New("Carrier Box Zone", vec2) + --return zone - return zone + self.zoneCarrierbox:UpdateFromVec2(vec2) + + return self.zoneCarrierbox end --- Get zone of landing runway. @@ -10923,6 +11034,8 @@ 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") + -- Stern coordinate. local S=self:_GetSternCoord() @@ -10945,9 +11058,12 @@ function AIRBOSS:_GetZoneRunwayBox() end -- Create polygon zone. - local zone=ZONE_POLYGON_BASE:New("Landing Runway Zone", vec2) + --local zone=ZONE_POLYGON_BASE:New("Landing Runway Zone", vec2) + --return zone - return zone + self.zoneRunwaybox:UpdateFromVec2(vec2) + + return self.zoneRunwaybox end @@ -11050,12 +11166,14 @@ function AIRBOSS:_GetZoneHolding(case, stack) -- Post 2.5 NM port of carrier. local Post=self:GetCoordinate():Translate(D, hdg+270) + --TODO: update zone not creating a new one. + -- Create holding zone. - 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 then - zoneHolding=ZONE_RADIUS:New("CASE I Holding Zone", self.carrier:GetVec2(), UTILS.NMToMeters(5)) + self.zoneHolding=ZONE_RADIUS:New("CASE I Holding Zone", self.carrier:GetVec2(), UTILS.NMToMeters(5)) end @@ -11074,10 +11192,12 @@ function AIRBOSS:_GetZoneHolding(case, stack) -- 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. - zoneHolding=ZONE_POLYGON_BASE:New("CASE II/III Holding Zone", p) + self.zoneHolding=self.zoneHolding or ZONE_POLYGON_BASE:New("CASE II/III Holding Zone") + + self.zoneHolding:UpdateFromVec2(p) end - return zoneHolding + return self.zoneHolding end --- Get zone where player are automatically commence when enter. @@ -11118,11 +11238,13 @@ function AIRBOSS:_GetZoneCommence(case, stack) end -- Create holding zone. - zone=ZONE_RADIUS:New("CASE I Commence Zone", Three:GetVec2(), R) + self.zoneCommence=self.zoneCommence or ZONE_RADIUS:New("CASE I Commence Zone") + + self.zoneCommence:UpdateFromVec2(Three:GetVec2(), R) else -- Case II/III - + stack=stack or 1 -- Start point at 21 NM for stack=1. @@ -11149,11 +11271,13 @@ function AIRBOSS:_GetZoneCommence(case, stack) end -- Zone polygon. - zone=ZONE_POLYGON_BASE:New("CASE II/III Commence Zone", p) + self.zoneCommence=self.zoneCommence or ZONE_POLYGON_BASE:New("CASE II/III Commence Zone") + + self.zoneCommence:UpdateFromVec2(p) end - return zone + return self.zoneCommence end ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -11207,15 +11331,15 @@ function AIRBOSS:_AttitudeMonitor(playerData) -- 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)) + 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) 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) + 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() @@ -11236,14 +11360,14 @@ function AIRBOSS:_AttitudeMonitor(playerData) 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)) + 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("\nGamma=%.1f° | Rho=%.1f°", relhead, phi) end MESSAGE:New(text, 1, nil , true):ToClient(playerData.client) @@ -11397,8 +11521,11 @@ end -- @return Core.Point#COORDINATE Optimal landing coordinate. function AIRBOSS:_GetOptLandingCoordinate() + -- Start with stern coordiante. + self.landingcoord:UpdateFromCoordinate(self:_GetSternCoord()) + -- Stern coordinate. - local stern=self:_GetSternCoord() + --local stern=self:_GetSternCoord() -- Final bearing. local FB=self:GetFinalBearing(false) @@ -11406,10 +11533,11 @@ function AIRBOSS:_GetOptLandingCoordinate() if self.carriertype==AIRBOSS.CarrierType.TARAWA then -- Landing 100 ft abeam, 120 ft alt. - 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. - stern:SetAltitude(UTILS.FeetToMeters(120)) + self.landingcoord:SetAltitude(UTILS.FeetToMeters(120)) else @@ -11417,15 +11545,15 @@ function AIRBOSS:_GetOptLandingCoordinate() 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 - stern=stern:Translate(w3, FB, true) + self.landingcoord:Translate(w3, FB, true, true) end -- Add 2 meters to account for aircraft height. - stern.y=stern.y+2 + self.landingcoord.y=self.landingcoord.y+2 end - return stern + return self.landingcoord end --- Get landing spot on Tarawa. @@ -11433,8 +11561,10 @@ end -- @return Core.Point#COORDINATE Primary landing spot coordinate. function AIRBOSS:_GetLandingSpotCoordinate() + self.landingspotcoord:UpdateFromCoordinate(self:_GetSternCoord()) + -- Stern coordinate. - local stern=self:_GetSternCoord() + --local stern=self:_GetSternCoord() if self.carriertype==AIRBOSS.CarrierType.TARAWA then @@ -11442,11 +11572,11 @@ function AIRBOSS:_GetLandingSpotCoordinate() local hdg=self:GetHeading() -- Primary landing spot 7.5 - stern=stern:Translate(57, hdg):SetAltitude(self.carrierparam.deckheight) + self.landingspotcoord:Translate(57, hdg, true, true):SetAltitude(self.carrierparam.deckheight) end - return stern + return self.landingspotcoord end --- Get true (or magnetic) heading of carrier. @@ -11509,7 +11639,7 @@ end --- Get wind speed on carrier deck parallel and perpendicular to runway. -- @param #AIRBOSS self --- @param #number alt Altitude in meters. Default 50 m. +-- @param #number alt Altitude in meters. Default 15 m. (change made from 50m from Discord discussion from Sickdog) -- @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. @@ -11532,7 +11662,7 @@ function AIRBOSS:GetWindOnDeck(alt) zc=UTILS.Rotate2D(zc, -self.carrierparam.rwyangle) -- Wind (from) vector - local vw=cv:GetWindWithTurbulenceVec3(alt or 50) + 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. @@ -11946,15 +12076,15 @@ function AIRBOSS:_EvalGrooveTime(playerData) local grade="" if t<9 then - grade="--" - elseif t<12 then - grade="(OK)" - elseif t<22 then - grade="OK" + grade="_NESA_" + elseif t<15 then + grade="NESA" + elseif t<19 then + grade="OK Groove" elseif t<=24 then - grade="(OK)" + grade="(LIG)" else - grade="--" + grade="LIG" end -- The unicorn! @@ -11993,9 +12123,9 @@ function AIRBOSS:_LSOgrade(playerData) local nS=count(G, '%(') local nN=N-nS-nL - -- Groove time 16-18 sec for a unicorn. + -- Groove time 15-18.99 sec for a unicorn. local Tgroove=playerData.Tgroove - local TgrooveUnicorn=Tgroove and (Tgroove>=16.0 and Tgroove<=18.0) or false + local TgrooveUnicorn=Tgroove and (Tgroove>=15.0 and Tgroove<=18.99) or false local grade local points @@ -12128,7 +12258,35 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep) -- Aircraft specific AoA values. 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") + end + end + + --Overshoot Start. + local O=nil + if step==AIRBOSS.PatternStep.GROOVE_XX then + if LUEacaoa.SLOW then @@ -12161,7 +12319,7 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep) A=little("LO") end - -- Line up. Good [-0.5, 0.5] + -- 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") @@ -12169,12 +12327,22 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep) D="LUL" elseif LUE>self.lue._max then D=little("LUL") - elseif LUE=Hupdate then - self:T(self.lid..string.format("Carrier heading changed by %d°.", deltaHeading)) + self:T(self.lid..string.format("Carrier heading changed by %d°.", deltaHeading)) Hchange=true end @@ -14025,6 +14208,8 @@ 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 @@ -14390,9 +14575,15 @@ end -- @param #AIRBOSS self -- @return Core.Point#COORDINATE Carrier coordinate. function AIRBOSS:GetCoordinate() - return self.carrier:GetCoordinate() + return self.carrier:GetCoord() end +--- Get carrier coordinate. +-- @param #AIRBOSS self +-- @return Core.Point#COORDINATE Carrier coordinate. +function AIRBOSS:GetCoord() + return self.carrier:GetCoord() +end --- Get static weather of this mission from env.mission.weather. -- @param #AIRBOSS self @@ -15451,7 +15642,7 @@ end 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) @@ -15474,7 +15665,7 @@ end 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) @@ -15527,11 +15718,11 @@ function AIRBOSS:_MarshalCallRecoveryStart(case) -- 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()) + text=text..string.format(" BRC %03d°.", self:GetBRC()) elseif case==2 then - text=text..string.format(" Marshal radial %03d°. BRC %03d°.", radial, self:GetBRC()) + 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)) + text=text..string.format(" Marshal radial %03d°. Final heading %03d°.", radial, self:GetFinalBearing(false)) end self:T(self.lid..text) @@ -15576,7 +15767,7 @@ function AIRBOSS:_MarshalCallArrived(modex, case, brc, altitude, charlie, qfe) 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) @@ -15753,11 +15944,11 @@ function AIRBOSS:_AddF10Commands(_unitName) 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, "+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) @@ -15814,7 +16005,7 @@ function AIRBOSS:_SkipperStartRecovery(_unitName, case) -- 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) + text=text..string.format(" Marshal radial %d°.", self.skipperOffset) end if self:IsRecovering() then text="negative, carrier is already recovering." @@ -15880,7 +16071,7 @@ function AIRBOSS:_SkipperRecoveryOffset(_unitName, offset) if playerData then -- Inform player. - local text=string.format("roger, relative CASE II/III Marshal radial set to %d°.", offset) + local text=string.format("roger, relative CASE II/III Marshal radial set to %d°.", offset) self:MessageToPlayer(playerData, text, "AIRBOSS") self.skipperOffset=offset @@ -16343,7 +16534,7 @@ function AIRBOSS:_RequestCommence(_unitName) -- For case 1 we want the BRC but above routine return FB. 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. @@ -16955,7 +17146,7 @@ function AIRBOSS:_DisplayCarrierInfo(_unitname) -- Only include current and future recovery windows. if Tabs1 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) + stacktext=stacktext..string.format("Select TACAN %03d°, %d DME\n", radial, angels+15) end end @@ -17354,7 +17545,7 @@ function AIRBOSS:_DisplayPlayerStatus(_unitName) 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 @@ -17369,7 +17560,7 @@ function AIRBOSS:_DisplayPlayerStatus(_unitName) 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 diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index df419a5b9..943b17cdd 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -368,7 +368,7 @@ function CSAR:New(Coalition, Template, Alias) self.mashprefix = {"MASH"} -- prefixes used to find MASHes self.mash = SET_GROUP:New():FilterCoalitions(self.coalition):FilterPrefixes(self.mashprefix):FilterOnce() -- currently only GROUP objects, maybe support STATICs also? self.autosmoke = false -- automatically smoke location when heli is near - self.autosmokedistance = 1000 -- distance for autosmoke + self.autosmokedistance = 2000 -- distance for autosmoke -- added 0.1.4 self.limitmaxdownedpilots = true self.maxdownedpilots = 25 @@ -886,6 +886,12 @@ function CSAR:_EventHandler(EventData) 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 if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_event.IniUnitName) then @@ -998,10 +1004,15 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) self:T("...helinunit nil!") return end - + local _heliCoord = _heliUnit:GetCoordinate() local _leaderCoord = _woundedGroup:GetCoordinate() local _distance = self:_GetDistance(_heliCoord,_leaderCoord) + -- autosmoke + if (self.autosmoke == true) and (_distance < self.autosmokedistance) and (_distance ~= -1) then + self:_PopSmokeForGroup(_woundedGroupName, _woundedGroup) + end + if _distance < self.approachdist_near and _distance > 0 then if self:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName) == true then -- we\'re close, reschedule @@ -1009,7 +1020,25 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) self:__Approach(-5,heliname,woundedgroupname) end elseif _distance >= self.approachdist_near and _distance < self.approachdist_far then - self.heliVisibleMessage[_lookupKeyHeli] = nil + -- 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! 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) + 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) + 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 _downedpilot.timestamp = timer.getAbsTime() self:__Approach(-10,heliname,woundedgroupname) @@ -1169,27 +1198,13 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG local _reset = true - if (self.autosmoke == true) and (_distance < self.autosmokedistance) then - self:_PopSmokeForGroup(_woundedGroupName, _woundedLeader) - end - - if self.heliVisibleMessage[_lookupKeyHeli] == nil then - if self.autosmoke == true then - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud! Land or hover by the smoke.", _heliName, _pilotName), self.messageTime,true,true) - else - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud! Request a Flare or Smoke if you need", _heliName, _pilotName), self.messageTime,true,true) - end - --mark as shown for THIS heli and THIS group - self.heliVisibleMessage[_lookupKeyHeli] = true - end - 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,true,true) + 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,true,true) + 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 --mark as shown for THIS heli and THIS group self.heliCloseMessage[_lookupKeyHeli] = true @@ -1206,7 +1221,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG 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, true) + 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 @@ -1520,8 +1535,9 @@ function CSAR:_SignalFlare(_unitName) end local _closest = self:_GetClosestDownedPilot(_heli) - - if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < 8000.0 then + 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 local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) local _distance = 0 @@ -1536,11 +1552,13 @@ function CSAR:_SignalFlare(_unitName) local _coord = _closest.pilot:GetCoordinate() _coord:FlareRed(_clockDir) else - local disttext = "4.3nm" - if _SETTINGS:IsMetric() then - disttext = "8km" - end - self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",disttext), self.messageTime) + 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) end return self end @@ -1572,8 +1590,10 @@ function CSAR:_Reqsmoke( _unitName ) if _heli == nil then return end + local smokedist = 8000 + if smokedist < self.approachdist_far then smokedist = self.approachdist_far end local _closest = self:_GetClosestDownedPilot(_heli) - if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < 8000.0 then + if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < smokedist then local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) local _distance = 0 if _SETTINGS:IsImperial() then @@ -1587,11 +1607,13 @@ function CSAR:_Reqsmoke( _unitName ) local color = self.smokecolor _coord:Smoke(color) else - local disttext = "4.3nm" - if _SETTINGS:IsMetric() then - disttext = "8km" - end - self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",disttext), self.messageTime) + 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) end return self end @@ -1776,7 +1798,8 @@ function CSAR:_GetClockDirection(_heli, _group) if _heading then local Aspect = Angle - _heading if Aspect == 0 then Aspect = 360 end - clock = math.floor(Aspect / 30) + --clock = math.floor(Aspect / 30) + clock = math.abs(UTILS.Round((Aspect / 30),0)) if clock == 0 then clock = 12 end end return clock diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 3488ee961..235f2946e 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1,4 +1,4 @@ ---- **Ops** -- Combat Troops & Logistics Deployment. +--- **Ops** -- Combat Troops & Logistics Department. -- -- === -- @@ -18,7 +18,7 @@ -- -- === -- --- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original), Thanks to: Shadowze, Cammel (testing) +-- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original), Thanks to: Shadowze, Cammel (testing), bbirchnz (additional code!!) -- @module Ops.CTLD -- @image OPS_CTLD.jpg @@ -37,6 +37,7 @@ do -- @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 -- @extends Core.Fsm#FSM CTLD_CARGO = { ClassName = "CTLD_CARGO", @@ -49,6 +50,7 @@ CTLD_CARGO = { CratesNeeded = 0, Positionable = nil, HasBeenDropped = false, + PerCrateMass = 0 } --- Define cargo types. @@ -59,6 +61,7 @@ CTLD_CARGO = { ["TROOPS"] = "Troops", -- #string troops ["FOB"] = "FOB", -- #string FOB ["CRATE"] = "Crate", -- #string crate + ["REPAIR"] = "Repair", -- #string repair } --- Function to create new CTLD_CARGO object. @@ -72,8 +75,9 @@ CTLD_CARGO = { -- @param #number CratesNeeded Crates needed to build. -- @param Wrapper.Positionable#POSITIONABLE Positionable Representation of cargo in the mission. -- @param #boolean Dropped Cargo/Troops have been unloaded from a chopper. + -- @param #number PerCrateMass Mass in kg -- @return #CTLD_CARGO self - function CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped) + function CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass) -- Inherit everything from BASE class. local self=BASE:Inherit(self, BASE:New()) -- #CTLD self:T({ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped}) @@ -86,6 +90,7 @@ CTLD_CARGO = { self.CratesNeeded = CratesNeeded or 0 -- #number self.Positionable = Positionable or nil -- Wrapper.Positionable#POSITIONABLE self.HasBeenDropped = Dropped or false --#boolean + self.PerCrateMass = PerCrateMass or 0 -- #number return self end @@ -169,13 +174,25 @@ CTLD_CARGO = { return false end end + --- Set WasDropped. -- @param #CTLD_CARGO self -- @param #boolean dropped function CTLD_CARGO:SetWasDropped(dropped) self.HasBeenDropped = dropped or false 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 + end + end do @@ -224,6 +241,8 @@ do -- -- 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 -- my_ctld:AddTroopsCargo("Anti-Air",{"AA","AA2"},CTLD_CARGO.Enum.TROOPS,4) @@ -231,10 +250,15 @@ 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. +-- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2,2775) -- -- -- 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) +-- -- ## 1.3 Add logistics zones -- -- Add zones for loading troops and crates and dropping, building crates @@ -272,6 +296,7 @@ do -- 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. -- -- ## 2.1 User functions -- @@ -353,12 +378,16 @@ do -- ... your code here ... -- end -- --- ## 3.6 OnAfterCratesBuild +-- ## 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: @@ -385,7 +414,7 @@ do -- -- ## 4.1 Manage Crates -- --- Use this entry to get, load, list nearby, drop, and build crates. Also @see options. +-- Use this entry to get, load, list nearby, drop, build and repair crates. Also @see options. -- -- ## 4.2 Manage Troops -- @@ -420,7 +449,7 @@ do -- 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 270 kph or 150 kn +-- my_ctld.HercMaxSpeed = 77 -- 77mps or 270kph or 150kn -- -- Also, the following options need to be set to `true`: -- @@ -528,7 +557,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="0.1.4r2" +CTLD.version="0.1.4r3" --- Instantiate a new CTLD. -- @param #CTLD self @@ -591,7 +620,8 @@ function CTLD:New(Coalition, Prefixes, Alias) 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("*", "CratesBuild", "*") -- CTLD build event. + self:AddTransition("*", "CratesRepaired", "*") -- CTLD repair event. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. -- tables @@ -652,6 +682,9 @@ function CTLD:New(Coalition, Prefixes, Alias) -- message suppression self.suppressmessages = false + -- time to repair a unit/group + self.repairtime = 300 + for i=1,100 do math.random() end @@ -745,7 +778,7 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param #table Cargotable Table of #CTLD_CARGO objects dropped. -- @return #CTLD self - --- FSM Function OnAfterCratesBuild. + --- FSM Function OnAfterCratesBuild. -- @function [parent=#CTLD] OnAfterCratesBuild -- @param #CTLD self -- @param #string From State. @@ -755,7 +788,18 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @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 @@ -919,13 +963,119 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) return else self.CargoCounter = self.CargoCounter + 1 - local loadcargotype = CTLD_CARGO:New(self.CargoCounter, Cargotype.Name, Cargotype.Templates, CTLD_CARGO.Enum.TROOPS, true, true, Cargotype.CratesNeeded) + local loadcargotype = CTLD_CARGO:New(self.CargoCounter, Cargotype.Name, Cargotype.Templates, CTLD_CARGO.Enum.TROOPS, 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) + 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 = 10000000 + for k,v in pairs(self.DroppedTroops) do + local distance = self:_GetDistance(v:GetCoordinate(),unitcoord) + if distance < nearestDistance and distance ~= -1 then + nearestGroup = v + nearestGroupIndex = k + nearestDistance = distance + end + end + + -- found one and matching distance? + if nearestGroup == nil or nearestDistance > 1000 then + self:_SendMessage("No unit close enough to repair!", 10, false, Group) + return nil, nil + end + + local groupname = nearestGroup:GetName() + --self:I(string.format("***** Found Group %s",groupname)) + + -- helper to find matching template + 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 + match = true + break + end + end + else + if type(String) == "string" then + if string.find(String,Table) then match = true 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 + Cargotype = v -- #CTLD_CARGO + break + end + end + + if Cargotype == nil then + --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. +function CTLD:_RepairObjectFromCrates(Group,Unit,Crates,Build,Number) + 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 + self:_SendMessage(string.format("Repair started using %s taking %d secs", build.Name, self.repairtime), 10, false, Group) + -- 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 + self:_SendMessage("Can't repair this unit with " .. build.Name, 10, false, Group) end return self end @@ -961,7 +1111,7 @@ end local nearestDistance = 10000000 for k,v in pairs(self.DroppedTroops) do local distance = self:_GetDistance(v:GetCoordinate(),unitcoord) - if distance < nearestDistance then + if distance < nearestDistance and distance ~= -1 then nearestGroup = v nearestGroupIndex = k nearestDistance = distance @@ -1005,12 +1155,13 @@ end return else self.CargoCounter = self.CargoCounter + 1 - local loadcargotype = CTLD_CARGO:New(self.CargoCounter, Cargotype.Name, Cargotype.Templates, CTLD_CARGO.Enum.TROOPS, true, true, Cargotype.CratesNeeded) + local loadcargotype = CTLD_CARGO:New(self.CargoCounter, Cargotype.Name, Cargotype.Templates, CTLD_CARGO.Enum.TROOPS, 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:_UpdateUnitCargoMass(Unit) self:__TroopsExtracted(1,Group, Unit, nearestGroup) -- clean up: @@ -1097,10 +1248,10 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) 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) + 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]) + realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,false,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],nil,cargotype.PerCrateMass) end table.insert(self.Spawned_Cargo, realcargo) end @@ -1132,7 +1283,7 @@ function CTLD:_ListCratesNearby( _group, _unit) if dropped then text:Add(string.format("Dropped crate for %s",name)) else - text:Add(string.format("Crate for %s",name)) + text:Add(string.format("Crate for %s, %d Kg",name, entry.PerCrateMass)) end end if text:GetCount() == 1 then @@ -1226,6 +1377,7 @@ function CTLD:_LoadCratesNearby(Group, Unit) 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 @@ -1266,7 +1418,8 @@ function CTLD:_LoadCratesNearby(Group, Unit) -- destroy crate crate:GetPositionable():Destroy() crate.Positionable = nil - self:_SendMessage(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()), 10, false, Group) + self:_SendMessage(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()), 10, false, Group) + self:_UpdateUnitCargoMass(Unit) self:__CratesPickedUp(1, Group, Unit, crate) end end @@ -1290,6 +1443,43 @@ function CTLD:_LoadCratesNearby(Group, Unit) 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 and not cargo:WasDropped() then + loadedmass = loadedmass + (cargo.PerCrateMass * cargo:GetCratesNeeded()) + end + if type ~= CTLD_CARGO.Enum.TROOPS and not cargo:WasDropped() then + loadedmass = loadedmass + cargo.PerCrateMass + end + end + 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()) +end + --- (Internal) Function to list loaded cargo. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group @@ -1303,6 +1493,7 @@ function CTLD:_ListCargo(Group, Unit) 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 @@ -1328,7 +1519,7 @@ function CTLD:_ListCargo(Group, Unit) for _,_cargo in pairs(cargotable) do local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum - if type ~= CTLD_CARGO.Enum.TROOPS then + if type ~= CTLD_CARGO.Enum.TROOPS and not cargo:WasDropped() then report:Add(string.format("Crate: %s size 1",cargo:GetName())) cratecount = cratecount + 1 end @@ -1337,6 +1528,7 @@ function CTLD:_ListCargo(Group, Unit) 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 @@ -1439,6 +1631,7 @@ function CTLD:_UnloadTroops(Group, Unit) 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) @@ -1507,6 +1700,7 @@ function CTLD:_UnloadCrates(Group, Unit) 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) @@ -1533,7 +1727,7 @@ function CTLD:_BuildCrates(Group, Unit) -- get dropped crates for _,_crate in pairs(crates) do local Crate = _crate -- #CTLD_CARGO - if Crate:WasDropped() then + if Crate:WasDropped() and not Crate:IsRepair() then -- we can build these - maybe local name = Crate:GetName() local required = Crate:GetCratesNeeded() @@ -1596,12 +1790,92 @@ function CTLD:_BuildCrates(Group, Unit) return self end +--- (Internal) Function to repair nearby vehicles / FOBs +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrappe.Unit#UNIT Unit +function CTLD:_RepairCrates(Group, Unit) + self:T(self.lid .. " _RepairCrates") + -- get nearby crates + local finddist = self.CrateDistance or 30 + 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() 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() + self:_SendMessage(text, 30, true, Group) + -- 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) + end + end + end + else + self:_SendMessage(string.format("No crates within %d meters!",finddist), 10, false, Group) + 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 -function CTLD:_BuildObjectFromCrates(Group,Unit,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 local position = Unit:GetCoordinate() or Group:GetCoordinate() @@ -1613,17 +1887,31 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build) local temptable = Build.Template or {} 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 + if canmove then 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) + else -- don't random position of e.g. SAM units build as FOB + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) + --:InitRandomizeUnits(true,20,2) + :InitDelayOff() + :SpawnFromVec2(randomcoord) + end if self.movetroopstowpzone and canmove then self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) end - self:__CratesBuild(1,Group,Unit,self.DroppedTroops[self.TroopCounter]) + 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 return self end @@ -1756,7 +2044,8 @@ function CTLD:_RefreshF10Menus() 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):Refresh() + 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 -- sub menu troops management if cantroops then @@ -1791,11 +2080,12 @@ function CTLD:_RefreshF10Menus() -- @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). -function CTLD:AddTroopsCargo(Name,Templates,Type,NoTroops) +-- @param #number PerTroopMass Mass in kg of each soldier +function CTLD:AddTroopsCargo(Name,Templates,Type,NoTroops,PerTroopMass) self:T(self.lid .. " AddTroopsCargo") self.CargoCounter = self.CargoCounter + 1 -- Troops are directly loadable - local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,true,NoTroops) + local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,true,NoTroops,nil,nil,PerTroopMass) table.insert(self.Cargo_Troops,cargo) return self end @@ -1806,11 +2096,28 @@ end -- @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. -function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates) +-- @param #number PerCrateMass Mass in kg of each crate +function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass) 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) + local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,false,NoCrates,nil,nil,PerCrateMass) + 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 +function CTLD:AddCratesRepair(Name,Template,Type,NoCrates, PerCrateMass) + 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) table.insert(self.Cargo_Crates,cargo) return self end @@ -1840,15 +2147,15 @@ function CTLD:ActivateZone(Name,ZoneType,NewState) self:T(self.lid .. " AddZone") local newstate = true -- set optional in case we\'re deactivating - if not NewState or NewState == false then - newstate = false - end + if NewState ~= nil then + newstate = NewState + end + -- get correct table - local zone = ZoneType -- #CTLD.CargoZone local table = {} - if zone.type == CTLD.CargoZoneType.LOAD then + if ZoneType == CTLD.CargoZoneType.LOAD then table = self.pickupZones - elseif zone.type == CTLD.CargoZoneType.DROP then + elseif ZoneType == CTLD.CargoZoneType.DROP then table = self.dropOffZones else table = self.wpZones @@ -1864,6 +2171,7 @@ function CTLD:ActivateZone(Name,ZoneType,NewState) 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. @@ -2348,6 +2656,20 @@ end return self end + --- (Internal) Run through DroppedTroops and capture alive units + -- @param #CTLD self + -- @return #CTLD self + function CTLD:CleanDroppedTroops() + local troops = self.DroppedTroops + local newtable = {} + for _index, _group in pairs (troops) do + if _group and _group:IsAlive() then + newtable[_index] = _group + end + end + self.DroppedTroops = newtable + return self + end ------------------------------------------------------------------- -- FSM functions ------------------------------------------------------------------- @@ -2387,6 +2709,7 @@ end -- @return #CTLD self function CTLD:onbeforeStatus(From, Event, To) self:T({From, Event, To}) + self:CleanDroppedTroops() self:_RefreshF10Menus() self:_RefreshRadioBeacons() self:CheckAutoHoverload() diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index eb6d472d8..efd02a03d 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -580,6 +580,18 @@ function UNIT:GetAmmo() return nil end +--- Sets the Unit's Internal Cargo Mass, in kg +-- @param #UNIT self +-- @param #number mass to set cargo to +-- @return #UNIT self +function UNIT:SetUnitInternalCargo(mass) + local DCSUnit = self:GetDCSObject() + if DCSUnit then + trigger.action.setUnitInternalCargo(DCSUnit:getName(), mass) + end + return self +end + --- Get the number of ammunition and in particular the number of shells, rockets, bombs and missiles a unit currently has. -- @param #UNIT self -- @return #number Total amount of ammo the unit has left. This is the sum of shells, rockets, bombs and missiles.