diff --git a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua index a9bae01c3..79f664a12 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua @@ -33,7 +33,7 @@ -- -- ## Infantry health. -- --- When infantry is unboarded from the APCs, the infantry is actually respawned into the battlefield. +-- When infantry is unboarded from the helicopters, the infantry is actually respawned into the battlefield. -- As a result, the unboarding infantry is very _healthy_ every time it unboards. -- This is due to the limitation of the DCS simulator, which is not able to specify the health of new spawned units as a parameter. -- However, infantry that was destroyed when unboarded, won't be respawned again. Destroyed is destroyed. @@ -67,18 +67,6 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) self:AddTransition( "Unloaded", "Pickup", "*" ) self:AddTransition( "Loaded", "Deploy", "*" ) - --[[ - self:AddTransition( { "Unloaded", "Loading" }, "Load", "Boarding" ) - self:AddTransition( "Boarding", "Board", "Boarding" ) - self:AddTransition( "Boarding", "Loaded", "Loaded" ) - self:AddTransition( "Boarding", "PickedUp", "Loaded" ) - self:AddTransition( "Boarding", "Deploy", "Loaded" ) - self:AddTransition( "Loaded", "Unload", "Unboarding" ) - self:AddTransition( "Unboarding", "Unboard", "Unboarding" ) - self:AddTransition( "Unboarding", "Unloaded", "Unboarding" ) - self:AddTransition( "Unboarding", "Deployed", "Unloaded" ) - self:AddTransition( "Unboarding", "Pickup", "Unloaded" ) - --]] self:AddTransition( "*", "Loaded", "Loaded" ) self:AddTransition( "Unboarding", "Pickup", "Unloaded" ) self:AddTransition( "Unloaded", "Unboard", "Unloaded" ) @@ -107,13 +95,31 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) -- @param #string Event -- @param #string To -- @param Core.Point#COORDINATE Coordinate - -- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. + -- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. + + --- PickedUp Handler OnAfter for AI_CARGO_HELICOPTER - Cargo set has been picked up, ready to deploy + -- @function [parent=#AI_CARGO_HELICOPTER] OnAfterPickedUp + -- @param #AI_CARGO_HELICOPTER self + -- @param Wrapper.Group#GROUP Helicopter The helicopter #GROUP object + -- @param #string From + -- @param #string Event + -- @param #string To + -- @param Wrapper.Unit#UNIT Unit The helicopter #UNIT object + + --- Unloaded Handler OnAfter for AI_CARGO_HELICOPTER - Cargo unloaded, carrier is empty + -- @function [parent=#AI_CARGO_HELICOPTER] OnAfterUnloaded + -- @param #AI_CARGO_HELICOPTER self + -- @param #string From + -- @param #string Event + -- @param #string To + -- @param Cargo.CargoGroup#CARGO_GROUP Cargo The #CARGO_GROUP object. + -- @param Wrapper.Unit#UNIT Unit The helicopter #UNIT object --- Pickup Trigger for AI_CARGO_HELICOPTER -- @function [parent=#AI_CARGO_HELICOPTER] Pickup -- @param #AI_CARGO_HELICOPTER self -- @param Core.Point#COORDINATE Coordinate - -- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. + -- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. --- Pickup Asynchronous Trigger for AI_CARGO_HELICOPTER -- @function [parent=#AI_CARGO_HELICOPTER] __Pickup @@ -129,7 +135,7 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) -- @param #string Event -- @param #string To -- @param Core.Point#COORDINATE Coordinate Place at which cargo is deployed. - -- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. + -- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. -- @return #boolean --- Deploy Handler OnAfter for AI_CARGO_HELICOPTER @@ -139,21 +145,42 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) -- @param #string Event -- @param #string To -- @param Core.Point#COORDINATE Coordinate - -- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. + -- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. + + --- Deployed Handler OnAfter for AI_CARGO_HELICOPTER + -- @function [parent=#AI_CARGO_HELICOPTER] OnAfterDeployed + -- @param #AI_CARGO_HELICOPTER self + -- @param #string From + -- @param #string Event + -- @param #string To --- Deploy Trigger for AI_CARGO_HELICOPTER -- @function [parent=#AI_CARGO_HELICOPTER] Deploy -- @param #AI_CARGO_HELICOPTER self -- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed. - -- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. + -- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. --- Deploy Asynchronous Trigger for AI_CARGO_HELICOPTER -- @function [parent=#AI_CARGO_HELICOPTER] __Deploy -- @param #number Delay Delay in seconds. -- @param #AI_CARGO_HELICOPTER self -- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed. - -- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. + -- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. + --- Home Trigger for AI_CARGO_HELICOPTER + -- @function [parent=#AI_CARGO_HELICOPTER] Home + -- @param #AI_CARGO_HELICOPTER self + -- @param Core.Point#COORDINATE Coordinate Place to which the helicopter will go. + -- @param #number Speed (optional) Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. + -- @param #number Height (optional) Height the Helicopter should be flying at. + + --- Home Asynchronous Trigger for AI_CARGO_HELICOPTER + -- @function [parent=#AI_CARGO_HELICOPTER] __Home + -- @param #number Delay Delay in seconds. + -- @param #AI_CARGO_HELICOPTER self + -- @param Core.Point#COORDINATE Coordinate Place to which the helicopter will go. + -- @param #number Speed (optional) Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. + -- @param #number Height (optional) Height the Helicopter should be flying at. -- We need to capture the Crash events for the helicopters. -- The helicopter reference is used in the semaphore AI_CARGO_QUEUE. @@ -235,7 +262,7 @@ end -- @param Event -- @param To function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To ) - + self:F({From, Event, To}) Helicopter:F( { Name = Helicopter:GetName() } ) if Helicopter and Helicopter:IsAlive() then @@ -276,7 +303,7 @@ end -- @param Core.Point#COORDINATE Coordinate -- @param #number Speed function AI_CARGO_HELICOPTER:onafterQueue( Helicopter, From, Event, To, Coordinate, Speed, DeployZone ) - + self:F({From, Event, To, Coordinate, Speed, DeployZone}) local HelicopterInZone = false if Helicopter and Helicopter:IsAlive() == true then @@ -359,7 +386,8 @@ end -- @param Core.Point#COORDINATE Coordinate -- @param #number Speed function AI_CARGO_HELICOPTER:onafterOrbit( Helicopter, From, Event, To, Coordinate ) - + self:F({From, Event, To, Coordinate}) + if Helicopter and Helicopter:IsAlive() then local Route = {} @@ -394,7 +422,7 @@ end -- @param #boolean Deployed Cargo is deployed. -- @return #boolean True if all cargo has been unloaded. function AI_CARGO_HELICOPTER:onafterDeployed( Helicopter, From, Event, To, DeployZone ) - self:F( { Helicopter, From, Event, To, DeployZone = DeployZone } ) + self:F( { From, Event, To, DeployZone = DeployZone } ) self:Orbit( Helicopter:GetCoordinate(), 50 ) @@ -416,7 +444,7 @@ end -- @param Event -- @param To -- @param Core.Point#COORDINATE Coordinate Pickup place. --- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. +-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. -- @param #number Height Height in meters to move to the pickup coordinate. This parameter is ignored for APCs. -- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided. function AI_CARGO_HELICOPTER:onafterPickup( Helicopter, From, Event, To, Coordinate, Speed, Height, PickupZone ) @@ -485,10 +513,10 @@ end -- @param Event -- @param To -- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed. --- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. +-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. -- @param #number Height Height in meters to move to the deploy coordinate. function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordinate, Speed, Height, DeployZone ) - + self:F({From, Event, To, Coordinate, Speed, Height, DeployZone}) if Helicopter and Helicopter:IsAlive() ~= nil then self.RouteDeploy = true @@ -550,11 +578,12 @@ end -- @param Event -- @param To -- @param Core.Point#COORDINATE Coordinate Home place. --- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. +-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. -- @param #number Height Height in meters to move to the home coordinate. -- @param Core.Zone#ZONE HomeZone The zone wherein the carrier will return when all cargo has been transported. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinate, Speed, Height, HomeZone ) - + self:F({From, Event, To, Coordinate, Speed, Height}) + if Helicopter and Helicopter:IsAlive() ~= nil then self.RouteHome = true @@ -563,7 +592,8 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat --- Calculate the target route point. - Coordinate.y = Height + --Coordinate.y = Height + Height = Height or 50 Speed = Speed or Helicopter:GetSpeedMax()*0.5 @@ -576,7 +606,7 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat --- Create a route point of type air. local CoordinateTo = Coordinate local landheight = CoordinateTo:GetLandHeight() -- get target height - CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground + CoordinateTo.y = landheight + Height -- flight height should be 50m above ground local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, Speed, true) @@ -589,12 +619,11 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() ) Route[#Route].task = Helicopter:TaskCombo( Tasks ) - + Route[#Route+1] = WaypointTo -- Now route the helicopter Helicopter:Route(Route, 0) - end end diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 612cd4cfd..9de97eb5b 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -361,9 +361,9 @@ do -- COORDINATE -- Adjust height if altitude==nil then - _coord.y=altitude - else _coord.y=self:GetLandHeight() + else + _coord.y=altitude end return _coord diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index bc043506b..c4564fb94 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -20,7 +20,7 @@ -- @module Functional.Mantis -- @image Functional.Mantis.jpg --- Date: Jan 2021 +-- Date: Feb 2021 ------------------------------------------------------------------------- --- **MANTIS** class, extends #Core.Base#BASE @@ -31,7 +31,7 @@ -- @field Core.Set#SET_GROUP SAM_Group The SAM #SET_GROUP -- @field #string EWR_Templates_Prefix Prefix to build the #SET_GROUP for EWR group -- @field Core.Set#SET_GROUP EWR_Group The EWR #SET_GROUP --- @field #Core.Set#SET_GROUP Adv_EWR_Group The EWR #SET_GROUP used for advanced mode +-- @field Core.Set#SET_GROUP Adv_EWR_Group The EWR #SET_GROUP used for advanced mode -- @field #string HQ_Template_CC The ME name of the HQ object -- @field Wrapper.Group#GROUP HQ_CC The #GROUP object of the HQ -- @field #table SAM_Table Table of SAM sites @@ -54,6 +54,7 @@ -- @field Functional.Shorad#SHORAD Shorad SHORAD Object, if available -- @field #boolean ShoradLink If true, #MANTIS has #SHORAD enabled -- @field #number ShoradTime Timer in seconds, how long #SHORAD will be active after a detection inside of the defense range +-- @field #number ShoradActDistance Distance of an attacker in meters from a Mantis SAM site, on which Shorad will be switched on. Useful to not give away Shorad sites too early. Default 15km. Should be smaller than checkradius. -- @extends Core.Base#BASE @@ -127,7 +128,7 @@ -- * grouping = 5000 (meters) - Detection (EWR) will group enemy flights to areas of 5km for tracking - `MANTIS:SetEWRGrouping(radius)` -- * acceptrange = 80000 (meters) - Detection (EWR) will on consider flights inside a 80km radius - `MANTIS:SetEWRRange(radius)` -- * detectinterval = 30 (seconds) - MANTIS will decide every 30 seconds which SAM to activate - `MANTIS:SetDetectInterval(interval)` --- * engagerange = 75 (percent) - SAMs will only fire if flights are inside of a 75% radius of their max firerange - `MANTIS:SetSAMRange(range)` +-- * engagerange = 85 (percent) - SAMs will only fire if flights are inside of a 85% radius of their max firerange - `MANTIS:SetSAMRange(range)` -- * dynamic = false - Group filtering is set to once, i.e. newly added groups will not be part of the setup by default - `MANTIS:New(name,samprefix,ewrprefix,hq,coaltion,dynamic)` -- * autorelocate = false - HQ and (mobile) EWR system will not relocate in random intervals between 30mins and 1 hour - `MANTIS:SetAutoRelocate(hq, ewr)` -- * debug = false - Debugging reports on screen are set to off - `MANTIS:Debug(onoff)` @@ -135,8 +136,26 @@ -- # 4. Advanced Mode -- -- Advanced mode will *decrease* reactivity of MANTIS, if HQ and/or EWR network dies. Awacs is counted as one EWR unit. It will set SAMs to RED state if both are dead. Requires usage of an **HQ** object and the **dynamic** option. --- E.g. `mymantis:SetAdvancedMode( true, 90 )` +-- +-- E.g. `mymantis:SetAdvancedMode( true, 90 )` +-- -- Use this option if you want to make use of or allow advanced SEAD tactics. +-- +-- # 5. Integrate SHORAD +-- +-- You can also choose to integrate Mantis with @{Functional.Shorad#SHORAD} for protection against HARMs and AGMs. When SHORAD detects a missile fired at one of MANTIS' SAM sites, it will activate SHORAD systems in +-- the given defense checkradius around that SAM site. Create a SHORAD object first, then integrate with MANTIS like so: +-- +-- `local SamSet = SET_GROUP:New():FilterPrefixes("Blue SAM"):FilterCoalitions("blue"):FilterStart()` +-- `myshorad = SHORAD:New("BlueShorad", "Blue SHORAD", SamSet, 22000, 600, "blue")` +-- `-- now set up MANTIS` +-- `mymantis = MANTIS:New("BlueMantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")` +-- `mymantis:AddShorad(myshorad,720)` +-- `mymantis:Start()` +-- +-- and (optionally) remove the link later on with +-- +-- `mymantis:RemoveShorad()` -- -- @field #MANTIS MANTIS = { @@ -169,7 +188,8 @@ MANTIS = { awacsrange = 250000, Shorad = nil, ShoradLink = false, - ShoradTime = 600, + ShoradTime = 600, + ShoradActDistance = 15000, } ----------------------------------------------------------------------- @@ -235,19 +255,20 @@ do self.verbose = false self.Adv_EWR_Group = nil self.AWACS_Prefix = awacs or nil - self.awacsrange = 250000 --TODO: 250km, User Function to change + self.awacsrange = 250000 --DONE: 250km, User Function to change self.Shorad = nil self.ShoradLink = false - self.ShoradTime = 600 + self.ShoradTime = 600 + self.ShoradActDistance = 15000 + if type(awacs) == "string" then self.advAwacs = true else self.advAwacs = false end - -- @field #string version - self.version="0.3.6" - env.info(string.format("***** Starting MANTIS Version %s *****", self.version)) + -- Inherit everything from BASE class. + local self = BASE:Inherit(self, BASE:New()) -- #MANTIS -- Set the string id for output to DCS.log file. self.lid=string.format("MANTIS %s | ", self.name) @@ -276,8 +297,10 @@ do if self.HQ_Template_CC then self.HQ_CC = GROUP:FindByName(self.HQ_Template_CC) end - -- Inherit everything from BASE class. - local self = BASE:Inherit(self, BASE:New()) -- #MANTIS + + -- @field #string version + self.version="0.3.7" + self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) return self end @@ -379,6 +402,14 @@ do end end end + + --- Function to set AWACS detection range. Defaults to 250.000m (250km) - use **before** starting your Mantis! + -- @param #MANTIS self + -- @param #number range Detection range of the AWACS group + function MANTIS:SetAwacsRange(range) + local range = range or 250000 + self.awacsrange = range + end --- Function to set the HQ object for further use -- @param #MANTIS self @@ -569,6 +600,7 @@ do -- @param #table dectset Table of coordinates of detected items -- @param samcoordinate Core.Point#COORDINATE Coordinate object. -- @return #boolean True if in any zone, else false + -- @return #number Distance Target distance in meters or zero when no object is in zone function MANTIS:CheckObjectInZone(dectset, samcoordinate) self:F(self.lid.."CheckObjectInZone Called") -- check if non of the coordinate is in the given defense zone @@ -585,10 +617,10 @@ do if self.verbose then env.info(self.lid..text) end -- end output to cross-check if targetdistance <= radius then - return true + return true, targetdistance end end - return false + return false, 0 end --- (Internal) Function to start the detection via EWR groups @@ -658,7 +690,7 @@ do -- @param #MANTIS self -- @return #MANTIS self function MANTIS:SetSAMStartState() - -- TODO: if using dynamic filtering, update SAM_Table and the (active) SEAD groups, pull req #1405/#1406 + -- DONE: if using dynamic filtering, update SAM_Table and the (active) SEAD groups, pull req #1405/#1406 self:F(self.lid.."Setting SAM Start States") -- get SAM Group local SAM_SET = self.SAM_Group @@ -769,13 +801,14 @@ do local samcoordinate = _data[2] local name = _data[1] local samgroup = GROUP:FindByName(name) - if self:CheckObjectInZone(detset, samcoordinate) then --check any target in zone + local IsInZone, Distance = self:CheckObjectInZone(detset, samcoordinate) + if IsInZone then --check any target in zone if samgroup:IsAlive() then -- switch off SAM samgroup:OptionAlarmStateRed() -- link in to SHORAD if available - -- TODO Test integration fully - if self.ShoradLink then + -- DONE: Test integration fully + if self.ShoradLink and Distance < self.ShoradActDistance then -- don't give SHORAD position away too early local Shorad = self.Shorad local radius = self.checkradius local ontime = self.ShoradTime diff --git a/Moose Development/Moose/Functional/Shorad.lua b/Moose Development/Moose/Functional/Shorad.lua index 7ed638f43..7ecf59d18 100644 --- a/Moose Development/Moose/Functional/Shorad.lua +++ b/Moose Development/Moose/Functional/Shorad.lua @@ -378,7 +378,14 @@ do -- @param #string TargetGroup Name of the target group used to build the #ZONE -- @param #number Radius Radius of the #ZONE -- @param #number ActiveTimer Number of seconds to stay active - -- @usage Use this function to integrate with other systems. + -- @usage Use this function to integrate with other systems, example + -- + -- local SamSet = SET_GROUP:New():FilterPrefixes("Blue SAM"):FilterCoalitions("blue"):FilterStart() + -- myshorad = SHORAD:New("BlueShorad", "Blue SHORAD", SamSet, 22000, 600, "blue") + -- myshorad:SwitchDebug(true) + -- mymantis = MANTIS:New("BlueMantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs") + -- mymantis:AddShorad(myshorad,720) + -- mymantis:Start() function SHORAD:WakeUpShorad(TargetGroup, Radius, ActiveTimer) self:F({TargetGroup, Radius, ActiveTimer}) local targetgroup = GROUP:FindByName(TargetGroup) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 8ffd7e6bf..2f3870403 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -966,7 +966,7 @@ function AIRWING:CheckTANKER() local altitude=patrol.altitude+1000*patrol.noccupied - local mission=AUFTRAG:NewTANKER(patrol.coord, altitude, patrol.speed, patrol.heading, patrol.leg, 0) + local mission=AUFTRAG:NewTANKER(patrol.coord, altitude, patrol.speed, patrol.heading, patrol.leg, 1) mission.patroldata=patrol @@ -984,7 +984,7 @@ function AIRWING:CheckTANKER() local altitude=patrol.altitude+1000*patrol.noccupied - local mission=AUFTRAG:NewTANKER(patrol.coord, altitude, patrol.speed, patrol.heading, patrol.leg, 1) + local mission=AUFTRAG:NewTANKER(patrol.coord, altitude, patrol.speed, patrol.heading, patrol.leg, 0) mission.patroldata=patrol diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 07ee32445..7cdf8a06d 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 (VNAO)(Player & AI)] -- * F/A-18C Hornet (AI) -- * F-14A Tomcat (AI) -- * E-2D Hawkeye (AI) @@ -1271,6 +1272,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", @@ -1705,7 +1707,7 @@ AIRBOSS.MenuF10Root=nil --- Airboss class version. -- @field #string version -AIRBOSS.version="1.1.5" +AIRBOSS.version="1.1.6" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -1939,7 +1941,7 @@ 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 @@ -1951,11 +1953,11 @@ function AIRBOSS:New(carriername, alias) 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() @@ -1993,7 +1995,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) @@ -2033,7 +2035,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) @@ -2646,6 +2648,15 @@ function AIRBOSS:SetRecoveryTurnTime(interval) return self end +--- Set multiplayer environment wire correction. +-- @param #AIRBOSS self +-- @param #number Dcorr Correction distance in meters. Default 8.7 m. +-- @return #AIRBOSS self +function AIRBOSS:SetMPWireCorrection(Dcorr) + self.mpWireCorrection=Dcorr or 8.7 + return self +end + --- Set time interval for updating queues and other stuff. -- @param #AIRBOSS self -- @param #number interval Time interval in seconds. Default 30 sec. @@ -3284,7 +3295,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,7 +3409,7 @@ function AIRBOSS:onafterStart(From, Event, To) --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. @@ -3484,9 +3495,9 @@ function AIRBOSS:onafterStatus(From, Event, To) -- Disable turn into the wind for this window so that we do not do this all over again. self.recoverywindow.WIND=false end - + end - + end @@ -3582,9 +3593,9 @@ 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. @@ -4145,7 +4156,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) @@ -4169,7 +4180,7 @@ function AIRBOSS:_InitStennis() -- Carrier Parameters. self.carrierparam.sterndist =-153 - self.carrierparam.deckheight = 19 + self.carrierparam.deckheight = 19.06 -- Total size of the carrier (approx as rectangle). self.carrierparam.totlength=310 -- Wiki says 332.8 meters overall length. @@ -4177,7 +4188,7 @@ function AIRBOSS:_InitStennis() self.carrierparam.totwidthstarboard=30 -- Landing runway. - self.carrierparam.rwyangle = -9 + self.carrierparam.rwyangle = -9.1359 self.carrierparam.rwylength = 225 self.carrierparam.rwywidth = 20 @@ -4320,7 +4331,7 @@ function AIRBOSS:_InitNimitz() -- Carrier Parameters. self.carrierparam.sterndist =-164 - self.carrierparam.deckheight = 20 + self.carrierparam.deckheight = 20.1494 --DCS World OpenBeta\CoreMods\tech\USS_Nimitz\Database\USS_CVN_7X.lua -- Total size of the carrier (approx as rectangle). self.carrierparam.totlength=332.8 -- Wiki says 332.8 meters overall length. @@ -4328,7 +4339,7 @@ function AIRBOSS:_InitNimitz() self.carrierparam.totwidthstarboard=35 -- Landing runway. - self.carrierparam.rwyangle = -9 + self.carrierparam.rwyangle = -9.1359 --DCS World OpenBeta\CoreMods\tech\USS_Nimitz\scripts\USS_Nimitz_RunwaysAndRoutes.lua self.carrierparam.rwylength = 250 self.carrierparam.rwywidth = 25 @@ -5471,6 +5482,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 +5509,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 +5702,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 +5715,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 +5730,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 +5738,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 +5753,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 +5776,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 +5789,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) @@ -6086,7 +6122,7 @@ function AIRBOSS:_ScanCarrierZone() -- 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 @@ -6123,7 +6159,7 @@ function AIRBOSS:_ScanCarrierZone() -- Break the loop to not have all flights at once! Spams the message screen. break - end -- Closed in or tanker/AWACS + end -- Closed in or tanker/AWACS end @@ -6229,7 +6265,7 @@ function AIRBOSS:_MarshalPlayer(playerData, stack) -- Set stack flag. flight.flag=stack - + -- Trigger Marshal event. self:Marshal(flight) end @@ -6488,7 +6524,7 @@ function AIRBOSS:_MarshalAI(flight, nstack, respawn) -- Route group. flight.group:Route(wp, 1) - + -- Trigger Marshal event. self:Marshal(flight) @@ -7024,7 +7060,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 @@ -7040,7 +7076,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. @@ -7052,7 +7088,7 @@ function AIRBOSS:_GetFreeStack(ai, case, empty) -- Get stack of flight. local n=flight.flag - + if n>nmax then nmax=n end @@ -7069,7 +7105,7 @@ function AIRBOSS:_GetFreeStack(ai, case, empty) end end - + local nfree=nil if stack[nmax]==0 then -- Max occupied stack is completely full! @@ -7085,7 +7121,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])) @@ -7097,7 +7133,7 @@ function AIRBOSS:_GetFreeStack(ai, case, empty) else nfree=nmax end - + end self:I(self.lid..string.format("Returning free stack %s", tostring(nfree))) @@ -10370,7 +10406,7 @@ function AIRBOSS:_GetSternCoord() 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. - self.sterncoord:Translate(self.carrierparam.sterndist, hdg, true, true):Translate(8.5, FB+90, true, true) + self.sterncoord:Translate(self.carrierparam.sterndist, hdg, true, true):Translate(9.5, FB+90, true, true) end -- Set altitude. @@ -10400,6 +10436,11 @@ function AIRBOSS:_GetWire(Lcoord, dc) -- Corrected landing distance wrt to stern. Landing distance needs to be reduced due to delayed landing event for human players. local d=Ldist-dc + + -- Multiplayer wire correction. + if self.mpWireCorrection then + d=d-self.mpWireCorrection + end -- Shift wires from stern to their correct position. local w1=self.carrierparam.wire1 @@ -10491,6 +10532,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. @@ -10617,7 +10661,7 @@ function AIRBOSS:_GetZoneInitial(case) -- Polygon zone. --local zone=ZONE_POLYGON_BASE:New("Zone CASE I/II Initial", vec2) - + self.zoneInitial:UpdateFromVec2(vec2) --return zone @@ -10646,13 +10690,13 @@ function AIRBOSS:_GetZoneLineup() -- Vec2 array. local vec2={c1:GetVec2(), c2:GetVec2(), c3:GetVec2(), c4:GetVec2(), c5:GetVec2()} - + self.zoneLineup:UpdateFromVec2(vec2) -- Polygon zone. --local zone=ZONE_POLYGON_BASE:New("Zone Lineup", vec2) --return zone - + return self.zoneLineup end @@ -10687,13 +10731,13 @@ function AIRBOSS:_GetZoneGroove(l, w, b) -- Vec2 array. local vec2={c1:GetVec2(), c2:GetVec2(), c3:GetVec2(), c4:GetVec2(), c5:GetVec2(), c6:GetVec2()} - + self.zoneGroove:UpdateFromVec2(vec2) -- Polygon zone. --local zone=ZONE_POLYGON_BASE:New("Zone Groove", vec2) --return zone - + return self.zoneGroove end @@ -10719,7 +10763,7 @@ 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 @@ -10948,9 +10992,9 @@ function AIRBOSS:_GetZoneCarrierBox() -- Create polygon zone. --local zone=ZONE_POLYGON_BASE:New("Carrier Box Zone", vec2) --return zone - + self.zoneCarrierbox:UpdateFromVec2(vec2) - + return self.zoneCarrierbox end @@ -10985,9 +11029,9 @@ function AIRBOSS:_GetZoneRunwayBox() -- Create polygon zone. --local zone=ZONE_POLYGON_BASE:New("Landing Runway Zone", vec2) --return zone - + self.zoneRunwaybox:UpdateFromVec2(vec2) - + return self.zoneRunwaybox end @@ -11118,7 +11162,7 @@ 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. self.zoneHolding=self.zoneHolding or ZONE_POLYGON_BASE:New("CASE II/III Holding Zone") - + self.zoneHolding:UpdateFromVec2(p) end @@ -11164,12 +11208,12 @@ function AIRBOSS:_GetZoneCommence(case, stack) -- Create holding zone. 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. @@ -11197,7 +11241,7 @@ function AIRBOSS:_GetZoneCommence(case, stack) -- Zone polygon. self.zoneCommence=self.zoneCommence or ZONE_POLYGON_BASE:New("CASE II/III Commence Zone") - + self.zoneCommence:UpdateFromVec2(p) end @@ -11445,7 +11489,7 @@ end -- @param #AIRBOSS self -- @return Core.Point#COORDINATE Optimal landing coordinate. function AIRBOSS:_GetOptLandingCoordinate() - + -- Start with stern coordiante. self.landingcoord:UpdateFromCoordinate(self:_GetSternCoord()) @@ -12001,15 +12045,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! @@ -12050,7 +12094,7 @@ function AIRBOSS:_LSOgrade(playerData) -- Groove time 16-18 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 @@ -14080,6 +14124,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 diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index edff50801..4080b6e32 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -31,6 +31,7 @@ -- @field #boolean clustermarkers If true, create cluster markers on F10 map. -- @field #number clustercounter Running number of clusters. -- @field #number dTforget Time interval in seconds before a known contact which is not detected any more is forgotten. +-- @field #number clusterradius Radius im kilometers in which groups/units are considered to belong to a cluster -- @extends Core.Fsm#FSM --- Top Secret! @@ -46,7 +47,7 @@ -- @field #INTEL INTEL = { ClassName = "INTEL", - verbose = 2, + verbose = 0, lid = nil, alias = nil, filterCategory = {}, @@ -56,6 +57,7 @@ INTEL = { ContactsUnknown = {}, Clusters = {}, clustercounter = 1, + clusterradius = 15, } --- Detected item info. @@ -74,6 +76,8 @@ INTEL = { -- @field #boolean isship -- @field #boolean ishelo -- @field #boolean isgrund +-- @field Ops.Auftrag#AUFTRAG mission The current Auftrag attached to this contact +-- @field #string recce The name of the recce unit that detected this contact --- Cluster info. -- @type INTEL.Cluster @@ -85,11 +89,12 @@ INTEL = { -- @field #number threatlevelAve Average of threat levels. -- @field Core.Point#COORDINATE coordinate Coordinate of the cluster. -- @field Wrapper.Marker#MARKER marker F10 marker. +-- @field Ops.Auftrag#AUFTRAG mission The current Auftrag attached to this cluster --- INTEL class version. -- @field #string version -INTEL.version="0.1.0" +INTEL.version="0.2.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -175,6 +180,8 @@ function INTEL:New(DetectionSet, Coalition, Alias) self:AddTransition("*", "NewContact", "*") -- New contact has been detected. self:AddTransition("*", "LostContact", "*") -- Contact could not be detected any more. + self:AddTransition("*", "NewCluster", "*") -- New cluster has been detected. + self:AddTransition("*", "LostCluster", "*") -- Cluster could not be detected any more. -- Defaults self:SetForgetTime() @@ -210,10 +217,43 @@ function INTEL:New(DetectionSet, Coalition, Alias) -- @function [parent=#INTEL] __Status -- @param #INTEL self -- @param #number delay Delay in seconds. + + --- On After "NewContact" event. + -- @function [parent=#INTEL] OnAfterNewContact + -- @param #INTEL self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #INTEL.Contact Contact Detected contact. + + --- On After "LostContact" event. + -- @function [parent=#INTEL] OnAfterLostContact + -- @param #INTEL self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #INTEL.Contact Contact Lost contact. + + --- On After "NewCluster" event. + -- @function [parent=#INTEL] OnAfterNewCluster + -- @param #INTEL self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #INTEL.Contact Contact Detected contact. + -- @param #INTEL.Cluster Cluster Detected cluster + + --- On After "LostCluster" event. + -- @function [parent=#INTEL] OnAfterLostCluster + -- @param #INTEL self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #INTEL.Cluster Cluster Lost cluster + -- @param Ops.Auftrag#AUFTRAG Mission The Auftrag connected with this cluster or nil return self end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- User functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -307,7 +347,7 @@ function INTEL:SetFilterCategory(Categories) for _,category in pairs(self.filterCategory) do text=text..string.format("%d,", category) end - self:I(self.lid..text) + self:T(self.lid..text) return self end @@ -334,7 +374,7 @@ function INTEL:FilterCategoryGroup(GroupCategories) for _,category in pairs(self.filterCategoryGroup) do text=text..string.format("%d,", category) end - self:I(self.lid..text) + self:T(self.lid..text) return self end @@ -351,6 +391,49 @@ function INTEL:SetClusterAnalysis(Switch, Markers) return self end +--- Set verbosity level for debugging. +-- @param #INTEL self +-- @param #number Verbosity The higher, the noisier, e.g. 0=off, 2=debug +-- @return #INTEL self +function INTEL:SetVerbosity(Verbosity) + self.verbose=Verbosity or 2 + return self +end + +--- Add a Mission (Auftrag) to a contact for tracking. +-- @param #INTEL self +-- @param #INTEL.Contact Contact The contact +-- @param Ops.Auftrag#AUFTRAG Mission The mission connected with this contact +-- @return #INTEL self +function INTEL:AddMissionToContact(Contact, Mission) + if Mission and Contact then + Contact.mission = Mission + end + return self +end + +--- Add a Mission (Auftrag) to a cluster for tracking. +-- @param #INTEL self +-- @param #INTEL.Cluster Cluster The cluster +-- @param Ops.Auftrag#AUFTRAG Mission The mission connected with this cluster +-- @return #INTEL self +function INTEL:AddMissionToCluster(Cluster, Mission) + if Mission and Cluster then + Cluster.mission = Mission + end + return self +end + +--- Change radius of the Clusters +-- @param #INTEL self +-- @param #number radius The radius of the clusters +-- @return #INTEL self +function INTEL:SetClusterRadius(radius) + local radius = radius or 15 + self.clusterradius = radius + return self +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Start & Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -389,10 +472,11 @@ function INTEL:onafterStatus(From, Event, To) -- Number of total contacts. local Ncontacts=#self.Contacts + local Nclusters=#self.Clusters -- Short info. if self.verbose>=1 then - local text=string.format("Status %s [Agents=%s]: Contacts=%d, New=%d, Lost=%d", fsmstate, self.detectionset:CountAlive(), Ncontacts, #self.ContactsUnknown, #self.ContactsLost) + local text=string.format("Status %s [Agents=%s]: Contacts=%d, Clusters=%d, New=%d, Lost=%d", fsmstate, self.detectionset:CountAlive(), Ncontacts, Nclusters, #self.ContactsUnknown, #self.ContactsLost) self:I(self.lid..text) end @@ -421,7 +505,8 @@ function INTEL:UpdateIntel() -- Set of all detected units. local DetectedUnits={} - + -- Set of which units was detected by which recce + local RecceDetecting = {} -- Loop over all units providing intel. for _,_group in pairs(self.detectionset.Set or {}) do local group=_group --Wrapper.Group#GROUP @@ -432,7 +517,7 @@ function INTEL:UpdateIntel() local recce=_recce --Wrapper.Unit#UNIT -- Get detected units. - self:GetDetectedUnits(recce, DetectedUnits) + self:GetDetectedUnits(recce, DetectedUnits, RecceDetecting) end @@ -489,7 +574,7 @@ function INTEL:UpdateIntel() end end if not keepit then - self:I(self.lid..string.format("Removing unit %s category=%d", unitname, unit:GetCategory())) + self:T(self.lid..string.format("Removing unit %s category=%d", unitname, unit:GetCategory())) table.insert(remove, unitname) end end @@ -502,17 +587,20 @@ function INTEL:UpdateIntel() end -- Create detected groups. - local DetectedGroups={} + local DetectedGroups={} + local RecceGroups={} for unitname,_unit in pairs(DetectedUnits) do local unit=_unit --Wrapper.Unit#UNIT local group=unit:GetGroup() if group then - DetectedGroups[group:GetName()]=group + local groupname = group:GetName() + DetectedGroups[groupname]=group + RecceGroups[groupname]=RecceDetecting[unitname] end end -- Create detected contacts. - self:CreateDetectedItems(DetectedGroups) + self:CreateDetectedItems(DetectedGroups, RecceGroups) -- Paint a picture of the battlefield. if self.clusteranalysis then @@ -528,8 +616,9 @@ end --- Create detected items. -- @param #INTEL self -- @param #table DetectedGroups Table of detected Groups -function INTEL:CreateDetectedItems(DetectedGroups) - +-- @param #table RecceDetecting Table of detecting recce names +function INTEL:CreateDetectedItems(DetectedGroups, RecceDetecting) + self:F({RecceDetecting=RecceDetecting}) -- Current time. local Tnow=timer.getAbsTime() @@ -569,7 +658,8 @@ function INTEL:CreateDetectedItems(DetectedGroups) item.position=group:GetCoordinate() item.velocity=group:GetVelocityVec3() item.speed=group:GetVelocityMPS() - + item.recce=RecceDetecting[groupname] + self:T(string.format("%s group detect by %s/%s", groupname, RecceDetecting[groupname] or "unknonw", item.recce or "unknown")) -- Add contact to table. self:AddContact(item) @@ -602,17 +692,20 @@ end -- If no detection method is given, the detection will use all the available methods by default. -- @param #INTEL self -- @param Wrapper.Unit#UNIT Unit The unit detecting. +-- @param #table DetectedUnits Table of detected units to be filled +-- @param #table RecceDetecting Table of recce per unit to be filled -- @param #boolean DetectVisual (Optional) If *false*, do not include visually detected targets. -- @param #boolean DetectOptical (Optional) If *false*, do not include optically detected targets. -- @param #boolean DetectRadar (Optional) If *false*, do not include targets detected by radar. -- @param #boolean DetectIRST (Optional) If *false*, do not include targets detected by IRST. -- @param #boolean DetectRWR (Optional) If *false*, do not include targets detected by RWR. -- @param #boolean DetectDLINK (Optional) If *false*, do not include targets detected by data link. -function INTEL:GetDetectedUnits(Unit, DetectedUnits, DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK) +function INTEL:GetDetectedUnits(Unit, DetectedUnits, RecceDetecting, DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK) -- Get detected DCS units. local detectedtargets=Unit:GetDetectedTargets(DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK) - + local reccename = Unit:GetName() + for DetectionObjectID, Detection in pairs(detectedtargets or {}) do local DetectedObject=Detection.object -- DCS#Object @@ -625,7 +718,8 @@ function INTEL:GetDetectedUnits(Unit, DetectedUnits, DetectVisual, DetectOptical local unitname=unit:GetName() DetectedUnits[unitname]=unit - + RecceDetecting[unitname]=reccename + self:T(string.format("Unit %s detect by %s", unitname, reccename)) end end end @@ -643,7 +737,7 @@ end -- @param #string To To state. -- @param #INTEL.Contact Contact Detected contact. function INTEL:onafterNewContact(From, Event, To, Contact) - self:I(self.lid..string.format("NEW contact %s", Contact.groupname)) + self:F(self.lid..string.format("NEW contact %s", Contact.groupname)) table.insert(self.ContactsUnknown, Contact) end @@ -654,10 +748,37 @@ end -- @param #string To To state. -- @param #INTEL.Contact Contact Detected contact. function INTEL:onafterLostContact(From, Event, To, Contact) - self:I(self.lid..string.format("LOST contact %s", Contact.groupname)) + self:F(self.lid..string.format("LOST contact %s", Contact.groupname)) table.insert(self.ContactsLost, Contact) end +--- On after "NewCluster" event. +-- @param #INTEL self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #INTEL.Contact Contact Detected contact. +-- @param #INTEL.Cluster Cluster Detected cluster +function INTEL:onafterNewCluster(From, Event, To, Contact, Cluster) + self:F(self.lid..string.format("NEW cluster %d size %d with contact %s", Cluster.index, Cluster.size, Contact.groupname)) +end + +--- On after "LostCluster" event. +-- @param #INTEL self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #INTEL.Cluster Cluster Lost cluster +-- @param Ops.Auftrag#AUFTRAG Mission The Auftrag connected with this cluster or nil +function INTEL:onafterLostCluster(From, Event, To, Cluster, Mission) + local text = self.lid..string.format("LOST cluster %d", Cluster.index) + if Mission then + local mission=Mission --Ops.Auftrag#AUFTRAG + text=text..string.format(" mission name=%s type=%s target=%s", mission.name, mission.type, mission:GetTargetName() or "unkown") + end + self:T(text) +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -752,11 +873,27 @@ function INTEL:PaintPicture() self:RemoveContactFromCluster(contact, cluster) end end - + -- clean up cluster table + local ClusterSet = {} + for _i,_cluster in pairs(self.Clusters) do + if (_cluster.size > 0) and (self:ClusterCountUnits(_cluster) > 0) then + table.insert(ClusterSet,_cluster) + else + local mission = _cluster.mission or nil + local marker = _cluster.marker + if marker then + marker:Remove() + end + self:LostCluster(_cluster, mission) + end + end + self.Clusters = ClusterSet + -- update positions + self:_UpdateClusterPositions() for _,_contact in pairs(self.Contacts) do local contact=_contact --#INTEL.Contact - + self:T(string.format("Paint Picture: checking for %s",contact.groupname)) -- Check if this contact is in any cluster. local isincluster=self:CheckContactInClusters(contact) @@ -764,7 +901,7 @@ function INTEL:PaintPicture() local currentcluster=self:GetClusterOfContact(contact) if currentcluster then - + --self:I(string.format("Paint Picture: %s has current cluster",contact.groupname)) --- -- Contact is currently part of a cluster. --- @@ -772,8 +909,8 @@ function INTEL:PaintPicture() -- Check if the contact is still connected to the cluster. local isconnected=self:IsContactConnectedToCluster(contact, currentcluster) - if not isconnected then - + if (not isconnected) and (currentcluster.size > 1) then + --self:I(string.format("Paint Picture: %s has LOST current cluster",contact.groupname)) local cluster=self:IsContactPartOfAnyClusters(contact) if cluster then @@ -782,6 +919,7 @@ function INTEL:PaintPicture() local newcluster=self:CreateCluster(contact.position) self:AddContactToCluster(contact, newcluster) + self:NewCluster(contact, newcluster) end end @@ -792,7 +930,7 @@ function INTEL:PaintPicture() --- -- Contact is not in any cluster yet. --- - + --self:I(string.format("Paint Picture: %s has NO current cluster",contact.groupname)) local cluster=self:IsContactPartOfAnyClusters(contact) if cluster then @@ -801,6 +939,7 @@ function INTEL:PaintPicture() local newcluster=self:CreateCluster(contact.position) self:AddContactToCluster(contact, newcluster) + self:NewCluster(contact, newcluster) end end @@ -810,16 +949,17 @@ function INTEL:PaintPicture() -- Update F10 marker text if cluster has changed. - for _,_cluster in pairs(self.Clusters) do - local cluster=_cluster --#INTEL.Cluster - - local coordinate=self:GetClusterCoordinate(cluster) - - - -- Update F10 marker. - self:UpdateClusterMarker(cluster) + if self.clustermarkers then + for _,_cluster in pairs(self.Clusters) do + local cluster=_cluster --#INTEL.Cluster + + local coordinate=self:GetClusterCoordinate(cluster) + + + -- Update F10 marker. + self:UpdateClusterMarker(cluster) + end end - end --- Create a new cluster. @@ -976,9 +1116,11 @@ function INTEL:IsContactConnectedToCluster(contact, cluster) if Contact.groupname~=contact.groupname then - local dist=Contact.position:Get2DDistance(contact.position) + --local dist=Contact.position:Get2DDistance(contact.position) + local dist=Contact.position:DistanceFromPointVec2(contact.position) - if dist<10*1000 then + local radius = self.clusterradius or 15 + if dist1000 then return true @@ -1073,6 +1216,27 @@ function INTEL:CheckClusterCoordinateChanged(cluster, coordinate) end +--- Update coordinates of the known clusters. +-- @param #INTEL self +function INTEL:_UpdateClusterPositions() + for _,_cluster in pairs (self.Clusters) do + local coord = self:GetClusterCoordinate(_cluster) + _cluster.coordinate = coord + self:T(self.lid..string.format("Cluster size: %s", _cluster.size)) + end +end + +--- Count number of units in cluster +-- @param #INTEL self +-- @param #INTEL.Cluster Cluster The cluster +-- @return #number unitcount +function INTEL:ClusterCountUnits(Cluster) + local unitcount = 0 + for _,_group in pairs (Cluster.Contacts) do -- get Wrapper.GROUP#GROUP _group + unitcount = unitcount + _group.group:CountAliveUnits() + end + return unitcount +end --- Update cluster F10 marker. -- @param #INTEL self @@ -1081,7 +1245,8 @@ end function INTEL:UpdateClusterMarker(cluster) -- Create a marker. - local text=string.format("Cluster #%d. Size %d, TLsum=%d", cluster.index, cluster.size, cluster.threatlevelSum) + local unitcount = self:ClusterCountUnits(cluster) + local text=string.format("Cluster #%d. Size %d, Units %d, TLsum=%d", cluster.index, cluster.size, unitcount, cluster.threatlevelSum) if not cluster.marker then cluster.marker=MARKER:New(cluster.coordinate, text):ToAll()