diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index bb4e25fe0..df182aac2 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -300,23 +300,23 @@ do -- Zones for ZoneID, ZoneData in pairs(env.mission.triggers.zones) do local ZoneName = ZoneData.name - + -- Color local color=ZoneData.color or {1, 0, 0, 0.15} - + -- Create new Zone local Zone=nil --Core.Zone#ZONE_BASE - + if ZoneData.type==0 then - + --- -- Circular zone --- - + self:I(string.format("Register ZONE: %s (Circular)", ZoneName)) - + Zone=ZONE:New(ZoneName) - + else --- @@ -324,51 +324,51 @@ do -- Zones --- self:I(string.format("Register ZONE: %s (Polygon, Quad)", ZoneName)) - + Zone=ZONE_POLYGON_BASE:New(ZoneName, ZoneData.verticies) - + --for i,vec2 in pairs(ZoneData.verticies) do -- local coord=COORDINATE:NewFromVec2(vec2) -- coord:MarkToAll(string.format("%s Point %d", ZoneName, i)) --end - + end - + if Zone then - -- Store color of zone. + -- Store color of zone. Zone.Color=color - + -- Store in DB. self.ZONENAMES[ZoneName] = ZoneName - + -- Add zone. self:AddZone(ZoneName, Zone) - + end - + end -- Polygon zones defined by late activated groups. for ZoneGroupName, ZoneGroup in pairs( self.GROUPS ) do if ZoneGroupName:match("#ZONE_POLYGON") then - + local ZoneName1 = ZoneGroupName:match("(.*)#ZONE_POLYGON") local ZoneName2 = ZoneGroupName:match(".*#ZONE_POLYGON(.*)") local ZoneName = ZoneName1 .. ( ZoneName2 or "" ) -- Debug output self:I(string.format("Register ZONE: %s (Polygon)", ZoneName)) - + -- Create a new polygon zone. local Zone_Polygon = ZONE_POLYGON:New( ZoneName, ZoneGroup ) - + -- Set color. Zone_Polygon:SetColor({1, 0, 0}, 0.15) - + -- Store name in DB. self.ZONENAMES[ZoneName] = ZoneName - + -- Add zone to DB. self:AddZone( ZoneName, Zone_Polygon ) end diff --git a/Moose Development/Moose/Core/SpawnStatic.lua b/Moose Development/Moose/Core/SpawnStatic.lua index b7a6fae28..5fde1c12c 100644 --- a/Moose Development/Moose/Core/SpawnStatic.lua +++ b/Moose Development/Moose/Core/SpawnStatic.lua @@ -138,24 +138,24 @@ SPAWNSTATIC = { -- @return #SPAWNSTATIC self function SPAWNSTATIC:NewFromStatic(SpawnTemplateName, SpawnCountryID) - local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC + local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC - local TemplateStatic, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate(SpawnTemplateName) - - if TemplateStatic then - self.SpawnTemplatePrefix = SpawnTemplateName - self.TemplateStaticUnit = UTILS.DeepCopy(TemplateStatic.units[1]) - self.CountryID = SpawnCountryID or CountryID - self.CategoryID = CategoryID - self.CoalitionID = CoalitionID - self.SpawnIndex = 0 - else - error( "SPAWNSTATIC:New: There is no static declared in the mission editor with SpawnTemplatePrefix = '" .. tostring(SpawnTemplateName) .. "'" ) - end + local TemplateStatic, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate(SpawnTemplateName) + + if TemplateStatic then + self.SpawnTemplatePrefix = SpawnTemplateName + self.TemplateStaticUnit = UTILS.DeepCopy(TemplateStatic.units[1]) + self.CountryID = SpawnCountryID or CountryID + self.CategoryID = CategoryID + self.CoalitionID = CoalitionID + self.SpawnIndex = 0 + else + error( "SPAWNSTATIC:New: There is no static declared in the mission editor with SpawnTemplatePrefix = '" .. tostring(SpawnTemplateName) .. "'" ) + end self:SetEventPriority( 5 ) - return self + return self end --- Creates the main object to spawn a @{Static} given a template table. diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index d9b6f3711..332a73dda 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -498,8 +498,8 @@ end -- -- @field #ZONE_RADIUS ZONE_RADIUS = { - ClassName="ZONE_RADIUS", - } + ClassName="ZONE_RADIUS", + } --- Constructor of @{#ZONE_RADIUS}, taking the zone name, the zone location and a radius. -- @param #ZONE_RADIUS self @@ -510,15 +510,15 @@ ZONE_RADIUS = { function ZONE_RADIUS:New( ZoneName, Vec2, Radius ) -- Inherit ZONE_BASE. - local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) -- #ZONE_RADIUS - self:F( { ZoneName, Vec2, Radius } ) + local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) -- #ZONE_RADIUS + self:F( { ZoneName, Vec2, Radius } ) - self.Radius = Radius - self.Vec2 = Vec2 + self.Radius = Radius + self.Vec2 = Vec2 - --self.Coordinate=COORDINATE:NewFromVec2(Vec2) + --self.Coordinate=COORDINATE:NewFromVec2(Vec2) - return self + return self end --- Update zone from a 2D vector. @@ -746,11 +746,11 @@ end -- @param #ZONE_RADIUS self -- @return DCS#Vec2 The location of the zone. function ZONE_RADIUS:GetVec2() - self:F2( self.ZoneName ) + self:F2( self.ZoneName ) - self:T2( { self.Vec2 } ) + self:T2( { self.Vec2 } ) - return self.Vec2 + return self.Vec2 end --- Sets the @{DCS#Vec2} of the zone. @@ -1165,20 +1165,20 @@ end -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. -- @return DCS#Vec2 The random location within the zone. function ZONE_RADIUS:GetRandomVec2( inner, outer ) - self:F( self.ZoneName, inner, outer ) + self:F( self.ZoneName, inner, outer ) - local Point = {} - local Vec2 = self:GetVec2() - local _inner = inner or 0 - local _outer = outer or self:GetRadius() + local Point = {} + local Vec2 = self:GetVec2() + local _inner = inner or 0 + local _outer = outer or self:GetRadius() - local angle = math.random() * math.pi * 2; - Point.x = Vec2.x + math.cos( angle ) * math.random(_inner, _outer); - Point.y = Vec2.y + math.sin( angle ) * math.random(_inner, _outer); + local angle = math.random() * math.pi * 2; + Point.x = Vec2.x + math.cos( angle ) * math.random(_inner, _outer); + Point.y = Vec2.y + math.sin( angle ) * math.random(_inner, _outer); - self:T( { Point } ) + self:T( { Point } ) - return Point + return Point end --- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 4b7596786..f9e10f545 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -1,22 +1,22 @@ --- **Functional** -- Modular, Automatic and Network capable Targeting and Interception System for Air Defenses --- +-- -- === --- +-- -- **MANTIS** - Moose derived Modular, Automatic and Network capable Targeting and Interception System -- Controls a network of SAM sites. Use detection to switch on the AA site closest to the enemy -- Leverage evasiveness from SEAD -- Leverage attack range setup added by DCS in 11/20 --- +-- -- === --- +-- -- ## Missions: -- -- ### [MANTIS - Modular, Automatic and Network capable Targeting and Interception System](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/MTS%20-%20Mantis/MTS-010%20-%20Basic%20Mantis%20Demo) --- +-- -- === --- +-- -- ### Author : **applevangelist ** --- +-- -- @module Functional.Mantis -- @image Functional.Mantis.jpg @@ -59,10 +59,10 @@ -- @extends Core.Base#BASE ---- *The worst thing that can happen to a good cause is, not to be skillfully attacked, but to be ineptly defended.* - Frédéric Bastiat --- +--- *The worst thing that can happen to a good cause is, not to be skillfully attacked, but to be ineptly defended.* - Frédéric Bastiat +-- -- Simple Class for a more intelligent Air Defense System --- +-- -- #MANTIS -- Moose derived Modular, Automatic and Network capable Targeting and Interception System. -- Controls a network of SAM sites. Use detection to switch on the AA site closest to the enemy. @@ -72,62 +72,62 @@ -- Set up your SAM sites in the mission editor. Name the groups with common prefix like "Red SAM". -- Set up your EWR system in the mission editor. Name the groups with common prefix like "Red EWR". Can be e.g. AWACS or a combination of AWACS and Search Radars like e.g. EWR 1L13 etc. -- [optional] Set up your HQ. Can be any group, e.g. a command vehicle. --- +-- -- # 1. Basic tactical considerations when setting up your SAM sites --- +-- -- ## 1.1 Radar systems and AWACS --- +-- -- Typically, your setup should consist of EWR (early warning) radars to detect and track targets, accompanied by AWACS if your scenario forsees that. Ensure that your EWR radars have a good coverage of the area you want to track. -- **Location** is of highest importantance here. Whilst AWACS in DCS has almost the "all seeing eye", EWR don't have that. Choose your location wisely, against a mountain backdrop or inside a valley even the best EWR system -- doesn't work well. Prefer higher-up locations with a good view; use F7 in-game to check where you actually placed your EWR and have a look around. Apart from the obvious choice, do also consider other radar units --- for this role, most have "SR" (search radar) or "STR" (search and track radar) in their names, use the encyclopedia to see what they actually do. --- +-- for this role, most have "SR" (search radar) or "STR" (search and track radar) in their names, use the encyclopedia to see what they actually do. +-- -- ## 1.2 SAM sites --- --- Typically your SAM should cover all attack ranges. The closer the enemy gets, the more systems you will need to deploy to defend your location. Use a combination of long-range systems like the SA-10/11, midrange like SA-6 and short-range like --- SA-2 for defense (Patriot, Hawk, Gepard, Blindfire for the blue side). For close-up defense and defense against HARMs or low-flying aircraft, helicopters it is also advisable to deploy SA-15 TOR systems, Shilka, Strela and Tunguska units, as well as manpads (Think Gepard, Avenger, Chaparral, +-- +-- Typically your SAM should cover all attack ranges. The closer the enemy gets, the more systems you will need to deploy to defend your location. Use a combination of long-range systems like the SA-10/11, midrange like SA-6 and short-range like +-- SA-2 for defense (Patriot, Hawk, Gepard, Blindfire for the blue side). For close-up defense and defense against HARMs or low-flying aircraft, helicopters it is also advisable to deploy SA-15 TOR systems, Shilka, Strela and Tunguska units, as well as manpads (Think Gepard, Avenger, Chaparral, -- Linebacker, Roland systems for the blue side). If possible, overlap ranges for mutual coverage. --- +-- -- ## 1.3 Typical problems --- --- Often times, people complain because the detection cannot "see" oncoming targets and/or Mantis switches on too late. Three typial problems here are --- --- * bad placement of radar units, --- * overestimation how far units can "see" and --- * not taking into account that a SAM site will take (e.g for a SA-6) 30-40 seconds between switching to RED, acquiring the target and firing. --- +-- +-- Often times, people complain because the detection cannot "see" oncoming targets and/or Mantis switches on too late. Three typial problems here are +-- +-- * bad placement of radar units, +-- * overestimation how far units can "see" and +-- * not taking into account that a SAM site will take (e.g for a SA-6) 30-40 seconds between switching to RED, acquiring the target and firing. +-- -- An attacker doing 350knots will cover ca 180meters/second or thus more than 6km until the SA-6 fires. Use triggers zones and the ruler in the missione editor to understand distances and zones. Take into account that the ranges given by the circles -- in the mission editor are absolute maximum ranges; in-game this is rather 50-75% of that depending on the system. Fiddle with placement and options to see what works best for your scenario, and remember **everything in here is in meters**. --- +-- -- # 2. Start up your MANTIS with a basic setting --- --- `myredmantis = MANTIS:New("myredmantis","Red SAM","Red EWR",nil,"red",false)` +-- +-- `myredmantis = MANTIS:New("myredmantis","Red SAM","Red EWR",nil,"red",false)` -- `myredmantis:Start()` --- --- [optional] Use --- --- * `MANTIS:SetEWRGrouping(radius)` --- * `MANTIS:SetEWRRange(radius)` --- * `MANTIS:SetSAMRadius(radius)` +-- +-- [optional] Use +-- +-- * `MANTIS:SetEWRGrouping(radius)` +-- * `MANTIS:SetEWRRange(radius)` +-- * `MANTIS:SetSAMRadius(radius)` -- * `MANTIS:SetDetectInterval(interval)` -- * `MANTIS:SetAutoRelocate(hq, ewr)` --- +-- -- before starting #MANTIS to fine-tune your setup. --- +-- -- If you want to use a separate AWACS unit (default detection range: 250km) to support your EWR system, use e.g. the following setup: --- --- `mybluemantis = MANTIS:New("bluemantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")` +-- +-- `mybluemantis = MANTIS:New("bluemantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")` -- `mybluemantis:Start()` -- -- # 3. Default settings --- +-- -- By default, the following settings are active: -- -- * SAM_Templates_Prefix = "Red SAM" - SAM site group names in the mission editor begin with "Red SAM" -- * EWR_Templates_Prefix = "Red EWR" - EWR group names in the mission editor begin with "Red EWR" - can also be combined with an AWACS unit -- * checkradius = 25000 (meters) - SAMs will engage enemy flights, if they are within a 25km around each SAM site - `MANTIS:SetSAMRadius(radius)` -- * grouping = 5000 (meters) - Detection (EWR) will group enemy flights to areas of 5km for tracking - `MANTIS:SetEWRGrouping(radius)` --- * acceptrange = 80000 (meters) - Detection (EWR) will on consider flights inside a 80km radius - `MANTIS:SetEWRRange(radius)` +-- * acceptrange = 80000 (meters) - Detection (EWR) will on consider flights inside a 80km radius - `MANTIS:SetEWRRange(radius)` -- * detectinterval = 30 (seconds) - MANTIS will decide every 30 seconds which SAM to activate - `MANTIS:SetDetectInterval(interval)` -- * engagerange = 85 (percent) - SAMs will only fire if flights are inside of a 85% radius of their max firerange - `MANTIS:SetSAMRange(range)` -- * dynamic = false - Group filtering is set to once, i.e. newly added groups will not be part of the setup by default - `MANTIS:New(name,samprefix,ewrprefix,hq,coaltion,dynamic)` @@ -135,28 +135,28 @@ -- * debug = false - Debugging reports on screen are set to off - `MANTIS:Debug(onoff)` -- -- # 4. Advanced Mode --- --- Advanced mode will *decrease* reactivity of MANTIS, if HQ and/or EWR network dies. Awacs is counted as one EWR unit. It will set SAMs to RED state if both are dead. Requires usage of an **HQ** object and the **dynamic** option. --- --- E.g. `mymantis:SetAdvancedMode( true, 90 )` --- --- Use this option if you want to make use of or allow advanced SEAD tactics. --- +-- +-- Advanced mode will *decrease* reactivity of MANTIS, if HQ and/or EWR network dies. Awacs is counted as one EWR unit. It will set SAMs to RED state if both are dead. Requires usage of an **HQ** object and the **dynamic** option. +-- +-- E.g. `mymantis:SetAdvancedMode( true, 90 )` +-- +-- Use this option if you want to make use of or allow advanced SEAD tactics. +-- -- # 5. Integrate SHORAD --- +-- -- You can also choose to integrate Mantis with @{Functional.Shorad#SHORAD} for protection against HARMs and AGMs. When SHORAD detects a missile fired at one of MANTIS' SAM sites, it will activate SHORAD systems in -- the given defense checkradius around that SAM site. Create a SHORAD object first, then integrate with MANTIS like so: --- +-- -- `local SamSet = SET_GROUP:New():FilterPrefixes("Blue SAM"):FilterCoalitions("blue"):FilterStart()` -- `myshorad = SHORAD:New("BlueShorad", "Blue SHORAD", SamSet, 22000, 600, "blue")` -- `-- now set up MANTIS` -- `mymantis = MANTIS:New("BlueMantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")` -- `mymantis:AddShorad(myshorad,720)` -- `mymantis:Start()` --- +-- -- and (optionally) remove the link later on with --- --- `mymantis:RemoveShorad()` +-- +-- `mymantis:RemoveShorad()` -- -- @field #MANTIS MANTIS = { @@ -222,31 +222,31 @@ do --@param #string coaltion Coalition side of your setup, e.g. "blue", "red" or "neutral" --@param #boolean dynamic Use constant (true) filtering or just filter once (false, default) (optional) --@param #string awacs Group name of your Awacs (optional) - --@param #boolean EmOnOff Make MANTIS switch Emissions on and off instead of changing the alarm state between RED and GREEN (optional) - --@param #number Padding For #SEAD - Extra number of seconds to add to radar switch-back-on time (optional) + --@param #boolean EmOnOff Make MANTIS switch Emissions on and off instead of changing the alarm state between RED and GREEN (optional) + --@param #number Padding For #SEAD - Extra number of seconds to add to radar switch-back-on time (optional) --@return #MANTIS self --@usage Start up your MANTIS with a basic setting -- - -- `myredmantis = MANTIS:New("myredmantis","Red SAM","Red EWR",nil,"red",false)` + -- `myredmantis = MANTIS:New("myredmantis","Red SAM","Red EWR",nil,"red",false)` -- `myredmantis:Start()` - -- - -- [optional] Use - -- - -- * `MANTIS:SetEWRGrouping(radius)` - -- * `MANTIS:SetEWRRange(radius)` - -- * `MANTIS:SetSAMRadius(radius)` + -- + -- [optional] Use + -- + -- * `MANTIS:SetEWRGrouping(radius)` + -- * `MANTIS:SetEWRRange(radius)` + -- * `MANTIS:SetSAMRadius(radius)` -- * `MANTIS:SetDetectInterval(interval)` -- * `MANTIS:SetAutoRelocate(hq, ewr)` - -- + -- -- before starting #MANTIS to fine-tune your setup. - -- + -- -- If you want to use a separate AWACS unit (default detection range: 250km) to support your EWR system, use e.g. the following setup: - -- - -- `mybluemantis = MANTIS:New("bluemantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")` + -- + -- `mybluemantis = MANTIS:New("bluemantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")` -- `mybluemantis:Start()` - -- + -- function MANTIS:New(name,samprefix,ewrprefix,hq,coaltion,dynamic,awacs, EmOnOff, Padding) - + -- DONE: Create some user functions for these -- DONE: Make HQ useful -- DONE: Set SAMs to auto if EWR dies @@ -269,7 +269,7 @@ do self.autorelocateunits = { HQ = false, EWR = false} self.advanced = false self.adv_ratio = 100 - self.adv_state = 0 + self.adv_state = 0 self.verbose = false self.Adv_EWR_Group = nil self.AWACS_Prefix = awacs or nil @@ -284,27 +284,27 @@ do self.SamStateTracker = {} -- table to hold alert states, so we don't trigger state changes twice in adv mode self.DLink = false self.Padding = Padding or 10 - + if EmOnOff then if EmOnOff == false then self.UseEmOnOff = false else self.UseEmOnOff = true end - end - + end + if type(awacs) == "string" then self.advAwacs = true else self.advAwacs = false end - + -- Inherit everything from BASE class. local self = BASE:Inherit(self, FSM:New()) -- #MANTIS - + -- Set the string id for output to DCS.log file. self.lid=string.format("MANTIS %s | ", self.name) - + -- Debug trace. if self.debug then BASE:TraceOnOff(true) @@ -312,7 +312,7 @@ do --BASE:TraceClass("SEAD") BASE:TraceLevel(1) end - + if self.dynamic then -- Set SAM SET_GROUP self.SAM_Group = SET_GROUP:New():FilterPrefixes(self.SAM_Templates_Prefix):FilterCoalitions(self.Coalition):FilterStart() @@ -324,18 +324,18 @@ do -- Set EWR SET_GROUP self.EWR_Group = SET_GROUP:New():FilterPrefixes({self.SAM_Templates_Prefix,self.EWR_Templates_Prefix}):FilterCoalitions(self.Coalition):FilterOnce() end - + -- set up CC if self.HQ_Template_CC then self.HQ_CC = GROUP:FindByName(self.HQ_Template_CC) end - + -- @field #string version self.version="0.6.2" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) - + --- FSM Functions --- - + -- Start State. self:SetStartState("Stopped") @@ -349,11 +349,11 @@ do self:AddTransition("*", "AdvStateChange", "*") -- MANTIS advanced mode state change. self:AddTransition("*", "ShoradActivated", "*") -- MANTIS woke up a connected SHORAD. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. - + ------------------------ --- Pseudo Functions --- ------------------------ - + --- Triggers the FSM event "Start". Starts the MANTIS. Initializes parameters and starts event handlers. -- @function [parent=#MANTIS] Start -- @param #MANTIS self @@ -379,7 +379,7 @@ do -- @function [parent=#MANTIS] __Status -- @param #MANTIS self -- @param #number delay Delay in seconds. - + --- On After "Relocating" event. HQ and/or EWR moved. -- @function [parent=#MANTIS] OnAfterRelocating -- @param #MANTIS self @@ -387,7 +387,7 @@ do -- @param #string Event The Event -- @param #string To The To State -- @return #MANTIS self - + --- On After "GreenState" event. A SAM group was switched to GREEN alert. -- @function [parent=#MANTIS] OnAfterGreenState -- @param #MANTIS self @@ -396,7 +396,7 @@ do -- @param #string To The To State -- @param Wrapper.Group#GROUP Group The GROUP object whose state was changed -- @return #MANTIS self - + --- On After "RedState" event. A SAM group was switched to RED alert. -- @function [parent=#MANTIS] OnAfterRedState -- @param #MANTIS self @@ -405,7 +405,7 @@ do -- @param #string To The To State -- @param Wrapper.Group#GROUP Group The GROUP object whose state was changed -- @return #MANTIS self - + --- On After "AdvStateChange" event. Advanced state changed, influencing detection speed. -- @function [parent=#MANTIS] OnAfterAdvStateChange -- @param #MANTIS self @@ -416,7 +416,7 @@ do -- @param #number Newstate New state - 0 = green, 1 = amber, 2 = red -- @param #number Interval Calculated detection interval based on state and advanced feature setting -- @return #MANTIS self - + --- On After "ShoradActivated" event. Mantis has activated a SHORAD. -- @function [parent=#MANTIS] OnAfterShoradActivated -- @param #MANTIS self @@ -427,21 +427,21 @@ do -- @param #number Radius Radius around the named group to find SHORAD groups -- @param #number Ontime Seconds the SHORAD will stay active - return self + return self end ----------------------------------------------------------------------- -- MANTIS helper functions ------------------------------------------------------------------------ - +----------------------------------------------------------------------- + --- [Internal] Function to get the self.SAM_Table -- @param #MANTIS self - -- @return #table table + -- @return #table table function MANTIS:_GetSAMTable() self:T(self.lid .. "GetSAMTable") return self.SAM_Table end - + --- [Internal] Function to set the self.SAM_Table -- @param #MANTIS self -- @return #MANTIS self @@ -450,7 +450,7 @@ do self.SAM_Table = table return self end - + --- Function to set the grouping radius of the detection in meters -- @param #MANTIS self -- @param #number radius Radius upon which detected objects will be grouped @@ -470,17 +470,17 @@ do self.acceptrange = radius return self end - + --- Function to set switch-on/off zone for the SAM sites in meters -- @param #MANTIS self - -- @param #number radius Radius of the firing zone + -- @param #number radius Radius of the firing zone function MANTIS:SetSAMRadius(radius) self:T(self.lid .. "SetSAMRadius") local radius = radius or 25000 self.checkradius = radius return self end - + --- Function to set SAM firing engage range, 0-100 percent, e.g. 75 -- @param #MANTIS self -- @param #number range Percent of the max fire range @@ -493,7 +493,7 @@ do self.engagerange = range return self end - + --- Function to set a new SAM firing engage range, use this method to adjust range while running MANTIS, e.g. for different setups day and night -- @param #MANTIS self -- @param #number range Percent of the max fire range @@ -508,7 +508,7 @@ do self.mysead.EngagementRange = range return self end - + --- Function to set switch-on/off the debug state -- @param #MANTIS self -- @param #boolean onoff Set true to switch on @@ -526,7 +526,7 @@ do end return self end - + --- Function to get the HQ object for further use -- @param #MANTIS self -- @return Wrapper.GROUP#GROUP The HQ #GROUP object or *nil* if it doesn't exist @@ -535,10 +535,10 @@ do if self.HQ_CC then return self.HQ_CC else - return nil - end + return nil + end end - + --- Function to set separate AWACS detection instance -- @param #MANTIS self -- @param #string prefix Name of the AWACS group in the mission editor @@ -562,7 +562,7 @@ do self.awacsrange = range return self end - + --- Function to set the HQ object for further use -- @param #MANTIS self -- @param Wrapper.GROUP#GROUP group The #GROUP object to be set as HQ @@ -580,7 +580,7 @@ do end return self end - + --- Function to set the detection interval -- @param #MANTIS self -- @param #number interval The interval in seconds @@ -589,8 +589,8 @@ do local interval = interval or 30 self.detectinterval = interval return self - end - + end + --- Function to set Advanded Mode -- @param #MANTIS self -- @param #boolean onoff If true, will activate Advanced Mode @@ -615,7 +615,7 @@ do end return self end - + --- Set using Emissions on/off instead of changing alarm state -- @param #MANTIS self -- @param #boolean switch Decide if we are changing alarm state or Emission state @@ -624,7 +624,7 @@ do self.UseEmOnOff = switch or false return self end - + --- Set using an #INTEL_DLINK object instead of #DETECTION -- @param #MANTIS self -- @param Ops.Intelligence#INTEL_DLINK DLink The data link object to be used. @@ -635,7 +635,7 @@ do self.DLTimeStamp = timer.getAbsTime() return self end - + --- [Internal] Function to check if HQ is alive -- @param #MANTIS self -- @return #boolean True if HQ is alive, else false @@ -654,11 +654,11 @@ do return true else --self:T(self.lid.." HQ is dead!") - return false + return false end end end - return self + return self end --- [Internal] Function to check if EWR is (at least partially) alive @@ -690,7 +690,7 @@ do return false end end - return self + return self end --- [Internal] Function to determine state of the advanced mode @@ -726,7 +726,7 @@ do end return newinterval, currstate end - + --- Function to set autorelocation for HQ and EWR objects. Note: Units must be actually mobile in DCS! -- @param #MANTIS self -- @param #boolean hq If true, will relocate HQ object @@ -742,8 +742,8 @@ do --self:T({self.autorelocate, self.autorelocateunits}) end return self - end - + end + --- [Internal] Function to execute the relocation -- @param #MANTIS self function MANTIS:_RelocateGroups() @@ -780,7 +780,7 @@ do end return self end - + --- [Internal] Function to check if any object is in the given SAM zone -- @param #MANTIS self -- @param #table dectset Table of coordinates of detected items @@ -796,12 +796,12 @@ do local coord = _coord -- get current coord to check -- output for cross-check local targetdistance = samcoordinate:DistanceFromPointVec2(coord) - if self.verbose or self.debug then + if self.verbose or self.debug then local dectstring = coord:ToStringLLDMS() local samstring = samcoordinate:ToStringLLDMS() local text = string.format("Checking SAM at % s - Distance %d m - Target %s", samstring, targetdistance, dectstring) local m = MESSAGE:New(text,10,"Check"):ToAllIf(self.debug) - self:I(self.lid..text) + self:I(self.lid..text) end -- end output to cross-check if targetdistance <= radius then @@ -816,20 +816,20 @@ do -- @return Functional.Detection #DETECTION_AREAS The running detection set function MANTIS:StartDetection() self:T(self.lid.."Starting Detection") - + -- start detection local groupset = self.EWR_Group local grouping = self.grouping or 5000 local acceptrange = self.acceptrange or 80000 local interval = self.detectinterval or 60 - + --@param Functional.Detection #DETECTION_AREAS _MANTISdetection [Internal] The MANTIS detection object local MANTISdetection = DETECTION_AREAS:New( groupset, grouping ) --[Internal] Grouping detected objects to 5000m zones MANTISdetection:FilterCategories({ Unit.Category.AIRPLANE, Unit.Category.HELICOPTER }) MANTISdetection:SetAcceptRange(acceptrange) MANTISdetection:SetRefreshTimeInterval(interval) MANTISdetection:Start() - + function MANTISdetection:OnAfterDetectedItem(From,Event,To,DetectedItem) --BASE:I( { From, Event, To, DetectedItem }) local debug = false @@ -838,30 +838,30 @@ do local text = "MANTIS: Detection at "..Coordinate:ToStringLLDMS() local m = MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) end - end + end return MANTISdetection end - + --- [Internal] Function to start the detection via AWACS if defined as separate -- @param #MANTIS self -- @return Functional.Detection #DETECTION_AREAS The running detection set function MANTIS:StartAwacsDetection() self:T(self.lid.."Starting Awacs Detection") - + -- start detection local group = self.AWACS_Prefix local groupset = SET_GROUP:New():FilterPrefixes(group):FilterCoalitions(self.Coalition):FilterStart() local grouping = self.grouping or 5000 --local acceptrange = self.acceptrange or 80000 local interval = self.detectinterval or 60 - + --@param Functional.Detection #DETECTION_AREAS _MANTISdetection [Internal] The MANTIS detection object local MANTISAwacs = DETECTION_AREAS:New( groupset, grouping ) --[Internal] Grouping detected objects to 5000m zones MANTISAwacs:FilterCategories({ Unit.Category.AIRPLANE, Unit.Category.HELICOPTER }) MANTISAwacs:SetAcceptRange(self.awacsrange) --250km MANTISAwacs:SetRefreshTimeInterval(interval) MANTISAwacs:Start() - + function MANTISAwacs:OnAfterDetectedItem(From,Event,To,DetectedItem) --BASE:I( { From, Event, To, DetectedItem }) local debug = false @@ -870,10 +870,10 @@ do local text = "Awacs Detection at "..Coordinate:ToStringLLDMS() local m = MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) end - end + end return MANTISAwacs end - + --- [Internal] Function to set the SAM start state -- @param #MANTIS self -- @return #MANTIS self @@ -912,7 +912,7 @@ do self.mysead = mysead return self end - + --- [Internal] Function to update SAM table and SEAD state -- @param #MANTIS self -- @return #MANTIS self @@ -944,7 +944,7 @@ do end return self end - + --- Function to link up #MANTIS with a #SHORAD installation -- @param #MANTIS self -- @param Functional.Shorad#SHORAD Shorad The #SHORAD object @@ -961,7 +961,7 @@ do end return self end - + --- Function to unlink #MANTIS from a #SHORAD installation -- @param #MANTIS self function MANTIS:RemoveShorad() @@ -969,11 +969,11 @@ do self.ShoradLink = false return self end - + ----------------------------------------------------------------------- -- MANTIS main functions ------------------------------------------------------------------------ - +----------------------------------------------------------------------- + --- [Internal] Check detection function -- @param #MANTIS self -- @param Functional.Detection#DETECTION_AREAS detection Detection object @@ -1024,7 +1024,7 @@ do if self.verbose then self:I(self.lid..text) end end end --end alive - else + else if samgroup:IsAlive() then -- switch off SAM if self.UseEmOnOff then @@ -1035,7 +1035,7 @@ do self:__GreenState(1,samgroup) self.SamStateTracker[name] = "GREEN" end - if self.debug or self.verbose then + if self.debug or self.verbose then local text = string.format("SAM %s switched to alarm state GREEN!", name) local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) if self.verbose then self:I(self.lid..text) end @@ -1044,8 +1044,8 @@ do end --end check end --for for loop return self - end - + end + --- [Internal] Relocation relay function -- @param #MANTIS self -- @return #MANTIS self @@ -1054,7 +1054,7 @@ do self:_RelocateGroups() return self end - + --- [Internal] Check advanced state -- @param #MANTIS self -- @return #MANTIS self @@ -1089,7 +1089,7 @@ do end -- end newstate vs oldstate return self end - + --- [Internal] Check DLink state -- @param #MANTIS self -- @return #MANTIS self @@ -1103,7 +1103,7 @@ do self:I(self.lid .. "Intel DLink not running - switching back to single detection!") end end - + --- [Internal] Function to set start state -- @param #MANTIS self -- @param #string From The From State @@ -1120,10 +1120,10 @@ do if self.advAwacs then self.AWACS_Detection = self:StartAwacsDetection() end - self:__Status(-math.random(1,10)) + self:__Status(-math.random(1,10)) return self end - + --- [Internal] Before status function for MANTIS -- @param #MANTIS self -- @param #string From The From State @@ -1141,7 +1141,7 @@ do if self.advAwacs and not self.state2flag then self:_Check(self.AWACS_Detection) end - + -- relocate HQ and EWR if self.autorelocate then local relointerval = self.relointerval @@ -1149,26 +1149,26 @@ do local timepassed = thistime - self.TimeStamp local halfintv = math.floor(timepassed / relointerval) - + --self:T({timepassed=timepassed, halfintv=halfintv}) - + if halfintv >= 1 then self.TimeStamp = timer.getAbsTime() self:_Relocate() self:__Relocating(1) end end - + -- advanced state check if self.advanced then self:_CheckAdvState() end - + -- check DLink state if self.DLink then self:_CheckDLinkState() end - + return self end @@ -1191,7 +1191,7 @@ do self:__Status(interval) return self end - + --- [Internal] Function to stop MANTIS -- @param #MANTIS self -- @param #string From The From State @@ -1200,9 +1200,9 @@ do -- @return #MANTIS self function MANTIS:onafterStop(From, Event, To) self:T({From, Event, To}) - return self + return self end - + --- [Internal] Function triggered by Event Relocating -- @param #MANTIS self -- @param #string From The From State @@ -1211,9 +1211,9 @@ do -- @return #MANTIS self function MANTIS:onafterRelocating(From, Event, To) self:T({From, Event, To}) - return self + return self end - + --- [Internal] Function triggered by Event GreenState -- @param #MANTIS self -- @param #string From The From State @@ -1223,9 +1223,9 @@ do -- @return #MANTIS self function MANTIS:onafterGreenState(From, Event, To, Group) self:T({From, Event, To, Group}) - return self + return self end - + --- [Internal] Function triggered by Event RedState -- @param #MANTIS self -- @param #string From The From State @@ -1235,9 +1235,9 @@ do -- @return #MANTIS self function MANTIS:onafterRedState(From, Event, To, Group) self:T({From, Event, To, Group}) - return self + return self end - + --- [Internal] Function triggered by Event AdvStateChange -- @param #MANTIS self -- @param #string From The From State @@ -1249,9 +1249,9 @@ do -- @return #MANTIS self function MANTIS:onafterAdvStateChange(From, Event, To, Oldstate, Newstate, Interval) self:T({From, Event, To, Oldstate, Newstate, Interval}) - return self + return self end - + --- [Internal] Function triggered by Event ShoradActivated -- @param #MANTIS self -- @param #string From The From State @@ -1262,7 +1262,7 @@ do -- @param #number Ontime Seconds the SHORAD will stay active function MANTIS:onafterShoradActivated(From, Event, To, Name, Radius, Ontime) self:T({From, Event, To, Name, Radius, Ontime}) - return self + return self end end ----------------------------------------------------------------------- diff --git a/Moose Development/Moose/Functional/MissileTrainer.lua b/Moose Development/Moose/Functional/MissileTrainer.lua index c0911ac36..cc8de571b 100644 --- a/Moose Development/Moose/Functional/MissileTrainer.lua +++ b/Moose Development/Moose/Functional/MissileTrainer.lua @@ -1,28 +1,28 @@ --- **Functional** -- Train missile defence and deflection. --- +-- -- === -- -- ## Features: --- +-- -- * Track the missiles fired at you and other players, providing bearing and range information of the missiles towards the airplanes. -- * Provide alerts of missile launches, including detailed information of the units launching, including bearing, range -- * Provide alerts when a missile would have killed your aircraft. -- * Provide alerts when the missile self destructs. -- * Enable / Disable and Configure the Missile Trainer using the various menu options. --- +-- -- === --- +-- -- ## Missions: --- +-- -- [MIT - Missile Trainer](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/MIT%20-%20Missile%20Trainer) --- +-- -- === --- +-- -- Uses the MOOSE messaging system to be alerted of any missiles fired, and when a missile would hit your aircraft, -- the class will destroy the missile within a certain range, to avoid damage to your aircraft. --- +-- -- When running a mission where the missile trainer is used, the following radio menu structure ( 'Radio Menu' -> 'Other (F10)' -> 'MissileTrainer' ) options are available for the players: --- +-- -- * **Messages**: Menu to configure all messages. -- * **Messages On**: Show all messages. -- * **Messages Off**: Disable all messages. @@ -45,23 +45,23 @@ -- * **Range Off**: Disable range information when a missile is fired to a target. -- * **Bearing On**: Shows bearing information when a missile is fired to a target. -- * **Bearing Off**: Disable bearing information when a missile is fired to a target. --- * **Distance**: Menu to configure the distance when a missile needs to be destroyed when near to a player, during tracking. This will improve/influence hit calculation accuracy, but has the risk of damaging the aircraft when the missile reaches the aircraft before the distance is measured. +-- * **Distance**: Menu to configure the distance when a missile needs to be destroyed when near to a player, during tracking. This will improve/influence hit calculation accuracy, but has the risk of damaging the aircraft when the missile reaches the aircraft before the distance is measured. -- * **50 meter**: Destroys the missile when the distance to the aircraft is below or equal to 50 meter. -- * **100 meter**: Destroys the missile when the distance to the aircraft is below or equal to 100 meter. -- * **150 meter**: Destroys the missile when the distance to the aircraft is below or equal to 150 meter. -- * **200 meter**: Destroys the missile when the distance to the aircraft is below or equal to 200 meter. --- +-- -- === --- +-- -- ### Authors: **FlightControl** --- +-- -- ### Contributions: --- --- * **Stuka (Danny)**: Who you can search on the Eagle Dynamics Forums. Working together with Danny has resulted in the MISSILETRAINER class. --- Danny has shared his ideas and together we made a design. +-- +-- * **Stuka (Danny)**: Who you can search on the Eagle Dynamics Forums. Working together with Danny has resulted in the MISSILETRAINER class. +-- Danny has shared his ideas and together we made a design. -- Together with the **476 virtual team**, we tested the MISSILETRAINER class, and got much positive feedback! -- * **132nd Squadron**: Testing and optimizing the logic. --- +-- -- === -- -- @module Functional.MissileTrainer @@ -76,7 +76,7 @@ --- -- -- # Constructor: --- +-- -- Create a new MISSILETRAINER object with the @{#MISSILETRAINER.New} method: -- -- * @{#MISSILETRAINER.New}: Creates a new MISSILETRAINER object taking the maximum distance to your aircraft to evaluate when a missile needs to be destroyed. @@ -84,7 +84,7 @@ -- MISSILETRAINER will collect each unit declared in the mission with a skill level "Client" and "Player", and will monitor the missiles shot at those. -- -- # Initialization: --- +-- -- A MISSILETRAINER object will behave differently based on the usage of initialization methods: -- -- * @{#MISSILETRAINER.InitMessagesOnOff}: Sets by default the display of any message to be ON or OFF. @@ -97,8 +97,8 @@ -- * @{#MISSILETRAINER.InitRangeOnOff}: Sets by default the display of range information of missiles ON of OFF. -- * @{#MISSILETRAINER.InitBearingOnOff}: Sets by default the display of bearing information of missiles ON of OFF. -- * @{#MISSILETRAINER.InitMenusOnOff}: Allows to configure the options through the radio menu. --- --- @field #MISSILETRAINER +-- +-- @field #MISSILETRAINER MISSILETRAINER = { ClassName = "MISSILETRAINER", TrackingMissiles = {}, @@ -167,7 +167,7 @@ end -- When a missile is fired a SCHEDULER is set off that follows the missile. When near a certain a client player, the missile will be destroyed. -- @param #MISSILETRAINER self -- @param #number Distance The distance in meters when a tracked missile needs to be destroyed when close to a player. --- @param #string Briefing (Optional) Will show a text to the players when starting their mission. Can be used for briefing purposes. +-- @param #string Briefing (Optional) Will show a text to the players when starting their mission. Can be used for briefing purposes. -- @return #MISSILETRAINER function MISSILETRAINER:New( Distance, Briefing ) local self = BASE:Inherit( self, BASE:New() ) @@ -194,8 +194,8 @@ function MISSILETRAINER:New( Distance, Briefing ) -- self:F( "ForEach:" .. Client.UnitName ) -- Client:Alive( self._Alive, self ) -- end --- - self.DBClients:ForEachClient( +-- + self.DBClients:ForEachClient( function( Client ) self:F( "ForEach:" .. Client.UnitName ) Client:Alive( self._Alive, self ) @@ -207,9 +207,9 @@ function MISSILETRAINER:New( Distance, Briefing ) -- self.DB:ForEachClient( -- --- @param Wrapper.Client#CLIENT Client -- function( Client ) --- +-- -- ... actions ... --- +-- -- end -- ) @@ -225,7 +225,7 @@ function MISSILETRAINER:New( Distance, Briefing ) self.DetailsRangeOnOff = true self.DetailsBearingOnOff = true - + self.MenusOnOff = true self.TrackingMissiles = {} @@ -293,7 +293,7 @@ end --- Increases, decreases the missile tracking message display frequency with the provided time interval in seconds. -- The default frequency is a 3 second interval, so the Tracking Frequency parameter specifies the increase or decrease from the default 3 seconds or the last frequency update. -- @param #MISSILETRAINER self --- @param #number TrackingFrequency Provide a negative or positive value in seconds to incraese or decrease the display frequency. +-- @param #number TrackingFrequency Provide a negative or positive value in seconds to incraese or decrease the display frequency. -- @return #MISSILETRAINER self function MISSILETRAINER:InitTrackingFrequency( TrackingFrequency ) self:F( TrackingFrequency ) @@ -478,30 +478,30 @@ function MISSILETRAINER:OnEventShot( EVentData ) if TrainerTargetDCSUnit then local TrainerTargetDCSUnitName = Unit.getName( TrainerTargetDCSUnit ) local TrainerTargetSkill = _DATABASE.Templates.Units[TrainerTargetDCSUnitName].Template.skill - + self:T(TrainerTargetDCSUnitName ) - + local Client = self.DBClients:FindClient( TrainerTargetDCSUnitName ) if Client then - + local TrainerSourceUnit = UNIT:Find( TrainerSourceDCSUnit ) local TrainerTargetUnit = UNIT:Find( TrainerTargetDCSUnit ) - + if self.MessagesOnOff == true and self.AlertsLaunchesOnOff == true then - + local Message = MESSAGE:New( string.format( "%s launched a %s", TrainerSourceUnit:GetTypeName(), TrainerWeaponName ) .. self:_AddRange( Client, TrainerWeapon ) .. self:_AddBearing( Client, TrainerWeapon ), 5, "Launch Alert" ) - + if self.AlertsToAll then Message:ToAll() else Message:ToClient( Client ) end end - + local ClientID = Client:GetID() self:T( ClientID ) local MissileData = {} @@ -579,52 +579,52 @@ function MISSILETRAINER:_TrackMissiles() end -- ALERTS PART - + -- Loop for all Player Clients to check the alerts and deletion of missiles. for ClientDataID, ClientData in pairs( self.TrackingMissiles ) do local Client = ClientData.Client - + if Client and Client:IsAlive() then for MissileDataID, MissileData in pairs( ClientData.MissileData ) do self:T3( MissileDataID ) - + local TrainerSourceUnit = MissileData.TrainerSourceUnit local TrainerWeapon = MissileData.TrainerWeapon local TrainerTargetUnit = MissileData.TrainerTargetUnit local TrainerWeaponTypeName = MissileData.TrainerWeaponTypeName local TrainerWeaponLaunched = MissileData.TrainerWeaponLaunched - + if Client and Client:IsAlive() and TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then local PositionMissile = TrainerWeapon:getPosition().p local TargetVec3 = Client:GetVec3() - + local Distance = ( ( PositionMissile.x - TargetVec3.x )^2 + ( PositionMissile.y - TargetVec3.y )^2 + ( PositionMissile.z - TargetVec3.z )^2 ) ^ 0.5 / 1000 - + if Distance <= self.Distance then -- Hit alert TrainerWeapon:destroy() if self.MessagesOnOff == true and self.AlertsHitsOnOff == true then - + self:T( "killed" ) - + local Message = MESSAGE:New( string.format( "%s launched by %s killed %s", TrainerWeapon:getTypeName(), TrainerSourceUnit:GetTypeName(), TrainerTargetUnit:GetPlayerName() ), 15, "Hit Alert" ) - + if self.AlertsToAll == true then Message:ToAll() else Message:ToClient( Client ) end - + MissileData = nil table.remove( ClientData.MissileData, MissileDataID ) self:T(ClientData.MissileData) @@ -639,7 +639,7 @@ function MISSILETRAINER:_TrackMissiles() TrainerWeaponTypeName, TrainerSourceUnit:GetTypeName() ), 5, "Tracking" ) - + if self.AlertsToAll == true then Message:ToAll() else @@ -660,41 +660,41 @@ function MISSILETRAINER:_TrackMissiles() if ShowMessages == true and self.MessagesOnOff == true and self.TrackingOnOff == true then -- Only do this when tracking information needs to be displayed. -- TRACKING PART - + -- For the current client, the missile range and bearing details are displayed To the Player Client. -- For the other clients, the missile range and bearing details are displayed To the other Player Clients. - -- To achieve this, a cross loop is done for each Player Client <-> Other Player Client missile information. - + -- To achieve this, a cross loop is done for each Player Client <-> Other Player Client missile information. + -- Main Player Client loop for ClientDataID, ClientData in pairs( self.TrackingMissiles ) do - + local Client = ClientData.Client --self:T2( { Client:GetName() } ) - - + + ClientData.MessageToClient = "" ClientData.MessageToAll = "" - + -- Other Players Client loop for TrackingDataID, TrackingData in pairs( self.TrackingMissiles ) do - + for MissileDataID, MissileData in pairs( TrackingData.MissileData ) do --self:T3( MissileDataID ) - + local TrainerSourceUnit = MissileData.TrainerSourceUnit local TrainerWeapon = MissileData.TrainerWeapon local TrainerTargetUnit = MissileData.TrainerTargetUnit local TrainerWeaponTypeName = MissileData.TrainerWeaponTypeName local TrainerWeaponLaunched = MissileData.TrainerWeaponLaunched - + if Client and Client:IsAlive() and TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then - + if ShowMessages == true then local TrackingTo TrackingTo = string.format( " -> %s", TrainerWeaponTypeName ) - + if ClientDataID == TrackingDataID then if ClientData.MessageToClient == "" then ClientData.MessageToClient = "Missiles to You:\n" @@ -712,7 +712,7 @@ function MISSILETRAINER:_TrackMissiles() end end end - + -- Once the Player Client and the Other Player Client tracking messages are prepared, show them. if ClientData.MessageToClient ~= "" or ClientData.MessageToAll ~= "" then local Message = MESSAGE:New( ClientData.MessageToClient .. ClientData.MessageToAll, 1, "Tracking" ):ToClient( Client ) diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index 55278f686..231a964c8 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -1,13 +1,13 @@ --- **Functional** - Create random airtraffic in your missions. --- +-- -- === --- --- The aim of the RAT class is to fill the empty DCS world with randomized air traffic and bring more life to your airports. --- In particular, it is designed to spawn AI air units at random airports. These units will be assigned a random flight path to another random airport on the map. +-- +-- The aim of the RAT class is to fill the empty DCS world with randomized air traffic and bring more life to your airports. +-- In particular, it is designed to spawn AI air units at random airports. These units will be assigned a random flight path to another random airport on the map. -- Even the mission designer will not know where aircraft will be spawned and which route they follow. --- +-- -- ## Features: --- +-- -- * Very simple interface. Just one unit and two lines of Lua code needed to fill your map. -- * High degree of randomization. Aircraft will spawn at random airports, have random routes and random destinations. -- * Specific departure and/or destination airports can be chosen. @@ -19,37 +19,37 @@ -- * Aircraft can report their status during the route. -- * All of the above can be customized by the user if necessary. -- * All current (Caucasus, Nevada, Normandy, Persian Gulf) and future maps are supported. --- +-- -- The RAT class creates an entry in the F10 radio menu which allows to: --- +-- -- * Create new groups on-the-fly, i.e. at run time within the mission, -- * Destroy specific groups (e.g. if they get stuck or damaged and block a runway), -- * Request the status of all RAT aircraft or individual groups, -- * Place markers at waypoints on the F10 map for each group. --- +-- -- Note that by its very nature, this class is suited best for civil or transport aircraft. However, it also works perfectly fine for military aircraft of any kind. --- +-- -- More of the documentation include some simple examples can be found further down this page. --- +-- -- === --- +-- -- ## Missions: -- -- ### [RAT - Random Air Traffic](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/RAT%20-%20Random%20Air%20Traffic) --- +-- -- === --- +-- -- # YouTube Channel --- --- ### [MOOSE YouTube Channel](https://www.youtube.com/channel/UCjrA9j5LQoWsG4SpS8i79Qg) +-- +-- ### [MOOSE YouTube Channel](https://www.youtube.com/channel/UCjrA9j5LQoWsG4SpS8i79Qg) -- ### [MOOSE - RAT - Random Air Traffic](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl0u4Zxywtg-mx_ov4vi68CO) --- +-- -- === --- +-- -- ### Author: **[funkyfranky](https://forums.eagle.ru/member.php?u=115026)** --- +-- -- ### Contributions: [FlightControl](https://forums.eagle.ru/member.php?u=89536) --- +-- -- === -- @module Functional.Rat -- @image RAT.JPG @@ -108,7 +108,7 @@ -- @field #number FLminuser Minimum flight level set by user. -- @field #number FLmaxuser Maximum flight level set by user. -- @field #boolean commute Aircraft commute between departure and destination, i.e. when respawned the departure airport becomes the new destiation. --- @field #boolean starshape If true, aircraft travel A-->B-->A-->C-->A-->D... for commute. +-- @field #boolean starshape If true, aircraft travel A-->B-->A-->C-->A-->D... for commute. -- @field #string homebase Home base for commute and return zone. Aircraft will always return to this base but otherwise travel in a star shaped way. -- @field #boolean continuejourney Aircraft will continue their journey, i.e. get respawned at their destination with a new random destination. -- @field #number ngroups Number of groups to be spawned in total. @@ -143,7 +143,7 @@ -- @field #number onboardnum0 (Optional) Starting value of the automatically appended numbering of aircraft within a flight. Default is 1. -- @field #boolean checkonrunway Aircraft are checked if they were accidentally spawned on the runway. Default is true. -- @field #number onrunwayradius Distance (in meters) from a runway spawn point until a unit is considered to have accidentally been spawned on a runway. Default is 75 m. --- @field #number onrunwaymaxretry Number of respawn retries (on ground) at other airports if a group gets accidentally spawned on the runway. Default is 3. +-- @field #number onrunwaymaxretry Number of respawn retries (on ground) at other airports if a group gets accidentally spawned on the runway. Default is 3. -- @field #boolean checkontop Aircraft are checked if they were accidentally spawned on top of another unit. Default is true. -- @field #number ontopradius Radius in meters until which a unit is considered to be on top of another. Default is 2 m. -- @field Wrapper.Airbase#AIRBASE.TerminalType termtype Type of terminal to be used when spawning at an airbase. @@ -157,25 +157,25 @@ --- Implements an easy to use way to randomly fill your map with AI aircraft. -- -- ## Airport Selection --- +-- -- ![Process](..\Presentations\RAT\RAT_Airport_Selection.png) --- +-- -- ### Default settings: --- +-- -- * By default, aircraft are spawned at airports of their own coalition (blue or red) or neutral airports. -- * Destination airports are by default also of neutral or of the same coalition as the template group of the spawned aircraft. -- * Possible destinations are restricted by their distance to the departure airport. The maximal distance depends on the max range of spawned aircraft type and its initial fuel amount. --- +-- -- ### The default behavior can be changed: --- +-- -- * A specific departure and/or destination airport can be chosen. -- * Valid coalitions can be set, e.g. only red, blue or neutral, all three "colours". -- * It is possible to start in air within a zone defined in the mission editor or within a zone above an airport of the map. --- +-- -- ## Flight Plan --- +-- -- ![Process](..\Presentations\RAT\RAT_Flight_Plan.png) --- +-- -- * A general flight plan has five main airborne segments: Climb, cruise, descent, holding and final approach. -- * Events monitored during the flight are: birth, engine-start, take-off, landing and engine-shutdown. -- * The default flight level (FL) is set to ~FL200, i.e. 20000 feet ASL but randomized for each aircraft. @@ -187,56 +187,56 @@ -- * The altitude of theholding point is ~1200 m AGL. Holding patterns might or might not happen with variable duration. -- * If an aircraft is spawned in air, the procedure omitts taxi and take-off and starts with the climb/cruising part. -- * All values are randomized for each spawned aircraft. --- +-- -- ## Mission Editor Setup --- +-- -- ![Process](..\Presentations\RAT\RAT_Mission_Setup.png) --- +-- -- Basic mission setup is very simple and essentially a three step process: --- +-- -- * Place your aircraft **anywhere** on the map. It really does not matter where you put it. -- * Give the group a good name. In the example above the group is named "RAT_YAK". --- * Activate the "LATE ACTIVATION" tick box. Note that this aircraft will not be spawned itself but serves a template for each RAT aircraft spawned when the mission starts. --- +-- * Activate the "LATE ACTIVATION" tick box. Note that this aircraft will not be spawned itself but serves a template for each RAT aircraft spawned when the mission starts. +-- -- Voilà, your already done! --- +-- -- Optionally, you can set a specific livery for the aircraft or give it some weapons. -- However, the aircraft will by default not engage any enemies. Think of them as beeing on a peaceful or ferry mission. --- +-- -- ## Basic Lua Script --- +-- -- ![Process](..\Presentations\RAT\RAT_Basic_Lua_Script.png) --- +-- -- The basic Lua script for one template group consits of two simple lines as shown in the picture above. --- +-- -- * **Line 2** creates a new RAT object "yak". The only required parameter for the constructor @{#RAT.New}() is the name of the group as defined in the mission editor. In this example it is "RAT_YAK". -- * **Line 5** trigger the command to spawn the aircraft. The (optional) parameter for the @{#RAT.Spawn}() function is the number of aircraft to be spawned of this object. -- By default each of these aircraft gets a random departure airport anywhere on the map and a random destination airport, which lies within range of the of the selected aircraft type. --- +-- -- In this simple example aircraft are respawned with a completely new flightplan when they have reached their destination airport. -- The "old" aircraft is despawned (destroyed) after it has shut-down its engines and a new aircraft of the same type is spawned at a random departure airport anywhere on the map. -- Hence, the default flight plan for a RAT aircraft will be: Fly from airport A to B, get respawned at C and fly to D, get respawned at E and fly to F, ... -- This ensures that you always have a constant number of AI aircraft on your map. --- +-- -- ## Parking Problems --- --- One big issue in DCS is that not all aircraft can be spawned on every airport or airbase. In particular, bigger aircraft might not have a valid parking spot at smaller airports and +-- +-- One big issue in DCS is that not all aircraft can be spawned on every airport or airbase. In particular, bigger aircraft might not have a valid parking spot at smaller airports and -- airstripes. This can lead to multiple problems in DCS. --- +-- -- * Landing: When an aircraft tries to land at an airport where it does not have a valid parking spot, it is immidiately despawned the moment its wheels touch the runway, i.e. -- when a landing event is triggered. This leads to the loss of the RAT aircraft. On possible way to circumvent the this problem is to let another RAT aircraft spawn at landing -- and not when it shuts down its engines. See the @{RAT.RespawnAfterLanding}() function. -- * Spawning: When a big aircraft is dynamically spawned on a small airbase a few things can go wrong. For example, it could be spawned at a parking spot with a shelter. -- Or it could be damaged by a scenery object when it is taxiing out to the runway, or it could overlap with other aircraft on parking spots near by. --- +-- -- You can check yourself if an aircraft has a valid parking spot at an airbase by dragging its group on the airport in the mission editor and set it to start from ramp. -- If it stays at the airport, it has a valid parking spot, if it jumps to another airport, it does not have a valid parking spot on that airbase. --- +-- -- ### Setting the Terminal Type -- Each parking spot has a specific type depending on its size or if a helicopter spot or a shelter etc. The classification is not perfect but it is the best we have. -- If you encounter problems described above, you can request a specific terminal type for the RAT aircraft. This can be done by the @{#RAT.SetTerminalType}(*terminaltype*) -- function. The parameter *terminaltype* can be set as follows --- +-- -- * AIRBASE.TerminalType.HelicopterOnly: Special spots for Helicopers. -- * AIRBASE.TerminalType.Shelter: Hardened Air Shelter. Currently only on Caucaus map. -- * AIRBASE.TerminalType.OpenMed: Open/Shelter air airplane only. @@ -244,96 +244,96 @@ -- * AIRBASE.TerminalType.OpenMedOrBig: Combines OpenMed and OpenBig spots. -- * AIRBASE.TerminalType.HelicopterUsable: Combines HelicopterOnly, OpenMed and OpenBig. -- * AIRBASE.TerminalType.FighterAircraft: Combines Shelter, OpenMed and OpenBig spots. So effectively all spots usable by fixed wing aircraft. --- +-- -- So for example -- c17=RAT:New("C-17") -- c17:SetTerminalType(AIRBASE.TerminalType.OpenBig) -- c17:Spawn(5) --- +-- -- This would randomly spawn five C-17s but only on airports which have big open air parking spots. Note that also only destination airports are allowed -- which do have this type of parking spot. This should ensure that the aircraft is able to land at the destination without beeing despawned immidiately. --- +-- -- Also, the aircraft are spawned only on the requested parking spot types and not on any other type. If no parking spot of this type is availabe at the -- moment of spawning, the group is automatically spawned in air above the selected airport. --- +-- -- ## Examples --- +-- -- Here are a few examples, how you can modify the default settings of RAT class objects. --- +-- -- ### Specify Departure and Destinations --- +-- -- ![Process](..\Presentations\RAT\RAT_Examples_Specify_Departure_and_Destination.png) --- +-- -- In the picture above you find a few possibilities how to modify the default behaviour to spawn at random airports and fly to random destinations. --- +-- -- In particular, you can specify fixed departure and/or destination airports. This is done via the @{#RAT.SetDeparture}() or @{#RAT.SetDestination}() functions, respectively. --- +-- -- * If you only fix a specific departure airport via @{#RAT.SetDeparture}() all aircraft will be spawned at that airport and get random destination airports. -- * If you only fix the destination airport via @{#RAT.SetDestination}(), aircraft a spawned at random departure airports but will all fly to the destination airport. -- * If you fix departure and destination airports, aircraft will only travel from between those airports. --- When the aircraft reaches its destination, it will be respawned at its departure and fly again to its destination. --- +-- When the aircraft reaches its destination, it will be respawned at its departure and fly again to its destination. +-- -- There is also an option that allows aircraft to "continue their journey" from their destination. This is achieved by the @{#RAT.ContinueJourney}() function. -- In that case, when the aircraft arrives at its first destination it will be respawned at that very airport and get a new random destination. -- So the flight plan in this case would be: Fly from airport A to B, then from B to C, then from C to D, ... --- +-- -- It is also possible to make aircraft "commute" between two airports, i.e. flying from airport A to B and then back from B to A, etc. -- This can be done by the @{#RAT.Commute}() function. Note that if no departure or destination airports are specified, the first departure and destination are chosen randomly. -- Then the aircraft will fly back and forth between those two airports indefinetly. --- --- +-- +-- -- ### Spawn in Air --- +-- -- ![Process](..\Presentations\RAT\RAT_Examples_Spawn_in_Air.png) --- +-- -- Aircraft can also be spawned in air rather than at airports on the ground. This is done by setting @{#RAT.SetTakeoff}() to "air". --- +-- -- By default, aircraft are spawned randomly above airports of the map. --- +-- -- The @{#RAT.SetDeparture}() option can be used to specify zones, which have been defined in the mission editor as departure zones. -- Aircraft will then be spawned at a random point within the zone or zones. --- +-- -- Note that @{#RAT.SetDeparture}() also accepts airport names. For an air takeoff these are treated like zones with a radius of XX kilometers. -- Again, aircraft are spawned at random points within these zones around the airport. --- +-- -- ### Misc Options --- +-- -- ![Process](..\Presentations\RAT\RAT_Examples_Misc.png) --- +-- -- The default "takeoff" type of RAT aircraft is that they are spawned with hot or cold engines. -- The choice is random, so 50% of aircraft will be spawned with hot engines while the other 50% will be spawned with cold engines. -- This setting can be changed using the @{#RAT.SetTakeoff}() function. The possible parameters for starting on ground are: --- +-- -- * @{#RAT.SetTakeoff}("cold"), which means that all aircraft are spawned with their engines off, -- * @{#RAT.SetTakeoff}("hot"), which means that all aircraft are spawned with their engines on, -- * @{#RAT.SetTakeoff}("runway"), which means that all aircraft are spawned already at the runway ready to takeoff. -- Note that in this case the default spawn intervall is set to 180 seconds in order to avoid aircraft jamms on the runway. Generally, this takeoff at runways should be used with care and problems are to be expected. --- --- +-- +-- -- The options @{#RAT.SetMinDistance}() and @{#RAT.SetMaxDistance}() can be used to restrict the range from departure to destination. For example --- +-- -- * @{#RAT.SetMinDistance}(100) will cause only random destination airports to be selected which are **at least** 100 km away from the departure airport. -- * @{#RAT.SetMaxDistance}(150) will allow only destination airports which are **less than** 150 km away from the departure airport. --- +-- -- ![Process](..\Presentations\RAT\RAT_Gaussian.png) --- +-- -- By default planes get a cruise altitude of ~20,000 ft ASL. The actual altitude is sampled from a Gaussian distribution. The picture shows this distribution -- if one would spawn 1000 planes. As can be seen most planes get a cruising alt of around FL200. Other values are possible but less likely the further away -- one gets from the expectation value. --- +-- -- The expectation value, i.e. the altitude most aircraft get, can be set with the function @{#RAT.SetFLcruise}(). -- It is possible to restrict the minimum cruise altitude by @{#RAT.SetFLmin}() and the maximum cruise altitude by @{#RAT.SetFLmax}() --- +-- -- The cruise altitude can also be given in meters ASL by the functions @{#RAT.SetCruiseAltitude}(), @{#RAT.SetMinCruiseAltitude}() and @{#RAT.SetMaxCruiseAltitude}(). --- +-- -- For example: --- +-- -- * @{#RAT.SetFLcruise}(300) will cause most planes fly around FL300. -- * @{#RAT.SetFLmin}(100) restricts the cruising alt such that no plane will fly below FL100. Note that this automatically changes the minimum distance from departure to destination. --- That means that only destinations are possible for which the aircraft has had enought time to reach that flight level and descent again. +-- That means that only destinations are possible for which the aircraft has had enought time to reach that flight level and descent again. -- * @{#RAT.SetFLmax}(200) will restrict the cruise alt to maximum FL200, i.e. no aircraft will travel above this height. --- --- +-- +-- -- @field #RAT RAT={ ClassName = "RAT", -- Name of class: RAT = Random Air Traffic. @@ -391,7 +391,7 @@ RAT={ homebase=nil, -- Home base for commute. continuejourney=false, -- Aircraft will continue their journey, i.e. get respawned at their destination with a new random destination. alive=0, -- Number of groups which are alive. - ngroups=nil, -- Number of groups to be spawned in total. + ngroups=nil, -- Number of groups to be spawned in total. f10menu=false, -- Add an F10 menu for RAT. Menu={}, -- F10 menu items for this RAT object. SubMenuName=nil, -- Submenu name for RAT object. @@ -414,11 +414,11 @@ RAT={ uncontrolled=false, -- Spawn uncontrolled aircraft. invisible=false, -- Spawn aircraft as invisible. immortal=false, -- Spawn aircraft as indestructible. - activate_uncontrolled=false, -- Activate uncontrolled aircraft (randomly). + activate_uncontrolled=false, -- Activate uncontrolled aircraft (randomly). activate_delay=5, -- Delay in seconds before first uncontrolled group is activated. activate_delta=5, -- Time interval in seconds between activation of uncontrolled groups. activate_frand=0, -- Randomization factor of time interval (activate_delta) between activating uncontrolled groups. - activate_max=1, -- Max number of uncontrolle aircraft, which will be activated at a time. + activate_max=1, -- Max number of uncontrolle aircraft, which will be activated at a time. onboardnum=nil, -- Tail number. onboardnum0=1, -- (Optional) Starting value of the automatically appended numbering of aircraft within a flight. Default is one. checkonrunway=true, -- Check whether aircraft have been spawned on the runway. @@ -436,7 +436,7 @@ RAT={ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Categories of the RAT class. +--- Categories of the RAT class. -- @list cat -- @field #string plane Plane. -- @field #string heli Heli. @@ -452,7 +452,7 @@ RAT.wp={ air=1, runway=2, hot=3, - cold=4, + cold=4, climb=5, cruise=6, descent=7, @@ -597,7 +597,7 @@ RAT.version={ --- Create a new RAT object. -- @param #RAT self -- @param #string groupname Name of the group as defined in the mission editor. This group is serving as a template for all spawned units. --- @param #string alias (Optional) Alias of the group. This is and optional parameter but must(!) be used if the same template group is used for more than one RAT object. +-- @param #string alias (Optional) Alias of the group. This is and optional parameter but must(!) be used if the same template group is used for more than one RAT object. -- @return #RAT Object of RAT class or nil if the group does not exist in the mission editor. -- @usage yak1:RAT("RAT_YAK") will create a RAT object called "yak1". The template group in the mission editor must have the name "RAT_YAK". -- @usage yak2:RAT("RAT_YAK", "Yak2") will create a RAT object "yak2". The template group in the mission editor must have the name "RAT_YAK" but the group will be called "Yak2" in e.g. the F10 menu. @@ -615,22 +615,22 @@ function RAT:New(groupname, alias) -- Welcome message. self:F(RAT.id..string.format("Creating new RAT object from template: %s.", groupname)) - + -- Set alias. alias=alias or groupname - + -- Alias of groupname. self.alias=alias - + -- Get template group defined in the mission editor. local DCSgroup=Group.getByName(groupname) - + -- Check the group actually exists. if DCSgroup==nil then self:E(RAT.id..string.format("ERROR: Group with name %s does not exist in the mission editor!", groupname)) return nil end - + -- Store template group. self.templategroup=GROUP:FindByName(groupname) @@ -639,13 +639,13 @@ function RAT:New(groupname, alias) -- Set own coalition. self.coalition=DCSgroup:getCoalition() - + -- Initialize aircraft parameters based on ME group template. self:_InitAircraft(DCSgroup) - + -- Get all airports of current map (Caucasus, NTTR, Normandy, ...). self:_GetAirportsOfMap() - + return self end @@ -670,46 +670,46 @@ function RAT:Spawn(naircraft) -- Number of aircraft to spawn. Default is one. self.ngroups=naircraft or 1 - + -- Init RAT ATC if not already done. if self.ATCswitch and not RAT.ATC.init then self:_ATCInit(self.airports_map) end - + -- Create F10 main menu if it does not exists yet. if self.f10menu and not RAT.MenuF10 then RAT.MenuF10 = MENU_MISSION:New("RAT") end - + -- Set the coalition table based on choice of self.coalition and self.friendly. self:_SetCoalitionTable() - + -- Get all airports of this map beloning to friendly coalition(s). self:_GetAirportsOfCoalition() - + -- Set submenuname if it has not been set by user. if not self.SubMenuName then self.SubMenuName=self.alias end - -- Get all departure airports inside a Moose zone. + -- Get all departure airports inside a Moose zone. if self.departure_Azone~=nil then self.departure_ports=self:_GetAirportsInZone(self.departure_Azone) end - - -- Get all destination airports inside a Moose zone. + + -- Get all destination airports inside a Moose zone. if self.destination_Azone~=nil then self.destination_ports=self:_GetAirportsInZone(self.destination_Azone) end - + -- Add all friendly airports to possible departures/destinations if self.addfriendlydepartures then self:_AddFriendlyAirports(self.departure_ports) end if self.addfriendlydestinations then self:_AddFriendlyAirports(self.destination_ports) - end - + end + -- Setting and possibly correction min/max/cruise flight levels. if self.FLcruise==nil then -- Default flight level (ASL). @@ -722,11 +722,11 @@ function RAT:Spawn(naircraft) end end - -- Enable helos to go to destinations 100 meters away. + -- Enable helos to go to destinations 100 meters away. if self.category==RAT.cat.heli then self.mindist=50 - end - + end + -- Run consistency checks. self:_CheckConsistency() @@ -759,7 +759,7 @@ function RAT:Spawn(naircraft) text=text..string.format("Return Zone: %s\n", tostring(self.returnzone)) text=text..string.format("Spawn delay: %4.1f\n", self.spawndelay) text=text..string.format("Spawn interval: %4.1f\n", self.spawninterval) - text=text..string.format("Respawn delay: %s\n", tostring(self.respawn_delay)) + text=text..string.format("Respawn delay: %s\n", tostring(self.respawn_delay)) text=text..string.format("Respawn off: %s\n", tostring(self.norespawn)) text=text..string.format("Respawn after landing: %s\n", tostring(self.respawn_at_landing)) text=text..string.format("Respawn after take-off: %s\n", tostring(self.respawn_after_takeoff)) @@ -805,7 +805,7 @@ function RAT:Spawn(naircraft) end text=text..string.format("******************************************************\n") self:T(RAT.id..text) - + -- Create submenus. if self.f10menu then self.Menu[self.SubMenuName]=MENU_MISSION:New(self.SubMenuName, RAT.MenuF10) @@ -814,7 +814,7 @@ function RAT:Spawn(naircraft) MENU_MISSION_COMMAND:New("Delete markers", self.Menu[self.SubMenuName], self._DeleteMarkers, self) MENU_MISSION_COMMAND:New("Status report", self.Menu[self.SubMenuName], self.Status, self, true) end - + -- Schedule spawning of aircraft. local Tstart=self.spawndelay local dt=self.spawninterval @@ -823,10 +823,10 @@ function RAT:Spawn(naircraft) dt=math.max(dt, 180) end local Tstop=Tstart+dt*(self.ngroups-1) - + -- Status check and report scheduler. SCHEDULER:New(nil, self.Status, {self}, Tstart+1, self.statusinterval) - + -- Handle events. self:HandleEvent(EVENTS.Birth, self._OnBirth) self:HandleEvent(EVENTS.EngineStartup, self._OnEngineStartup) @@ -841,15 +841,15 @@ function RAT:Spawn(naircraft) if self.ngroups==0 then return nil end - + -- Start scheduled spawning. SCHEDULER:New(nil, self._SpawnWithRoute, {self}, Tstart, dt, 0.0, Tstop) - + -- Start scheduled activation of uncontrolled groups. if self.uncontrolled and self.activate_uncontrolled then SCHEDULER:New(nil, self._ActivateUncontrolled, {self}, self.activate_delay, self.activate_delta, self.activate_frand) end - + return true end @@ -864,7 +864,7 @@ function RAT:_CheckConsistency() -- User has used SetDeparture() if not self.random_departure then - + -- Count departure airports and zones. for _,name in pairs(self.departure_ports) do if self:_AirportExists(name) then @@ -873,7 +873,7 @@ function RAT:_CheckConsistency() self.Ndeparture_Zones=self.Ndeparture_Zones+1 end end - + -- What can go wrong? -- Only zones but not takeoff air == > Enable takeoff air. if self.Ndeparture_Zones>0 and self.takeoff~=RAT.wp.air then @@ -891,7 +891,7 @@ function RAT:_CheckConsistency() -- User has used SetDestination() if not self.random_destination then - + -- Count destination airports and zones. for _,name in pairs(self.destination_ports) do if self:_AirportExists(name) then @@ -899,10 +899,10 @@ function RAT:_CheckConsistency() elseif self:_ZoneExists(name) then self.Ndestination_Zones=self.Ndestination_Zones+1 end - end - + end + -- One zone specified as destination ==> Enable destination zone. - -- This does not apply to return zone because the destination is the zone and not the final destination which can be an airport. + -- This does not apply to return zone because the destination is the zone and not the final destination which can be an airport. if self.Ndestination_Zones>0 and self.landing~=RAT.wp.air and not self.returnzone then self.landing=RAT.wp.air self.destinationzone=true @@ -915,19 +915,19 @@ function RAT:_CheckConsistency() self:E(RAT.id.."ERROR: "..text) MESSAGE:New(text, 30):ToAll() end - end - + end + -- Destination zone and return zone should not be used together. if self.destinationzone and self.returnzone then self:E(RAT.id.."ERROR: Destination zone _and_ return to zone not possible! Disabling return to zone.") self.returnzone=false end - -- If returning to a zone, we set the landing type to "air" if takeoff is in air. + -- If returning to a zone, we set the landing type to "air" if takeoff is in air. -- Because if we start in air we want to end in air. But default landing is ground. if self.returnzone and self.takeoff==RAT.wp.air then self.landing=RAT.wp.air end - + -- Ensure that neither FLmin nor FLmax are above the aircrafts service ceiling. if self.FLminuser then self.FLminuser=math.min(self.FLminuser, self.aircraft.ceiling) @@ -938,17 +938,17 @@ function RAT:_CheckConsistency() if self.FLcruise then self.FLcruise=math.min(self.FLcruise, self.aircraft.ceiling) end - + -- FL min > FL max case ==> spaw values if self.FLminuser and self.FLmaxuser then if self.FLminuser > self.FLmaxuser then local min=self.FLminuser local max=self.FLmaxuser self.FLminuser=max - self.FLmaxuser=min + self.FLmaxuser=min end end - + -- Cruise alt < FL min if self.FLminuser and self.FLcruise FL max if self.FLmaxuser and self.FLcruise>self.FLmaxuser then self.FLcruise=self.FLmaxuser end - + -- Uncontrolled aircraft must start with engines off. if self.uncontrolled then -- SOLVED: Strangly, it does not work with RAT.wp.cold only with RAT.wp.hot! @@ -995,11 +995,11 @@ end --- Set coalition of RAT group. You can make red templates blue and vice versa. -- Note that a country is also set automatically if it has not done before via RAT:SetCountry. --- +-- -- * For blue, the country is set to USA. -- * For red, the country is set to RUSSIA. -- * For neutral, the country is set to SWITZERLAND. --- +-- -- This is important, since it is ultimately the COUNTRY that determines the coalition of the aircraft. -- You can set the country explicitly via the RAT:SetCountry() function if necessary. -- @param #RAT self @@ -1021,14 +1021,14 @@ function RAT:SetCoalitionAircraft(color) self.coalition=coalition.side.NEUTRAL if not self.country then self.country=country.id.SWITZERLAND - end + end end return self end --- Set country of RAT group. -- See [DCS_enum_country](https://wiki.hoggitworld.com/view/DCS_enum_country). --- +-- -- This overrules the coalition settings. So if you want your group to be of a specific coalition, you have to set a country that is part of that coalition. -- @param #RAT self -- @param DCS#country.id id DCS country enumerator ID. For example country.id.USA or country.id.RUSSIA. @@ -1045,7 +1045,7 @@ end -- @param #RAT self -- @param Wrapper.Airbase#AIRBASE.TerminalType termtype Type of terminal. Use enumerator AIRBASE.TerminalType.XXX. -- @return #RAT RAT self object. --- +-- -- @usage -- c17=RAT:New("C-17 BIG Plane") -- c17:SetTerminalType(AIRBASE.TerminalType.OpenBig) -- Only very big parking spots are used. @@ -1111,35 +1111,35 @@ function RAT:SetDespawnAirOFF() end --- Set takeoff type. Starting cold at airport, starting hot at airport, starting at runway, starting in the air. --- Default is "takeoff-coldorhot". So there is a 50% chance that the aircraft starts with cold engines and 50% that it starts with hot engines. +-- Default is "takeoff-coldorhot". So there is a 50% chance that the aircraft starts with cold engines and 50% that it starts with hot engines. -- @param #RAT self -- @param #string type Type can be "takeoff-cold" or "cold", "takeoff-hot" or "hot", "takeoff-runway" or "runway", "air". -- @return #RAT RAT self object. -- @usage RAT:Takeoff("hot") will spawn RAT objects at airports with engines started. -- @usage RAT:Takeoff("cold") will spawn RAT objects at airports with engines off. --- @usage RAT:Takeoff("air") will spawn RAT objects in air over random airports or within pre-defined zones. +-- @usage RAT:Takeoff("air") will spawn RAT objects in air over random airports or within pre-defined zones. function RAT:SetTakeoff(type) self:F2(type) - + local _Type if type:lower()=="takeoff-cold" or type:lower()=="cold" then _Type=RAT.wp.cold elseif type:lower()=="takeoff-hot" or type:lower()=="hot" then _Type=RAT.wp.hot - elseif type:lower()=="takeoff-runway" or type:lower()=="runway" then + elseif type:lower()=="takeoff-runway" or type:lower()=="runway" then _Type=RAT.wp.runway elseif type:lower()=="air" then _Type=RAT.wp.air else _Type=RAT.wp.coldorhot end - + self.takeoff=_Type - + return self end ---- Set takeoff type cold. Aircraft will spawn at a parking spot with engines off. +--- Set takeoff type cold. Aircraft will spawn at a parking spot with engines off. -- @param #RAT self -- @return #RAT RAT self object. function RAT:SetTakeoffCold() @@ -1147,7 +1147,7 @@ function RAT:SetTakeoffCold() return self end ---- Set takeoff type to hot. Aircraft will spawn at a parking spot with engines on. +--- Set takeoff type to hot. Aircraft will spawn at a parking spot with engines on. -- @param #RAT self -- @return #RAT RAT self object. function RAT:SetTakeoffHot() @@ -1155,7 +1155,7 @@ function RAT:SetTakeoffHot() return self end ---- Set takeoff type to runway. Aircraft will spawn directly on the runway. +--- Set takeoff type to runway. Aircraft will spawn directly on the runway. -- @param #RAT self -- @return #RAT RAT self object. function RAT:SetTakeoffRunway() @@ -1163,7 +1163,7 @@ function RAT:SetTakeoffRunway() return self end ---- Set takeoff type to cold or hot. Aircraft will spawn at a parking spot with 50:50 change of engines on or off. +--- Set takeoff type to cold or hot. Aircraft will spawn at a parking spot with 50:50 change of engines on or off. -- @param #RAT self -- @return #RAT RAT self object. function RAT:SetTakeoffColdOrHot() @@ -1171,7 +1171,7 @@ function RAT:SetTakeoffColdOrHot() return self end ---- Set takeoff type to air. Aircraft will spawn in the air. +--- Set takeoff type to air. Aircraft will spawn in the air. -- @param #RAT self -- @return #RAT RAT self object. function RAT:SetTakeoffAir() @@ -1191,21 +1191,21 @@ function RAT:SetDeparture(departurenames) -- Random departure is deactivated now that user specified departure ports. self.random_departure=false - + -- Convert input to table. local names if type(departurenames)=="table" then - names=departurenames + names=departurenames elseif type(departurenames)=="string" then names={departurenames} else -- error message self:E(RAT.id.."ERROR: Input parameter must be a string or a table in SetDeparture()!") end - + -- Put names into arrays. for _,name in pairs(names) do - + if self:_AirportExists(name) then -- If an airport with this name exists, we put it in the ports array. table.insert(self.departure_ports, name) @@ -1215,9 +1215,9 @@ function RAT:SetDeparture(departurenames) else self:E(RAT.id.."ERROR: No departure airport or zone found with name "..name) end - + end - + return self end @@ -1231,7 +1231,7 @@ function RAT:SetDestination(destinationnames) -- Random departure is deactivated now that user specified departure ports. self.random_destination=false - + -- Convert input to table local names if type(destinationnames)=="table" then @@ -1242,10 +1242,10 @@ function RAT:SetDestination(destinationnames) -- Error message. self:E(RAT.id.."ERROR: Input parameter must be a string or a table in SetDestination()!") end - + -- Put names into arrays. for _,name in pairs(names) do - + if self:_AirportExists(name) then -- If an airport with this name exists, we put it in the ports array. table.insert(self.destination_ports, name) @@ -1255,7 +1255,7 @@ function RAT:SetDestination(destinationnames) else self:E(RAT.id.."ERROR: No destination airport or zone found with name "..name) end - + end return self @@ -1266,13 +1266,13 @@ end -- @return #RAT RAT self object. function RAT:DestinationZone() self:F2() - + -- Destination is a zone. Needs special care. self.destinationzone=true - + -- Landing type is "air" because we don't actually land at the airport. self.landing=RAT.wp.air - + return self end @@ -1296,10 +1296,10 @@ function RAT:SetDestinationsFromZone(zone) -- Random departure is deactivated now that user specified departure ports. self.random_destination=false - + -- Set zone. self.destination_Azone=zone - + return self end @@ -1309,13 +1309,13 @@ end -- @return #RAT RAT self object. function RAT:SetDeparturesFromZone(zone) self:F2(zone) - + -- Random departure is deactivated now that user specified departure ports. self.random_departure=false -- Set zone. self.departure_Azone=zone - + return self end @@ -1351,7 +1351,7 @@ function RAT:ExcludedAirports(ports) return self end ---- Set skill of AI aircraft. Default is "High". +--- Set skill of AI aircraft. Default is "High". -- @param #RAT self -- @param #string skill Skill, options are "Average", "Good", "High", "Excellent" and "Random". Parameter is case insensitive. -- @return #RAT RAT self object. @@ -1422,7 +1422,7 @@ function RAT:Commute(starshape) return self end ---- Set the delay before first group is spawned. +--- Set the delay before first group is spawned. -- @param #RAT self -- @param #number delay Delay in seconds. Default is 5 seconds. Minimum delay is 0.5 seconds. -- @return #RAT RAT self object. @@ -1537,7 +1537,7 @@ end --- Check if aircraft have accidentally been spawned on the runway. If so they will be removed immediatly. -- @param #RAT self -- @param #boolean switch If true, check is performed. If false, this check is omitted. --- @param #number radius Distance in meters until a unit is considered to have spawned accidentally on the runway. Default is 75 m. +-- @param #number radius Distance in meters until a unit is considered to have spawned accidentally on the runway. Default is 75 m. -- @return #RAT RAT self object. function RAT:CheckOnRunway(switch, distance) self:F2(switch) @@ -1617,7 +1617,7 @@ function RAT:RadioModulation(modulation) return self end ---- Radio menu On. Default is off. +--- Radio menu On. Default is off. -- @param #RAT self -- @return #RAT RAT self object. function RAT:RadioMenuON() @@ -1626,7 +1626,7 @@ function RAT:RadioMenuON() return self end ---- Radio menu Off. This is the default setting. +--- Radio menu Off. This is the default setting. -- @param #RAT self -- @return #RAT RAT self object. function RAT:RadioMenuOFF() @@ -1635,7 +1635,7 @@ function RAT:RadioMenuOFF() return self end ---- Aircraft are invisible. +--- Aircraft are invisible. -- @param #RAT self -- @return #RAT RAT self object. function RAT:Invisible() @@ -1644,7 +1644,7 @@ function RAT:Invisible() return self end ---- Turn EPLRS datalink on/off. +--- Turn EPLRS datalink on/off. -- @param #RAT self -- @param #boolean switch If true (or nil), turn EPLRS on. -- @return #RAT RAT self object. @@ -1657,7 +1657,7 @@ function RAT:SetEPLRS(switch) return self end ---- Aircraft are immortal. +--- Aircraft are immortal. -- @param #RAT self -- @return #RAT RAT self object. function RAT:Immortal() @@ -1675,7 +1675,7 @@ function RAT:Uncontrolled() return self end ---- Activate uncontrolled aircraft. +--- Activate uncontrolled aircraft. -- @param #RAT self -- @param #number maxactivated Maximal numnber of activated aircraft. Absolute maximum will be the number of spawned groups. Default is 1. -- @param #number delay Time delay in seconds before (first) aircraft is activated. Default is 1 second. @@ -1690,21 +1690,21 @@ function RAT:ActivateUncontrolled(maxactivated, delay, delta, frand) self.activate_delay=delay or 1 self.activate_delta=delta or 1 self.activate_frand=frand or 0 - + -- Ensure min delay is one second. self.activate_delay=math.max(self.activate_delay,1) - + -- Ensure min delta is one second. self.activate_delta=math.max(self.activate_delta,0) - + -- Ensure frand is in [0,...,1] self.activate_frand=math.max(self.activate_frand,0) self.activate_frand=math.min(self.activate_frand,1) - + return self end ---- Set the time after which inactive groups will be destroyed. +--- Set the time after which inactive groups will be destroyed. -- @param #RAT self -- @param #number time Time in seconds. Default is 600 seconds = 10 minutes. Minimum is 60 seconds. -- @return #RAT RAT self object. @@ -1759,7 +1759,7 @@ end -- @return #RAT RAT self object. function RAT:SetROE(roe) self:F2(roe) - if roe=="return" then + if roe=="return" then self.roe=RAT.ROE.returnfire elseif roe=="free" then self.roe=RAT.ROE.weaponfree @@ -1811,7 +1811,7 @@ end --- Turn messages from ATC on or off. Default is on. This setting effects all RAT objects and groups! -- @param #RAT self -- @param #boolean switch Enable (true) or disable (false) messages from ATC. --- @return #RAT RAT self object. +-- @return #RAT RAT self object. function RAT:ATC_Messages(switch) self:F2(switch) if switch==nil then @@ -1821,7 +1821,7 @@ function RAT:ATC_Messages(switch) return self end ---- Max number of planes that get landing clearance of the RAT ATC. This setting effects all RAT objects and groups! +--- Max number of planes that get landing clearance of the RAT ATC. This setting effects all RAT objects and groups! -- @param #RAT self -- @param #number n Number of aircraft that are allowed to land simultaniously. Default is 2. -- @return #RAT RAT self object. @@ -1831,7 +1831,7 @@ function RAT:ATC_Clearance(n) return self end ---- Delay between granting landing clearance for simultanious landings. This setting effects all RAT objects and groups! +--- Delay between granting landing clearance for simultanious landings. This setting effects all RAT objects and groups! -- @param #RAT self -- @param #number time Delay time when the next aircraft will get landing clearance event if the previous one did not land yet. Default is 240 sec. -- @return #RAT RAT self object. @@ -1987,7 +1987,7 @@ end --- Set onboard number prefix. Same as setting "TAIL #" in the mission editor. Note that if you dont use this function, the values defined in the template group of the ME are taken. -- @param #RAT self --- @param #string tailnumprefix String of the tail number prefix. If flight consists of more than one aircraft, two digits are appended automatically, i.e. 001, 002, ... +-- @param #string tailnumprefix String of the tail number prefix. If flight consists of more than one aircraft, two digits are appended automatically, i.e. 001, 002, ... -- @param #number zero (Optional) Starting value of the automatically appended numbering of aircraft within a flight. Default is 0. -- @return #RAT RAT self object. function RAT:SetOnboardNum(tailnumprefix, zero) @@ -2013,7 +2013,7 @@ function RAT:_InitAircraft(DCSgroup) local DCSdesc=DCSunit:getDesc() local DCScategory=DCSgroup:getCategory() local DCStype=DCSunit:getTypeName() - + -- set category if DCScategory==Group.Category.AIRPLANE then self.category=RAT.cat.plane @@ -2023,37 +2023,37 @@ function RAT:_InitAircraft(DCSgroup) self.category="other" self:E(RAT.id.."ERROR: Group of RAT is neither airplane nor helicopter!") end - + -- Get type of aircraft. self.aircraft.type=DCStype - + -- inital fuel in % self.aircraft.fuel=DCSunit:getFuel() -- operational range in NM converted to m self.aircraft.Rmax = DCSdesc.range*RAT.unit.nm2m - + -- effective range taking fuel into accound and a 5% reserve self.aircraft.Reff = self.aircraft.Rmax*self.aircraft.fuel*0.95 - + -- max airspeed from group self.aircraft.Vmax = DCSdesc.speedMax - + -- max climb speed in m/s self.aircraft.Vymax=DCSdesc.VyMax - + -- service ceiling in meters self.aircraft.ceiling=DCSdesc.Hmax - + -- Store all descriptors. --self.aircraft.descriptors=DCSdesc - + -- aircraft dimensions self.aircraft.length=DCSdesc.box.max.x self.aircraft.height=DCSdesc.box.max.y self.aircraft.width=DCSdesc.box.max.z self.aircraft.box=math.max(self.aircraft.length,self.aircraft.width) - + -- info message local text=string.format("\n******************************************************\n") text=text..string.format("Aircraft parameters:\n") @@ -2099,7 +2099,7 @@ function RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing, _live -- Set takeoff type. local takeoff=self.takeoff local landing=self.landing - + -- Overrule takeoff/landing by what comes in. if _takeoff then takeoff=_takeoff @@ -2107,27 +2107,27 @@ function RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing, _live if _landing then landing=_landing end - + -- Random choice between cold and hot. if takeoff==RAT.wp.coldorhot then local temp={RAT.wp.cold, RAT.wp.hot} takeoff=temp[math.random(2)] end - + -- Number of respawn attempts after spawning on runway. local nrespawn=0 if _nrespawn then nrespawn=_nrespawn end - + -- Set flight plan. local departure, destination, waypoints, WPholding, WPfinal = self:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) - + -- Return nil if we could not find a departure destination or waypoints if not (departure and destination and waypoints) then return nil end - + -- Set (another) livery. local livery if _livery then @@ -2141,20 +2141,20 @@ function RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing, _live else livery=nil end - + -- Modify the spawn template to follow the flight plan. local successful=self:_ModifySpawnTemplate(waypoints, livery, _lastpos, departure, takeoff, parkingdata) if not successful then return nil end - + -- Actually spawn the group. local group=self:SpawnWithIndex(self.SpawnIndex) -- Wrapper.Group#GROUP - + -- Increase counter of alive groups (also uncontrolled ones). self.alive=self.alive+1 self:T(RAT.id..string.format("Alive groups counter now = %d.",self.alive)) - + -- ATC is monitoring this flight (if it is supposed to land). if self.ATCswitch and landing==RAT.wp.landing then if self.returnzone then @@ -2163,34 +2163,34 @@ function RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing, _live self:_ATCAddFlight(group:GetName(), destination:GetName()) end end - + -- Place markers of waypoints on F10 map. if self.placemarkers then self:_PlaceMarkers(waypoints, self.SpawnIndex) end - + -- Set group to be invisible. if self.invisible then self:_CommandInvisible(group, true) end - + -- Set group to be immortal. if self.immortal then self:_CommandImmortal(group, true) end - + -- Set group to be immortal. if self.eplrs then group:CommandEPLRS(true, 1) - end - + end + -- Set ROE, default is "weapon hold". self:_SetROE(group, self.roe) - + -- Set ROT, default is "no reaction". self:_SetROT(group, self.rot) - -- Init ratcraft array. + -- Init ratcraft array. self.ratcraft[self.SpawnIndex]={} self.ratcraft[self.SpawnIndex]["group"]=group self.ratcraft[self.SpawnIndex]["destination"]=destination @@ -2218,28 +2218,28 @@ function RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing, _live self.ratcraft[self.SpawnIndex]["P0"]=group:GetCoordinate() self.ratcraft[self.SpawnIndex]["Pnow"]=group:GetCoordinate() self.ratcraft[self.SpawnIndex]["Distance"]=0 - + -- Each aircraft gets its own takeoff type. self.ratcraft[self.SpawnIndex].takeoff=takeoff self.ratcraft[self.SpawnIndex].landing=landing self.ratcraft[self.SpawnIndex].wpholding=WPholding self.ratcraft[self.SpawnIndex].wpfinal=WPfinal - + -- Aircraft is active or spawned in uncontrolled state. self.ratcraft[self.SpawnIndex].active=not self.uncontrolled - + -- Set status to spawned. This will be overwritten in birth event. self.ratcraft[self.SpawnIndex]["status"]=RAT.status.Spawned - + -- Livery self.ratcraft[self.SpawnIndex].livery=livery - + -- If this switch is set to true, the aircraft will be despawned the next time the status function is called. self.ratcraft[self.SpawnIndex].despawnme=false - + -- Number of preformed spawn attempts for this group. self.ratcraft[self.SpawnIndex].nrespawn=nrespawn - + -- Create submenu for this group. if self.f10menu then local name=self.aircraft.type.." ID "..tostring(self.SpawnIndex) @@ -2254,13 +2254,13 @@ function RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing, _live self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"]=MENU_MISSION:New("Set ROT", self.Menu[self.SubMenuName].groups[self.SpawnIndex]) MENU_MISSION_COMMAND:New("No reaction", self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"], self._SetROT, self, group, RAT.ROT.noreaction) MENU_MISSION_COMMAND:New("Passive defense", self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"], self._SetROT, self, group, RAT.ROT.passive) - MENU_MISSION_COMMAND:New("Evade on fire", self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"], self._SetROT, self, group, RAT.ROT.evade) + MENU_MISSION_COMMAND:New("Evade on fire", self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"], self._SetROT, self, group, RAT.ROT.evade) -- F10/RAT//Group X/ MENU_MISSION_COMMAND:New("Despawn group", self.Menu[self.SubMenuName].groups[self.SpawnIndex], self._Despawn, self, group) MENU_MISSION_COMMAND:New("Place markers", self.Menu[self.SubMenuName].groups[self.SpawnIndex], self._PlaceMarkers, self, waypoints, self.SpawnIndex) MENU_MISSION_COMMAND:New("Status report", self.Menu[self.SubMenuName].groups[self.SpawnIndex], self.Status, self, true, self.SpawnIndex) end - + return self.SpawnIndex end @@ -2280,10 +2280,10 @@ end -- @param Core.Point#COORDINATE lastpos Last known position of the group. -- @param #number delay Delay before respawn function RAT:_Respawn(index, lastpos, delay) - + -- Get the spawn index from group --local index=self:GetSpawnIndexFromGroup(group) - + -- Get departure and destination from previous journey. local departure=self.ratcraft[index].departure local destination=self.ratcraft[index].destination @@ -2292,7 +2292,7 @@ function RAT:_Respawn(index, lastpos, delay) local livery=self.ratcraft[index].livery local lastwp=self.ratcraft[index].waypoints[#self.ratcraft[index].waypoints] --local lastpos=group:GetCoordinate() - + local _departure=nil local _destination=nil local _takeoff=nil @@ -2300,15 +2300,15 @@ function RAT:_Respawn(index, lastpos, delay) local _livery=nil local _lastwp=nil local _lastpos=nil - + if self.continuejourney then - + -- We continue our journey from the old departure airport. _departure=destination:GetName() - + -- Use the same livery for next aircraft. _livery=livery - + -- Last known position of the aircraft, which should be the sparking spot location. -- Note: we have to check that it was supposed to land and not respawned directly after landing or after takeoff. -- TODO: Need to think if continuejourney with respawn_after_takeoff actually makes sense. @@ -2318,15 +2318,15 @@ function RAT:_Respawn(index, lastpos, delay) _lastpos=lastpos end end - + if self.destinationzone then - + -- Case: X --> Zone --> Zone --> Zone _takeoff=RAT.wp.air _landing=RAT.wp.air - + elseif self.returnzone then - + -- Case: X --> Zone --> X, X --> Zone --> X -- We flew to a zone and back. Takeoff type does not change. _takeoff=self.takeoff @@ -2337,22 +2337,22 @@ function RAT:_Respawn(index, lastpos, delay) else _landing=RAT.wp.landing end - + -- Departure stays the same. (The destination is the zone here.) _departure=departure:GetName() - + else - + -- Default case. Takeoff and landing type does not change. _takeoff=self.takeoff _landing=self.landing - + end - + elseif self.commute then - + -- We commute between departure and destination. - + if self.starshape==true then if destination:GetName()==self.homebase then -- We are at our home base ==> destination is again randomly selected. @@ -2368,10 +2368,10 @@ function RAT:_Respawn(index, lastpos, delay) _departure=destination:GetName() _destination=departure:GetName() end - + -- Use the same livery for next aircraft. _livery=livery - + -- Last known position of the aircraft, which should be the sparking spot location. -- Note: we have to check that it was supposed to land and not respawned directly after landing or after takeoff. -- TODO: Need to think if commute with respawn_after_takeoff actually makes sense. @@ -2379,22 +2379,22 @@ function RAT:_Respawn(index, lastpos, delay) -- Check that we have landed on an airport or FARP but not a ship (which would be categroy 1). if destination:GetCategory()==4 then _lastpos=lastpos - end + end end - + -- Handle takeoff type. if self.destinationzone then -- self.takeoff is either RAT.wp.air or RAT.wp.cold -- self.landing is RAT.wp.Air - + if self.takeoff==RAT.wp.air then - + -- Case: Zone <--> Zone (both have takeoff air) _takeoff=RAT.wp.air -- = self.takeoff (because we just checked) _landing=RAT.wp.air -- = self.landing (because destinationzone) - + else - + -- Case: Airport <--> Zone if takeoff==RAT.wp.air then -- Last takeoff was air so we are at the airport now, takeoff is from ground. @@ -2405,31 +2405,31 @@ function RAT:_Respawn(index, lastpos, delay) _takeoff=RAT.wp.air _landing=RAT.wp.landing end - + end - + elseif self.returnzone then - + -- We flew to a zone and back. No need to swap departure and destination. _departure=departure:GetName() _destination=destination:GetName() - + -- Takeoff and landing should also not change. _takeoff=self.takeoff _landing=self.landing - - end - + + end + end - + -- Take the last waypoint as initial waypoint for next plane. if _takeoff==RAT.wp.air and (self.continuejourney or self.commute) then _lastwp=lastwp end - + -- Debug self:T2({departure=_departure, destination=_destination, takeoff=_takeoff, landing=_landing, livery=_livery, lastwp=_lastwp}) - + -- We should give it at least 3 sec since this seems to be the time until free parking spots after despawn are available again (Sirri Island test). local respawndelay if delay then @@ -2439,7 +2439,7 @@ function RAT:_Respawn(index, lastpos, delay) else respawndelay=3 end - + -- Spawn new group. local arg={} arg.self=self @@ -2452,7 +2452,7 @@ function RAT:_Respawn(index, lastpos, delay) arg.lastpos=_lastpos self:T(RAT.id..string.format("%s delayed respawn in %.1f seconds.", self.alias, respawndelay)) SCHEDULER:New(nil, self._SpawnWithRouteTimer, {arg}, respawndelay) - + end --- Delayed spawn function called by scheduler. @@ -2476,7 +2476,7 @@ end -- @return #table Table of flight plan waypoints. -- @return #nil If no valid departure or destination airport could be found. function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) - + -- Max cruise speed. local VxCruiseMax if self.Vcruisemax then @@ -2486,39 +2486,39 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) -- Max cruise speed 90% of Vmax or 900 km/h whichever is lower. VxCruiseMax = math.min(self.aircraft.Vmax*0.90, 250) end - + -- Min cruise speed 70% of max cruise or 600 km/h whichever is lower. local VxCruiseMin = math.min(VxCruiseMax*0.70, 166) - + -- Cruise speed (randomized). Expectation value at midpoint between min and max. local VxCruise = UTILS.RandomGaussian((VxCruiseMax-VxCruiseMin)/2+VxCruiseMin, (VxCruiseMax-VxCruiseMax)/4, VxCruiseMin, VxCruiseMax) - + -- Climb speed 90% ov Vmax but max 720 km/h. local VxClimb = math.min(self.aircraft.Vmax*0.90, 200) - + -- Descent speed 60% of Vmax but max 500 km/h. local VxDescent = math.min(self.aircraft.Vmax*0.60, 140) - + -- Holding speed is 90% of descent speed. local VxHolding = VxDescent*0.9 - + -- Final leg is 90% of holding speed. local VxFinal = VxHolding*0.9 - + -- Reasonably civil climb speed Vy=1500 ft/min = 7.6 m/s but max aircraft specific climb rate. local VyClimb=math.min(self.Vclimb*RAT.unit.ft2meter/60, self.aircraft.Vymax) - + -- Climb angle in rad. local AlphaClimb=math.asin(VyClimb/VxClimb) - + -- Descent angle in rad. local AlphaDescent=math.rad(self.AlphaDescent) - + -- Expected cruise level (peak of Gaussian distribution) local FLcruise_expect=self.FLcruise - - - -- DEPARTURE AIRPORT + + + -- DEPARTURE AIRPORT -- Departure airport or zone. local departure=nil if _departure then @@ -2535,15 +2535,15 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) else local text=string.format("ERROR! Specified departure airport %s does not exist for %s.", _departure, self.alias) self:E(RAT.id..text) - end - + end + else departure=self:_PickDeparture(takeoff) if self.commute and self.starshape==true and self.homebase==nil then self.homebase=departure:GetName() end end - + -- Return nil if no departure could be found. if not departure then local text=string.format("ERROR! No valid departure airport could be found for %s.", self.alias) @@ -2561,11 +2561,11 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) -- For an air start, we take a random point within the spawn zone. local vec2=departure:GetRandomVec2() Pdeparture=COORDINATE:NewFromVec2(vec2) - end + end else Pdeparture=departure:GetCoordinate() end - + -- Height ASL of departure point. local H_departure if takeoff==RAT.wp.air then @@ -2576,7 +2576,7 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) else Hmin=50 end - -- Departure altitude is 70% of default cruise with 30% variation and limited to 1000 m AGL (50 m for helos). + -- Departure altitude is 70% of default cruise with 30% variation and limited to 1000 m AGL (50 m for helos). H_departure=self:_Randomize(FLcruise_expect*0.7, 0.3, Pdeparture.y+Hmin, FLcruise_expect) if self.FLminuser then H_departure=math.max(H_departure,self.FLminuser) @@ -2588,18 +2588,18 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) else H_departure=Pdeparture.y end - + -- Adjust min distance between departure and destination for user set min flight level. local mindist=self.mindist if self.FLminuser then - + -- We can conly consider the symmetric case, because no destination selected yet. local hclimb=self.FLminuser-H_departure local hdescent=self.FLminuser-H_departure - + -- Minimum distance for l local Dclimb, Ddescent, Dtot=self:_MinDistance(AlphaClimb, AlphaDescent, hclimb, hdescent) - + if takeoff==RAT.wp.air and landing==RAT.wpair then mindist=0 -- Takeoff and landing are in air. No mindist required. elseif takeoff==RAT.wp.air then @@ -2609,32 +2609,32 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) else mindist=Dtot -- Takeoff and landing on ground. Need both space to climb and descent. end - + -- Mindist is at least self.mindist. mindist=math.max(self.mindist, mindist) - + local text=string.format("Adjusting min distance to %d km (for given min FL%03d)", mindist/1000, self.FLminuser/RAT.unit.FL2m) self:T(RAT.id..text) end - + -- DESTINATION AIRPORT local destination=nil if _destination then - + if self:_AirportExists(_destination) then - + destination=AIRBASE:FindByName(_destination) if landing==RAT.wp.air or self.returnzone then destination=destination:GetZone() end - + elseif self:_ZoneExists(_destination) then destination=ZONE:New(_destination) else local text=string.format("ERROR: Specified destination airport/zone %s does not exist for %s!", _destination, self.alias) self:E(RAT.id.."ERROR: "..text) end - + else -- This handles the case where we have a journey and the first flight is done, i.e. _departure is set. @@ -2644,7 +2644,7 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) if self.continuejourney and _departure and #self.destination_ports<3 then random=true end - + -- In case of a returnzone the destination (i.e. return point) is always a zone. local mylanding=landing local acrange=self.aircraft.Reff @@ -2652,11 +2652,11 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) mylanding=RAT.wp.air acrange=self.aircraft.Reff/2 -- Aircraft needs to go to zone and back home. end - + -- Pick a destination airport. destination=self:_PickDestination(departure, Pdeparture, mindist, math.min(acrange, self.maxdist), random, mylanding) end - + -- Return nil if no departure could be found. if not destination then local text=string.format("No valid destination airport could be found for %s!", self.alias) @@ -2664,14 +2664,14 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) self:E(RAT.id.."ERROR: "..text) return nil end - + -- Check that departure and destination are not the same. Should not happen due to mindist. if destination:GetName()==departure:GetName() then local text=string.format("%s: Destination and departure are identical. Airport/zone %s.", self.alias, destination:GetName()) MESSAGE:New(text, 30):ToAll() self:E(RAT.id.."ERROR: "..text) end - + -- Get a random point inside zone return zone. local Preturn local destination_returnzone @@ -2684,7 +2684,7 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) -- Set departure to destination. destination=departure end - + -- Get destination coordinate. Either in a zone or exactly at the airport. local Pdestination if landing==RAT.wp.air then @@ -2693,10 +2693,10 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) else Pdestination=destination:GetCoordinate() end - + -- Height ASL of destination airport/zone. local H_destination=Pdestination.y - + -- DESCENT/HOLDING POINT -- Get a random point between 5 and 10 km away from the destination. local Rhmin=8000 @@ -2706,14 +2706,14 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) Rhmin=500 Rhmax=1000 end - + -- Coordinates of the holding point. y is the land height at that point. local Vholding=Pdestination:GetRandomVec2InRadius(Rhmax, Rhmin) local Pholding=COORDINATE:NewFromVec2(Vholding) - + -- AGL height of holding point. local H_holding=Pholding.y - + -- Holding point altitude. For planes between 1600 and 2400 m AGL. For helos 160 to 240 m AGL. local h_holding if self.category==RAT.cat.plane then @@ -2722,38 +2722,38 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) h_holding=150 end h_holding=self:_Randomize(h_holding, 0.2) - + -- This is the actual height ASL of the holding point we want to fly to local Hh_holding=H_holding+h_holding - + -- When we dont land, we set the holding altitude to the departure or cruise alt. -- This is used in the calculations. if landing==RAT.wp.air then Hh_holding=H_departure end - + -- Distance from holding point to final destination. local d_holding=Pholding:Get2DDistance(Pdestination) - + -- GENERAL local heading local d_total if self.returnzone then - + -- Heading from departure to destination in return zone. heading=self:_Course(Pdeparture, Preturn) - + -- Total distance to return zone and back. d_total=Pdeparture:Get2DDistance(Preturn) + Preturn:Get2DDistance(Pholding) - + else -- Heading from departure to holding point of destination. heading=self:_Course(Pdeparture, Pholding) - + -- Total distance between departure and holding point near destination. d_total=Pdeparture:Get2DDistance(Pholding) end - + -- Max height in case of air start, i.e. if we only would descent to holding point for the given distance. if takeoff==RAT.wp.air then local H_departure_max @@ -2764,15 +2764,15 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) end H_departure=math.min(H_departure, H_departure_max) end - + -------------------------------------------- - + -- Height difference between departure and destination. local deltaH=math.abs(H_departure-Hh_holding) - + -- Slope between departure and destination. local phi = math.atan(deltaH/d_total) - + -- Adjusted climb/descent angles. local phi_climb local phi_descent @@ -2791,18 +2791,18 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) else D_total = math.sqrt(deltaH*deltaH+d_total*d_total) end - + -- SSA triangle for sloped case. local gamma=math.rad(180)-phi_climb-phi_descent local a = D_total*math.sin(phi_climb)/math.sin(gamma) local b = D_total*math.sin(phi_descent)/math.sin(gamma) local hphi_max = b*math.sin(phi_climb) local hphi_max2 = a*math.sin(phi_descent) - + -- Height of triangle. local h_max1 = b*math.sin(AlphaClimb) local h_max2 = a*math.sin(AlphaDescent) - + -- Max height relative to departure or destination. local h_max if (H_departure > Hh_holding) then @@ -2810,23 +2810,23 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) else h_max=math.max(h_max1, h_max2) end - + -- Max flight level aircraft can reach for given angles and distance. local FLmax = h_max+H_departure - - --CRUISE + + --CRUISE -- Min cruise alt is just above holding point at destination or departure height, whatever is larger. local FLmin=math.max(H_departure, Hh_holding) - + -- For helicopters we take cruise alt between 50 to 1000 meters above ground. Default cruise alt is ~150 m. - if self.category==RAT.cat.heli then + if self.category==RAT.cat.heli then FLmin=math.max(H_departure, H_destination)+50 FLmax=math.max(H_departure, H_destination)+1000 end - + -- Ensure that FLmax not above its service ceiling. FLmax=math.min(FLmax, self.aircraft.ceiling) - + -- Overrule setting if user specified min/max flight level explicitly. if self.FLminuser then FLmin=math.max(self.FLminuser, FLmin) -- Still take care that we dont fly too high. @@ -2834,12 +2834,12 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) if self.FLmaxuser then FLmax=math.min(self.FLmaxuser, FLmax) -- Still take care that we dont fly too low. end - + -- If the route is very short we set FLmin a bit lower than FLmax. if FLmin>FLmax then FLmin=FLmax end - + -- Expected cruise altitude - peak of gaussian distribution. if FLcruise_expectFLmax then FLcruise_expect=FLmax end - + -- Set cruise altitude. Selected from Gaussian distribution but limited to FLmin and FLmax. local FLcruise=UTILS.RandomGaussian(FLcruise_expect, math.abs(FLmax-FLmin)/4, FLmin, FLmax) - + -- Overrule setting if user specified a flight level explicitly. if self.FLuser then FLcruise=self.FLuser @@ -2862,12 +2862,12 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) -- Climb and descent heights. local h_climb = FLcruise - H_departure local h_descent = FLcruise - Hh_holding - + -- Distances. local d_climb = h_climb/math.tan(AlphaClimb) local d_descent = h_descent/math.tan(AlphaDescent) - local d_cruise = d_total-d_climb-d_descent - + local d_cruise = d_total-d_climb-d_descent + -- debug message local text=string.format("\n******************************************************\n") text=text..string.format("Template = %s\n", self.SpawnTemplatePrefix) @@ -2899,7 +2899,7 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) text=text..string.format("FLmin = %6.1f m ASL = FL%03d\n", FLmin, FLmin/RAT.unit.FL2m) text=text..string.format("FLcruise = %6.1f m ASL = FL%03d\n", FLcruise, FLcruise/RAT.unit.FL2m) text=text..string.format("FLmax = %6.1f m ASL = FL%03d\n", FLmax, FLmax/RAT.unit.FL2m) - text=text..string.format("\nAngles:\n") + text=text..string.format("\nAngles:\n") text=text..string.format("Alpha climb = %6.2f Deg\n", math.deg(AlphaClimb)) text=text..string.format("Alpha descent = %6.2f Deg\n", math.deg(AlphaDescent)) text=text..string.format("Phi (slope) = %6.2f Deg\n", math.deg(phi)) @@ -2909,7 +2909,7 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) -- Max heights and distances if we would travel at FLmax. local h_climb_max = FLmax - H_departure local h_descent_max = FLmax - Hh_holding - local d_climb_max = h_climb_max/math.tan(AlphaClimb) + local d_climb_max = h_climb_max/math.tan(AlphaClimb) local d_descent_max = h_descent_max/math.tan(AlphaDescent) local d_cruise_max = d_total-d_climb_max-d_descent_max text=text..string.format("Heading = %6.1f Deg\n", heading) @@ -2932,7 +2932,7 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) end text=text..string.format("******************************************************\n") self:T2(RAT.id..text) - + -- Ensure that cruise distance is positve. Can be slightly negative in special cases. And we don't want to turn back. if d_cruise<0 then d_cruise=100 @@ -2943,80 +2943,80 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) local c={} local wpholding=nil local wpfinal=nil - + -- Departure/Take-off c[#c+1]=Pdeparture wp[#wp+1]=self:_Waypoint(#wp+1, "Departure", takeoff, c[#wp+1], VxClimb, H_departure, departure) self.waypointdescriptions[#wp]="Departure" self.waypointstatus[#wp]=RAT.status.Departure - + -- Climb if takeoff==RAT.wp.air then - + -- Air start. if d_climb < 5000 or d_cruise < 5000 then -- We omit the climb phase completely and add it to the cruise part. d_cruise=d_cruise+d_climb - else + else -- Only one waypoint at the end of climb = begin of cruise. c[#c+1]=c[#c]:Translate(d_climb, heading) - + wp[#wp+1]=self:_Waypoint(#wp+1, "Begin of Cruise", RAT.wp.cruise, c[#wp+1], VxCruise, FLcruise) self.waypointdescriptions[#wp]="Begin of Cruise" self.waypointstatus[#wp]=RAT.status.Cruise end - + else - + -- Ground start. c[#c+1]=c[#c]:Translate(d_climb/2, heading) c[#c+1]=c[#c]:Translate(d_climb/2, heading) - + wp[#wp+1]=self:_Waypoint(#wp+1, "Climb", RAT.wp.climb, c[#wp+1], VxClimb, H_departure+(FLcruise-H_departure)/2) self.waypointdescriptions[#wp]="Climb" self.waypointstatus[#wp]=RAT.status.Climb - + wp[#wp+1]=self:_Waypoint(#wp+1, "Begin of Cruise", RAT.wp.cruise, c[#wp+1], VxCruise, FLcruise) self.waypointdescriptions[#wp]="Begin of Cruise" self.waypointstatus[#wp]=RAT.status.Cruise - + end - + -- Cruise - + -- First add the little bit from begin of cruise to the return point. - if self.returnzone then - c[#c+1]=Preturn + if self.returnzone then + c[#c+1]=Preturn wp[#wp+1]=self:_Waypoint(#wp+1, "Return Zone", RAT.wp.cruise, c[#wp+1], VxCruise, FLcruise) self.waypointdescriptions[#wp]="Return Zone" self.waypointstatus[#wp]=RAT.status.Uturn end - + if landing==RAT.wp.air then - + -- Next waypoint is already the final destination. c[#c+1]=Pdestination wp[#wp+1]=self:_Waypoint(#wp+1, "Final Destination", RAT.wp.finalwp, c[#wp+1], VxCruise, FLcruise) self.waypointdescriptions[#wp]="Final Destination" self.waypointstatus[#wp]=RAT.status.Destination - + elseif self.returnzone then - - -- The little bit back to end of cruise. - c[#c+1]=c[#c]:Translate(d_cruise/2, heading-180) + + -- The little bit back to end of cruise. + c[#c+1]=c[#c]:Translate(d_cruise/2, heading-180) wp[#wp+1]=self:_Waypoint(#wp+1, "End of Cruise", RAT.wp.cruise, c[#wp+1], VxCruise, FLcruise) self.waypointdescriptions[#wp]="End of Cruise" self.waypointstatus[#wp]=RAT.status.Descent - + else - + c[#c+1]=c[#c]:Translate(d_cruise, heading) wp[#wp+1]=self:_Waypoint(#wp+1, "End of Cruise", RAT.wp.cruise, c[#wp+1], VxCruise, FLcruise) self.waypointdescriptions[#wp]="End of Cruise" self.waypointstatus[#wp]=RAT.status.Descent - + end - + -- Descent (only if we acually want to land) if landing==RAT.wp.landing then if self.returnzone then @@ -3031,45 +3031,45 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) self.waypointstatus[#wp]=RAT.status.DescentHolding end end - + -- Holding and final destination. if landing==RAT.wp.landing then -- Holding point - c[#c+1]=Pholding + c[#c+1]=Pholding wp[#wp+1]=self:_Waypoint(#wp+1, "Holding Point", RAT.wp.holding, c[#wp+1], VxHolding, H_holding+h_holding) self.waypointdescriptions[#wp]="Holding Point" self.waypointstatus[#wp]=RAT.status.Holding wpholding=#wp -- Final destination. - c[#c+1]=Pdestination + c[#c+1]=Pdestination wp[#wp+1]=self:_Waypoint(#wp+1, "Final Destination", landing, c[#wp+1], VxFinal, H_destination, destination) self.waypointdescriptions[#wp]="Final Destination" self.waypointstatus[#wp]=RAT.status.Destination - + end - + -- Final Waypoint wpfinal=#wp - + -- Fill table with waypoints. local waypoints={} for _,p in ipairs(wp) do table.insert(waypoints, p) end - + -- Some info on the route. self:_Routeinfo(waypoints, "Waypoint info in set_route:") - + -- Return departure, destination and waypoints. if self.returnzone then -- We return the actual zone here because returning the departure leads to problems with commute. - return departure, destination_returnzone, waypoints, wpholding, wpfinal + return departure, destination_returnzone, waypoints, wpholding, wpfinal else return departure, destination, waypoints, wpholding, wpfinal end - + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -3084,41 +3084,41 @@ function RAT:_PickDeparture(takeoff) -- Array of possible departure airports or zones. local departures={} - + if self.random_departure then - + -- Airports of friendly coalitions. for _,_airport in pairs(self.airports) do - + local airport=_airport --Wrapper.Airbase#AIRBASE - + local name=airport:GetName() if not self:_Excluded(name) then if takeoff==RAT.wp.air then - + table.insert(departures, airport:GetZone()) -- insert zone object. - + else - + -- Check if airbase has the right terminals. local nspots=1 if self.termtype~=nil then nspots=airport:GetParkingSpotsNumber(self.termtype) end - + if nspots>0 then table.insert(departures, airport) -- insert airport object. end end end - + end - + else - + -- Destination airports or zones specified by user. for _,name in pairs(self.departure_ports) do - + local dep=nil if self:_AirportExists(name) then if takeoff==RAT.wp.air then @@ -3143,22 +3143,22 @@ function RAT:_PickDeparture(takeoff) else self:E(RAT.id..string.format("ERROR: No airport or zone found with name %s.", name)) end - + -- Add to departures table. if dep then table.insert(departures, dep) end - - end - + + end + end - + -- Info message. self:T(RAT.id..string.format("Number of possible departures for %s= %d", self.alias, #departures)) - + -- Select departure airport or zone. local departure=departures[math.random(#departures)] - + local text if departure and departure:GetName() then if takeoff==RAT.wp.air then @@ -3172,7 +3172,7 @@ function RAT:_PickDeparture(takeoff) self:E(RAT.id..string.format("ERROR! No departure airport or zone found for %s.", self.alias)) departure=nil end - + return departure end @@ -3193,18 +3193,18 @@ function RAT:_PickDestination(departure, q, minrange, maxrange, random, landing) -- All possible destinations. local destinations={} - + if random then - + -- Airports of friendly coalitions. for _,_airport in pairs(self.airports) do local airport=_airport --Wrapper.Airbase#AIRBASE local name=airport:GetName() if self:_IsFriendly(name) and not self:_Excluded(name) and name~=departure:GetName() then - + -- Distance from departure to possible destination local distance=q:Get2DDistance(airport:GetCoordinate()) - + -- Check if distance form departure to destination is within min/max range. if distance>=minrange and distance<=maxrange then if landing==RAT.wp.air then @@ -3222,12 +3222,12 @@ function RAT:_PickDestination(departure, q, minrange, maxrange, random, landing) end end end - + else - + -- Destination airports or zones specified by user. for _,name in pairs(self.destination_ports) do - + -- Make sure departure and destination are not identical. if name ~= departure:GetName() then @@ -3255,11 +3255,11 @@ function RAT:_PickDestination(departure, q, minrange, maxrange, random, landing) else self:E(RAT.id..string.format("ERROR! No airport or zone found with name %s", name)) end - + if dest then -- Distance from departure to possible destination local distance=q:Get2DDistance(dest:GetCoordinate()) - + -- Add as possible destination if zone is within range. if distance>=minrange and distance<=maxrange then table.insert(destinations, dest) @@ -3268,14 +3268,14 @@ function RAT:_PickDestination(departure, q, minrange, maxrange, random, landing) self:T(RAT.id..text) end end - + end - end + end end - + -- Info message. self:T(RAT.id..string.format("Number of possible destinations = %s.", #destinations)) - + if #destinations > 0 then --- Compare distance of destination airports. -- @param Core.Point#COORDINATE a Coordinate of point a. @@ -3290,15 +3290,15 @@ function RAT:_PickDestination(departure, q, minrange, maxrange, random, landing) else destinations=nil end - - + + -- Randomly select one possible destination. local destination if destinations and #destinations>0 then - + -- Random selection. destination=destinations[math.random(#destinations)] -- Wrapper.Airbase#AIRBASE - + -- Debug message. local text if landing==RAT.wp.air then @@ -3308,15 +3308,15 @@ function RAT:_PickDestination(departure, q, minrange, maxrange, random, landing) end self:T(RAT.id..text) --MESSAGE:New(text, 30):ToAllIf(self.Debug) - + else self:E(RAT.id.."ERROR! No destination airport or zone found.") destination=nil end - + -- Return the chosen destination. - return destination - + return destination + end --- Find airports within a zone. @@ -3328,7 +3328,7 @@ function RAT:_GetAirportsInZone(zone) for _,airport in pairs(self.airports) do local name=airport:GetName() local coord=airport:GetCoordinate() - + if zone:IsPointVec3InZone(coord) then table.insert(airports, name) end @@ -3369,9 +3369,9 @@ end -- @param #RAT self function RAT:_GetAirportsOfMap() local _coalition - + for i=0,2 do -- cycle coalition.side 0=NEUTRAL, 1=RED, 2=BLUE - + -- set coalition if i==0 then _coalition=coalition.side.NEUTRAL @@ -3380,33 +3380,33 @@ function RAT:_GetAirportsOfMap() elseif i==2 then _coalition=coalition.side.BLUE end - + -- get airbases of coalition local ab=coalition.getAirbases(i) - + -- loop over airbases and put them in a table for _,airbase in pairs(ab) do - + local _id=airbase:getID() local _p=airbase:getPosition().p local _name=airbase:getName() local _myab=AIRBASE:FindByName(_name) - + if _myab then - + -- Add airport to table. table.insert(self.airports_map, _myab) - + local text="MOOSE: Airport ID = ".._myab:GetID().." and Name = ".._myab:GetName()..", Category = ".._myab:GetCategory()..", TypeName = ".._myab:GetTypeName() self:T(RAT.id..text) - + else - + self:E(RAT.id..string.format("WARNING: Airbase %s does not exsist as MOOSE object!", tostring(_name))) - + end end - + end end @@ -3424,7 +3424,7 @@ function RAT:_GetAirportsOfCoalition() -- Planes cannot land on ships. --local condition2=self.category==RAT.cat.plane and airport:GetCategory()==1 local condition2=self.category==RAT.cat.plane and category==Airbase.Category.SHIP - + -- Check that airport has the requested terminal types. -- NOT good here because we would also not allow any airport zones! --[[ @@ -3434,14 +3434,14 @@ function RAT:_GetAirportsOfCoalition() end local condition3 = nspots==0 ]] - + if not (condition1 or condition2) then table.insert(self.airports, airport) end end end end - + if #self.airports==0 then local text=string.format("No possible departure/destination airports found for RAT %s.", tostring(self.alias)) MESSAGE:New(text, 10):ToAll() @@ -3460,26 +3460,26 @@ function RAT:Status(message, forID) -- Optional arguments. if message==nil then message=false - end + end if forID==nil then forID=false end - + -- Current time. local Tnow=timer.getTime() - + -- Alive counter. local nalive=0 - + -- Loop over all ratcraft. for spawnindex,ratcraft in ipairs(self.ratcraft) do - + -- Get group. local group=ratcraft.group --Wrapper.Group#GROUP - + if group and group:IsAlive() then nalive=nalive+1 - + -- Gather some information. local prefix=self:_GetPrefixFromGroup(group) local life=self:_GetLife(group) @@ -3495,7 +3495,7 @@ function RAT:Status(message, forID) local active=ratcraft.active local Nunits=ratcraft.nunits -- group:GetSize() local N0units=group:GetInitialSize() - + -- Monitor time and distance on ground. local Tg=0 local Dg=0 @@ -3512,51 +3512,51 @@ function RAT:Status(message, forID) if ratcraft["Tground"] then -- Aircraft was already on ground. Calculate total time on ground. Tg=Tnow-ratcraft["Tground"] - + -- Distance on ground since last check. Dg=coords:Get2DDistance(ratcraft["Pground"]) - + -- Time interval since last check. dTlast=Tnow-ratcraft["Tlastcheck"] - + -- If more than Tinactive seconds passed since last check ==> check how much we moved meanwhile. if dTlast > self.Tinactive then - + --[[ if Dg<50 and active and status~=RAT.status.EventBirth then stationary=true end ]] - + -- Loop over all units. for _,_unit in pairs(group:GetUnits()) do - + if _unit and _unit:IsAlive() then - + -- Unit name, coord and distance since last check. local unitname=_unit:GetName() local unitcoord=_unit:GetCoordinate() local Ug=unitcoord:Get2DDistance(ratcraft.Uground[unitname]) - + -- Debug info self:T2(RAT.id..string.format("Unit %s travelled distance on ground %.1f m since %d seconds.", unitname, Ug, dTlast)) - + -- If aircraft did not move more than 50 m since last check, we call it stationary and despawn it. - -- Aircraft which are spawned uncontrolled or starting their engines are not counted. + -- Aircraft which are spawned uncontrolled or starting their engines are not counted. if Ug<50 and active and status~=RAT.status.EventBirth then stationary=true end - + -- Update coords. ratcraft["Uground"][unitname]=unitcoord end end - + -- Set the current time to know when the next check is necessary. ratcraft["Tlastcheck"]=Tnow - ratcraft["Pground"]=coords + ratcraft["Pground"]=coords end - + else -- First time we see that the aircraft is on ground. Initialize the times and position. ratcraft["Tground"]=Tnow @@ -3569,18 +3569,18 @@ function RAT:Status(message, forID) end end end - + -- Monitor travelled distance since last check. local Pn=coords local Dtravel=Pn:Get2DDistance(ratcraft["Pnow"]) ratcraft["Pnow"]=Pn - + -- Add up the travelled distance. ratcraft["Distance"]=ratcraft["Distance"]+Dtravel - + -- Distance remaining to destination. local Ddestination=Pn:Get2DDistance(ratcraft.destination:GetCoordinate()) - + -- Status report. if (forID and spawnindex==forID) or (not forID) then local text=string.format("ID %i of flight %s", spawnindex, prefix) @@ -3617,44 +3617,44 @@ function RAT:Status(message, forID) MESSAGE:New(text, 20):ToAll() end end - + -- Despawn groups if they are on ground and don't move or are damaged. if not airborne then - + -- Despawn unit if it did not move more then 50 m in the last 180 seconds. if stationary then local text=string.format("Group %s is despawned after being %d seconds inaktive on ground.", self.alias, dTlast) self:T(RAT.id..text) self:_Despawn(group) end - + -- Despawn group if life is < 10% and distance travelled < 100 m. if life<10 and Dtravel<100 then local text=string.format("Damaged group %s is despawned. Life = %3.0f", self.alias, life) self:T(RAT.id..text) self:_Despawn(group) end - + end - + -- Despawn groups after they have reached their destination zones. if ratcraft.despawnme then - + local text=string.format("Flight %s will be despawned NOW!", self.alias) self:T(RAT.id..text) - + -- Respawn group if (not self.norespawn) and (not self.respawn_after_takeoff) then local idx=self:GetSpawnIndexFromGroup(group) - local coord=group:GetCoordinate() + local coord=group:GetCoordinate() self:_Respawn(idx, coord, 0) end - + -- Despawn old group. if self.despawnair then self:_Despawn(group, 0) end - + end else @@ -3662,14 +3662,14 @@ function RAT:Status(message, forID) local text=string.format("Group does not exist in loop ratcraft status.") self:T2(RAT.id..text) end - + end - + -- Alive groups. local text=string.format("Alive groups of %s: %d, nalive=%d/%d", self.alias, self.alive, nalive, self.ngroups) self:T(RAT.id..text) MESSAGE:New(text, 20):ToAllIf(message and not forID) - + end --- Get (relative) life of first unit of a group. @@ -3701,26 +3701,26 @@ function RAT:_SetStatus(group, status) -- Get index from groupname. local index=self:GetSpawnIndexFromGroup(group) - + if self.ratcraft[index] then - + -- Set new status. self.ratcraft[index].status=status - + -- No status update message for "first waypoint", "holding" local no1 = status==RAT.status.Departure local no2 = status==RAT.status.EventBirthAir local no3 = status==RAT.status.Holding - + local text=string.format("Flight %s: %s.", group:GetName(), status) self:T(RAT.id..text) - + if not (no1 or no2 or no3) then MESSAGE:New(text, 10):ToAllIf(self.reportstatus) end - + end - + end end @@ -3734,16 +3734,16 @@ function RAT:GetStatus(group) -- Get index from groupname. local index=self:GetSpawnIndexFromGroup(group) - + if self.ratcraft[index] then - + -- Set new status. return self.ratcraft[index].status - + end - + end - + return "nonexistant" end @@ -3758,20 +3758,20 @@ function RAT:_OnBirth(EventData) self:T3(RAT.id.."Captured event birth!") local SpawnGroup = EventData.IniGroup --Wrapper.Group#GROUP - + if SpawnGroup then - + -- Get the template name of the group. This can be nil if this was not a spawned group. local EventPrefix = self:_GetPrefixFromGroup(SpawnGroup) - + if EventPrefix then - + -- Check that the template name actually belongs to this object. if EventPrefix == self.alias then - + local text="Event: Group "..SpawnGroup:GetName().." was born." self:T(RAT.id..text) - + -- Set status. local status="unknown in birth" if SpawnGroup:InAir() then @@ -3782,7 +3782,7 @@ function RAT:_OnBirth(EventData) status=RAT.status.EventBirth end self:_SetStatus(SpawnGroup, status) - + -- Get some info ablout this flight. local i=self:GetSpawnIndexFromGroup(SpawnGroup) local _departure=self.ratcraft[i].departure:GetName() @@ -3791,10 +3791,10 @@ function RAT:_OnBirth(EventData) local _takeoff=self.ratcraft[i].takeoff local _landing=self.ratcraft[i].landing local _livery=self.ratcraft[i].livery - + -- Some is only useful for an actual airbase (not a zone). local _airbase=AIRBASE:FindByName(_departure) - + -- Check if aircraft group was accidentally spawned on the runway. -- This can happen due to no parking slots available and other DCS bugs. local onrunway=false @@ -3802,12 +3802,12 @@ function RAT:_OnBirth(EventData) -- Check that we did not want to spawn at a runway or in air. if self.checkonrunway and _takeoff ~= RAT.wp.runway and _takeoff ~= RAT.wp.air then onrunway=_airbase:CheckOnRunWay(SpawnGroup, self.onrunwayradius, false) - end + end end - + -- Workaround if group was spawned on runway. if onrunway then - + -- Error message. local text=string.format("ERROR: RAT group of %s was spawned on runway. Group #%d will be despawned immediately!", self.alias, i) MESSAGE:New(text,30):ToAllIf(self.Debug) @@ -3815,42 +3815,42 @@ function RAT:_OnBirth(EventData) if self.Debug then SpawnGroup:FlareRed() end - + -- Despawn the group. self:_Despawn(SpawnGroup) - + -- Try to respawn the group if there is at least another airport or random airport selection is used. if (self.Ndeparture_Airports>=2 or self.random_departure) and _nrespawn new state %s.", SpawnGroup:GetName(), currentstate, status) self:T(RAT.id..text) - + -- Respawn group. local idx=self:GetSpawnIndexFromGroup(SpawnGroup) local coord=SpawnGroup:GetCoordinate() @@ -4041,13 +4041,13 @@ function RAT:_OnEngineShutdown(EventData) -- Despawn group. text="Event: Group "..SpawnGroup:GetName().." will be destroyed now." self:T(RAT.id..text) - self:_Despawn(SpawnGroup) + self:_Despawn(SpawnGroup) end end end - + else self:T2(RAT.id.."ERROR: Group does not exist in RAT:_OnEngineShutdown().") end @@ -4059,19 +4059,19 @@ end function RAT:_OnHit(EventData) self:F3(EventData) self:T(RAT.id..string.format("Captured event Hit by %s! Initiator %s. Target %s", self.alias, tostring(EventData.IniUnitName), tostring(EventData.TgtUnitName))) - + local SpawnGroup = EventData.TgtGroup --Wrapper.Group#GROUP - + if SpawnGroup then - + -- Get the template name of the group. This can be nil if this was not a spawned group. local EventPrefix = self:_GetPrefixFromGroup(SpawnGroup) - + -- Check that the template name actually belongs to this object. if EventPrefix and EventPrefix == self.alias then -- Debug info. self:T(RAT.id..string.format("Event: Group %s was hit. Unit %s.", SpawnGroup:GetName(), tostring(EventData.TgtUnitName))) - + local text=string.format("%s, unit %s was hit!", self.alias, EventData.TgtUnitName) MESSAGE:New(text, 10):ToAllIf(self.reportstatus or self.Debug) end @@ -4084,37 +4084,37 @@ end function RAT:_OnDeadOrCrash(EventData) self:F3(EventData) self:T3(RAT.id.."Captured event DeadOrCrash!") - + local SpawnGroup = EventData.IniGroup --Wrapper.Group#GROUP - + if SpawnGroup then - + -- Get the template name of the group. This can be nil if this was not a spawned group. local EventPrefix = self:_GetPrefixFromGroup(SpawnGroup) - + if EventPrefix then - + -- Check that the template name actually belongs to this object. if EventPrefix == self.alias then - + -- Decrease group alive counter. self.alive=self.alive-1 - + -- Debug info. - local text=string.format("Event: Group %s crashed or died. Alive counter = %d.", SpawnGroup:GetName(), self.alive) + local text=string.format("Event: Group %s crashed or died. Alive counter = %d.", SpawnGroup:GetName(), self.alive) self:T(RAT.id..text) - + -- Split crash and dead events. if EventData.id == world.event.S_EVENT_CRASH then - - -- Call crash event. This handles when a group crashed or + + -- Call crash event. This handles when a group crashed or self:_OnCrash(EventData) - + elseif EventData.id == world.event.S_EVENT_DEAD then - + -- Call dead event. self:_OnDead(EventData) - + end end end @@ -4127,26 +4127,26 @@ end function RAT:_OnDead(EventData) self:F3(EventData) self:T3(RAT.id.."Captured event Dead!") - + local SpawnGroup = EventData.IniGroup --Wrapper.Group#GROUP - + if SpawnGroup then - + -- Get the template name of the group. This can be nil if this was not a spawned group. local EventPrefix = self:_GetPrefixFromGroup(SpawnGroup) - + if EventPrefix then - + -- Check that the template name actually belongs to this object. if EventPrefix == self.alias then - - local text=string.format("Event: Group %s died. Unit %s.", SpawnGroup:GetName(), EventData.IniUnitName) + + local text=string.format("Event: Group %s died. Unit %s.", SpawnGroup:GetName(), EventData.IniUnitName) self:T(RAT.id..text) - + -- Set status. local status=RAT.status.EventDead self:_SetStatus(SpawnGroup, status) - + end end @@ -4163,29 +4163,29 @@ function RAT:_OnCrash(EventData) self:T3(RAT.id.."Captured event Crash!") local SpawnGroup = EventData.IniGroup --Wrapper.Group#GROUP - + if SpawnGroup then -- Get the template name of the group. This can be nil if this was not a spawned group. local EventPrefix = self:_GetPrefixFromGroup(SpawnGroup) - + -- Check that the template name actually belongs to this object. if EventPrefix and EventPrefix == self.alias then - + -- Update number of alive units in the group. local _i=self:GetSpawnIndexFromGroup(SpawnGroup) self.ratcraft[_i].nunits=self.ratcraft[_i].nunits-1 local _n=self.ratcraft[_i].nunits local _n0=SpawnGroup:GetInitialSize() - - -- Debug info. + + -- Debug info. local text=string.format("Event: Group %s crashed. Unit %s. Units still alive %d of %d.", SpawnGroup:GetName(), EventData.IniUnitName, _n, _n0) self:T(RAT.id..text) - + -- Set status. local status=RAT.status.EventCrash self:_SetStatus(SpawnGroup, status) - + -- Respawn group if all units are dead. if _n==0 and self.respawn_after_crash and not self.norespawn then local text=string.format("No units left of group %s. Group will be respawned now.", SpawnGroup:GetName()) @@ -4197,7 +4197,7 @@ function RAT:_OnCrash(EventData) end end - + else if self.Debug then self:E(RAT.id.."ERROR: Group does not exist in RAT:_OnCrash().") @@ -4213,16 +4213,16 @@ end function RAT:_Despawn(group, delay) if group ~= nil then - + -- Get spawnindex of group. local index=self:GetSpawnIndexFromGroup(group) - + if index ~= nil then - + self.ratcraft[index].group=nil self.ratcraft[index]["status"]="Dead" - - --TODO: Maybe here could be some more arrays deleted? + + --TODO: Maybe here could be some more arrays deleted? --TODO: Somehow this causes issues. --[[ --self.ratcraft[index]["group"]=group @@ -4248,8 +4248,8 @@ function RAT:_Despawn(group, delay) ]] -- Remove ratcraft table entry. --table.remove(self.ratcraft, index) - - + + -- We should give it at least 3 sec since this seems to be the time until free parking spots after despawn are available again (Sirri Island test). local despawndelay=0 if delay then @@ -4259,20 +4259,20 @@ function RAT:_Despawn(group, delay) -- Despawn afer respawn_delay. Actual respawn happens in +3 seconds to allow for free parking. despawndelay=self.respawn_delay end - - -- This will destroy the DCS group and create a single DEAD event. + + -- This will destroy the DCS group and create a single DEAD event. --if despawndelay>0.5 then self:T(RAT.id..string.format("%s delayed despawn in %.1f seconds.", self.alias, despawndelay)) SCHEDULER:New(nil, self._Destroy, {self, group}, despawndelay) --else --self:_Destroy(group) - --end + --end -- Remove submenu for this group. if self.f10menu and self.SubMenuName ~= nil then self.Menu[self.SubMenuName]["groups"][index]:Remove() end - + end end end @@ -4288,23 +4288,23 @@ function RAT:_Destroy(group) local DCSGroup = group:GetDCSObject() -- DCS#Group if DCSGroup and DCSGroup:isExist() then - + -- Cread one single Dead event and delete units from database. local triggerdead=true for _,DCSUnit in pairs(DCSGroup:getUnits()) do - + -- Dead event. if DCSUnit then if triggerdead then self:_CreateEventDead(timer.getTime(), DCSUnit) triggerdead=false end - + -- Delete from data base. _DATABASE:DeleteUnit(DCSUnit:getName()) end end - + -- Destroy DCS group. DCSGroup:destroy() DCSGroup = nil @@ -4345,10 +4345,10 @@ function RAT:_Waypoint(index, description, Type, Coord, Speed, Altitude, Airport -- Altitude of input parameter or y-component of 3D-coordinate. local _Altitude=Altitude or Coord.y - + -- Land height at given coordinate. local Hland=Coord:GetLandHeight() - + -- convert type and action in DCS format local _Type=nil local _Action=nil @@ -4361,7 +4361,7 @@ function RAT:_Waypoint(index, description, Type, Coord, Speed, Altitude, Airport _Altitude = 10 _alttype="RADIO" elseif Type==RAT.wp.hot then - -- take-off with engine on + -- take-off with engine on _Type="TakeOffParkingHot" _Action="From Parking Area Hot" _Altitude = 10 @@ -4434,7 +4434,7 @@ function RAT:_Waypoint(index, description, Type, Coord, Speed, Altitude, Airport end text=text.."******************************************************\n" self:T2(RAT.id..text) - + -- define waypoint local RoutePoint = {} -- coordinates and altitude @@ -4443,7 +4443,7 @@ function RAT:_Waypoint(index, description, Type, Coord, Speed, Altitude, Airport RoutePoint.alt = _Altitude -- altitude type: BARO=ASL or RADIO=AGL RoutePoint.alt_type = _alttype - -- type + -- type RoutePoint.type = _Type RoutePoint.action = _Action -- speed in m/s @@ -4454,7 +4454,7 @@ function RAT:_Waypoint(index, description, Type, Coord, Speed, Altitude, Airport RoutePoint.ETA_locked = false -- waypoint description RoutePoint.name=description - + if (Airport~=nil) and (Type~=RAT.wp.air) then local AirbaseID = Airport:GetID() local AirbaseCategory = Airport:GetAirbaseCategory() @@ -4468,7 +4468,7 @@ function RAT:_Waypoint(index, description, Type, Coord, Speed, Altitude, Airport RoutePoint.airdromeId = AirbaseID else self:T(RAT.id.."Unknown Airport category in _Waypoint()!") - end + end end -- properties RoutePoint.properties = { @@ -4482,16 +4482,16 @@ function RAT:_Waypoint(index, description, Type, Coord, Speed, Altitude, Airport local TaskCombo = {} local TaskHolding = self:_TaskHolding({x=Coord.x, y=Coord.z}, Altitude, Speed, self:_Randomize(90,0.9)) local TaskWaypoint = self:_TaskFunction("RAT._WaypointFunction", self, index) - + RoutePoint.task = {} RoutePoint.task.id = "ComboTask" RoutePoint.task.params = {} - + TaskCombo[#TaskCombo+1]=TaskWaypoint if Type==RAT.wp.holding then TaskCombo[#TaskCombo+1]=TaskHolding end - + RoutePoint.task.params.tasks = TaskCombo -- Return waypoint. @@ -4533,10 +4533,10 @@ function RAT:_Routeinfo(waypoints, comment) end text=text..string.format("Total distance = %6.1f km\n", total/1000) text=text..string.format("******************************************************\n") - + -- Debug info. self:T2(RAT.id..text) - + -- return total route length in meters return total end @@ -4562,7 +4562,7 @@ function RAT:_TaskHolding(P1, Altitude, Speed, Duration) dx=200 dy=0 end - + local P2={} P2.x=P1.x+dx P2.y=P1.y+dy @@ -4577,12 +4577,12 @@ function RAT:_TaskHolding(P1, Altitude, Speed, Duration) altitude = Altitude } } - + local DCSTask={} DCSTask.id="ControlledTask" DCSTask.params={} DCSTask.params.task=Task - + if self.ATCswitch then -- Set stop condition for holding. Either flag=1 or after max. X min holding. local userflagname=string.format("%s#%03d", self.alias, self.SpawnIndex+1) @@ -4591,7 +4591,7 @@ function RAT:_TaskHolding(P1, Altitude, Speed, Duration) else DCSTask.params.stopCondition={duration=Duration} end - + return DCSTask end @@ -4604,32 +4604,32 @@ function RAT._WaypointFunction(group, rat, wp) -- Current time and Spawnindex. local Tnow=timer.getTime() local sdx=rat:GetSpawnIndexFromGroup(group) - + -- Departure and destination names. local departure=rat.ratcraft[sdx].departure:GetName() local destination=rat.ratcraft[sdx].destination:GetName() local landing=rat.ratcraft[sdx].landing local WPholding=rat.ratcraft[sdx].wpholding local WPfinal=rat.ratcraft[sdx].wpfinal - - + + -- For messages local text - + -- Info on passing waypoint. text=string.format("Flight %s passing waypoint #%d %s.", group:GetName(), wp, rat.waypointdescriptions[wp]) BASE.T(rat, RAT.id..text) - + -- New status. local status=rat.waypointstatus[wp] rat:_SetStatus(group, status) - + if wp==WPholding then - + -- Aircraft arrived at holding point text=string.format("Flight %s to %s ATC: Holding and awaiting landing clearance.", group:GetName(), destination) MESSAGE:New(text, 10):ToAllIf(rat.reportstatus) - + -- Register aircraft at ATC. if rat.ATCswitch then if rat.f10menu then @@ -4638,12 +4638,12 @@ function RAT._WaypointFunction(group, rat, wp) rat._ATCRegisterFlight(rat, group:GetName(), Tnow) end end - + if wp==WPfinal then text=string.format("Flight %s arrived at final destination %s.", group:GetName(), destination) MESSAGE:New(text, 10):ToAllIf(rat.reportstatus) BASE.T(rat, RAT.id..text) - + if landing==RAT.wp.air then text=string.format("Activating despawn switch for flight %s! Group will be detroyed soon.", group:GetName()) MESSAGE:New(text, 10):ToAllIf(rat.Debug) @@ -4659,14 +4659,14 @@ end -- @param #string FunctionString Name of the function to be called. function RAT:_TaskFunction(FunctionString, ... ) self:F2({FunctionString, arg}) - + local DCSTask local ArgumentKey - + -- Templatename and anticipated name the group will get local templatename=self.templategroup:GetName() local groupname=self:_AnticipatedGroupName() - + local DCSScript = {} DCSScript[#DCSScript+1] = "local MissionControllable = GROUP:FindByName(\""..groupname.."\") " DCSScript[#DCSScript+1] = "local RATtemplateControllable = GROUP:FindByName(\""..templatename.."\") " @@ -4679,7 +4679,7 @@ function RAT:_TaskFunction(FunctionString, ... ) else DCSScript[#DCSScript+1] = FunctionString .. "( MissionControllable )" end - + DCSTask = self.templategroup:TaskWrappedAction(self.templategroup:CommandDoScript(table.concat(DCSScript))) return DCSTask @@ -4687,7 +4687,7 @@ end --- Anticipated group name from alias and spawn index. -- @param #RAT self --- @param #number index Spawnindex of group if given or self.SpawnIndex+1 by default. +-- @param #number index Spawnindex of group if given or self.SpawnIndex+1 by default. -- @return #string Name the group will get after it is spawned. function RAT:_AnticipatedGroupName(index) local index=index or self.SpawnIndex+1 @@ -4700,21 +4700,21 @@ end -- @param #RAT self function RAT:_ActivateUncontrolled() self:F() - - -- Spawn indices of uncontrolled inactive aircraft. + + -- Spawn indices of uncontrolled inactive aircraft. local idx={} local rat={} - + -- Number of active aircraft. local nactive=0 - + -- Loop over RAT groups and count the active ones. for spawnindex,ratcraft in pairs(self.ratcraft) do - + local group=ratcraft.group --Wrapper.Group#GROUP - + if group and group:IsAlive() then - + local text=string.format("Uncontrolled: Group = %s (spawnindex = %d), active = %s.", ratcraft.group:GetName(), spawnindex, tostring(ratcraft.active)) self:T2(RAT.id..text) @@ -4723,22 +4723,22 @@ function RAT:_ActivateUncontrolled() else table.insert(idx, spawnindex) end - + end end - + -- Debug message. local text=string.format("Uncontrolled: Ninactive = %d, Nactive = %d (of max %d).", #idx, nactive, self.activate_max) self:T(RAT.id..text) - + if #idx>0 and nactive Less effort. @@ -5231,7 +5231,7 @@ function RAT:_ModifySpawnTemplate(waypoints, livery, spawnplace, departure, take -- Terminal type specified explicitly. self:T(RAT.id..string.format("Helo group %s is at %s using terminal type %d.", self.alias, departure:GetName(), termtype)) spots=departure:FindFreeParkingSpotForAircraft(TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits) - nfree=#spots + nfree=#spots end else -- Fixed wing aircraft is spawned. @@ -5248,15 +5248,15 @@ function RAT:_ModifySpawnTemplate(waypoints, livery, spawnplace, departure, take nfree=#spots if nfree=1 then - + -- All units get the same spot. DCS takes care of the rest. for i=1,nunits do table.insert(parkingspots, spots[1].Coordinate) @@ -5292,46 +5292,46 @@ function RAT:_ModifySpawnTemplate(waypoints, livery, spawnplace, departure, take end -- This is actually used... PointVec3=spots[1].Coordinate - + else -- If there is absolutely not spot ==> air start! _notenough=true end - + elseif spawnonairport then - + if nfree>=nunits then - + for i=1,nunits do table.insert(parkingspots, spots[i].Coordinate) table.insert(parkingindex, spots[i].TerminalID) end - + else -- Not enough spots for the whole group ==> air start! - _notenough=true - end + _notenough=true + end end - + -- Not enough spots ==> Prepare airstart. if _notenough then - - if self.respawn_inair and not self.SpawnUnControlled then + + if self.respawn_inair and not self.SpawnUnControlled then self:E(RAT.id..string.format("WARNING: Group %s has no parking spots at %s ==> air start!", self.SpawnTemplatePrefix, departure:GetName())) - + -- Not enough parking spots at the airport ==> Spawn in air. spawnonground=false spawnonship=false spawnonfarp=false spawnonrunway=false - + -- Set waypoint type/action to turning point. waypoints[1].type = GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][1] -- type = Turning Point waypoints[1].action = GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][2] -- action = Turning Point - + -- Adjust altitude to be 500-1000 m above the airbase. PointVec3.x=PointVec3.x+math.random(-1500,1500) - PointVec3.z=PointVec3.z+math.random(-1500,1500) + PointVec3.z=PointVec3.z+math.random(-1500,1500) if self.category==RAT.cat.heli then PointVec3.y=PointVec3:GetLandHeight()+math.random(100,1000) else @@ -5343,34 +5343,34 @@ function RAT:_ModifySpawnTemplate(waypoints, livery, spawnplace, departure, take return nil end end - + else - + -- Air start requested initially! - + --PointVec3.y is already set from first waypoint here! - + end --- new - + -- Translate the position of the Group Template to the Vec3. for UnitID = 1, nunits do - + -- Template of the current unit. local UnitTemplate = SpawnTemplate.units[UnitID] - - -- Tranlate position and preserve the relative position/formation of all aircraft. + + -- Tranlate position and preserve the relative position/formation of all aircraft. local SX = UnitTemplate.x - local SY = UnitTemplate.y + local SY = UnitTemplate.y local BX = SpawnTemplate.route.points[1].x local BY = SpawnTemplate.route.points[1].y local TX = PointVec3.x + (SX-BX) local TY = PointVec3.z + (SY-BY) - + if spawnonground then - + -- Ships and FARPS seem to have a build in queue. if spawnonship or spawnonfarp or spawnonrunway or automatic then self:T(RAT.id..string.format("RAT group %s spawning at farp, ship or runway %s.", self.alias, departure:GetName())) @@ -5378,87 +5378,87 @@ function RAT:_ModifySpawnTemplate(waypoints, livery, spawnplace, departure, take -- Spawn on ship. We take only the position of the ship. SpawnTemplate.units[UnitID].x = PointVec3.x --TX SpawnTemplate.units[UnitID].y = PointVec3.z --TY - SpawnTemplate.units[UnitID].alt = PointVec3.y + SpawnTemplate.units[UnitID].alt = PointVec3.y else self:T(RAT.id..string.format("RAT group %s spawning at airbase %s on parking spot id %d", self.alias, departure:GetName(), parkingindex[UnitID])) - + -- Get coordinates of parking spot. SpawnTemplate.units[UnitID].x = parkingspots[UnitID].x SpawnTemplate.units[UnitID].y = parkingspots[UnitID].z - SpawnTemplate.units[UnitID].alt = parkingspots[UnitID].y + SpawnTemplate.units[UnitID].alt = parkingspots[UnitID].y end - - else + + else self:T(RAT.id..string.format("RAT group %s spawning in air at %s.", self.alias, departure:GetName())) - + -- Spawn in air as requested initially. Original template orientation is perserved, altitude is already correctly set. SpawnTemplate.units[UnitID].x = TX SpawnTemplate.units[UnitID].y = TY - SpawnTemplate.units[UnitID].alt = PointVec3.y + SpawnTemplate.units[UnitID].alt = PointVec3.y end - - -- Place marker at spawn position. + + -- Place marker at spawn position. if self.Debug then local unitspawn=COORDINATE:New(SpawnTemplate.units[UnitID].x, SpawnTemplate.units[UnitID].alt, SpawnTemplate.units[UnitID].y) unitspawn:MarkToAll(string.format("RAT %s Spawnplace unit #%d", self.alias, UnitID)) end - + -- Parking spot id. UnitTemplate.parking = nil UnitTemplate.parking_id = nil if parkingindex[UnitID] and not automatic then UnitTemplate.parking = parkingindex[UnitID] end - + -- Debug info. self:T2(RAT.id..string.format("RAT group %s unit number %d: Parking = %s",self.alias, UnitID, tostring(UnitTemplate.parking))) self:T2(RAT.id..string.format("RAT group %s unit number %d: Parking ID = %s",self.alias, UnitID, tostring(UnitTemplate.parking_id))) - + -- Set initial heading. SpawnTemplate.units[UnitID].heading = heading SpawnTemplate.units[UnitID].psi = -heading - + -- Set livery (will be the same for all units of the group). if livery then SpawnTemplate.units[UnitID].livery_id = livery end - + -- Set type of aircraft. if self.actype then SpawnTemplate.units[UnitID]["type"] = self.actype end - + -- Set AI skill. SpawnTemplate.units[UnitID]["skill"] = self.skill - + -- Onboard number. if self.onboardnum then SpawnTemplate.units[UnitID]["onboard_num"] = string.format("%s%d%02d", self.onboardnum, (self.SpawnIndex-1)%10, (self.onboardnum0-1)+UnitID) end - + -- Modify coaltion and country of template. SpawnTemplate.CoalitionID=self.coalition if self.country then SpawnTemplate.CountryID=self.country end - + end - + -- Copy waypoints into spawntemplate. By this we avoid the nasty DCS "landing bug" :) for i,wp in ipairs(waypoints) do SpawnTemplate.route.points[i]=wp end - + -- Also modify x,y of the template. Not sure why. SpawnTemplate.x = PointVec3.x SpawnTemplate.y = PointVec3.z - + -- Enable/disable radio. Same as checking the COMM box in the ME if self.radio then SpawnTemplate.communication=self.radio end - + -- Set radio frequency and modulation. if self.frequency then SpawnTemplate.frequency=self.frequency @@ -5466,12 +5466,12 @@ function RAT:_ModifySpawnTemplate(waypoints, livery, spawnplace, departure, take if self.modulation then SpawnTemplate.modulation=self.modulation end - + -- Debug output. self:T(SpawnTemplate) end end - + return true end @@ -5544,15 +5544,15 @@ function RAT:_ATCStatus() -- Current time. local Tnow=timer.getTime() - + for name,_ in pairs(RAT.ATC.flight) do -- Holding time at destination. local hold=RAT.ATC.flight[name].holding local dest=RAT.ATC.flight[name].destination - + if hold >= 0 then - + -- Some string whether the runway is busy or not. local busy="Runway state is unknown" if RAT.ATC.airport[dest].Nonfinal>0 then @@ -5560,29 +5560,29 @@ function RAT:_ATCStatus() else busy="Runway is currently clear" end - + -- Aircraft is holding. local text=string.format("ATC %s: Flight %s is holding for %i:%02d. %s.", dest, name, hold/60, hold%60, busy) BASE:T(RAT.id..text) - + elseif hold==RAT.ATC.onfinal then - + -- Aircarft is on final approach for landing. local Tfinal=Tnow-RAT.ATC.flight[name].Tonfinal - + local text=string.format("ATC %s: Flight %s is on final. Waiting %i:%02d for landing event.", dest, name, Tfinal/60, Tfinal%60) BASE:T(RAT.id..text) - + elseif hold==RAT.ATC.unregistered then - + -- Aircraft has not arrived at holding point. --self:T(string.format("ATC %s: Flight %s is not registered yet (hold %d).", dest, name, hold)) - + else BASE:E(RAT.id.."ERROR: Unknown holding time in RAT:_ATCStatus().") end end - + end --- Main ATC function. Updates the landing queue of all airports and inceases holding time for all flights. @@ -5591,17 +5591,17 @@ function RAT:_ATCCheck() -- Init queue of flights at all airports. RAT:_ATCQueue() - + -- Current time. local Tnow=timer.getTime() - + for name,_ in pairs(RAT.ATC.airport) do - + for qID,flight in ipairs(RAT.ATC.airport[name].queue) do - + -- Number of aircraft in queue. local nqueue=#RAT.ATC.airport[name].queue - + -- Conditions to clear an aircraft for landing local landing1 if RAT.ATC.airport[name].Tlastclearance then @@ -5615,31 +5615,31 @@ function RAT:_ATCCheck() if not landing1 and not landing2 then - + -- Update holding time. RAT.ATC.flight[flight].holding=Tnow-RAT.ATC.flight[flight].Tarrive - + -- Debug message. local text=string.format("ATC %s: Flight %s runway is busy. You are #%d of %d in landing queue. Your holding time is %i:%02d.", name, flight,qID, nqueue, RAT.ATC.flight[flight].holding/60, RAT.ATC.flight[flight].holding%60) BASE:T(RAT.id..text) - + else - + local text=string.format("ATC %s: Flight %s was cleared for landing. Your holding time was %i:%02d.", name, flight, RAT.ATC.flight[flight].holding/60, RAT.ATC.flight[flight].holding%60) BASE:T(RAT.id..text) - + -- Clear flight for landing. RAT:_ATCClearForLanding(name, flight) - + end - + end - + end - + -- Update queue of flights at all airports. RAT:_ATCQueue() - + end --- Giving landing clearance for aircraft by setting user flag. @@ -5656,13 +5656,13 @@ function RAT:_ATCClearForLanding(airport, flight) -- Number of planes on final approach. RAT.ATC.airport[airport].Nonfinal=RAT.ATC.airport[airport].Nonfinal+1 -- Last time an aircraft got landing clearance. - RAT.ATC.airport[airport].Tlastclearance=timer.getTime() + RAT.ATC.airport[airport].Tlastclearance=timer.getTime() -- Current time. RAT.ATC.flight[flight].Tonfinal=timer.getTime() -- Set user flag to 1 ==> stop condition for holding. trigger.action.setUserFlag(flight, 1) local flagvalue=trigger.misc.getUserFlag(flight) - + -- Debug message. local text1=string.format("ATC %s: Flight %s cleared for landing (flag=%d).", airport, flight, flagvalue) local text2=string.format("ATC %s: Flight %s you are cleared for landing.", airport, flight) @@ -5676,33 +5676,33 @@ end function RAT:_ATCFlightLanded(name) if RAT.ATC.flight[name] then - + -- Destination airport. local dest=RAT.ATC.flight[name].destination - + -- Times for holding and final approach. local Tnow=timer.getTime() local Tfinal=Tnow-RAT.ATC.flight[name].Tonfinal local Thold=RAT.ATC.flight[name].Tonfinal-RAT.ATC.flight[name].Tarrive - + -- Airport is not busy any more. RAT.ATC.airport[dest].busy=false - + -- No aircraft on final any more. RAT.ATC.airport[dest].onfinal[name]=nil - + -- Decrease number of aircraft on final. RAT.ATC.airport[dest].Nonfinal=RAT.ATC.airport[dest].Nonfinal-1 - + -- Remove this flight from list of flights. RAT:_ATCDelFlight(RAT.ATC.flight, name) - + -- Increase landing counter to monitor traffic. RAT.ATC.airport[dest].traffic=RAT.ATC.airport[dest].traffic+1 - + -- Number of planes landing per hour. local TrafficPerHour=RAT.ATC.airport[dest].traffic/(timer.getTime()-RAT.ATC.T0)*3600 - + -- Debug info local text1=string.format("ATC %s: Flight %s landed. Tholding = %i:%02d, Tfinal = %i:%02d.", dest, name, Thold/60, Thold%60, Tfinal/60, Tfinal%60) local text2=string.format("ATC %s: Number of flights still on final %d.", dest, RAT.ATC.airport[dest].Nonfinal) @@ -5713,7 +5713,7 @@ function RAT:_ATCFlightLanded(name) BASE:T(RAT.id..text3) MESSAGE:New(text4, 10):ToAllIf(RAT.ATC.messages) end - + end --- Creates a landing queue for all flights holding at airports. Aircraft with longest holding time gets first permission to land. @@ -5721,7 +5721,7 @@ end function RAT:_ATCQueue() for airport,_ in pairs(RAT.ATC.airport) do - + -- Local airport queue. local _queue={} @@ -5729,26 +5729,26 @@ function RAT:_ATCQueue() for name,_ in pairs(RAT.ATC.flight) do --fvh local Tnow=timer.getTime() - + -- Update holding time (unless holing is set to onfinal=-100) if RAT.ATC.flight[name].holding>=0 then RAT.ATC.flight[name].holding=Tnow-RAT.ATC.flight[name].Tarrive end local hold=RAT.ATC.flight[name].holding local dest=RAT.ATC.flight[name].destination - + -- Flight is holding at this airport. if hold>=0 and airport==dest then _queue[#_queue+1]={name,hold} end end - + -- Sort queue w.r.t holding time in ascending order. local function compare(a,b) return a[2] > b[2] end table.sort(_queue, compare) - + -- Transfer queue to airport queue. RAT.ATC.airport[airport].queue={} for k,v in ipairs(_queue) do @@ -5776,36 +5776,36 @@ end -- @extends Core.Base#BASE ---# RATMANAGER class, extends @{Core.Base#BASE} --- The RATMANAGER class manages spawning of multiple RAT objects in a very simple way. It is created by the @{#RATMANAGER.New}() contructor. +-- The RATMANAGER class manages spawning of multiple RAT objects in a very simple way. It is created by the @{#RATMANAGER.New}() contructor. -- RAT objects with different "tasks" can be defined as usual. However, they **must not** be spawned via the @{#RAT.Spawn}() function. --- +-- -- Instead, these objects can be added to the manager via the @{#RATMANAGER.Add}(ratobject, min) function, where the first parameter "ratobject" is the @{#RAT} object, while the second parameter "min" defines the -- minimum number of RAT aircraft of that object, which are alive at all time. --- +-- -- The @{#RATMANAGER} must be started by the @{#RATMANAGER.Start}(startime) function, where the optional argument "startime" specifies the delay time in seconds after which the manager is started and the spawning beginns. -- If desired, the @{#RATMANAGER} can be stopped by the @{#RATMANAGER.Stop}(stoptime) function. The parameter "stoptime" specifies the time delay in seconds after which the manager stops. -- When this happens, no new aircraft will be spawned and the population will eventually decrease to zero. --- +-- -- When you are using a time intervall like @{#RATMANAGER.dTspawn}(delay), @{#RATMANAGER} will ignore the amount set with @{#RATMANAGER.New}(). @{#RATMANAGER.dTspawn}(delay) will spawn infinite groups. --- +-- -- ## Example -- In this example, three different @{#RAT} objects are created (but not spawned manually). The @{#RATMANAGER} takes care that at least five aircraft of each type are alive and that the total number of aircraft -- spawned is 25. The @{#RATMANAGER} is started after 30 seconds and stopped after two hours. --- +-- -- local a10c=RAT:New("RAT_A10C", "A-10C managed") -- a10c:SetDeparture({"Batumi"}) --- +-- -- local f15c=RAT:New("RAT_F15C", "F15C managed") -- f15c:SetDeparture({"Sochi-Adler"}) -- f15c:DestinationZone() -- f15c:SetDestination({"Zone C"}) --- +-- -- local av8b=RAT:New("RAT_AV8B", "AV8B managed") -- av8b:SetDeparture({"Zone C"}) -- av8b:SetTakeoff("air") -- av8b:DestinationZone() -- av8b:SetDestination({"Zone A"}) --- +-- -- local manager=RATMANAGER:New(25) -- manager:Add(a10c, 5) -- manager:Add(f15c, 5) @@ -5841,13 +5841,13 @@ function RATMANAGER:New(ntot) -- Inherit BASE. local self=BASE:Inherit(self, BASE:New()) -- #RATMANAGER - + -- Total number of RAT groups. self.ntot=ntot or 1 - + -- Debug info self:E(RATMANAGER.id..string.format("Creating manager for %d groups.", ntot)) - + return self end @@ -5862,21 +5862,21 @@ function RATMANAGER:Add(ratobject,min) --Automatic respawning is disabled. ratobject.norespawn=true ratobject.f10menu=false - + -- Increase RAT object counter. self.nrat=self.nrat+1 - + self.rat[self.nrat]=ratobject self.alive[self.nrat]=0 self.name[self.nrat]=ratobject.alias self.min[self.nrat]=min or 1 - + -- Debug info. self:T(RATMANAGER.id..string.format("Adding ratobject %s with min flights = %d", self.name[self.nrat],self.min[self.nrat])) - + -- Call spawn to initialize RAT parameters. ratobject:Spawn(0) - + return self end @@ -5900,7 +5900,7 @@ function RATMANAGER:Start(delay) -- Start scheduler. SCHEDULER:New(nil, self._Start, {self}, delay) - + return self end @@ -5918,7 +5918,7 @@ function RATMANAGER:_Start() -- Get randum number of new RAT groups. local N=self:_RollDice(self.nrat, self.ntot, self.min, self.alive) - + -- Loop over all RAT objects and spawn groups. local time=0.0 for i=1,self.nrat do @@ -5927,26 +5927,26 @@ function RATMANAGER:_Start() SCHEDULER:New(nil, RAT._SpawnWithRoute, {self.rat[i]}, time) end end - + -- Start activation scheduler for uncontrolled aircraft. - for i=1,self.nrat do + for i=1,self.nrat do if self.rat[i].uncontrolled and self.rat[i].activate_uncontrolled then -- Start activating stuff but not before the latest spawn has happend. - local Tactivate=math.max(time+1, self.rat[i].activate_delay) + local Tactivate=math.max(time+1, self.rat[i].activate_delay) SCHEDULER:New(self.rat[i], self.rat[i]._ActivateUncontrolled, {self.rat[i]}, Tactivate, self.rat[i].activate_delta, self.rat[i].activate_frand) end end - + -- Start the manager. But not earlier than the latest spawn has happened! local TstartManager=math.max(time+1, self.Tcheck) - + -- Start manager scheduler. self.manager, self.managerid = SCHEDULER:New(self, self._Manage, {self}, TstartManager, self.Tcheck) --Core.Scheduler#SCHEDULER - + -- Info local text=string.format(RATMANAGER.id.."Starting RAT manager with scheduler ID %s in %d seconds. Repeat interval %d seconds.", self.managerid, TstartManager, self.Tcheck) self:E(text) - + return self end @@ -5995,14 +5995,14 @@ function RATMANAGER:_Manage() -- Count total number of groups. local ntot=self:_Count() - + -- Debug info. local text=string.format("Number of alive groups %d. New groups to be spawned %d.", ntot, self.ntot-ntot) self:T(RATMANAGER.id..text) - + -- Get number of necessary spawns. local N=self:_RollDice(self.nrat, self.ntot, self.min, self.alive) - + -- Loop over all RAT objects and spawn new groups if necessary. local time=0.0 for i=1,self.nrat do @@ -6019,13 +6019,13 @@ function RATMANAGER:_Count() -- Init total counter. local ntotal=0 - + -- Loop over all RAT objects. for i=1,self.nrat do local n=0 - + local ratobject=self.rat[i] --#RAT - + -- Loop over the RAT groups of this object. for spawnindex,ratcraft in pairs(ratobject.ratcraft) do local group=ratcraft.group --Wrapper.Group#GROUP @@ -6033,18 +6033,18 @@ function RATMANAGER:_Count() n=n+1 end end - + -- Alive groups of this RAT object. self.alive[i]=n - + -- Grand total. ntotal=ntotal+n - + -- Debug output. local text=string.format("Number of alive groups of %s = %d", self.name[i], n) self:T(RATMANAGER.id..text) end - + -- Return grand total. return ntotal end @@ -6056,7 +6056,7 @@ end -- @param #table min Minimum number of groups for each RAT object. -- @param #table alive Number of alive groups of each RAT object. function RATMANAGER:_RollDice(nrat,ntot,min,alive) - + -- Calculate sum. local function sum(A,index) local summe=0 @@ -6064,8 +6064,8 @@ function RATMANAGER:_RollDice(nrat,ntot,min,alive) summe=summe+A[i] end return summe - end - + end + -- Table of number of groups. local N={} local M={} @@ -6075,57 +6075,57 @@ function RATMANAGER:_RollDice(nrat,ntot,min,alive) M[#M+1]=math.max(alive[i], min[i]) P[#P+1]=math.max(min[i]-alive[i],0) end - + -- Min/max group arrays. local mini={} local maxi={} - + -- Arrays. local rattab={} for i=1,nrat do table.insert(rattab,i) end local done={} - + -- Number of new groups to be added. local nnew=ntot for i=1,nrat do nnew=nnew-alive[i] end - + for i=1,nrat-1 do - + -- Random entry from . local r=math.random(#rattab) -- Get value local j=rattab[r] - + table.remove(rattab, r) table.insert(done,j) - + -- Sum up the number of already distributed groups. local sN=sum(N, done) - -- Sum up the minimum number of yet to be distributed groups. + -- Sum up the minimum number of yet to be distributed groups. local sP=sum(P, rattab) - + -- Max number that can be distributed for this object. maxi[j]=nnew-sN-sP - + -- Min number that should be distributed for this object mini[j]=P[j] - + -- Random number of new groups for this RAT object. if maxi[j] >= mini[j] then N[j]=math.random(mini[j], maxi[j]) else N[j]=0 end - + -- Debug info self:T3(string.format("RATMANAGER: i=%d, alive=%d, min=%d, mini=%d, maxi=%d, add=%d, sumN=%d, sumP=%d", j, alive[j], min[j], mini[j], maxi[j], N[j],sN, sP)) - + end - + -- Last RAT object, number of groups is determined from number of already distributed groups and nnew. local j=rattab[1] N[j]=nnew-sum(N, done) @@ -6133,7 +6133,7 @@ function RATMANAGER:_RollDice(nrat,ntot,min,alive) maxi[j]=nnew-sum(N, done) table.remove(rattab, 1) table.insert(done,j) - + -- Debug info local text=RATMANAGER.id.."\n" for i=1,nrat do @@ -6141,8 +6141,7 @@ function RATMANAGER:_RollDice(nrat,ntot,min,alive) end text=text..string.format("Total # of groups to add = %d", sum(N, done)) self:T(text) - + -- Return number of groups to be spawned. return N end - diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index 736bc64e7..9e5db273e 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -32,7 +32,9 @@ -- -- === -- --- ## Missions: Example missions will be added later. +-- ## Missions: +-- +-- * [MAR - On the Range - MOOSE - SC](https://www.digitalcombatsimulator.com/en/files/3317765/) by shagrat -- -- === -- diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index 591c74d20..550305ddc 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -1,52 +1,52 @@ --- **Functional** -- Make SAM sites execute evasive and defensive behaviour when being fired upon. --- +-- -- === --- +-- -- ## Features: --- +-- -- * When SAM sites are being fired upon, the SAMs will take evasive action will reposition themselves when possible. -- * When SAM sites are being fired upon, the SAMs will take defensive action by shutting down their radars. --- +-- -- === --- +-- -- ## Missions: --- +-- -- [SEV - SEAD Evasion](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SEV%20-%20SEAD%20Evasion) --- +-- -- === --- +-- -- ### Authors: **FlightControl**, **applevangelist** --- +-- -- Last Update: Aug 2021 --- +-- -- === --- +-- -- @module Functional.Sead -- @image SEAD.JPG ---- +--- -- @type SEAD -- @extends Core.Base#BASE --- Make SAM sites execute evasive and defensive behaviour when being fired upon. --- +-- -- This class is very easy to use. Just setup a SEAD object by using @{#SEAD.New}() and SAMs will evade and take defensive action when being fired upon. --- +-- -- # Constructor: --- +-- -- Use the @{#SEAD.New}() constructor to create a new SEAD object. --- +-- -- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } ) --- +-- -- @field #SEAD SEAD = { - ClassName = "SEAD", + ClassName = "SEAD", TargetSkill = { Average = { Evade = 30, DelayOn = { 40, 60 } } , Good = { Evade = 20, DelayOn = { 30, 50 } } , High = { Evade = 15, DelayOn = { 20, 40 } } , - Excellent = { Evade = 10, DelayOn = { 10, 30 } } - }, + Excellent = { Evade = 10, DelayOn = { 10, 30 } } + }, SEADGroupPrefixes = {}, SuppressedGroups = {}, EngagementRange = 75, -- default 75% engagement range Feature Request #1355 @@ -69,7 +69,7 @@ SEAD = { ["X_31"] = "X_31", ["Kh25"] = "Kh25", } - + --- Missile enumerators - from DCS ME and Wikipedia -- @field HarmData SEAD.HarmData = { @@ -86,7 +86,7 @@ SEAD = { ["X_31"] = {150, 3}, ["Kh25"] = {25, 0.8}, } - + --- Creates the main object which is handling defensive actions for SA sites or moving SA vehicles. -- When an anti radiation missile is fired (KH-58, KH-31P, KH-31A, KH-25MPU, HARM missiles), the SA will shut down their radars and will take evasive actions... -- Chances are big that the missile will miss. @@ -102,7 +102,7 @@ function SEAD:New( SEADGroupPrefixes, Padding ) local self = BASE:Inherit( self, BASE:New() ) self:F( SEADGroupPrefixes ) - + if type( SEADGroupPrefixes ) == 'table' then for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix @@ -110,13 +110,13 @@ function SEAD:New( SEADGroupPrefixes, Padding ) else self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes end - + local padding = Padding or 10 if padding < 10 then padding = 10 end self.Padding = padding - + self:HandleEvent( EVENTS.Shot, self.HandleEventShot ) - + self:I("*** SEAD - Started Version 0.3.1") return self end @@ -128,7 +128,7 @@ end function SEAD:UpdateSet( SEADGroupPrefixes ) self:T( SEADGroupPrefixes ) - + if type( SEADGroupPrefixes ) == 'table' then for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix @@ -177,9 +177,9 @@ end local hit = false local name = "" for _,_name in pairs (SEAD.Harms) do - if string.find(WeaponName,_name,1) then + if string.find(WeaponName,_name,1) then hit = true - name = _name + name = _name break end end @@ -212,7 +212,7 @@ end return -1 end end - + --- Detects if an SAM site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME. -- @see SEAD -- @param #SEAD @@ -229,7 +229,7 @@ function SEAD:HandleEventShot( EventData ) self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName) --self:T({ SEADWeapon }) - + if self:_CheckHarms(SEADWeaponName) then self:T( '*** SEAD - Weapon Match' ) local _targetskill = "Random" @@ -253,7 +253,7 @@ function SEAD:HandleEventShot( EventData ) self:T( '*** SEAD - Group Match Found' ) break end - end + end if SEADGroupFound == true then -- yes we are being attacked if _targetskill == "Random" then -- when skill is random, choose a skill local Skills = { "Average", "Good", "High", "Excellent" } @@ -284,19 +284,19 @@ function SEAD:HandleEventShot( EventData ) else _distance = 0 end - + self:T( string.format("*** SEAD - target skill %s, distance %dkm, reach %dkm, tti %dsec", _targetskill, _distance,reach,_tti )) - + if reach >= _distance then self:T("*** SEAD - Shot in Reach") - + local function SuppressionStart(args) self:T(string.format("*** SEAD - %s Radar Off & Relocating",args[2])) local grp = args[1] -- Wrapper.Group#GROUP grp:OptionAlarmStateGreen() grp:RelocateGroundRandomInRadius(20,300,false,false,"Diamond") end - + local function SuppressionStop(args) self:T(string.format("*** SEAD - %s Radar On",args[2])) local grp = args[1] -- Wrapper.Group#GROUP @@ -304,22 +304,22 @@ function SEAD:HandleEventShot( EventData ) grp:OptionEngageRange(self.EngagementRange) self.SuppressedGroups[args[2]] = false end - + -- randomize switch-on time local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2]) if delay > _tti then delay = delay / 2 end -- speed up if _tti > (3*delay) then delay = (_tti / 2) * 0.9 end -- shot from afar - - local SuppressionStartTime = timer.getTime() + delay + + local SuppressionStartTime = timer.getTime() + delay local SuppressionEndTime = timer.getTime() + _tti + self.Padding - + if not self.SuppressedGroups[_targetgroupname] then self:T(string.format("*** SEAD - %s | Parameters TTI %ds | Switch-Off in %ds",_targetgroupname,_tti,delay)) timer.scheduleFunction(SuppressionStart,{_targetgroup,_targetgroupname},SuppressionStartTime) timer.scheduleFunction(SuppressionStop,{_targetgroup,_targetgroupname},SuppressionEndTime) self.SuppressedGroups[_targetgroupname] = true end - + end end end diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 97e6cb764..08839f4a6 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -32,6 +32,8 @@ -- * [USS George Washington](https://en.wikipedia.org/wiki/USS_George_Washington_(CVN-73)) (CVN-73) [Super Carrier Module] -- * [USS Harry S. Truman](https://en.wikipedia.org/wiki/USS_Harry_S._Truman) (CVN-75) [Super Carrier Module] -- * [USS Tarawa](https://en.wikipedia.org/wiki/USS_Tarawa_(LHA-1)) (LHA-1) [**WIP**] +-- * [USS America](https://en.wikipedia.org/wiki/USS_America_(LHA-6)) (LHA-6) [**WIP**] +-- * [Juan Carlos I](https://en.wikipedia.org/wiki/Spanish_amphibious_assault_ship_Juan_Carlos_I) (L61) [**WIP**] -- -- **Supported Aircraft:** -- @@ -48,8 +50,8 @@ -- -- At the moment, optimized parameters are available for the F/A-18C Hornet (Lot 20) and A-4E community mod as aircraft and the USS John C. Stennis as carrier. -- --- The AV-8B Harrier and the USS Tarawa are WIP. Those two can only be used together, i.e. the Tarawa is the only carrier the harrier is supposed to land on and --- the no other fixed wing aircraft (human or AI controlled) are supposed to land on the Tarawa. Currently only Case I is supported. Case II/III take slightly steps from the CVN carrier. +-- The AV-8B Harrier, the USS Tarawa, USS America and Juan Carlos I are WIP. The AV-8B harrier and the LHA's and LHD can only be used together, i.e. these ships are the only carriers the harrier is supposed to land on and +-- no other fixed wing aircraft (human or AI controlled) are supposed to land on these ships. Currently only Case I is supported. Case II/III take slightly different steps from the CVN carrier. -- However, the two Case II/III pattern are very similar so this is not a big drawback. -- -- Heatblur's mighty F-14B Tomcat has been added (March 13th 2019) as well. Same goes for the A version. @@ -102,12 +104,13 @@ -- ### 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 -- -- * [Harrier Ship Landing Mission with Auto LSO!](https://www.youtube.com/watch?v=lqmVvpunk2c) +-- * [Harrier Practice pattern USS America](https://youtu.be/99NigITYmcI) -- -- === -- @@ -295,6 +298,8 @@ -- ![Banner Image](..\Presentations\AIRBOSS\Airboss_Case1_Landing.png) -- -- Once the aircraft reaches the Initial, the landing pattern begins. The important steps of the pattern are shown in the image above. +-- The AV-8B Harrier pattern is very similar, the only differences are as there is no angled deck there is no wake check. from the ninety you wil fly a straight approach offset 26 ft to port (left) of the tram line. +-- The aim is to arrive abeam the landing spot in a stable hover at 120 ft with forward speed matched to the boat. From there the LSO will call "cleared to land". You then level cross to the tram line at the designated landing spot at land vertcally. -- -- -- ## CASE III @@ -919,9 +924,9 @@ -- -- ## Sound Packs -- --- The AIRBOSS currently has two different "sound packs" for both LSO and Marshal radios. These contain voice overs by different actors. +-- The AIRBOSS currently has two different "sound packs" for LSO and three different "sound Packs" for Marshal radios. These contain voice overs by different actors. -- These can be set by @{#AIRBOSS.SetVoiceOversLSOByRaynor}() and @{#AIRBOSS.SetVoiceOversMarshalByRaynor}(). These are the default settings. --- The other sound files can be set by @{#AIRBOSS.SetVoiceOversLSOByFF}() and @{#AIRBOSS.SetVoiceOversMarshalByFF}(). +-- The other sound files can be set by @{#AIRBOSS.SetVoiceOversLSOByFF}(), @{#AIRBOSS.SetVoiceOversMarshalByGabriella}() and @{#AIRBOSS.SetVoiceOversMarshalByFF}(). -- Also combinations can be used, e.g. -- -- airbossStennis:SetVoiceOversLSOByFF() @@ -1256,7 +1261,7 @@ AIRBOSS = { --- Aircraft types capable of landing on carrier (human+AI). -- @type AIRBOSS.AircraftCarrier --- @field #string AV8B AV-8B Night Harrier. Works only with the USS Tarawa. +-- @field #string AV8B AV-8B Night Harrier. Works only with the USS Tarawa, USS America and Juan Carlos I. -- @field #string A4EC A-4E Community mod. -- @field #string HORNET F/A-18C Lot 20 Hornet by Eagle Dynamics. -- @field #string F14A F-14A by Heatblur. @@ -1292,6 +1297,8 @@ AIRBOSS.AircraftCarrier={ -- @field #string TRUMAN USS Harry S. Truman (CVN-75) [Super Carrier Module] -- @field #string VINSON USS Carl Vinson (CVN-70) [Obsolete] -- @field #string TARAWA USS Tarawa (LHA-1) +-- @field #string AMERICA USS America (LHA-6) +-- @field #string JCARLOS Juan Carlos I (L61) -- @field #string KUZNETSOV Admiral Kuznetsov (CV 1143.5) AIRBOSS.CarrierType={ ROOSEVELT="CVN_71", @@ -1301,6 +1308,8 @@ AIRBOSS.CarrierType={ STENNIS="Stennis", VINSON="VINSON", TARAWA="LHA_Tarawa", + AMERICA="USS America LHA-6", + JCARLOS="L61", KUZNETSOV="KUZNECOW", } @@ -1420,8 +1429,8 @@ AIRBOSS.PatternStep={ -- @field #string IM "IM": In the middle. -- @field #string IC "IC": In close. -- @field #string AR "AR": At the ramp. --- @field #string AL "AL": Abeam landing position (Tarawa). --- @field #string LC "LC": Level crossing (Tarawa). +-- @field #string AL "AL": Abeam landing position (V/STOL). +-- @field #string LC "LC": Level crossing (V/STOL). -- @field #string IW "IW": In the wires. AIRBOSS.GroovePos={ X0="X0", @@ -1486,6 +1495,7 @@ AIRBOSS.GroovePos={ -- @field #AIRBOSS.RadioCall DEPARTANDREENTER "Depart and re-enter" call. -- @field #AIRBOSS.RadioCall EXPECTHEAVYWAVEOFF "Expect heavy wavoff" call. -- @field #AIRBOSS.RadioCall EXPECTSPOT75 "Expect spot 7.5" call. +-- @field #AIRBOSS.RadioCall EXPECTSPOT5 "Expect spot 5" call. -- @field #AIRBOSS.RadioCall FAST "You're fast" call. -- @field #AIRBOSS.RadioCall FOULDECK "Foul Deck" call. -- @field #AIRBOSS.RadioCall HIGH "You're high" call. @@ -1970,6 +1980,12 @@ function AIRBOSS:New(carriername, alias) elseif self.carriertype==AIRBOSS.CarrierType.TARAWA then -- Tarawa parameters. self:_InitTarawa() + elseif self.carriertype==AIRBOSS.CarrierType.AMERICA then + -- Use America parameters. + self:_InitAmerica() + elseif self.carriertype==AIRBOSS.CarrierType.JCARLOS then + -- Use Juan Carlos parameters. + self:_InitJcarlos() elseif self.carriertype==AIRBOSS.CarrierType.KUZNETSOV then -- Kusnetsov parameters - maybe... self:_InitStennis() @@ -2061,7 +2077,7 @@ function AIRBOSS:New(carriername, alias) -- Carrier specific. - if self.carrier:GetTypeName()~=AIRBOSS.CarrierType.TARAWA then + if self.carrier:GetTypeName()~=AIRBOSS.CarrierType.TARAWA or self.carrier:GetTypeName()~=AIRBOSS.CarrierType.AMERICA or self.carrier:GetTypeName()~=AIRBOSS.CarrierType.JCARLOS then -- Flare wires. local w1=stern:Translate(self.carrierparam.wire1, FB) @@ -2834,7 +2850,7 @@ function AIRBOSS:SetLineupErrorThresholds(_max,_min, Left, LeftMed, LEFT, Right, 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.RightMed=RightMed or 2.0 self.lue.RIGHT=RIGHT or 3.0 return self end @@ -4401,6 +4417,85 @@ function AIRBOSS:_InitTarawa() end +--- Init parameters for LHA-6 America carrier. +-- @param #AIRBOSS self +function AIRBOSS:_InitAmerica() + + -- Init Stennis as default. + self:_InitStennis() + + -- Carrier Parameters. + self.carrierparam.sterndist =-125 + self.carrierparam.deckheight = 20 --67 ft + + -- Total size of the carrier (approx as rectangle). + self.carrierparam.totlength=257 + self.carrierparam.totwidthport=11 + self.carrierparam.totwidthstarboard=25 + + -- Landing runway. + self.carrierparam.rwyangle = 0 + self.carrierparam.rwylength = 240 + self.carrierparam.rwywidth = 15 + + -- Wires. + self.carrierparam.wire1=nil + self.carrierparam.wire2=nil + self.carrierparam.wire3=nil + self.carrierparam.wire4=nil + + -- Late break. + self.BreakLate.name="Late Break" + self.BreakLate.Xmin=-UTILS.NMToMeters(1) -- Not more than 1 NM behind the boat. Last check was at 0. + self.BreakLate.Xmax= UTILS.NMToMeters(5) -- Not more than 5 NM in front of the boat. Enough for late breaks? + self.BreakLate.Zmin=-UTILS.NMToMeters(1.6) -- Not more than 1.6 NM port. + self.BreakLate.Zmax= UTILS.NMToMeters(1) -- Not more than 1 NM starboard. + self.BreakLate.LimitXmin= 0 -- Check and next step 0.8 NM port and in front of boat. + self.BreakLate.LimitXmax= nil + self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.5) -- 926 m port, closer than the stennis as abeam is 0.8-1.0 rather than 1.2 + self.BreakLate.LimitZmax= nil + +end + +--- Init parameters for L61 Juan Carlos carrier. +-- @param #AIRBOSS self +function AIRBOSS:_InitJcarlos() + + -- Init Stennis as default. + self:_InitStennis() + + -- Carrier Parameters. + self.carrierparam.sterndist =-125 + self.carrierparam.deckheight = 20 --67 ft + + -- Total size of the carrier (approx as rectangle). + self.carrierparam.totlength=231 + self.carrierparam.totwidthport=10 + self.carrierparam.totwidthstarboard=22 + + -- Landing runway. + self.carrierparam.rwyangle = 0 + self.carrierparam.rwylength = 202 + self.carrierparam.rwywidth = 14 + + -- Wires. + self.carrierparam.wire1=nil + self.carrierparam.wire2=nil + self.carrierparam.wire3=nil + self.carrierparam.wire4=nil + + -- Late break. + self.BreakLate.name="Late Break" + self.BreakLate.Xmin=-UTILS.NMToMeters(1) -- Not more than 1 NM behind the boat. Last check was at 0. + self.BreakLate.Xmax= UTILS.NMToMeters(5) -- Not more than 5 NM in front of the boat. Enough for late breaks? + self.BreakLate.Zmin=-UTILS.NMToMeters(1.6) -- Not more than 1.6 NM port. + self.BreakLate.Zmax= UTILS.NMToMeters(1) -- Not more than 1 NM starboard. + self.BreakLate.LimitXmin= 0 -- Check and next step 0.8 NM port and in front of boat. + self.BreakLate.LimitXmax= nil + self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.5) -- 926 m port, closer than the stennis as abeam is 0.8-1.0 rather than 1.2 + self.BreakLate.LimitZmax= nil + +end --- Init parameters for Marshal Voice overs *Gabriella* by HighwaymanEd. -- @param #AIRBOSS self -- @param #string mizfolder (Optional) Folder within miz file where the sound files are located. @@ -4555,6 +4650,7 @@ function AIRBOSS:SetVoiceOversLSOByRaynor(mizfolder) self.LSOCall.DEPARTANDREENTER.duration=1.10 self.LSOCall.EXPECTHEAVYWAVEOFF.duration=1.30 self.LSOCall.EXPECTSPOT75.duration=1.85 + self.LSOCall.EXPECTSPOT5.duration=1.3 self.LSOCall.FAST.duration=0.75 self.LSOCall.FOULDECK.duration=0.75 self.LSOCall.HIGH.duration=0.65 @@ -4613,6 +4709,7 @@ function AIRBOSS:SetVoiceOversLSOByFF(mizfolder) self.LSOCall.DEPARTANDREENTER.duration=1.10 self.LSOCall.EXPECTHEAVYWAVEOFF.duration=1.20 self.LSOCall.EXPECTSPOT75.duration=2.00 + self.LSOCall.EXPECTSPOT5.duration=1.3 self.LSOCall.FAST.duration=0.70 self.LSOCall.FOULDECK.duration=0.62 self.LSOCall.HIGH.duration=0.65 @@ -4880,6 +4977,14 @@ function AIRBOSS:_InitVoiceOvers() subtitle="Expect spot 7.5", duration=2.0, subduration=5, + }, + EXPECTSPOT5={ + file="LSO-ExpectSpot5", + suffix="ogg", + loud=false, + subtitle="Expect spot 5", + duration=1.3, + subduration=5, }, STABILIZED={ file="LSO-Stabilized", @@ -5540,14 +5645,14 @@ function AIRBOSS:_GetAircraftAoA(playerData) aoa.Fast = 8.25 --=17.5/2 aoa.FAST = 8.00 --=16.5/2 elseif harrier then - -- AV-8B Harrier parameters. This might need further tuning. + -- AV-8B Harrier parameters. Tuning done on the Fast AoA to allow for abeam and ninety at Nozzles 60 - 73. aoa.SLOW = 14.0 aoa.Slow = 13.0 aoa.OnSpeedMax = 12.0 aoa.OnSpeed = 11.0 aoa.OnSpeedMin = 10.0 - aoa.Fast = 9.0 - aoa.FAST = 8.0 + aoa.Fast = 8.0 + aoa.FAST = 7.5 end return aoa @@ -5807,7 +5912,7 @@ function AIRBOSS:_GetAircraftParameters(playerData, step) alt=UTILS.FeetToMeters(300) --? elseif harrier then -- 300-325 ft - alt=UTILS.FeetToMeters(300) + alt=UTILS.FeetToMeters(300)-- Need to verify end aoa=aoaac.OnSpeed @@ -6746,8 +6851,8 @@ function AIRBOSS:_GetMarshalAltitude(stack, case) -- Second point 1.5 NM ahead. p2=Carrier:Translate(UTILS.NMToMeters(1.5), hdg) - -- Tarawa Delta pattern. - if self.carriertype==AIRBOSS.CarrierType.TARAWA then + -- Tarawa,LHA,LHD Delta patterns. + if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS then -- Pattern is directly overhead the carrier. p1=Carrier:Translate(UTILS.NMToMeters(1.0), hdg+90) @@ -8592,7 +8697,7 @@ function AIRBOSS:OnEventLand(EventData) self:T(self.lid..text) -- Check carrier type. - if self.carriertype==AIRBOSS.CarrierType.TARAWA then + if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS then -- Power "Idle". self:RadioTransmission(self.LSORadio, self.LSOCall.IDLE, false, 1, nil, true) @@ -8627,7 +8732,7 @@ function AIRBOSS:OnEventLand(EventData) -- AI unit landed -- -------------------- - if self.carriertype~=AIRBOSS.CarrierType.TARAWA then + if self.carriertype~=AIRBOSS.CarrierType.TARAWA or self.carriertype~=AIRBOSS.CarrierType.AMERICA or self.carriertype~=AIRBOSS.CarrierType.JCARLOS then -- Coordinate at landing event local coord=EventData.IniUnit:GetCoordinate() @@ -9534,8 +9639,10 @@ function AIRBOSS:_Bullseye(playerData) -- Hint for player about altitude, AoA etc. self:_PlayerHint(playerData) - -- LSO expect spot 7.5 call - if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then + -- LSO expect spot 5 or 7.5 call + if playerData.actype==AIRBOSS.AircraftCarrier.AV8B and self.carriertype==AIRBOSS.CarrierType.JCARLOS then + self:RadioTransmission(self.LSORadio, self.LSOCall.EXPECTSPOT5, nil, nil, nil, true) + elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B then self:RadioTransmission(self.LSORadio, self.LSOCall.EXPECTSPOT75, nil, nil, nil, true) end @@ -9671,8 +9778,8 @@ function AIRBOSS:_CheckForLongDownwind(playerData) -- 1.6 NM from carrier is too far. local limit=UTILS.NMToMeters(-1.6) - -- For the tarawa we give a bit more space. - if self.carriertype==AIRBOSS.CarrierType.TARAWA then + -- For the tarawa, other LHA and LHD we give a bit more space. + if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS then limit=UTILS.NMToMeters(-2.0) end @@ -9717,8 +9824,10 @@ function AIRBOSS:_Abeam(playerData) -- Paddles contact. self:RadioTransmission(self.LSORadio, self.LSOCall.PADDLESCONTACT, nil, nil, nil, true) - -- LSO expect spot 7.5 call - if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then + -- LSO expect spot 5 or 7.5 call + if playerData.actype==AIRBOSS.AircraftCarrier.AV8B and self.carriertype==AIRBOSS.CarrierType.JCARLOS then + self:RadioTransmission(self.LSORadio, self.LSOCall.EXPECTSPOT5, false, 5, nil, true) + elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B then self:RadioTransmission(self.LSORadio, self.LSOCall.EXPECTSPOT75, false, 5, nil, true) end @@ -9755,7 +9864,7 @@ function AIRBOSS:_Ninety(playerData) self:_PlayerHint(playerData) -- Next step: wake. - if self.carriertype==AIRBOSS.CarrierType.TARAWA then + if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS then -- Harrier has no wake stop. It stays port of the boat. self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.FINAL) else @@ -10108,7 +10217,7 @@ function AIRBOSS:_Groove(playerData) -- Drift on lineup. if rho>=RAR and rho<=RIM then - if gd.LUE>0.22 and lineupError<-0.22 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)) @@ -10123,7 +10232,7 @@ function AIRBOSS:_Groove(playerData) 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)) + 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 @@ -10429,7 +10538,7 @@ function AIRBOSS:_GetSternCoord() --local stern=self:GetCoordinate() -- Stern coordinate (sterndist<0). - if self.carriertype==AIRBOSS.CarrierType.TARAWA then + if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS then -- Tarawa: Translate 8 meters port. self.sterncoord:Translate(self.carrierparam.sterndist, hdg, true, true):Translate(8, FB-90, true, true) elseif self.carriertype==AIRBOSS.CarrierType.STENNIS then @@ -10467,7 +10576,7 @@ 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 @@ -11172,7 +11281,7 @@ function AIRBOSS:_GetZoneHolding(case, stack) self.zoneHolding=ZONE_RADIUS:New("CASE I Holding Zone", Post:GetVec2(), self.marshalradius) -- Delta pattern. - if self.carriertype==AIRBOSS.CarrierType.TARAWA then + if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS then self.zoneHolding=ZONE_RADIUS:New("CASE I Holding Zone", self.carrier:GetVec2(), UTILS.NMToMeters(5)) end @@ -11225,7 +11334,7 @@ function AIRBOSS:_GetZoneCommence(case, stack) -- Three position local Three=self:GetCoordinate():Translate(D, hdg+275) - if self.carriertype==AIRBOSS.CarrierType.TARAWA then + if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS then local Dx=UTILS.NMToMeters(2.25) @@ -11516,7 +11625,7 @@ function AIRBOSS:_GetAltCarrier(unit) return h end ---- Get optimal landing position of the aircraft. Usually between second and third wire. In case of Tarawa we take the abeam landing spot 120 ft abeam the 7.5 position. +--- Get optimal landing position of the aircraft. Usually between second and third wire. In case of Tarawa and America we take the abeam landing spot 120 ft abeam the 7.5 position, for the Juan Carlos I it is 120 ft and abeam the 5 position. -- @param #AIRBOSS self -- @return Core.Point#COORDINATE Optimal landing coordinate. function AIRBOSS:_GetOptLandingCoordinate() @@ -11536,6 +11645,23 @@ function AIRBOSS:_GetOptLandingCoordinate() self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate()):Translate(35, FB-90, true, true) --stern=self:_GetLandingSpotCoordinate():Translate(35, FB-90) + -- Alitude 120 ft. + self.landingcoord:SetAltitude(UTILS.FeetToMeters(120)) + elseif self.carriertype==AIRBOSS.CarrierType.AMERICA then + + -- Landing 100 ft abeam, 120 ft alt. To allow adjustments to match different deck configurations. + self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate()):Translate(35, FB-90, true, true) + --stern=self:_GetLandingSpotCoordinate():Translate(35, FB-90) + + -- Alitude 120 ft. + self.landingcoord:SetAltitude(UTILS.FeetToMeters(120)) + + elseif self.carriertype==AIRBOSS.CarrierType.JCARLOS then + + -- Landing 100 ft abeam, 120 ft alt. + self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate()):Translate(35, FB-100, true, true) + --stern=self:_GetLandingSpotCoordinate():Translate(35, FB-100) + -- Alitude 120 ft. self.landingcoord:SetAltitude(UTILS.FeetToMeters(120)) @@ -11573,6 +11699,21 @@ function AIRBOSS:_GetLandingSpotCoordinate() -- Primary landing spot 7.5 self.landingspotcoord:Translate(57, hdg, true, true):SetAltitude(self.carrierparam.deckheight) + elseif self.carriertype==AIRBOSS.CarrierType.AMERICA then + + -- Landing 100 ft abeam, 120 alt. + local hdg=self:GetHeading() + + -- Primary landing spot 7.5 a little further forwad on the America + self.landingspotcoord:Translate(59, hdg, true, true):SetAltitude(self.carrierparam.deckheight) + + elseif self.carriertype==AIRBOSS.CarrierType.JCARLOS then + + -- Landing 100 ft abeam, 120 alt. + local hdg=self:GetHeading() + + -- Primary landing spot 5.0 -- TODO voice for different landing Spots. + self.landingspotcoord:Translate(89, hdg, true, true):SetAltitude(self.carrierparam.deckheight) end @@ -12065,6 +12206,11 @@ end -- * > 24 seconds: No Grade "--" -- -- If you manage to be between 16.4 and and 16.6 seconds, you will even get and okay underline "\_OK\_". +-- No groove time for Harrier on LHA, LHD set to Tgroove Unicorn as starting point to allow possible _OK_ 5.0. +-- If time in the AV-8B +-- +-- * < 90 seconds: OK V/STOL +-- * > 91 Seconds: SLOW V/STOL (Early hover stop selection) -- -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data table. @@ -12083,6 +12229,13 @@ function AIRBOSS:_EvalGrooveTime(playerData) grade="OK Groove" elseif t<=24 then grade="(LIG)" + -- Time in groove for AV-8B + elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B and t<55 then -- VSTOL Late Hover stop selection too fast to Abeam LDG Spot AV-8B. + grade="FAST V/STOL Groove" + elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B and t<90 then -- VSTOL Operations with AV-8B. + grade="OK V/STOL Groove" + elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B and t>=91 then -- VSTOL Early Hover stop selection slow to Abeam LDG Spot AV-8B. + grade="SLOW V/STOL Groove" else grade="LIG" end @@ -12092,6 +12245,11 @@ function AIRBOSS:_EvalGrooveTime(playerData) grade="_OK_" end + -- V/STOL Unicorn! + if playerData.actype==AIRBOSS.AircraftCarrier.AV8B and (t>=65.0 and t<=75.0) then + grade="_OK_ V/STOL" + end + return grade end @@ -12108,7 +12266,7 @@ function AIRBOSS:_LSOgrade(playerData) return select(2, string.gsub(base, pattern, "")) end - -- Analyse flight data and conver to LSO text. + -- Analyse flight data and convert to LSO text. local GXX,nXX=self:_Flightdata2Text(playerData, AIRBOSS.GroovePos.XX) local GIM,nIM=self:_Flightdata2Text(playerData, AIRBOSS.GroovePos.IM) local GIC,nIC=self:_Flightdata2Text(playerData, AIRBOSS.GroovePos.IC) @@ -12117,25 +12275,37 @@ function AIRBOSS:_LSOgrade(playerData) -- Put everything together. local G=GXX.." "..GIM.." ".." "..GIC.." "..GAR - -- Count number of minor, normal and major deviations. + -- Count number of minor, normal and major deviations. TODO - work on Harrier counts due slower approach speed. local N=nXX+nIM+nIC+nAR local nL=count(G, '_')/2 local nS=count(G, '%(') local nN=N-nS-nL - -- Groove time 15-18.99 sec for a unicorn. + -- Groove time 15-18.99 sec for a unicorn. Or 65-70 for V/STOL unicorn. local Tgroove=playerData.Tgroove local TgrooveUnicorn=Tgroove and (Tgroove>=15.0 and Tgroove<=18.99) or false + local TgrooveVstolUnicorn=Tgroove and (Tgroove>=65.0 and Tgroove<=70.0)and playerData.actype==AIRBOSS.AircraftCarrier.AV8B or false local grade local points - if N==0 and TgrooveUnicorn then + if N==0 and (TgrooveUnicorn or TgrooveVstolUnicorn ) then -- No deviations, should be REALLY RARE! grade="_OK_" points=5.0 G="Unicorn" else - if nL>0 then + + -- Add AV-8B Harrier devation allowances due to lower groundspeed and 3x conventional groove time, this allows to maintain LSO tolerances while respecting the deviations are not unsafe. (WIP requires feedback) + -- Large devaitions still result in a No Grade, A Unicorn still requires a clean pass with no deviation. + if nL>3 and playerData.actype==AIRBOSS.AircraftCarrier.AV8B then + -- Larger deviations ==> "No grade" 2.0 points. + grade="--" + points=2.0 + elseif nN>2 and playerData.actype==AIRBOSS.AircraftCarrier.AV8B then + -- Only average deviations ==> "Fair Pass" Pass with average deviations and corrections. + grade="(OK)" + points=3.0 + elseif nL>0 then -- Larger deviations ==> "No grade" 2.0 points. grade="--" points=2.0 @@ -12148,7 +12318,8 @@ function AIRBOSS:_LSOgrade(playerData) grade="OK" points=4.0 end - end + +end -- Replace" )"( and "__" G=G:gsub("%)%(", "") @@ -12258,35 +12429,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 + elseif LUE>self.lue.RightMed then P="AA " - elseif + 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 @@ -12356,7 +12527,7 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep) if P then G=G..P n=n - end + end -- Speed. if S then G=G..S @@ -12382,7 +12553,7 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep) G=G..O n=n+1 end - + -- Add current step. local step=self:_GS(step) step=step:gsub("XX","X") @@ -12444,7 +12615,7 @@ function AIRBOSS:_GS(step, n) if n==-1 then gp=AIRBOSS.GroovePos.IC elseif n==1 then - if self.carriertype==AIRBOSS.CarrierType.TARAWA then + if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS then gp=AIRBOSS.GroovePos.AL else gp=AIRBOSS.GroovePos.IW @@ -14334,17 +14505,17 @@ function AIRBOSS:_IsCarrierAircraft(unit) -- Get aircraft type name local aircrafttype=unit:GetTypeName() - -- Special case for Harrier which can only land on Tarawa. + -- Special case for Harrier which can only land on Tarawa, LHA and LHD. if aircrafttype==AIRBOSS.AircraftCarrier.AV8B then - if self.carriertype==AIRBOSS.CarrierType.TARAWA then + if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS then return true else return false end end - -- Also only Harriers can land on the Tarawa. - if self.carriertype==AIRBOSS.CarrierType.TARAWA then + -- Also only Harriers can land on the Tarawa, LHA and LHD. + if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS then if aircrafttype~=AIRBOSS.AircraftCarrier.AV8B then return false end @@ -17713,8 +17884,8 @@ function AIRBOSS:_MarkCaseZones(_unitName, flare) self:_GetZoneBullseye(case):FlareZone(FLARECOLOR.Green, 45) end - -- Tarawa landing spots. - if self.carriertype==AIRBOSS.CarrierType.TARAWA then + -- Tarawa, LHA and LHD landing spots. + if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS then text=text.."\n* abeam landing stop with RED flares" -- Abeam landing spot zone. local ALSPT=self:_GetZoneAbeamLandingSpot() diff --git a/Moose Development/Moose/Sound/RadioSpeech.lua b/Moose Development/Moose/Sound/RadioSpeech.lua index d16b92706..ae951053f 100644 --- a/Moose Development/Moose/Sound/RadioSpeech.lua +++ b/Moose Development/Moose/Sound/RadioSpeech.lua @@ -1,9 +1,9 @@ --- **Core** - Makes the radio talk. --- +-- -- === --- +-- -- ## Features: --- +-- -- * Send text strings using a vocabulary that is converted in spoken language. -- * Possiblity to implement multiple language. -- @@ -15,10 +15,10 @@ -- @image Core_Radio.JPG --- Makes the radio speak. --- +-- -- # RADIOSPEECH usage --- --- +-- +-- -- @type RADIOSPEECH -- @extends Core.RadioQueue#RADIOQUEUE RADIOSPEECH = { @@ -59,24 +59,24 @@ RADIOSPEECH.Vocabulary.EN = { ["70"] = { "70", 0.48 }, ["80"] = { "80", 0.26 }, ["90"] = { "90", 0.36 }, - ["100"] = { "100", 0.55 }, - ["200"] = { "200", 0.55 }, - ["300"] = { "300", 0.61 }, - ["400"] = { "400", 0.60 }, - ["500"] = { "500", 0.61 }, - ["600"] = { "600", 0.65 }, - ["700"] = { "700", 0.70 }, - ["800"] = { "800", 0.54 }, - ["900"] = { "900", 0.60 }, - ["1000"] = { "1000", 0.60 }, - ["2000"] = { "2000", 0.61 }, - ["3000"] = { "3000", 0.64 }, - ["4000"] = { "4000", 0.62 }, - ["5000"] = { "5000", 0.69 }, - ["6000"] = { "6000", 0.69 }, - ["7000"] = { "7000", 0.75 }, - ["8000"] = { "8000", 0.59 }, - ["9000"] = { "9000", 0.65 }, + ["100"] = { "100", 0.55 }, + ["200"] = { "200", 0.55 }, + ["300"] = { "300", 0.61 }, + ["400"] = { "400", 0.60 }, + ["500"] = { "500", 0.61 }, + ["600"] = { "600", 0.65 }, + ["700"] = { "700", 0.70 }, + ["800"] = { "800", 0.54 }, + ["900"] = { "900", 0.60 }, + ["1000"] = { "1000", 0.60 }, + ["2000"] = { "2000", 0.61 }, + ["3000"] = { "3000", 0.64 }, + ["4000"] = { "4000", 0.62 }, + ["5000"] = { "5000", 0.69 }, + ["6000"] = { "6000", 0.69 }, + ["7000"] = { "7000", 0.75 }, + ["8000"] = { "8000", 0.59 }, + ["9000"] = { "9000", 0.65 }, ["chevy"] = { "chevy", 0.35 }, ["colt"] = { "colt", 0.35 }, @@ -94,10 +94,10 @@ RADIOSPEECH.Vocabulary.EN = { ["meters"] = { "meters", 0.41 }, ["mi"] = { "miles", 0.45 }, ["feet"] = { "feet", 0.29 }, - + ["br"] = { "br", 1.1 }, ["bra"] = { "bra", 0.3 }, - + ["returning to base"] = { "returning_to_base", 0.85 }, ["on route to ground target"] = { "on_route_to_ground_target", 1.05 }, @@ -143,24 +143,24 @@ RADIOSPEECH.Vocabulary.RU = { ["70"] = { "70", 0.68 }, ["80"] = { "80", 0.84 }, ["90"] = { "90", 0.71 }, - ["100"] = { "100", 0.35 }, - ["200"] = { "200", 0.59 }, - ["300"] = { "300", 0.53 }, - ["400"] = { "400", 0.70 }, - ["500"] = { "500", 0.50 }, - ["600"] = { "600", 0.58 }, - ["700"] = { "700", 0.64 }, - ["800"] = { "800", 0.77 }, - ["900"] = { "900", 0.75 }, - ["1000"] = { "1000", 0.87 }, - ["2000"] = { "2000", 0.83 }, - ["3000"] = { "3000", 0.84 }, - ["4000"] = { "4000", 1.00 }, - ["5000"] = { "5000", 0.77 }, - ["6000"] = { "6000", 0.90 }, - ["7000"] = { "7000", 0.77 }, - ["8000"] = { "8000", 0.92 }, - ["9000"] = { "9000", 0.87 }, + ["100"] = { "100", 0.35 }, + ["200"] = { "200", 0.59 }, + ["300"] = { "300", 0.53 }, + ["400"] = { "400", 0.70 }, + ["500"] = { "500", 0.50 }, + ["600"] = { "600", 0.58 }, + ["700"] = { "700", 0.64 }, + ["800"] = { "800", 0.77 }, + ["900"] = { "900", 0.75 }, + ["1000"] = { "1000", 0.87 }, + ["2000"] = { "2000", 0.83 }, + ["3000"] = { "3000", 0.84 }, + ["4000"] = { "4000", 1.00 }, + ["5000"] = { "5000", 0.77 }, + ["6000"] = { "6000", 0.90 }, + ["7000"] = { "7000", 0.77 }, + ["8000"] = { "8000", 0.92 }, + ["9000"] = { "9000", 0.87 }, ["градусы"] = { "degrees", 0.5 }, ["километры"] = { "kilometers", 0.65 }, @@ -170,10 +170,10 @@ RADIOSPEECH.Vocabulary.RU = { ["метров"] = { "meters", 0.41 }, ["m"] = { "meters", 0.41 }, ["ноги"] = { "feet", 0.37 }, - + ["br"] = { "br", 1.1 }, ["bra"] = { "bra", 0.3 }, - + ["возвращение на базу"] = { "returning_to_base", 1.40 }, ["на пути к наземной цели"] = { "on_route_to_ground_target", 1.45 }, @@ -200,11 +200,11 @@ function RADIOSPEECH:New(frequency, modulation) -- Inherit base local self = BASE:Inherit( self, RADIOQUEUE:New( frequency, modulation ) ) -- #RADIOSPEECH - + self.Language = "EN" - + self:BuildTree() - + return self end @@ -262,7 +262,7 @@ end function RADIOSPEECH:BuildTree() self.Speech = {} - + for Language, Sentences in pairs( self.Vocabulary ) do self:I( { Language = Language, Sentences = Sentences }) self.Speech[Language] = {} @@ -271,7 +271,7 @@ function RADIOSPEECH:BuildTree() self:AddSentenceToSpeech( Sentence, self.Speech[Language], Sentence, Data ) end end - + self:I( { Speech = self.Speech } ) return self @@ -290,7 +290,7 @@ function RADIOSPEECH:SpeakWords( Sentence, Speech, Language ) local Word, RemainderSentence = Sentence:match( "^[., ]*([^ .,]+)(.*)" ) self:I( { Word = Word, Speech = Speech[Word], RemainderSentence = RemainderSentence } ) - + if Word then if Word ~= "" and tonumber(Word) == nil then @@ -302,7 +302,7 @@ function RADIOSPEECH:SpeakWords( Sentence, Speech, Language ) if Speech[Word].Next == nil then self:I( { Sentence = Speech[Word].Sentence, Data = Speech[Word].Data } ) self:NewTransmission( Speech[Word].Data[1] .. ".wav", Speech[Word].Data[2], Language .. "/" ) - else + else if RemainderSentence and RemainderSentence ~= "" then return self:SpeakWords( RemainderSentence, Speech[Word].Next, Language ) end @@ -310,11 +310,11 @@ function RADIOSPEECH:SpeakWords( Sentence, Speech, Language ) end return RemainderSentence end - return OriginalSentence + return OriginalSentence else return "" - end - + end + end --- Speak a sentence. @@ -333,7 +333,7 @@ function RADIOSPEECH:SpeakDigits( Sentence, Speech, Langauge ) if Digits then if Digits ~= "" and tonumber( Digits ) ~= nil then - + -- Construct numbers local Number = tonumber( Digits ) local Multiple = nil @@ -357,7 +357,7 @@ function RADIOSPEECH:SpeakDigits( Sentence, Speech, Langauge ) end return RemainderSentence end - return OriginalSentence + return OriginalSentence else return "" end @@ -374,26 +374,26 @@ function RADIOSPEECH:Speak( Sentence, Language ) self:I( { Sentence, Language } ) local Language = Language or "EN" - + self:I( { Language = Language } ) - + -- If there is no node for Speech, then we start at the first nodes of the language. local Speech = self.Speech[Language] - + self:I( { Speech = Speech, Language = Language } ) - + self:NewTransmission( "_In.wav", 0.52, Language .. "/" ) - + repeat Sentence = self:SpeakWords( Sentence, Speech, Language ) - + self:I( { Sentence = Sentence } ) Sentence = self:SpeakDigits( Sentence, Speech, Language ) self:I( { Sentence = Sentence } ) - + -- Sentence = self:SpeakSymbols( Sentence, Speech ) -- -- self:I( { Sentence = Sentence } ) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 15f6646f0..06d6affc3 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -767,12 +767,12 @@ end function UTILS.GetCharacters(str) local chars={} - + for i=1,#str do local c=str:sub(i,i) table.insert(chars, c) end - + return chars end @@ -1379,7 +1379,7 @@ function UTILS.GMTToLocalTimeDifference() elseif theatre==DCSMAP.Syria then return 3 -- Damascus is UTC+3 hours elseif theatre==DCSMAP.MarianaIslands then - return 10 -- Guam is UTC+10 hours. + return 10 -- Guam is UTC+10 hours. else BASE:E(string.format("ERROR: Unknown Map %s in UTILS.GMTToLocal function. Returning 0", tostring(theatre))) return 0 @@ -1577,17 +1577,17 @@ function UTILS.IsLoadingDoorOpen( unit_name ) local unit = Unit.getByName(unit_name) if unit ~= nil then local type_name = unit:getTypeName() - + if type_name == "Mi-8MT" and unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(86) == 1 or unit:getDrawArgumentValue(250) == 1 then BASE:T(unit_name .. " Cargo doors are open or cargo door not present") ret_val = true end - + if type_name == "Mi-24P" and unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(86) == 1 then BASE:T(unit_name .. " a side door is open") ret_val = true end - + if type_name == "UH-1H" and unit:getDrawArgumentValue(43) == 1 or unit:getDrawArgumentValue(44) == 1 then BASE:T(unit_name .. " a side door is open ") ret_val = true @@ -1602,9 +1602,9 @@ function UTILS.IsLoadingDoorOpen( unit_name ) BASE:T(unit_name .. " all doors are closed") end return ret_val - + end -- nil - + return nil end @@ -1643,13 +1643,13 @@ function UTILS.GenerateVHFrequencies() 905,907,920,935,942,950,995, 1000,1025,1030,1050,1065,1116,1175,1182,1210 } - + local FreeVHFFrequencies = {} - + -- first range local _start = 200000 while _start < 400000 do - + -- skip existing NDB frequencies# local _found = false for _, value in pairs(_skipFrequencies) do @@ -1663,7 +1663,7 @@ function UTILS.GenerateVHFrequencies() end _start = _start + 10000 end - + -- second range _start = 400000 while _start < 850000 do @@ -1680,7 +1680,7 @@ function UTILS.GenerateVHFrequencies() end _start = _start + 10000 end - + -- third range _start = 850000 while _start <= 999000 do -- adjusted for Gazelle @@ -1720,7 +1720,7 @@ end -- @return #table Laser Codes. function UTILS.GenerateLaserCodes() local jtacGeneratedLaserCodes = {} - + -- helper function local function ContainsDigit(_number, _numberToFind) local _thisNumber = _number @@ -1734,7 +1734,7 @@ function UTILS.GenerateLaserCodes() end return false end - + -- generate list of laser codes local _code = 1111 local _count = 1 diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 0f4eda499..0e3a157bd 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -547,6 +547,13 @@ function AIRBASE:Register(AirbaseName) self.isHelipad=true elseif self.category==Airbase.Category.SHIP then self.isShip=true + -- DCS bug: Oil rigs and gas platforms have category=2 (ship). Also they cannot be retrieved by coalition.getStaticObjects() + if self.descriptors.typeName=="Oil rig" or self.descriptors.typeName=="Ga" then + self.isHelipad=true + self.isShip=false + self.category=Airbase.Category.HELIPAD + _DATABASE:AddStatic(AirbaseName) + end else self:E("ERROR: Unknown airbase category!") end diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 223eab9e6..e50d143e3 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -1851,27 +1851,27 @@ do -- Patrol methods -- Calculate the new Route. local FromCoord = PatrolGroup:GetCoordinate() - + -- test for submarine local depth = 0 local IsSub = false if PatrolGroup:IsShip() then local navalvec3 = FromCoord:GetVec3() - if navalvec3.y < 0 then + if navalvec3.y < 0 then depth = navalvec3.y IsSub = true end end - - + + local Waypoint = Waypoints[1] local Speed = Waypoint.speed or (20 / 3.6) local From = FromCoord:WaypointGround( Speed ) - - if IsSub then + + if IsSub then From = FromCoord:WaypointNaval( Speed, Waypoint.alt ) end - + table.insert( Waypoints, 1, From ) local TaskRoute = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolRoute" ) @@ -1916,7 +1916,7 @@ do -- Patrol methods local IsSub = false if PatrolGroup:IsShip() then local navalvec3 = FromCoord:GetVec3() - if navalvec3.y < 0 then + if navalvec3.y < 0 then depth = navalvec3.y IsSub = true end @@ -1982,16 +1982,16 @@ do -- Patrol methods self:F( { PatrolGroup = PatrolGroup:GetName() } ) if PatrolGroup:IsGround() or PatrolGroup:IsShip() then - + -- Calculate the new Route. local FromCoord = PatrolGroup:GetCoordinate() - + -- test for submarine local depth = 0 local IsSub = false if PatrolGroup:IsShip() then local navalvec3 = FromCoord:GetVec3() - if navalvec3.y < 0 then + if navalvec3.y < 0 then depth = navalvec3.y IsSub = true end diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index 53db8e870..d21d58cac 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -598,16 +598,16 @@ function POSITIONABLE:GetHeading() if DCSPositionable then local PositionablePosition = DCSPositionable:getPosition() - + if PositionablePosition then local PositionableHeading = math.atan2( PositionablePosition.x.z, PositionablePosition.x.x ) - + if PositionableHeading < 0 then PositionableHeading = PositionableHeading + 2 * math.pi end - + PositionableHeading = PositionableHeading * 180 / math.pi - + return PositionableHeading end end @@ -1484,7 +1484,7 @@ do -- Cargo ["Dry-cargo ship-1"] = 70000, ["Dry-cargo ship-2"] = 70000, ["Higgins_boat"] = 3700, -- Higgins Boat can load 3700 kg of general cargo or 36 men (source wikipedia). - ["USS_Samuel_Chase"] = 25000, -- Let's say 25 tons for now. Wiki says 33 Higgins boats, which would be 264 tons (can't be right!) and/or 578 troops. + ["USS_Samuel_Chase"] = 25000, -- Let's say 25 tons for now. Wiki says 33 Higgins boats, which would be 264 tons (can't be right!) and/or 578 troops. ["LST_Mk2"] =2100000, -- Can carry 2100 tons according to wiki source! } self.__.CargoBayWeightLimit = ( Weights[Desc.typeName] or 50000 )