From cbc0579c7964f23f7c2b4943fa739cad7a585bdd Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Tue, 10 Oct 2017 23:30:22 +0200 Subject: [PATCH 01/25] Suppression Fire --- Moose Development/Moose/Core/Cargo.lua | 4 +- .../Moose/Functional/SuppressionFire.lua | 139 ++++++++++++++++++ Moose Mission Setup/Moose.files | 1 + Moose Mission Setup/Moose.lua | 3 +- 4 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 Moose Development/Moose/Functional/SuppressionFire.lua diff --git a/Moose Development/Moose/Core/Cargo.lua b/Moose Development/Moose/Core/Cargo.lua index 1dd1bee64..6ec648cd8 100644 --- a/Moose Development/Moose/Core/Cargo.lua +++ b/Moose Development/Moose/Core/Cargo.lua @@ -1331,7 +1331,9 @@ function CARGO_GROUP:onenterUnLoaded( From, Event, To, ToPointVec2, ... ) -- For each Cargo object within the CARGO_GROUP, route each object to the CargoLoadPointVec2 self.CargoSet:ForEach( function( Cargo ) - Cargo:UnLoad( ToPointVec2 ) + --Cargo:UnLoad( ToPointVec2 ) + local RandomVec2=ToPointVec2:GetRandomPointVec2InRadius(10) + Cargo:UnLoad( RandomVec2 ) end ) diff --git a/Moose Development/Moose/Functional/SuppressionFire.lua b/Moose Development/Moose/Functional/SuppressionFire.lua new file mode 100644 index 000000000..2efa62ab6 --- /dev/null +++ b/Moose Development/Moose/Functional/SuppressionFire.lua @@ -0,0 +1,139 @@ +--- +-- @module AI_Suppression + +--- @type AI_Suppression +-- @extends Core.Fsm#FSM_CONTROLLABLE +-- + +--TODO: Figure out who was shooting and move away from him. +--TODO: Move behind a scenery building if there is one nearby. +--TODO: Retreat to a given zone or point. +--TODO: + +-- @field AI_Suppression +AI_Suppression={} + +--- Creates a new AI_suppression object +-- @param #AI_Suppression self +-- @param Wrapper.Group#GROUP Group The GROUP object for which suppression should be applied. +-- @return #AI_Suppression +function AI_Suppression:New(Group) + env.info("Suppression Fire for group "..Group:GetName()) + + -- Inherits from FSM_CONTROLLABLE + local self=BASE:Inherit(self, FSM_CONTROLLABLE:New()) -- #AI_Suppression + + self:SetControllable(Group) + + self.life=self.Controllable:GetLife() + + self.Tsuppressed=0 + + -- Time the group is suppressed after being hit. + self.Tsuppress=40 + + self:SetStartState("CombatReady") + + self:AddTransition("*", "Status", "*") + + self:AddTransition("*", "Hit", "Suppressed") + + self:AddTransition("Suppressed", "Recovered", "CombatReady") + + self:AddTransition("*", "Hit", "TakeCover") + + -- Handle the event hit. + self:HandleEvent(EVENTS.Hit, self.OnEventHit) + + -- Handle the event dead. + self:HandleEvent(EVENTS.Dead, self.OnEventDead) + + --self:AddTransition("Suppressed", "Status", "CombatReady") + +end + + +--- Before status event. +-- @param #AI_Suppression self +function AI_Suppression:OnBeforeStatus() + return self.CheckStatus +end + +--- After status event. +-- @param #AI_Suppression self +function AI_Suppression:OnBeforeStatus() + self:__Status(10) +end + +--- After hit event. +-- @param #AI_Suppression self +function AI_Suppression:OnAfterHit(From, Event, To) + +end + +--- After hit event. +-- @param #AI_Suppression self +function AI_Suppression:OnAfterRecover(From, Event, To) + local Tnow=timer.getTime() + if Tnow-self.Tsuppressed > self.Tsuppress then + self:CombatReady() + end +end + +--- After hit event. +-- @param #AI_Suppression self +function AI_Suppression:OnEnterCombatReady(From, Event, To) + -- Group can fight again. + self.Controllable:OptionROEOpenFire() +end + +--- Entering suppressed state. +-- @param #AI_Suppression self +function AI_Suppression:OnEnterSuppressed(From, Event, To) + + local Tnow=timer.getTime() + + -- Group will hold their weapons. + self.Controllable:OptionROEHoldFire() + + + -- Recovery will be in Tsuppress seconds. + self:__Recover(self.Tsuppress) + + + if From=="CombatReady" then + + + elseif From=="Suppressed" then + + else + + end + +end + + + +--- @param #AI_Suppression self +-- @param Core.Event#EVENTDATA EventData +function AI_Suppression:OnEventHit(EventData) + self:E({"EventHit", EventData }) + env.info("Hitevent") + + if EventData.IniDCSUnit then + + --self:Hit() + + end +end + +--- @param #AI_Suppression self +-- @param Core.Event#EVENTDATA EventData +function AI_Suppression:OnEventDead(EventData) + self:E({"EventHit", EventData }) + env.info("Deadevent") + if EventData.IniDCSUnit then + --blabla + end +end + diff --git a/Moose Mission Setup/Moose.files b/Moose Mission Setup/Moose.files index 657ffe1bd..d5e5b233d 100644 --- a/Moose Mission Setup/Moose.files +++ b/Moose Mission Setup/Moose.files @@ -47,6 +47,7 @@ Functional/RAT.lua Functional/ZoneGoal.lua Functional/ZoneGoalCoalition.lua Functional/ZoneCaptureCoalition.lua +Functional/SuppressionFire.lua AI/AI_Balancer.lua AI/AI_A2A.lua diff --git a/Moose Mission Setup/Moose.lua b/Moose Mission Setup/Moose.lua index c80cf58af..a45a026a8 100644 --- a/Moose Mission Setup/Moose.lua +++ b/Moose Mission Setup/Moose.lua @@ -1,5 +1,5 @@ env.info( '*** MOOSE DYNAMIC INCLUDE START *** ' ) -env.info( 'Moose Generation Timestamp: 20171010_2141' ) +env.info( 'Moose Generation Timestamp: 20171010_2216' ) local base = _G @@ -67,6 +67,7 @@ __Moose.Include( 'Functional/RAT.lua' ) __Moose.Include( 'Functional/ZoneGoal.lua' ) __Moose.Include( 'Functional/ZoneGoalCoalition.lua' ) __Moose.Include( 'Functional/ZoneCaptureCoalition.lua' ) +__Moose.Include( 'Functional/SuppressionFire.lua' ) __Moose.Include( 'AI/AI_Balancer.lua' ) __Moose.Include( 'AI/AI_A2A.lua' ) __Moose.Include( 'AI/AI_A2A_Patrol.lua' ) From a575dfea7d8e8f4aaca706c88b3f749e2dfee47f Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Wed, 11 Oct 2017 17:27:25 +0200 Subject: [PATCH 02/25] Suppression Fire --- .../Moose/Functional/SuppressionFire.lua | 315 ++++++++++++++---- 1 file changed, 259 insertions(+), 56 deletions(-) diff --git a/Moose Development/Moose/Functional/SuppressionFire.lua b/Moose Development/Moose/Functional/SuppressionFire.lua index 2efa62ab6..fe5189288 100644 --- a/Moose Development/Moose/Functional/SuppressionFire.lua +++ b/Moose Development/Moose/Functional/SuppressionFire.lua @@ -1,120 +1,303 @@ ---- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- **Functional** - Suppress fire of ground units when they get hit. +-- +-- ==== +-- +-- When ground units get hit by (suppressive) enemy fire, they will not be able to shoot back for a certain amount of time. +-- +-- The implementation is based on an idea and script by MBot. See DCS forum threat https://forums.eagle.ru/showthread.php?t=107635 for details. +-- +-- ==== +-- +-- # Demo Missions +-- +-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases) +-- +-- ==== +-- +-- # YouTube Channel +-- +-- ### [MOOSE YouTube Channel](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl1jirWIo4t4YxqN-HxjqRkL) +-- +-- === +-- +-- ### Author: **[funkyfranky](https://forums.eagle.ru/member.php?u=115026)** +-- +-- ### Contributions: **Sven van de Velde ([FlightControl](https://forums.eagle.ru/member.php?u=89536))** +-- +-- ==== -- @module AI_Suppression ---- @type AI_Suppression +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- AI_Suppression class +-- @type AI_Suppression +-- @field #string ClassName Name of the class. +-- @field #number Tsuppress_min Minimum time in seconds the group gets suppressed. +-- @field #number Tsuppress_max Maximum time in seconds the group gets suppressed. +-- @field #number life Relative life in precent of the group. +-- @field #number Tsuppress Time in seconds the groups is suppressed. Randomly chosen between Tsuppress_min and Tsuppress_max. +-- @field #number Thit Last time the unit was hit. +-- @field #number Nhit Number of times the unit was hit since it last was in state "CombatReady". +-- @field Core.Zone#ZONE Zone_Retreat Zone into which a group retreats. +-- @field #number LifeMin Life of group in percent at which the group will be ordered to retreat. -- @extends Core.Fsm#FSM_CONTROLLABLE -- ---TODO: Figure out who was shooting and move away from him. +---# AI_Suppression class, extends @{Core.Fsm#FSM_CONTROLLABLE} +-- Mimic suppressive fire and make ground units take cover. +-- +-- ## Some Example... +-- +-- @field AI_Suppression +AI_Suppression={ + ClassName = "AI_Suppression", + Tsuppress_min = 5, + Tsuppress_max = 20, + Tsuppress = nil, + Thit = nil, + Nhit = 0, + Zone_Retreat = nil, + LifeMin = 10, +} + +--- Some ID to identify who we are in output of the DCS.log file. +-- @field #string id +AI_Suppression.id="SFX | " + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--TODO: Figure out who was shooting and move away from him <== not possible. --TODO: Move behind a scenery building if there is one nearby. --TODO: Retreat to a given zone or point. ---TODO: --- @field AI_Suppression -AI_Suppression={} +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Creates a new AI_suppression object +--- Creates a new AI_suppression object. -- @param #AI_Suppression self -- @param Wrapper.Group#GROUP Group The GROUP object for which suppression should be applied. -- @return #AI_Suppression function AI_Suppression:New(Group) - env.info("Suppression Fire for group "..Group:GetName()) + env.info("Suppression fire for group "..Group:GetName()) + + -- Check that we actually have a ground group. + if Group:IsGround()==false then + env.error("Suppression fire group "..Group:GetName().." has to be a GROUND group!") + return nil + end -- Inherits from FSM_CONTROLLABLE local self=BASE:Inherit(self, FSM_CONTROLLABLE:New()) -- #AI_Suppression + -- Set the controllable for the FSM. self:SetControllable(Group) - self.life=self.Controllable:GetLife() - - self.Tsuppressed=0 - - -- Time the group is suppressed after being hit. - self.Tsuppress=40 - + -- Group is initially in state CombatReady. self:SetStartState("CombatReady") - self:AddTransition("*", "Status", "*") + -- Transitions: + --------------- + -- Transition from anything to "Suppressed" after event "Hit". self:AddTransition("*", "Hit", "Suppressed") + -- Transition from "Suppressed" back to "CombatReady after the unit had time to recover. self:AddTransition("Suppressed", "Recovered", "CombatReady") - self:AddTransition("*", "Hit", "TakeCover") + -- Transition from "Suppressed" to "TakeCover" after event "Hit". + --self:AddTransition("Suppressed", "Hit", "TakeCover") - -- Handle the event hit. + -- Transition from anything to "Retreating" after e.g. being severely damaged. + self:AddTransition("*", "Retreat", "Retreating") + + -- Transition from anything to "Dead" after group died. + self:AddTransition("*", "Died", "Dead") + + + -- Handle DCS event hit. self:HandleEvent(EVENTS.Hit, self.OnEventHit) - -- Handle the event dead. + -- Handle DCS event dead. self:HandleEvent(EVENTS.Dead, self.OnEventDead) - --self:AddTransition("Suppressed", "Status", "CombatReady") + -- return self + return self + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Set minimum and (optionally) maximum time a unit is suppressed each time it gets hit. +-- @param #AI_Suppression self +-- @param #number Tmin Minimum time in seconds. +-- @param #number Tmax (Optional) Maximum suppression time. If no value is given, the is set to Tmin. +function AI_Suppression:SetSuppressionTime(Tmin, Tmax) + self.Tsuppress_min=Tmin + self.Tsuppress_max=Tmax or Tmin + env.info(AI_Suppression.id..string.format("Min suppression time %d seconds.", self.Tsuppress_min)) + env.info(AI_Suppression.id..string.format("Max suppression time %d seconds.", self.Tsuppress_max)) +end + +--- Set the zone to which a group retreats after being damaged too much. +-- @param #AI_Suppression self +-- @param Core.Zone#ZONE zone MOOSE zone object. +function AI_Suppression:SetRetreatZone(zone) + self.Zone_Retreat=zone + env.info(AI_Suppression.id..string.format("Retreat zone for group %s is %s.", self.Controllable:GetName(), self.Zone_Retreat:GetName())) +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Before "Hit" event. (Of course, this is not really before the group got hit.) +-- @param #AI_Suppression self +function AI_Suppression:OnBeforeHit(From, Event, To) + env.info(AI_Suppression.id..string.format("OnBeforeHit: From %s, Event %s, To %s", From, Event, To)) + -- Increase Hit counter. + self.Nhit=self.Nhit+1 + env.info(AI_Suppression.id..string.format("Group has just been hit %d times.", self.Nhit)) +end + +--- After "Hit" event. +-- @param #AI_Suppression self +function AI_Suppression:OnAfterHit(From, Event, To) + env.info(AI_Suppression.id..string.format("OnAfterHit: From %s, Event %s, To %s", From, Event, To)) + + -- Nothing to do yet. Just monitoring the event. + -- This should go into Suppressed state. +end + + +--- Before "Recovered" event. +-- @param #AI_Suppression self +function AI_Suppression:OnBeforeRecovered(From, Event, To) + env.info(AI_Suppression.id..string.format("OnBeforeRecovered: From %s, Event %s, To %s", From, Event, To)) + + -- Current time. + local Tnow=timer.getTime() + + -- Here I need to figure our how to correctly go back to "CombatReady". + -- Problem is that another "Hit" event might occur while the group is recovering. + -- If that happens the time to recover should be reset. + -- Only after a unit has not been hit for X seconds. + -- We can return false if the recovery should not be executed! + + env.info(AI_Suppression.id..string.format("OnBeforeRecover: Time: %d - Time over: %d", Tnow, self.TsuppressionOver)) + + -- Recovery is only possible if enough time since the last hit has passed. + if Tnow > self.TsuppressionOver then + return true + else + return false + end end - ---- Before status event. +--- After "Recovered" event. -- @param #AI_Suppression self -function AI_Suppression:OnBeforeStatus() - return self.CheckStatus +function AI_Suppression:OnAfterRecovered(From, Event, To) + env.info(AI_Suppression.id..string.format("OnAfterRecovered: From %s, Event %s, To %s", From, Event, To)) + MESSAGE:New("Group has recovered.", 30):ToAll() + -- Nothing to do yet. Just monitoring the event. end ---- After status event. --- @param #AI_Suppression self -function AI_Suppression:OnBeforeStatus() - self:__Status(10) -end ---- After hit event. +--- Before "Retreat" event. -- @param #AI_Suppression self -function AI_Suppression:OnAfterHit(From, Event, To) - -end - ---- After hit event. --- @param #AI_Suppression self -function AI_Suppression:OnAfterRecover(From, Event, To) - local Tnow=timer.getTime() - if Tnow-self.Tsuppressed > self.Tsuppress then - self:CombatReady() +function AI_Suppression:OnBeforeRetreat(From, Event, To) + env.info(AI_Suppression.id..string.format("OnBeforeRetreat: From %s, Event %s, To %s", From, Event, To)) + + -- Retreat is only possible if a zone has been defined by the user. + if self.Zone_Retreat==nil then + return false + else + return true end + end ---- After hit event. +--- After "Retreat" event. +-- @param #AI_Suppression self +function AI_Suppression:OnAfterRetreat(From, Event, To) + env.info(AI_Suppression.id..string.format("OnAfterHit: From %s, Event %s, To %s", From, Event, To)) + local text=string.format("Group %s is retreating to zone %s.", self.Controllable:GetName(), self.Zone_Retreat:GetName()) + MESSAGE:New(text, 30):ToAll() +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Entering "CombatReady" state. The group will be able to fight back. -- @param #AI_Suppression self function AI_Suppression:OnEnterCombatReady(From, Event, To) + env.info(AI_Suppression.id..string.format("OnEnterCombatReady: From %s, Event %s, To %s", From, Event, To)) + -- Group can fight again. self.Controllable:OptionROEOpenFire() + end ---- Entering suppressed state. +--- Leaving "CombatReady" state. +-- @param #AI_Suppression self +function AI_Suppression:OnLeaveCombatReady(From, Event, To) + env.info(AI_Suppression.id..string.format("OnLeaveCombatReady: From %s, Event %s, To %s", From, Event, To)) + + -- Nothing to do yet. Just monitoring the event +end + + +--- Entering "Suppressed" state. Group will not fight but hold their weapons. -- @param #AI_Suppression self function AI_Suppression:OnEnterSuppressed(From, Event, To) + env.info(AI_Suppression.id..string.format("OnEnterSuppressed: From %s, Event %s, To %s", From, Event, To)) + -- Current time. local Tnow=timer.getTime() -- Group will hold their weapons. self.Controllable:OptionROEHoldFire() + -- Get randomized time the unit is suppressed. + self.Tsuppress=math.random(self.Tsuppress_min, self.Tsuppress_max) - -- Recovery will be in Tsuppress seconds. - self:__Recover(self.Tsuppress) + -- Time at which the suppression is over. + self.TsuppressionOver=Tnow+self.Tsuppress - - if From=="CombatReady" then + -- Recovery event will be called in Tsuppress seconds. (We add one second to be sure the time has really passed when recovery is checked.) + self:__Recover(self.Tsuppress+1) - - elseif From=="Suppressed" then - - else + -- Get life of group in %. + self.life=self:_GetLife(self.Controllable) + -- If life is below threshold, the group is ordered to retreat (if a zone has been specified). + if self.life < self.LifeMin then + self:Retreat() end - + end +--- Entering "Retreating" state. Group will be send to a zone. +-- @param #AI_Suppression self +function AI_Suppression:OnEnterRetreating(From, Event, To) + env.info(AI_Suppression.id..string.format("OnEnterRetreating: From %s, Event %s, To %s", From, Event, To)) ---- @param #AI_Suppression self + --TODO: Here we need to set the ALARM STATE to GREEN. Then the unit can move even if it is under fire. + --self.Controllable:OptionROEOpenFire() + + --TODO: Route the group to a zone. + +end + +--- Leaving "Retreating" state. +-- @param #AI_Suppression self +function AI_Suppression:OnLeaveRetreating(From, Event, To) + env.info(AI_Suppression.id..string.format("OnLeaveRetreating: From %s, Event %s, To %s", From, Event, To)) + + -- TODO: Here we need to set the ALARM STATE back to AUTO. +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Handle the DCS event hit. +-- @param #AI_Suppression self -- @param Core.Event#EVENTDATA EventData function AI_Suppression:OnEventHit(EventData) self:E({"EventHit", EventData }) @@ -122,18 +305,38 @@ function AI_Suppression:OnEventHit(EventData) if EventData.IniDCSUnit then - --self:Hit() + -- Call "Hit" event. + self:Hit() end end ---- @param #AI_Suppression self +--- Handle the DCS event dead. +-- @param #AI_Suppression self -- @param Core.Event#EVENTDATA EventData function AI_Suppression:OnEventDead(EventData) - self:E({"EventHit", EventData }) + self:E({"EventDead", EventData }) env.info("Deadevent") if EventData.IniDCSUnit then - --blabla + --self:Died() end end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Get (relative) life of first unit of a group. +-- @param #AI_Suppression self +-- @param Wrapper.Group#GROUP group Group of unit. +-- @return #number Life of unit in percent. +function AI_Suppression:_GetLife(group) + local life=0.0 + if group and group:IsAlive() then + local unit=group:GetUnit(1) + if unit then + life=unit:GetLife()/unit:GetLife0()*100 + end + end + return life +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file From 3a0f60adc98343ada7a197bf37ab91312545d82f Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Thu, 12 Oct 2017 00:36:21 +0200 Subject: [PATCH 03/25] Suppression Fire and Alarm State --- .../Moose/Functional/SuppressionFire.lua | 159 ++++++++++++------ .../Moose/Wrapper/Controllable.lua | 66 ++++++++ 2 files changed, 175 insertions(+), 50 deletions(-) diff --git a/Moose Development/Moose/Functional/SuppressionFire.lua b/Moose Development/Moose/Functional/SuppressionFire.lua index fe5189288..8cc071725 100644 --- a/Moose Development/Moose/Functional/SuppressionFire.lua +++ b/Moose Development/Moose/Functional/SuppressionFire.lua @@ -26,9 +26,10 @@ -- ### Contributions: **Sven van de Velde ([FlightControl](https://forums.eagle.ru/member.php?u=89536))** -- -- ==== --- @module AI_Suppression +-- @module ai_suppression ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + --- AI_Suppression class -- @type AI_Suppression -- @field #string ClassName Name of the class. @@ -48,7 +49,7 @@ -- -- ## Some Example... -- --- @field AI_Suppression +-- @field #AI_Suppression AI_Suppression={ ClassName = "AI_Suppression", Tsuppress_min = 5, @@ -66,7 +67,7 @@ AI_Suppression.id="SFX | " ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---TODO: Figure out who was shooting and move away from him <== not possible. +--TODO: Figure out who was shooting and move away from him <== Not possible! --TODO: Move behind a scenery building if there is one nearby. --TODO: Retreat to a given zone or point. @@ -77,9 +78,16 @@ AI_Suppression.id="SFX | " -- @param Wrapper.Group#GROUP Group The GROUP object for which suppression should be applied. -- @return #AI_Suppression function AI_Suppression:New(Group) - env.info("Suppression fire for group "..Group:GetName()) + + -- Check that group is present. + if Group then + env.info("Suppression fire for group "..Group:GetName()) + else + env.info("Suppression fire: Group does not exist!") + return nil + end - -- Check that we actually have a ground group. + -- Check that we actually have a GROUND group. if Group:IsGround()==false then env.error("Suppression fire group "..Group:GetName().." has to be a GROUND group!") return nil @@ -104,7 +112,7 @@ function AI_Suppression:New(Group) self:AddTransition("Suppressed", "Recovered", "CombatReady") -- Transition from "Suppressed" to "TakeCover" after event "Hit". - --self:AddTransition("Suppressed", "Hit", "TakeCover") + --self:AddTransition("Suppressed", "TakeCover", "Hiding") -- Transition from anything to "Retreating" after e.g. being severely damaged. self:AddTransition("*", "Retreat", "Retreating") @@ -149,27 +157,29 @@ end --- Before "Hit" event. (Of course, this is not really before the group got hit.) -- @param #AI_Suppression self -function AI_Suppression:OnBeforeHit(From, Event, To) - env.info(AI_Suppression.id..string.format("OnBeforeHit: From %s, Event %s, To %s", From, Event, To)) +function AI_Suppression:OnBeforeHit(Controlable, From, Event, To) + env.info(AI_Suppression.id..string.format("OnBeforeHit: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) + -- Increase Hit counter. self.Nhit=self.Nhit+1 + + -- Info on hit times. env.info(AI_Suppression.id..string.format("Group has just been hit %d times.", self.Nhit)) end --- After "Hit" event. -- @param #AI_Suppression self -function AI_Suppression:OnAfterHit(From, Event, To) - env.info(AI_Suppression.id..string.format("OnAfterHit: From %s, Event %s, To %s", From, Event, To)) - +function AI_Suppression:OnAfterHit(Controlable, From, Event, To) + env.info(AI_Suppression.id..string.format("OnAfterHit: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) + -- Nothing to do yet. Just monitoring the event. - -- This should go into Suppressed state. end --- Before "Recovered" event. -- @param #AI_Suppression self -function AI_Suppression:OnBeforeRecovered(From, Event, To) - env.info(AI_Suppression.id..string.format("OnBeforeRecovered: From %s, Event %s, To %s", From, Event, To)) +function AI_Suppression:OnBeforeRecovered(Controlable, From, Event, To) + env.info(AI_Suppression.id..string.format("OnBeforeRecovered: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) -- Current time. local Tnow=timer.getTime() @@ -180,7 +190,7 @@ function AI_Suppression:OnBeforeRecovered(From, Event, To) -- Only after a unit has not been hit for X seconds. -- We can return false if the recovery should not be executed! - env.info(AI_Suppression.id..string.format("OnBeforeRecover: Time: %d - Time over: %d", Tnow, self.TsuppressionOver)) + env.info(AI_Suppression.id..string.format("OnBeforeRecovered: Time: %d - Time over: %d", Tnow, self.TsuppressionOver)) -- Recovery is only possible if enough time since the last hit has passed. if Tnow > self.TsuppressionOver then @@ -193,22 +203,27 @@ end --- After "Recovered" event. -- @param #AI_Suppression self -function AI_Suppression:OnAfterRecovered(From, Event, To) - env.info(AI_Suppression.id..string.format("OnAfterRecovered: From %s, Event %s, To %s", From, Event, To)) - MESSAGE:New("Group has recovered.", 30):ToAll() +function AI_Suppression:OnAfterRecovered(Controlable, From, Event, To) + env.info(AI_Suppression.id..string.format("OnAfterRecovered: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) + + -- Send message. + MESSAGE:New(string.format("Group %s has recovered.", Controlable:GetName()), 30):ToAll() + -- Nothing to do yet. Just monitoring the event. end --- Before "Retreat" event. -- @param #AI_Suppression self -function AI_Suppression:OnBeforeRetreat(From, Event, To) - env.info(AI_Suppression.id..string.format("OnBeforeRetreat: From %s, Event %s, To %s", From, Event, To)) - +function AI_Suppression:OnBeforeRetreat(Controlable, From, Event, To) + env.info(AI_Suppression.id..string.format("OnBeforeRetreat: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) + -- Retreat is only possible if a zone has been defined by the user. if self.Zone_Retreat==nil then + env.info("SFX: Retreat NOT possible! No Zone specified.") return false else + env.info("SFX: Retreat possible, zone specified.") return true end @@ -216,9 +231,11 @@ end --- After "Retreat" event. -- @param #AI_Suppression self -function AI_Suppression:OnAfterRetreat(From, Event, To) - env.info(AI_Suppression.id..string.format("OnAfterHit: From %s, Event %s, To %s", From, Event, To)) - local text=string.format("Group %s is retreating to zone %s.", self.Controllable:GetName(), self.Zone_Retreat:GetName()) +function AI_Suppression:OnAfterRetreat(Controlable, From, Event, To) + env.info(AI_Suppression.id..string.format("OnAfterRetreat: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) + + -- Message. + local text=string.format("Group %s is retreating to zone %s.", Controlable:GetName(), self.Zone_Retreat:GetName()) MESSAGE:New(text, 30):ToAll() end @@ -226,8 +243,8 @@ end --- Entering "CombatReady" state. The group will be able to fight back. -- @param #AI_Suppression self -function AI_Suppression:OnEnterCombatReady(From, Event, To) - env.info(AI_Suppression.id..string.format("OnEnterCombatReady: From %s, Event %s, To %s", From, Event, To)) +function AI_Suppression:OnEnterCombatReady(Controlable, From, Event, To) + env.info(AI_Suppression.id..string.format("OnEnterCombatReady: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) -- Group can fight again. self.Controllable:OptionROEOpenFire() @@ -236,8 +253,8 @@ end --- Leaving "CombatReady" state. -- @param #AI_Suppression self -function AI_Suppression:OnLeaveCombatReady(From, Event, To) - env.info(AI_Suppression.id..string.format("OnLeaveCombatReady: From %s, Event %s, To %s", From, Event, To)) +function AI_Suppression:OnLeaveCombatReady(Controlable, From, Event, To) + env.info(AI_Suppression.id..string.format("OnLeaveCombatReady: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) -- Nothing to do yet. Just monitoring the event end @@ -245,14 +262,14 @@ end --- Entering "Suppressed" state. Group will not fight but hold their weapons. -- @param #AI_Suppression self -function AI_Suppression:OnEnterSuppressed(From, Event, To) - env.info(AI_Suppression.id..string.format("OnEnterSuppressed: From %s, Event %s, To %s", From, Event, To)) +function AI_Suppression:OnEnterSuppressed(Controlable, From, Event, To) + env.info(AI_Suppression.id..string.format("OnEnterSuppression: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) -- Current time. local Tnow=timer.getTime() -- Group will hold their weapons. - self.Controllable:OptionROEHoldFire() + Controlable:OptionROEHoldFire() -- Get randomized time the unit is suppressed. self.Tsuppress=math.random(self.Tsuppress_min, self.Tsuppress_max) @@ -261,13 +278,13 @@ function AI_Suppression:OnEnterSuppressed(From, Event, To) self.TsuppressionOver=Tnow+self.Tsuppress -- Recovery event will be called in Tsuppress seconds. (We add one second to be sure the time has really passed when recovery is checked.) - self:__Recover(self.Tsuppress+1) + self:__Recovered(self.Tsuppress+1) -- Get life of group in %. - self.life=self:_GetLife(self.Controllable) + local life_min, life_max, life_ave=self:_GetLife() - -- If life is below threshold, the group is ordered to retreat (if a zone has been specified). - if self.life < self.LifeMin then + -- If life of one unit is below threshold, the group is ordered to retreat (if a zone has been specified). + if life_min < self.LifeMin then self:Retreat() end @@ -276,22 +293,27 @@ end --- Entering "Retreating" state. Group will be send to a zone. -- @param #AI_Suppression self -function AI_Suppression:OnEnterRetreating(From, Event, To) - env.info(AI_Suppression.id..string.format("OnEnterRetreating: From %s, Event %s, To %s", From, Event, To)) +-- @param Wrapper.Controllable#CONTROLLABLE Controlable Controllable of the AI group. +function AI_Suppression:OnEnterRetreating(Controlable, From, Event, To) + env.info(AI_Suppression.id..string.format("OnEnterRetreating: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) - --TODO: Here we need to set the ALARM STATE to GREEN. Then the unit can move even if it is under fire. - --self.Controllable:OptionROEOpenFire() + -- Set the ALARM STATE to GREEN. Then the unit can move even if it is under fire. + Controlable:OptionAlarmStateGreen() --TODO: Route the group to a zone. - + MESSAGE:New(string.format("Group %s would be(!) retreating to retreat zone!", Controlable:GetName()), 30):ToAll() + self:_Retreat(self.Zone_Retreat, 50, "Vee") + end --- Leaving "Retreating" state. -- @param #AI_Suppression self -function AI_Suppression:OnLeaveRetreating(From, Event, To) - env.info(AI_Suppression.id..string.format("OnLeaveRetreating: From %s, Event %s, To %s", From, Event, To)) +-- @param Wrapper.Controllable#CONTROLLABLE Controlable Controllable of the AI group. +function AI_Suppression:OnLeaveRetreating(Controlable, From, Event, To) + env.info(AI_Suppression.id..string.format("OnLeveRetreating: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) - -- TODO: Here we need to set the ALARM STATE back to AUTO. + -- Set the ALARM STATE back to AUTO. + Controlable:OptionAlarmStateAuto() end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -304,7 +326,6 @@ function AI_Suppression:OnEventHit(EventData) env.info("Hitevent") if EventData.IniDCSUnit then - -- Call "Hit" event. self:Hit() @@ -328,15 +349,53 @@ end -- @param #AI_Suppression self -- @param Wrapper.Group#GROUP group Group of unit. -- @return #number Life of unit in percent. -function AI_Suppression:_GetLife(group) - local life=0.0 +function AI_Suppression:_GetLife() + local group=self.Controllable if group and group:IsAlive() then - local unit=group:GetUnit(1) - if unit then - life=unit:GetLife()/unit:GetLife0()*100 + local life_min=100 + local life_max=0 + local life_ave=0 + local n=0 + local units=group:GetUnits() + for _,unit in pairs(units) do + if unit then + n=n+1 + local life=unit:GetLife()/unit:GetLife0()*100 + if life < life_min then + life_min=life + end + if life > life_max then + life_max=life + end + life_ave=life_ave+life + end end + life_ave=life_ave/n + return life_min, life_max, life_ave + else + return 0, 0, 0 end - return life +end + + +--- Retreat to a zone. +-- @param #AI_Suppression self +-- @param Core.Zone#ZONE zone Zone to which the group retreats. +-- @param #number speed Speed of the group. Default max speed the specific group can do. +-- @param #string formation Formation of the Group. Default "Vee". +function AI_Suppression:_Retreat(zone, speed, formation) + + -- Set zone, speed and formation if they are not given + zone=zone or self.Zone_Retreat + speed = speed or 999 + formation = formation or "Vee" + + -- Get a random point in the retreat zone. + local ZonePoint=zone:GetRandomPointVec2() + + -- Set task to go to zone. + self.Controllable:TaskRouteToVec2(ZonePoint, speed, formation) + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 0d381f6cc..fc70b10e4 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -2537,6 +2537,72 @@ function CONTROLLABLE:OptionROTVertical() return nil end +--- Alarm state to Auto: AI will automatically switch alarm states based on the presence of threats. The AI kind of cheats in this regard. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionAlarmStateAuto() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + + if self:IsGround() then + Controller:setOption(AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.AUTO) + elseif self:IsShip() then + Controller:setOption(AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.AUTO) + end + + return self + end + + return nil +end + +--- Alarm state to Green: Group is not combat ready. Sensors are stowed if possible. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionAlarmStateGreen() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + + if self:IsGround() then + Controller:setOption(AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.GREEN) + elseif self:IsShip() then + Controller:setOption(AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.GREEN) + end + + return self + end + + return nil +end + +--- Alarm state to Red: Group is combat ready and actively searching for targets. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionAlarmStateRed() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + + if self:IsGround() then + Controller:setOption(AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.RED) + elseif self:IsShip() then + Controller:setOption(AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.RED) + end + + return self + end + + return nil +end + --- Set RTB on bingo fuel. -- @param #CONTROLLABLE self From 88260ae4f30746792d339473ca9c48c518e427fb Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Thu, 12 Oct 2017 14:55:50 +0200 Subject: [PATCH 04/25] Suppression Fire --- .../Moose/Functional/SuppressionFire.lua | 109 +++++++++++++++--- 1 file changed, 96 insertions(+), 13 deletions(-) diff --git a/Moose Development/Moose/Functional/SuppressionFire.lua b/Moose Development/Moose/Functional/SuppressionFire.lua index 8cc071725..6a02a0d90 100644 --- a/Moose Development/Moose/Functional/SuppressionFire.lua +++ b/Moose Development/Moose/Functional/SuppressionFire.lua @@ -58,7 +58,7 @@ AI_Suppression={ Thit = nil, Nhit = 0, Zone_Retreat = nil, - LifeMin = 10, + LifeMin = 25, } --- Some ID to identify who we are in output of the DCS.log file. @@ -111,15 +111,18 @@ function AI_Suppression:New(Group) -- Transition from "Suppressed" back to "CombatReady after the unit had time to recover. self:AddTransition("Suppressed", "Recovered", "CombatReady") - -- Transition from "Suppressed" to "TakeCover" after event "Hit". - --self:AddTransition("Suppressed", "TakeCover", "Hiding") + -- Transition from "Suppressed" to "Hiding" after event "Hit". + self:AddTransition("Suppressed", "TakeCover", "Hiding") -- Transition from anything to "Retreating" after e.g. being severely damaged. self:AddTransition("*", "Retreat", "Retreating") -- Transition from anything to "Dead" after group died. self:AddTransition("*", "Died", "Dead") - + + -- Check status of the group. + self:AddTransition("*", "Status", "*") + -- Handle DCS event hit. self:HandleEvent(EVENTS.Hit, self.OnEventHit) @@ -139,7 +142,7 @@ end -- @param #number Tmin Minimum time in seconds. -- @param #number Tmax (Optional) Maximum suppression time. If no value is given, the is set to Tmin. function AI_Suppression:SetSuppressionTime(Tmin, Tmax) - self.Tsuppress_min=Tmin + self.Tsuppress_min=Tmin or 1 self.Tsuppress_max=Tmax or Tmin env.info(AI_Suppression.id..string.format("Min suppression time %d seconds.", self.Tsuppress_min)) env.info(AI_Suppression.id..string.format("Max suppression time %d seconds.", self.Tsuppress_max)) @@ -155,6 +158,23 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Before "Status" event. +-- @param #AI_Suppression self +function AI_Suppression:OnBeforeStatus(Controlable, From, Event, To) + env.info(AI_Suppression.id..string.format("OnBeforeStatus: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) + local text=string.format("Group %s is in state %s.", Controlable:GetName(), self:GetState()) + MESSAGE:New(text, 10):ToAll() +end + +--- After "Status" event. +-- @param #AI_Suppression self +function AI_Suppression:OnAfterStatus(Controlable, From, Event, To) + env.info(AI_Suppression.id..string.format("OnAfterStatus: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) + -- Get new status in 30 sec. + self:__Status(30) +end + + --- Before "Hit" event. (Of course, this is not really before the group got hit.) -- @param #AI_Suppression self function AI_Suppression:OnBeforeHit(Controlable, From, Event, To) @@ -239,6 +259,40 @@ function AI_Suppression:OnAfterRetreat(Controlable, From, Event, To) MESSAGE:New(text, 30):ToAll() end +--- Before "TakeCover" event. +-- @param #AI_Suppression self +function AI_Suppression:OnBeforeTakeCover(Controlable, From, Event, To) + env.info(AI_Suppression.id..string.format("OnBeforeTakeCover: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) + + -- We search objects in a zone with radius 100 m around the group. + -- TODO: Maybe make the zone radius larger for vehicles. + local Zone = ZONE_GROUP:New("Zone_Hiding", Controlable, 100) + + -- Scan for Scenery objects to run/drive to. + Zone:Scan( Object.Category.SCENERY ) + + local gothideout=false + for SceneryTypeName, SceneryData in pairs( Zone:GetScannedScenery() ) do + for SceneryName, SceneryObject in pairs( SceneryData ) do + local SceneryObject = SceneryObject -- Wrapper.Scenery#SCENERY + MESSAGE:NewType( "Scenery: " .. SceneryObject:GetTypeName() .. ", Coord LL DMS: " .. SceneryObject:GetCoordinate():ToStringLLDMS(), MESSAGE.Type.Information ):ToAll() + -- TODO: Add check if scenery name matches a specific type like tree or building. This might be tricky though! + end + end + + -- Only take cover if we found a hideout. + return gothideout + +end + +--- After "TakeCover" event. +-- @param #AI_Suppression self +function AI_Suppression:OnBeforeTakeCover(Controlable, From, Event, To) + env.info(AI_Suppression.id..string.format("OnAfterTakeCover: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) + local text=string.format("Group %s is taking cover!", Controlable:GetName()) + MESSAGE:New(text, 30):ToAll() +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Entering "CombatReady" state. The group will be able to fight back. @@ -297,12 +351,12 @@ end function AI_Suppression:OnEnterRetreating(Controlable, From, Event, To) env.info(AI_Suppression.id..string.format("OnEnterRetreating: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) - -- Set the ALARM STATE to GREEN. Then the unit can move even if it is under fire. + -- Set the ALARM STATE to GREEN. Then the unit will move even if it is under fire. Controlable:OptionAlarmStateGreen() - --TODO: Route the group to a zone. - MESSAGE:New(string.format("Group %s would be(!) retreating to retreat zone!", Controlable:GetName()), 30):ToAll() - self:_Retreat(self.Zone_Retreat, 50, "Vee") + -- Route the group to a zone. + MESSAGE:New(string.format("Group %s is retreating!", Controlable:GetName()), 30):ToAll() + self:_RetreatToZone(self.Zone_Retreat, 50, "Vee") end @@ -316,6 +370,33 @@ function AI_Suppression:OnLeaveRetreating(Controlable, From, Event, To) Controlable:OptionAlarmStateAuto() end + +--- Entering "Hiding" state. Group will try to take cover at neargy scenery objects. +-- @param #AI_Suppression self +-- @param Wrapper.Controllable#CONTROLLABLE Controlable Controllable of the AI group. +function AI_Suppression:OnEnterHiding(Controlable, From, Event, To) + env.info(AI_Suppression.id..string.format("OnEnterHiding: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) + + -- Set the ALARM STATE to GREEN. Then the unit will move even if it is under fire. + Controlable:OptionAlarmStateGreen() + + -- Route the group to a zone. + MESSAGE:New(string.format("Group %s would be(!) hiding now!", Controlable:GetName()), 30):ToAll() + + --TODO: Search place to hide. For each unit (disperse) or same for all? + +end + +--- Leaving "Hiding" state. +-- @param #AI_Suppression self +-- @param Wrapper.Controllable#CONTROLLABLE Controlable Controllable of the AI group. +function AI_Suppression:OnLeaveHiding(Controlable, From, Event, To) + env.info(AI_Suppression.id..string.format("OnLeveHiding: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) + + -- Set the ALARM STATE back to AUTO. + Controlable:OptionAlarmStateAuto() +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Handle the DCS event hit. @@ -345,10 +426,12 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Get (relative) life of first unit of a group. +--- Get (relative) life in percent of a group. Function returns the value of the units with the smallest and largest life. Also the average value of all groups is returned. -- @param #AI_Suppression self -- @param Wrapper.Group#GROUP group Group of unit. --- @return #number Life of unit in percent. +-- @return #number Smallest life value of all units. +-- @return #number Largest life value of all units. +-- @return #number Average life value. function AI_Suppression:_GetLife() local group=self.Controllable if group and group:IsAlive() then @@ -378,12 +461,12 @@ function AI_Suppression:_GetLife() end ---- Retreat to a zone. +--- Retreat to a random point within a zone. -- @param #AI_Suppression self -- @param Core.Zone#ZONE zone Zone to which the group retreats. -- @param #number speed Speed of the group. Default max speed the specific group can do. -- @param #string formation Formation of the Group. Default "Vee". -function AI_Suppression:_Retreat(zone, speed, formation) +function AI_Suppression:_RetreatToZone(zone, speed, formation) -- Set zone, speed and formation if they are not given zone=zone or self.Zone_Retreat From e1d12cbd8e23d74aa5646cba17c22dc4b7a10213 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Mon, 16 Oct 2017 00:14:41 +0200 Subject: [PATCH 05/25] Suppression + RAT --- Moose Development/Moose/Core/Event.lua | 2 +- Moose Development/Moose/Functional/RAT.lua | 296 +++++++++-- .../Moose/Functional/SuppressionFire.lua | 460 ++++++++++++++---- 3 files changed, 605 insertions(+), 153 deletions(-) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 01235b650..2608c4ffe 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -1019,7 +1019,7 @@ function EVENT:onEvent( Event ) end end else - self:E( { EventMeta.Text, Event } ) + self:T( { EventMeta.Text, Event } ) end Event = nil diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index 03055ea3f..07ce6dc0f 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -64,6 +64,7 @@ -- @type RAT -- @field #string ClassName Name of the Class. -- @field #boolean debug Turn debug messages on or off. +-- @field Core.Group#GROUP templategroup Group serving as template for the RAT aircraft. -- @field #string alias Alias for spawned group. -- @field #number spawndelay Delay time in seconds before first spawning happens. -- @field #number spawninterval Interval between spawning units/groups. Note that we add a randomization of 50%. @@ -89,6 +90,8 @@ -- @field #table departure_ports Array containing the names of the destination airports. -- @field #table destination_ports Array containing the names of the destination airports. -- @field #table excluded_ports Array containing the names of explicitly excluded airports. +-- @field #table destination_zones Array containing the names of the destination zones. +-- @field #boolean destinationzone Destination is a zone and not an airport. -- @field Core.Zone#ZONE departure_Azone Zone containing the departure airports. -- @field Core.Zone#ZONE destination_Azone Zone containing the destination airports. -- @field #table ratcraft Array with the spawned RAT aircraft. @@ -107,6 +110,8 @@ -- @field #table Menu F10 menu items for this RAT object. -- @field #string SubMenuName Submenu name for RAT object. -- @field #boolean respawn_at_landing Respawn aircraft the moment they land rather than at engine shutdown. +-- @field #boolean norespawn Aircraft will not be respawned after they have finished their route. +-- @field #boolean respawn_after_takeoff Aircraft will be respawned directly after take-off. -- @field #number respawn_delay Delay in seconds until repawn happens after landing. -- @field #table markerids Array with marker IDs. -- @field #string livery Livery of the aircraft set by user. @@ -262,6 +267,7 @@ RAT={ ClassName = "RAT", -- Name of class: RAT = Random Air Traffic. debug=false, -- Turn debug messages on or off. + templategroup=nil, -- Template group for the RAT aircraft. alias=nil, -- Alias for spawned group. spawndelay=5, -- Delay time in seconds before first spawning happens. spawninterval=5, -- Interval between spawning units/groups. Note that we add a randomization of 50%. @@ -286,6 +292,8 @@ RAT={ departure_zones={}, -- Array containing the names of the departure zones. departure_ports={}, -- Array containing the names of the departure airports. destination_ports={}, -- Array containing the names of the destination airports. + destination_zones={}, -- Array containing the names of destination zones. + destinationzone=false, -- Destination is a zone and not an airport. excluded_ports={}, -- Array containing the names of explicitly excluded airports. departure_Azone=nil, -- Zone containing the departure airports. destination_Azone=nil, -- Zone containing the destination airports. @@ -305,11 +313,16 @@ RAT={ Menu={}, -- F10 menu items for this RAT object. SubMenuName=nil, -- Submenu name for RAT object. respawn_at_landing=false, -- Respawn aircraft the moment they land rather than at engine shutdown. + norespawn=false, -- Aircraft will not get respawned. + respawn_after_takeoff=false, -- Aircraft will be respawned directly after takeoff. respawn_delay=nil, -- Delay in seconds until repawn happens after landing. markerids={}, -- Array with marker IDs. livery=nil, -- Livery of the aircraft. skill="High", -- Skill of AI. ATCswitch=true, -- Enable ATC. + parking_id=nil, + argkey=nil, + arg={}, } ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -336,6 +349,7 @@ RAT.wp={ descent=7, holding=8, landing=9, + finalwp=10, } --- RAT friendly coalitions. @@ -458,6 +472,9 @@ function RAT:New(groupname, alias) env.error("Group with name "..groupname.." does not exist in the mission editor!") return nil end + + -- Store template group. + self.templategroup=GROUP:FindByName(groupname) -- Set own coalition. self.coalition=DCSgroup:getCoalition() @@ -529,6 +546,8 @@ function RAT:Spawn(naircraft) 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 after landing: %s\n", tostring(self.respawn_at_landing)) + text=text..string.format("Respawning off: %s\n", tostring(self.norespawn)) + text=text..string.format("Respawn after take-off: %s\n", tostring(self.respawn_after_takeoff)) text=text..string.format("Respawn delay: %s\n", tostring(self.respawn_delay)) text=text..string.format("ROE: %s\n", tostring(self.roe)) text=text..string.format("ROT: %s\n", tostring(self.rot)) @@ -577,7 +596,7 @@ function RAT:Spawn(naircraft) self:HandleEvent(EVENTS.Land, self._OnLand) self:HandleEvent(EVENTS.EngineShutdown, self._OnEngineShutdown) self:HandleEvent(EVENTS.Dead, self._OnDead) - self:HandleEvent(EVENTS.Crash, self._OnCrash) + --self:HandleEvent(EVENTS.Crash, self._OnCrash) -- TODO: add hit event? end @@ -734,6 +753,36 @@ function RAT:SetDestination(names) end +--- Set name of destination zones for the AI aircraft. If multiple names are given as a table, one zone is picked randomly as destination. +-- @param #RAT self +-- @param #string names Name or table of names of zones defined in the mission editor. +function RAT:SetDestinationZone(names) + + -- Random destination is deactivated now that user specified destination zone(s). + self.random_destination=false + -- Destination is a zone. Needs special care. + self.destinationzone=true + -- No ATC required. + self.ATCswitch=false + + if type(names)=="table" then + + for _,name in pairs(names) do + table.insert(self.destination_zones, ZONE:New(name)) + end + + elseif type(names)=="string" then + + table.insert(self.destination_zones, ZONE:New(names)) + + else + -- Error message. + env.error("Input parameter must be a string or a table!") + end + +end + + --- Include all airports which lie in a zone as possible destinations. -- @param #RAT self -- @param Core.Zone#ZONE zone Zone in which the airports lie. @@ -818,6 +867,26 @@ function RAT:RespawnAfterLanding(delay) self.respawn_delay=delay end +--- Aircraft will not get respawned when they finished their route. +-- @param #RAT self +function RAT:NoRespawn() + self.norespawn=true +end + +--- Aircraft will be respawned directly after take-off. +-- @param #RAT self +function RAT:RespawnAfterTakeoff() + self.respawn_after_takeoff=true +end + +--- Set parking id of aircraft. +-- @param #RAT self +-- @param #string id Parking ID of the aircraft. +function RAT:SetParkingID(id) + self.parking_id=id + env.info(RAT.id.."Setting parking ID to "..self.parking_id) +end + --- Set the time after which inactive groups will be destroyed. Default is 300 seconds. -- @param #RAT self -- @param #number time Time in seconds. @@ -1098,6 +1167,13 @@ function RAT:_SpawnWithRoute(_departure, _destination) RAT:_ATCAddFlight(group:GetName(), destination:GetName()) end + if self.destinationzone then +-- env.info(RAT.id.." setstate") +-- self:E(self.argkey) +-- self:E(self.arg) +-- group:SetState(group, self.argkey, self.arg ) + end + -- Set ROE, default is "weapon hold". self:_SetROE(group, self.roe) @@ -1148,11 +1224,14 @@ function RAT:_SpawnWithRoute(_departure, _destination) 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("Clear for landing", self.Menu[self.SubMenuName].groups[self.SpawnIndex], self.ClearForLanding, self, group:GetName()) + if self.ATCswitch then + MENU_MISSION_COMMAND:New("Clear for landing", self.Menu[self.SubMenuName].groups[self.SpawnIndex], self.ClearForLanding, self, group:GetName()) + end MENU_MISSION_COMMAND:New("Place markers", self.Menu[self.SubMenuName].groups[self.SpawnIndex], self._PlaceMarkers, self, waypoints) MENU_MISSION_COMMAND:New("Status report", self.Menu[self.SubMenuName].groups[self.SpawnIndex], self.Status, self, true, self.SpawnIndex) end + env.info("RAT debug before end of _SpawnWithRoute") return self.SpawnIndex end @@ -1502,26 +1581,46 @@ function RAT:_SetRoute(takeoff, _departure, _destination) d_cruise=100 end - -- Coordinates of route from departure (0) to cruise (1) to descent (2) to holing (3) to destination (4). - local c0=Pdeparture - local c1=c0:Translate(d_climb/2, heading) - local c2=c1:Translate(d_climb/2, heading) - local c3=c2:Translate(d_cruise, heading) - local c4=c3:Translate(d_descent/2, heading) - local c5=Pholding - local c6=Pdestination + local waypoints + if not self.destinationzone then - --Convert coordinates into route waypoints. - local wp0=self:_Waypoint(takeoff, c0, VxClimb, H_departure, departure) - local wp1=self:_Waypoint(RAT.wp.climb, c1, VxClimb, H_departure+(FLcruise-H_departure)/2) - local wp2=self:_Waypoint(RAT.wp.cruise, c2, VxCruise, FLcruise) - local wp3=self:_Waypoint(RAT.wp.cruise, c3, VxCruise, FLcruise) - local wp4=self:_Waypoint(RAT.wp.descent, c4, VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) - local wp5=self:_Waypoint(RAT.wp.holding, c5, VxHolding, H_holding+h_holding) - local wp6=self:_Waypoint(RAT.wp.landing, c6, VxFinal, H_destination, destination) + -- Coordinates of route from departure (0) to cruise (1) to descent (2) to holing (3) to destination (4). + local c0=Pdeparture + local c1=c0:Translate(d_climb/2, heading) + local c2=c1:Translate(d_climb/2, heading) + local c3=c2:Translate(d_cruise, heading) + local c4=c3:Translate(d_descent/2, heading) + local c5=Pholding + local c6=Pdestination + + --Convert coordinates into route waypoints. + local wp0=self:_Waypoint(takeoff, c0, VxClimb, H_departure, departure) + local wp1=self:_Waypoint(RAT.wp.climb, c1, VxClimb, H_departure+(FLcruise-H_departure)/2) + local wp2=self:_Waypoint(RAT.wp.cruise, c2, VxCruise, FLcruise) + local wp3=self:_Waypoint(RAT.wp.cruise, c3, VxCruise, FLcruise) + local wp4=self:_Waypoint(RAT.wp.descent, c4, VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) + local wp5=self:_Waypoint(RAT.wp.holding, c5, VxHolding, H_holding+h_holding) + local wp6=self:_Waypoint(RAT.wp.landing, c6, VxFinal, H_destination, destination) + + -- set waypoints + waypoints = {wp0, wp1, wp2, wp3, wp4, wp5, wp6} + + else - -- set waypoints - local waypoints = {wp0, wp1, wp2, wp3, wp4, wp5, wp6} + local c0=Pdeparture + local c1=c0:Translate(d_climb/2, heading) + local c2=c1:Translate(d_climb/2, heading) + local c3=Pdestination + + local wp0=self:_Waypoint(takeoff, c0, VxClimb, H_departure, departure) + local wp1=self:_Waypoint(RAT.wp.climb, c1, VxClimb, H_departure+(FLcruise-H_departure)/2) + local wp2=self:_Waypoint(RAT.wp.cruise, c2, VxCruise, FLcruise) + local wp3=self:_Waypoint(RAT.wp.finalwp, c3, VxCruise, FLcruise) + + -- set waypoints + waypoints = {wp0, wp1, wp2, wp3} + + end -- Place markers of waypoints on F10 map. if self.placemarkers then @@ -1637,7 +1736,12 @@ function RAT:_PickDestination(destinations, _random) destination=destinations[math.random(#destinations)] -- Wrapper.Airbase#AIRBASE -- Debug message. - local text="Chosen destination airport: "..destination:GetName().." (ID "..destination:GetID()..")" + local text + if self.destinationzone then + text="Chosen destination zone: "..destination:GetName() + else + text="Chosen destination airport: "..destination:GetName().." (ID "..destination:GetID()..")" + end env.info(RAT.id..text) if self.debug then MESSAGE:New(text, 30):ToAll() @@ -1686,13 +1790,23 @@ function RAT:_GetDestinations(departure, q, minrange, maxrange) else - -- Airports specified by user. - for _,name in pairs(self.destination_ports) do - --if self:_IsFriendly(name) and not self:_Excluded(name) and name~=departure:GetName() then - if name~=departure:GetName() then - local airport=AIRBASE:FindByName(name) - --TODO: Maybe here I should check min/max distance as well? But the user explicitly specified the airports... - table.insert(possible_destinations, airport) + if self.destinationzone then + + -- Zones specified by user. + for _,zone in pairs(self.destination_zones) do + table.insert(possible_destinations, zone) + end + + else + + -- Airports specified by user. + for _,name in pairs(self.destination_ports) do + --if self:_IsFriendly(name) and not self:_Excluded(name) and name~=departure:GetName() then + if name~=departure:GetName() then + local airport=AIRBASE:FindByName(name) + --TODO: Maybe here I should check min/max distance as well? But the user explicitly specified the airports... + table.insert(possible_destinations, airport) + end end end @@ -1713,7 +1827,7 @@ function RAT:_GetDestinations(departure, q, minrange, maxrange) end table.sort(possible_destinations, compare) else - env.error(RAT.id.."No possible destination airports found!") + env.error(RAT.id.."No possible destinations found!") possible_destinations=nil end @@ -1933,7 +2047,11 @@ function RAT:Status(message, forID) local Ddestination=Pn:Get2DDistance(self.ratcraft[i].destination:GetCoordinate()) -- Distance remaining to holding point, which is waypoint 6 - local Hp=COORDINATE:New(self.ratcraft[i].waypoints[6].x, self.ratcraft[i].waypoints[6].alt, self.ratcraft[i].waypoints[6].y) + local idx=6 + if self.destinationzone then + idx=4 + end + local Hp=COORDINATE:New(self.ratcraft[i].waypoints[idx].x, self.ratcraft[i].waypoints[idx].alt, self.ratcraft[i].waypoints[idx].y) local Dholding=Pn:Get2DDistance(Hp) -- Status shortcut. @@ -2143,6 +2261,14 @@ function RAT:_OnTakeoff(EventData) -- Set status. self:_SetStatus(SpawnGroup, "On journey (after takeoff)") + if self.respawn_after_takeoff then + text="Event: Group "..SpawnGroup:GetName().." will be respawned." + env.info(RAT.id..text) + + -- Respawn group. + self:_Respawn(SpawnGroup) + end + end end @@ -2180,7 +2306,7 @@ function RAT:_OnLand(EventData) RAT:_ATCFlightLanded(SpawnGroup:GetName()) end - if self.respawn_at_landing then + if self.respawn_at_landing and not self.norespawn then text="Event: Group "..SpawnGroup:GetName().." will be respawned." env.info(RAT.id..text) @@ -2220,7 +2346,7 @@ function RAT:_OnEngineShutdown(EventData) -- Set status. self:_SetStatus(SpawnGroup, "Parking (shutting down engines)") - if not self.respawn_at_landing then + if not self.respawn_at_landing and not self.norespawn then text="Event: Group "..SpawnGroup:GetName().." will be respawned." env.info(RAT.id..text) @@ -2281,9 +2407,9 @@ function RAT:_OnCrash(EventData) local SpawnGroup = EventData.IniGroup --Wrapper.Group#GROUP - env.info(string.format("%sGroup %s crashed!", RAT.id, SpawnGroup:GetName())) - if SpawnGroup then + + env.info(string.format("%sGroup %s crashed!", RAT.id, SpawnGroup:GetName())) -- Get the template name of the group. This can be nil if this was not a spawned group. local EventPrefix = self:_GetPrefixFromGroup(SpawnGroup) @@ -2319,10 +2445,13 @@ end -- @param Wrapper.Group#GROUP group Group to be despawned. function RAT:_Despawn(group) + env.info("RAT debug _despawn 0") + local index=self:GetSpawnIndexFromGroup(group) + env.info("RAT debug index = "..index) self.ratcraft[index].group:Destroy() self.ratcraft[index].group=nil - + env.info("RAT debug _despawn 1") -- Decrease group alive counter. self.alive=self.alive-1 @@ -2330,7 +2459,7 @@ function RAT:_Despawn(group) if self.f10menu then self.Menu[self.SubMenuName]["groups"][index]:Remove() end - + env.info("RAT debug _despawn 2") --TODO: Maybe here could be some more arrays deleted? end @@ -2410,6 +2539,10 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport) _Altitude = 0 _alttype="RADIO" _AID = Airport:GetID() + elseif Type==RAT.wp.finalwp then + _Type="Turning Point" + _Action="Fly Over Point" + _alttype="BARO" else env.error("Unknown waypoint type in RAT:Waypoint() function!") _Type="Turning Point" @@ -2478,9 +2611,6 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport) --env.error(RAT.id.."Unknown Airport categoryin _Waypoint()!") end end --- if _AID then --- RoutePoint.airdromeId=_AID --- end -- properties RoutePoint.properties = { ["vnav"] = 1, @@ -2494,6 +2624,16 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport) -- Duration of holing. Between 10 and 170 seconds. local Duration=self:_Randomize(90,0.9) RoutePoint.task=self:_TaskHolding({x=Coord.x, y=Coord.z}, Altitude, Speed, Duration) + elseif Type==RAT.wp.finalwp then + local TaskRespawn, argkey, arg = self:_TaskFunction("RAT._FinalWaypoint", self) + self.argkey=argkey + self.arg=arg + local TaskCombo = {TaskRespawn} + RoutePoint.task = {} + RoutePoint.task.id = "ComboTask" + RoutePoint.task.params = {} + RoutePoint.task.params.tasks = TaskCombo + self:E(TaskRespawn) else RoutePoint.task = {} RoutePoint.task.id = "ComboTask" @@ -2551,9 +2691,6 @@ function RAT:_Routeinfo(waypoints, comment) return total end -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Orbit at a specified position at a specified alititude with a specified speed. @@ -2607,6 +2744,68 @@ function RAT:_TaskHolding(P1, Altitude, Speed, Duration) return DCSTask end + +--- Function called if aircraft reached its final waypoint. Aircraft gets destroyed and respawned. +-- @param Core.Group#GROUP group Group of aircraft. +-- @param #RAT rat RAT object. +function RAT._FinalWaypoint(group, rat) + env.info(RAT.id.."FinalWaypoint:") + BASE:E(group) + BASE:E(rat) + + -- Spawn new group. + rat:_Respawn(group) + + -- Despawn old group. + rat:_Despawn(group) +end + +--- Orbit at a specified position at a specified alititude with a specified speed. +-- @param #RAT self +-- @param #string FunctionString Name of the function to be called. +function RAT:_TaskFunction(FunctionString, ... ) + self:F2({FunctionString, arg}) + + local DCSTask + local ArgumentKey + + local templatename=self.templategroup:GetName() + local groupname=self:_AnticipatedGroupName() + + env.info(RAT.id.."template name "..templatename) + env.info(RAT.id.."anticipated name "..groupname) + + local DCSScript = {} + --DCSScript[#DCSScript+1] = "local MissionControllable = GROUP:Find( ... ) " + DCSScript[#DCSScript+1] = "env.info(\"RAT blabla\") " + DCSScript[#DCSScript+1] = "local MissionControllable = GROUP:FindByName(\""..groupname.."\") " + DCSScript[#DCSScript+1] = "local RATtemplateControllable = GROUP:FindByName(\""..templatename.."\") " + + if arg and arg.n > 0 then + ArgumentKey = '_' .. tostring(arg):match("table: (.*)") + env.info(RAT.id.."Argumentkey: "..ArgumentKey) + self.templategroup:SetState(self.templategroup, ArgumentKey, arg) + DCSScript[#DCSScript+1] = "local Arguments = RATtemplateControllable:GetState(RATtemplateControllable, '" .. ArgumentKey .. "' ) " + DCSScript[#DCSScript+1] = FunctionString .. "( MissionControllable, unpack( Arguments ) )" + else + DCSScript[#DCSScript+1] = FunctionString .. "( MissionControllable )" + end + + DCSTask = self.templategroup:TaskWrappedAction(self.templategroup:CommandDoScript(table.concat(DCSScript))) + + env.info(RAT.id.."Taskfunction:") + self:E( DCSTask ) + + return DCSTask, ArgumentKey, arg +end + +--- Anticipated group name from alias and spawn index. +-- @param #RAT self +-- @return #string Name the group will get after it is spawned. +function RAT:_AnticipatedGroupName() + return string.format("%s#%03d", self.alias, self.SpawnIndex+1) +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Calculate the max flight level for a given distance and fixed climb and descent rates. @@ -2855,9 +3054,11 @@ function RAT:_PlaceMarkers(waypoints) self:_SetMarker("Climb", waypoints[2]) self:_SetMarker("Begin of Cruise", waypoints[3]) self:_SetMarker("End of Cruise", waypoints[4]) - self:_SetMarker("Descent", waypoints[5]) - self:_SetMarker("Holding Point", waypoints[6]) - self:_SetMarker("Destination", waypoints[7]) + if #waypoints>4 then + self:_SetMarker("Descent", waypoints[5]) + self:_SetMarker("Holding Point", waypoints[6]) + self:_SetMarker("Destination", waypoints[7]) + end end @@ -2949,7 +3150,8 @@ function RAT:_ModifySpawnTemplate(waypoints) -- Parking spot. UnitTemplate.parking = nil - UnitTemplate.parking_id = nil + UnitTemplate.parking_id = self.parking_id + --env.info(RAT.id.."Parking ID "..tostring(self.parking_id)) -- Initial altitude UnitTemplate.alt=PointVec3.y @@ -2969,7 +3171,7 @@ function RAT:_ModifySpawnTemplate(waypoints) --SpawnTemplate.uncontrolled=true -- Update modified template for spawn group. - self.SpawnGroups[self.SpawnIndex].SpawnTemplate=SpawnTemplate + --self.SpawnGroups[self.SpawnIndex].SpawnTemplate=SpawnTemplate self:T(SpawnTemplate) end diff --git a/Moose Development/Moose/Functional/SuppressionFire.lua b/Moose Development/Moose/Functional/SuppressionFire.lua index 6a02a0d90..5e21a8de0 100644 --- a/Moose Development/Moose/Functional/SuppressionFire.lua +++ b/Moose Development/Moose/Functional/SuppressionFire.lua @@ -36,11 +36,14 @@ -- @field #number Tsuppress_min Minimum time in seconds the group gets suppressed. -- @field #number Tsuppress_max Maximum time in seconds the group gets suppressed. -- @field #number life Relative life in precent of the group. --- @field #number Tsuppress Time in seconds the groups is suppressed. Randomly chosen between Tsuppress_min and Tsuppress_max. +-- @field #number TsuppressionStart Time at which the suppression started. +-- @field #number TsuppressionOver Time at which the suppression will be over. -- @field #number Thit Last time the unit was hit. -- @field #number Nhit Number of times the unit was hit since it last was in state "CombatReady". -- @field Core.Zone#ZONE Zone_Retreat Zone into which a group retreats. --- @field #number LifeMin Life of group in percent at which the group will be ordered to retreat. +-- @field #number LifeThreshold Life of group in percent at which the group will be ordered to retreat. +-- @field #number IniGroupStrength Number of units in a group at start. +-- @field #number GroupStrengthThreshold Threshold of group strength before retreat is ordered. -- @extends Core.Fsm#FSM_CONTROLLABLE -- @@ -54,11 +57,14 @@ AI_Suppression={ ClassName = "AI_Suppression", Tsuppress_min = 5, Tsuppress_max = 20, - Tsuppress = nil, + TsuppressStart = nil, + TsuppressOver = nil, Thit = nil, Nhit = 0, Zone_Retreat = nil, - LifeMin = 25, + LifeThreshold = 25, + IniGroupStrength = nil, + GroupStrengthThreshold=80, } --- Some ID to identify who we are in output of the DCS.log file. @@ -67,7 +73,7 @@ AI_Suppression.id="SFX | " ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---TODO: Figure out who was shooting and move away from him <== Not possible! +--TODO: Figure out who was shooting and move away from him. --TODO: Move behind a scenery building if there is one nearby. --TODO: Retreat to a given zone or point. @@ -81,38 +87,52 @@ function AI_Suppression:New(Group) -- Check that group is present. if Group then - env.info("Suppression fire for group "..Group:GetName()) + env.info(AI_Suppression.id.."Suppression fire for group "..Group:GetName()) else - env.info("Suppression fire: Group does not exist!") + env.info(AI_Suppression.id.."Suppression fire: Requested group does not exist! (Has to be a MOOSE group.)") return nil end -- Check that we actually have a GROUND group. if Group:IsGround()==false then - env.error("Suppression fire group "..Group:GetName().." has to be a GROUND group!") + env.error(AI_Suppression.id.."Suppression fire group "..Group:GetName().." has to be a GROUND group!") return nil end -- Inherits from FSM_CONTROLLABLE local self=BASE:Inherit(self, FSM_CONTROLLABLE:New()) -- #AI_Suppression + -- Set the controllable for the FSM. self:SetControllable(Group) + -- Initial group strength. + self.IniGroupStrength=#Group:GetUnits() + + + -- Get life of group in %. + local life_min, life_max, life_ave, groupstrength=self:_GetLife() + -- Group is initially in state CombatReady. - self:SetStartState("CombatReady") + self:SetStartState("none") -- Transitions: --------------- - -- Transition from anything to "Suppressed" after event "Hit". - self:AddTransition("*", "Hit", "Suppressed") + -- Transition from anything to "Suppressed" after event "Hit". + self:AddTransition("*", "Start", "CombatReady") + + -- Transition from anything to "Suppressed" after event "Hit". + self:AddTransition("*", "Hit", "*") -- Transition from "Suppressed" back to "CombatReady after the unit had time to recover. - self:AddTransition("Suppressed", "Recovered", "CombatReady") + self:AddTransition("*", "Recovered", "*") + + -- Transition from "Suppressed" back to "CombatReady after the unit had time to recover. + self:AddTransition("*", "Suppress", "Suppressed") -- Transition from "Suppressed" to "Hiding" after event "Hit". - self:AddTransition("Suppressed", "TakeCover", "Hiding") + self:AddTransition("*", "TakeCover", "Hiding") -- Transition from anything to "Retreating" after e.g. being severely damaged. self:AddTransition("*", "Retreat", "Retreating") @@ -123,12 +143,7 @@ function AI_Suppression:New(Group) -- Check status of the group. self:AddTransition("*", "Status", "*") - - -- Handle DCS event hit. - self:HandleEvent(EVENTS.Hit, self.OnEventHit) - - -- Handle DCS event dead. - self:HandleEvent(EVENTS.Dead, self.OnEventDead) + --self:TakeCover() -- return self return self @@ -158,6 +173,21 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- After "Start" event. +-- @param #AI_Suppression self +function AI_Suppression:onafterStart(Controlable, From, Event, To) + env.info(AI_Suppression.id..string.format("onafterStart: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) + + -- Handle DCS event hit. + self:HandleEvent(EVENTS.Hit, self._OnHit) + + -- Handle DCS event dead. + self:HandleEvent(EVENTS.Dead, self._OnDead) + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + --- Before "Status" event. -- @param #AI_Suppression self function AI_Suppression:OnBeforeStatus(Controlable, From, Event, To) @@ -174,10 +204,16 @@ function AI_Suppression:OnAfterStatus(Controlable, From, Event, To) self:__Status(30) end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Before "Hit" event. (Of course, this is not really before the group got hit.) -- @param #AI_Suppression self -function AI_Suppression:OnBeforeHit(Controlable, From, Event, To) +-- @param Wrapper.Controllable#CONTROLLABLE Controlable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Point#COORDINATE Fallback Fallback coordinates (or nil if no attacker could be found). +function AI_Suppression:OnBeforeHit(Controlable, From, Event, To, Fallback) env.info(AI_Suppression.id..string.format("OnBeforeHit: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) -- Increase Hit counter. @@ -185,16 +221,51 @@ function AI_Suppression:OnBeforeHit(Controlable, From, Event, To) -- Info on hit times. env.info(AI_Suppression.id..string.format("Group has just been hit %d times.", self.Nhit)) + end --- After "Hit" event. -- @param #AI_Suppression self -function AI_Suppression:OnAfterHit(Controlable, From, Event, To) +function AI_Suppression:OnAfterHit(Controlable, From, Event, To, Fallback) env.info(AI_Suppression.id..string.format("OnAfterHit: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) - - -- Nothing to do yet. Just monitoring the event. + + -- Suppress fire of group. + self:_Suppress() + + -- Get life of group in %. + local life_min, life_max, life_ave, groupstrength=self:_GetLife() + + if self:is("CombatReady") then + env.info(AI_Suppression.id..string.format("Group %s is currently CombatReady.", Controlable:GetName())) + self:Suppress() + elseif self:Is("Suppressed") then + env.info(AI_Suppression.id..string.format("Group %s is currently Suppressed.", Controlable:GetName())) + elseif self:Is("Retreating") then + env.info(AI_Suppression.id..string.format("Group %s is currently Retreating.", Controlable:GetName())) + elseif self:is("Hiding") then + env.info(AI_Suppression.id..string.format("Group %s is currently Hiding.", Controlable:GetName())) + end + + -- After three hits fall back a bit. + local nfallback=3 + if self.Nhit==nfallback then + env.info(AI_Suppression.id..string.format("Group %s is falling back after %d hits.", Controlable:GetName(), nfallback)) + Fallback:SmokeGreen() + local FallbackMarkerID=Fallback:MarkToAll("Fall back position for group "..Controlable:GetName():GetName()) + self:_FallBack(Fallback) + end + + -- If life of one unit is below threshold, the group is ordered to retreat (if a zone has been specified). + if not self:Is("Retreating") then + if groupstrengthself.TsuppressionOver then + self.TsuppressionOver=Tnow+Tsuppress + else + renew=false + end + else + self.TsuppressionOver=Tnow+Tsuppress + end + + -- Recovery event will be called in Tsuppress seconds. (We add one second to be sure the time has really passed when recovery is checked.) + if renew then + self:__Recovered(self.TsuppressionOver-Tnow) + end + + -- Debug message. + local text=string.format("Group %s is suppressed for %d seconds.", Controlable:GetName(), Tsuppress) + MESSAGE:New(text, 30):ToAll() + env.info(AI_Suppression.id..text) + text=string.format("Suppression starts at %f and ends at %f.", Tnow, self.TsuppressionOver) + env.info(AI_Suppression.id..text) + +end + + --- Get (relative) life in percent of a group. Function returns the value of the units with the smallest and largest life. Also the average value of all groups is returned. -- @param #AI_Suppression self -- @param Wrapper.Group#GROUP group Group of unit. @@ -440,10 +595,12 @@ function AI_Suppression:_GetLife() local life_ave=0 local n=0 local units=group:GetUnits() + local groupstrength=#units/self.IniGroupStrength*100 for _,unit in pairs(units) do - if unit then + local unit=unit -- Wrapper.Unit#UNIT + if unit and unit:IsActive() then n=n+1 - local life=unit:GetLife()/unit:GetLife0()*100 + local life=unit:GetLife()/(unit:GetLife0()+1)*100 if life < life_min then life_min=life end @@ -451,10 +608,13 @@ function AI_Suppression:_GetLife() life_max=life end life_ave=life_ave+life + local text=string.format("n=%d: Life = %3.1f, Life0 = %3.1f, min=%3.1f, max=%3.1f, ave=%3.1f, group=%3.1f", n, unit:GetLife(), unit:GetLife0(), life_min, life_max, life_ave/n,groupstrength) + env.info(AI_Suppression.id..text) end end life_ave=life_ave/n - return life_min, life_max, life_ave + + return life_min, life_max, life_ave, groupstrength else return 0, 0, 0 end @@ -473,12 +633,102 @@ function AI_Suppression:_RetreatToZone(zone, speed, formation) speed = speed or 999 formation = formation or "Vee" + -- + env.info(AI_Suppression.id.."Retreat zone : "..zone:GetName()) + -- Get a random point in the retreat zone. - local ZonePoint=zone:GetRandomPointVec2() + local ZoneCoord=zone:GetRandomCoordinate() -- Core.Point#COORDINATE + local ZoneVec2=ZoneCoord:GetVec2() + + -- Debug smoke zone and point. + ZoneCoord:SmokeBlue() + zone:SmokeZone(SMOKECOLOR.Red, 12) -- Set task to go to zone. - self.Controllable:TaskRouteToVec2(ZonePoint, speed, formation) + self.Controllable:TaskRouteToVec2(ZoneVec2, speed, formation) end +--- Determine the coordinate to which a unit should fall back. +--@param #AI_Suppression self +--@param Core.Point#COORDINATE a Coordinate of the defending group. +--@param Core.Point#COORDINATE b Coordinate of the attacking group. +--@return Core.Point#COORDINATE Fallback coordinates. +function AI_Suppression:_FallBackCoord(a, b, distance) + local dx = b.x-a.x + -- take the right value for y-coordinate (if we have "alt" then "y" if not "z") + local ay + if a.alt then + ay=a.y + else + ay=a.z + end + local by + if b.alt then + by=b.y + else + by=b.z + end + local dy = by-ay + local angle = math.deg(math.atan2(dy,dx)) + if angle < 0 then + angle = 360 + angle + end + angle=angle-180 + local fbp=a:Translate(distance, angle) + return fbp +end + + +--- Fall back (move away) from enemy who is shooting on the group. +--@param #AI_Suppression self +--@param Core.Point#COORDINATE coord_fbp Coordinate of the fall back point. +function AI_Suppression:_FallBack(coord_fbp) + + local group=self.Controllable -- Wrapper.Controllable#CONTROLLABLE + + local Waypoints = group:GetTemplateRoutePoints() + + local coord_grp = group:GetCoordinate() + local wp1 = coord_grp:WaypointGround(99, "Vee") + local wp2 = coord_fbp:WaypointGround(99, "Vee") + + table.insert(Waypoints, 1, wp1) + table.insert(Waypoints, 2, wp2) + + -- Condition to wait. + local ConditionWait=group:TaskCondition(nil, nil, nil, nil, 30, nil) + + -- Task to hold. + local TaskHold = group:TaskHold() + + local TaskRoute1 = group:TaskFunction("AI_Suppression._Passing_Waypoint", self, 0) + local TaskCombo2 = {} + TaskCombo2[#TaskCombo2+1] = group:TaskFunction("AI_Suppression._Passing_Waypoint", self, 1) + TaskCombo2[#TaskCombo2+1] = group:TaskControlled(TaskHold, ConditionWait) + local TaskRoute2 = group:TaskCombo(TaskCombo2) + + group:SetTaskWaypoint(Waypoints[1], TaskRoute1) + group:SetTaskWaypoint(Waypoints[2], TaskRoute2) + + group:Route(Waypoints) + +end + + +--- Group has reached a waypoint. +--@param #AI_Suppression self +--@param #number i Waypoint number that has been reached. +function AI_Suppression._Passing_Waypoint(group, Fsm, i) + env.info(AI_Suppression.id.."Passing waypoint") + BASE:E(group) + BASE:E(Fsm) + BASE:E(i) + + MESSAGE:New(string.format("Group %s passing waypoint %d", group:GetName(), i),30):ToAll() + if i==1 then + MESSAGE:New(string.format("Group %s has reached fallback point.", group:GetName(), i),30):ToAll() + end +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file From a661c6e711c704ae96c69dbaa50f911dca29f346 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Tue, 17 Oct 2017 00:33:08 +0200 Subject: [PATCH 06/25] RAT Improved Flightplan Logic --- Moose Development/Moose/Functional/RAT.lua | 99 ++++++++++++++++------ 1 file changed, 72 insertions(+), 27 deletions(-) diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index 07ce6dc0f..dc393a7d3 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -1456,9 +1456,6 @@ function RAT:_SetRoute(takeoff, _departure, _destination) -- Distance from holding point to final destination. local d_holding=Pholding:Get2DDistance(Pdestination) - -- Height difference between departure and holding point. - local deltaH=math.abs(h_holding+H_holding-H_departure) - -- GENERAL -- Heading from departure to holding point of destination. local heading=self:_Course(Pdeparture, Pholding) @@ -1466,19 +1463,67 @@ function RAT:_SetRoute(takeoff, _departure, _destination) -- Total distance between departure and holding point near destination. local d_total=Pdeparture:Get2DDistance(Pholding) - -- Climb/descent angle from departure to holding point - local phi=math.atan(deltaH/d_total) + -- max height if we only would descent to holding point for the given distance + if takeoff==RAT.wp.air then + local H_departure_max = d_total * math.tan(AlphaDescent) + H_holding + h_holding + H_departure=math.min(H_departure, H_departure_max) + end - -- Corrected climb angle. - local PhiClimb=AlphaClimb+phi + -------------------------------------------- + local deltaH=H_departure-h_holding-H_holding - -- Corrected descent angle. - local PhiDescent=AlphaDescent-phi + -- Slope between departure and destination. + local phi = math.atan(deltaH/d_total) + + -- attation with the sign! + local phi_climb + local phi_descent + if (H_departure > H_holding+h_holding) then + phi_climb=AlphaClimb+phi + phi_descent=AlphaDescent-phi + else + phi_climb=AlphaClimb-phi + phi_descent=AlphaDescent+phi + end + -- Total distance including slope. + local D_total = math.sqrt(deltaH*deltaH+d_total*d_total) + --local D_total2 =d_total/math.cos(phi) + --local D_total3 = deltaH/math.sin(phi) + + -- 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) + + -- Max height + local h_max1 = b*math.sin(AlphaClimb) + local h_max2 = a*math.sin(AlphaDescent) + + local h_max + if (H_departure > H_holding+h_holding) then + h_max=math.min(h_max1, h_max2) + 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 + + -- Max heights and distances + local h_climb_max = FLmax-H_departure + local h_descent_max = FLmax - (H_holding+h_holding) + local d_climb_max = h_climb_max/math.tan(AlphaClimb) + local d_descent_max = h_descent_max/math.tan(AlphaDescent) + local d_cruise_max = d_total-d_climb_max-d_descent_max + local d_total_max = d_climb_max + d_cruise_max + d_descent_max + --CRUISE -- Max flight level the aircraft can reach if it only climbs and immidiately descents again (i.e. no cruising part). - local FLmax=self:_FLmax(AlphaClimb, AlphaDescent, d_total, phi, H_departure) + --local FLmax=self:_FLmax(AlphaClimb, AlphaDescent, d_total, phi, H_departure) -- Min cruise alt is just above holding point at destination or departure height, whatever is larger. local FLmin=math.max(H_departure, H_holding+h_holding) @@ -1520,22 +1565,22 @@ function RAT:_SetRoute(takeoff, _departure, _destination) if self.FLuser then FLcruise=self.FLuser end - - -- CLIMB - -- Height of climb relative to ASL height of departure airport. - local h_climb=FLcruise-H_departure - -- x-distance of climb part - local d_climb=h_climb/math.tan(PhiClimb) - -- DESCENT - -- Height difference for descent form cruise alt to holding point. - local h_descent=FLcruise-(H_holding+h_holding) - -- x-distance of descent part - local d_descent=h_descent/math.tan(PhiDescent) + -- Cruise alt should not be below departure alt + --FLcruise = math.max(FLcruise, H_departure) - -- CRUISE - -- Distance of the cruising part. This should in principle not become negative, but can happen for very short legs. - local d_cruise=d_total-d_climb-d_descent + -- Cruise alt should not be above max FL + --FLcruise = math.min(FLcruise, FLmax) + + -- actual heits + local h_climb = (FLcruise-H_departure) --math.abs ? + local h_descent = FLcruise - (H_holding+h_holding) + -- actual 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_total_cruise = d_climb + d_cruise + d_descent + -------------------------------------------- -- debug message local text=string.format("\n******************************************************\n") @@ -1570,8 +1615,8 @@ function RAT:_SetRoute(takeoff, _departure, _destination) text=text..string.format("Alpha climb = %6.1f Deg\n", math.deg(AlphaClimb)) text=text..string.format("Alpha descent = %6.1f Deg\n", math.deg(AlphaDescent)) text=text..string.format("Phi (slope) = %6.1f Deg\n", math.deg(phi)) - text=text..string.format("Phi climb = %6.1f Deg\n", math.deg(PhiClimb)) - text=text..string.format("Phi descent = %6.1f Deg\n", math.deg(PhiDescent)) + text=text..string.format("Phi climb = %6.1f Deg\n", math.deg(phi_climb)) + text=text..string.format("Phi descent = %6.1f Deg\n", math.deg(phi_descent)) text=text..string.format("Heading = %6.1f Deg\n", heading) text=text..string.format("******************************************************\n") env.info(RAT.id..text) @@ -3272,7 +3317,7 @@ function RAT:_ATCStatus() --TODO: Trigger landing for another aircraft when Tfinal > x min? -- After five minutes we set the runway to green. ==> Increase the landing frequency a bit. if Tfinal>300 then - --RAT.ATC.airport[dest].busy=false + RAT.ATC.airport[dest].busy=false end elseif hold==RAT.ATC.unregistered then From 32e61da58835f29f01b926c29db8da44786df37c Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Tue, 17 Oct 2017 17:13:34 +0200 Subject: [PATCH 07/25] RAT --- Moose Development/Moose/Functional/RAT.lua | 242 ++++++++++++--------- 1 file changed, 135 insertions(+), 107 deletions(-) diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index dc393a7d3..ab772a2cb 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -114,9 +114,12 @@ -- @field #boolean respawn_after_takeoff Aircraft will be respawned directly after take-off. -- @field #number respawn_delay Delay in seconds until repawn happens after landing. -- @field #table markerids Array with marker IDs. +-- @field #table waypointdescriptions Table with strings for waypoint descriptions of markers. -- @field #string livery Livery of the aircraft set by user. -- @field #string skill Skill of AI. --- @field #boolean ATCswitch Enable/disable ATC if set to true/false. +-- @field #boolean ATCswitch Enable/disable ATC if set to true/false. +-- @field #string parking_id String with a special parking ID for the aircraft. +-- @field #number wp_final_index Index of the holding or final waypoint. -- @extends Core.Spawn#SPAWN ---# RAT class, extends @{Spawn#SPAWN} @@ -317,12 +320,12 @@ RAT={ respawn_after_takeoff=false, -- Aircraft will be respawned directly after takeoff. respawn_delay=nil, -- Delay in seconds until repawn happens after landing. markerids={}, -- Array with marker IDs. + waypointdescriptions={}, -- Array with descriptions for waypoint markers. livery=nil, -- Livery of the aircraft. skill="High", -- Skill of AI. ATCswitch=true, -- Enable ATC. - parking_id=nil, - argkey=nil, - arg={}, + parking_id=nil, -- Specific parking ID when aircraft are spawned at airports. + wp_final_index=nil, -- Index of the holding for final waypoint. } ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1470,12 +1473,14 @@ function RAT:_SetRoute(takeoff, _departure, _destination) end -------------------------------------------- + + -- Height difference between departure and destination. local deltaH=H_departure-h_holding-H_holding -- Slope between departure and destination. local phi = math.atan(deltaH/d_total) - -- attation with the sign! + -- Adjusted climb/descent angles. local phi_climb local phi_descent if (H_departure > H_holding+h_holding) then @@ -1498,10 +1503,11 @@ function RAT:_SetRoute(takeoff, _departure, _destination) local hphi_max = b*math.sin(phi_climb) local hphi_max2 = a*math.sin(phi_descent) - -- Max height + -- 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 > H_holding+h_holding) then h_max=math.min(h_max1, h_max2) @@ -1512,7 +1518,7 @@ function RAT:_SetRoute(takeoff, _departure, _destination) -- Max flight level aircraft can reach for given angles and distance. local FLmax = h_max+H_departure - -- Max heights and distances + -- Max heights and distances if we would travel at FLmax. local h_climb_max = FLmax-H_departure local h_descent_max = FLmax - (H_holding+h_holding) local d_climb_max = h_climb_max/math.tan(AlphaClimb) @@ -1521,7 +1527,6 @@ function RAT:_SetRoute(takeoff, _departure, _destination) local d_total_max = d_climb_max + d_cruise_max + d_descent_max --CRUISE - -- Max flight level the aircraft can reach if it only climbs and immidiately descents again (i.e. no cruising part). --local FLmax=self:_FLmax(AlphaClimb, AlphaDescent, d_total, phi, H_departure) @@ -1572,10 +1577,11 @@ function RAT:_SetRoute(takeoff, _departure, _destination) -- Cruise alt should not be above max FL --FLcruise = math.min(FLcruise, FLmax) - -- actual heits + -- Climb and descent heights. local h_climb = (FLcruise-H_departure) --math.abs ? local h_descent = FLcruise - (H_holding+h_holding) - -- actual distances + + -- 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 @@ -1626,45 +1632,85 @@ function RAT:_SetRoute(takeoff, _departure, _destination) d_cruise=100 end - local waypoints - if not self.destinationzone then - - -- Coordinates of route from departure (0) to cruise (1) to descent (2) to holing (3) to destination (4). - local c0=Pdeparture - local c1=c0:Translate(d_climb/2, heading) - local c2=c1:Translate(d_climb/2, heading) - local c3=c2:Translate(d_cruise, heading) - local c4=c3:Translate(d_descent/2, heading) - local c5=Pholding - local c6=Pdestination - - --Convert coordinates into route waypoints. - local wp0=self:_Waypoint(takeoff, c0, VxClimb, H_departure, departure) - local wp1=self:_Waypoint(RAT.wp.climb, c1, VxClimb, H_departure+(FLcruise-H_departure)/2) - local wp2=self:_Waypoint(RAT.wp.cruise, c2, VxCruise, FLcruise) - local wp3=self:_Waypoint(RAT.wp.cruise, c3, VxCruise, FLcruise) - local wp4=self:_Waypoint(RAT.wp.descent, c4, VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) - local wp5=self:_Waypoint(RAT.wp.holding, c5, VxHolding, H_holding+h_holding) - local wp6=self:_Waypoint(RAT.wp.landing, c6, VxFinal, H_destination, destination) - - -- set waypoints - waypoints = {wp0, wp1, wp2, wp3, wp4, wp5, wp6} - - else + --local waypoints + local wp={} + if self.destinationzone then + -- Destination is a zone. No need for holding and landing point. local c0=Pdeparture local c1=c0:Translate(d_climb/2, heading) local c2=c1:Translate(d_climb/2, heading) local c3=Pdestination - local wp0=self:_Waypoint(takeoff, c0, VxClimb, H_departure, departure) - local wp1=self:_Waypoint(RAT.wp.climb, c1, VxClimb, H_departure+(FLcruise-H_departure)/2) - local wp2=self:_Waypoint(RAT.wp.cruise, c2, VxCruise, FLcruise) - local wp3=self:_Waypoint(RAT.wp.finalwp, c3, VxCruise, FLcruise) + wp[1]=self:_Waypoint(takeoff, c0, VxClimb, H_departure, departure) + self.waypointdescriptions[1]="Departure" + wp[2]=self:_Waypoint(RAT.wp.climb, c1, VxClimb, H_departure+(FLcruise-H_departure)/2) + self.waypointdescriptions[2]="Climb" + wp[3]=self:_Waypoint(RAT.wp.cruise, c2, VxCruise, FLcruise) + self.waypointdescriptions[3]="Begin of Cruise" + wp[4]=self:_Waypoint(RAT.wp.finalwp, c3, VxCruise, FLcruise) + self.waypointdescriptions[4]="Final Destination" - -- set waypoints - waypoints = {wp0, wp1, wp2, wp3} + else + + if takeoff==RAT.wp.air then + -- Airstart, simplify climb waypoints. + local c0=Pdeparture + local c1=c0:Translate(d_climb, heading) + local c2=c1:Translate(d_cruise, heading) + local c3=c2:Translate(d_descent/2, heading) + local c4=Pholding + local c5=Pdestination + + -- Waypoints + wp[1]=self:_Waypoint(takeoff, c0, VxClimb, H_departure, departure) + self.waypointdescriptions[1]="Departure (air)" + wp[2]=self:_Waypoint(RAT.wp.cruise, c1, VxCruise, FLcruise) + self.waypointdescriptions[2]="Begin of Cruise" + wp[3]=self:_Waypoint(RAT.wp.cruise, c2, VxCruise, FLcruise) + self.waypointdescriptions[3]="End of Cruise" + wp[4]=self:_Waypoint(RAT.wp.descent, c3, VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) + self.waypointdescriptions[4]="Descent" + wp[5]=self:_Waypoint(RAT.wp.holding, c4, VxHolding, H_holding+h_holding) + self.waypointdescriptions[5]="Holding Point" + wp[6]=self:_Waypoint(RAT.wp.landing, c5, VxFinal, H_destination, destination) + self.waypointdescriptions[6]="Destination" + + else + + -- Coordinates: departure (0), middle of climb (1), begin of climb (2), end of climb (3), middle of descent (4) holding (5), destination (6) + local c0=Pdeparture + local c1=c0:Translate(d_climb/2, heading) + local c2=c1:Translate(d_climb/2, heading) + local c3=c2:Translate(d_cruise, heading) + local c4=c3:Translate(d_descent/2, heading) + local c5=Pholding + local c6=Pdestination + + -- Waypoints + wp[1]=self:_Waypoint(takeoff, c0, VxClimb, H_departure, departure) + self.waypointdescriptions[1]="Departure" + wp[2]=self:_Waypoint(RAT.wp.climb, c1, VxClimb, H_departure+(FLcruise-H_departure)/2) + self.waypointdescriptions[2]="Climb" + wp[3]=self:_Waypoint(RAT.wp.cruise, c2, VxCruise, FLcruise) + self.waypointdescriptions[3]="Begin of Cruise" + wp[4]=self:_Waypoint(RAT.wp.cruise, c3, VxCruise, FLcruise) + self.waypointdescriptions[4]="End of Cruise" + wp[5]=self:_Waypoint(RAT.wp.descent, c4, VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) + self.waypointdescriptions[5]="Descent" + wp[6]=self:_Waypoint(RAT.wp.holding, c5, VxHolding, H_holding+h_holding) + self.waypointdescriptions[6]="Holding Point" + wp[7]=self:_Waypoint(RAT.wp.landing, c6, VxFinal, H_destination, destination) + self.waypointdescriptions[7]="Destination" + end + + end + + -- Fill table with waypoints. + local waypoints={} + for _,p in ipairs(wp) do + table.insert(waypoints, p) end -- Place markers of waypoints on F10 map. @@ -1672,7 +1718,7 @@ function RAT:_SetRoute(takeoff, _departure, _destination) self:_PlaceMarkers(waypoints) end - -- some info on the route as message + -- Some info on the route. self:_Routeinfo(waypoints, "Waypoint info in set_route:") -- return departure, destination and waypoints @@ -2091,10 +2137,16 @@ function RAT:Status(message, forID) -- Distance remaining to destination. local Ddestination=Pn:Get2DDistance(self.ratcraft[i].destination:GetCoordinate()) - -- Distance remaining to holding point, which is waypoint 6 - local idx=6 + -- Distance remaining to holding point or final waypoint + local idx if self.destinationzone then - idx=4 + idx=4 --final waypoint for destination zone is 4 + else + if self.takeoff==RAT.wp.air then + idx=5 -- holing waypoint for air start is 5 + else + idx=6 -- holing waypoint for normal start is 5 + end end local Hp=COORDINATE:New(self.ratcraft[i].waypoints[idx].x, self.ratcraft[i].waypoints[idx].alt, self.ratcraft[i].waypoints[idx].y) local Dholding=Pn:Get2DDistance(Hp) @@ -2102,7 +2154,7 @@ function RAT:Status(message, forID) -- Status shortcut. local status=self.ratcraft[i].status - -- Range from holding point + -- Range from holding point for registering at ATC queue. local DRholding if self.category==RAT.cat.plane then DRholding=8000 @@ -2110,7 +2162,7 @@ function RAT:Status(message, forID) DRholding=2000 end - -- If distance to holding point is less then 6 km we register the plane. + -- If distance to holding point is less then X km we register the plane. if self.ATCswitch and Dholding<=DRholding and string.match(status, "On journey") then RAT:_ATCRegisterFlight(group:GetName(), Tnow) self.ratcraft[i].status="Holding" @@ -2561,22 +2613,19 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport) elseif Type==RAT.wp.climb then _Type="Turning Point" _Action="Turning Point" - --_Action="Fly Over Point" _alttype="BARO" elseif Type==RAT.wp.cruise then _Type="Turning Point" _Action="Turning Point" - --_Action="Fly Over Point" _alttype="BARO" elseif Type==RAT.wp.descent then _Type="Turning Point" _Action="Turning Point" - --_Action="Fly Over Point" _alttype="BARO" elseif Type==RAT.wp.holding then _Type="Turning Point" - _Action="Turning Point" - --_Action="Fly Over Point" + --_Action="Turning Point" + _Action="Fly Over Point" _alttype="BARO" elseif Type==RAT.wp.landing then _Type="Land" @@ -2670,9 +2719,7 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport) local Duration=self:_Randomize(90,0.9) RoutePoint.task=self:_TaskHolding({x=Coord.x, y=Coord.z}, Altitude, Speed, Duration) elseif Type==RAT.wp.finalwp then - local TaskRespawn, argkey, arg = self:_TaskFunction("RAT._FinalWaypoint", self) - self.argkey=argkey - self.arg=arg + local TaskRespawn = self:_TaskFunction("RAT._FinalWaypoint", self) local TaskCombo = {TaskRespawn} RoutePoint.task = {} RoutePoint.task.id = "ComboTask" @@ -2841,7 +2888,7 @@ function RAT:_TaskFunction(FunctionString, ... ) env.info(RAT.id.."Taskfunction:") self:E( DCSTask ) - return DCSTask, ArgumentKey, arg + return DCSTask end --- Anticipated group name from alias and spawn index. @@ -3053,48 +3100,16 @@ function RAT:_Random_Gaussian(x0, sigma, xmin, xmax) return r ---old version ---[[ - -- Standard deviation. Default 10 if not given. - sigma=sigma or 10 - - -- Uniform numbers in [0,1). We need two. - local x1=math.random() - local x2=math.random() - - -- Transform to Gaussian exp(-(x-x0)²/(2*sigma²). - local r = math.sqrt(-2*sigma*sigma * math.log(x1)) * math.cos(2*math.pi * x2)+x0 - --local r2 = math.sqrt(-2*sigma*sigma * math.log(x1)) * math.sin(2*math.pi * x2)+x0 - - -- Cut-off distribution at xmin. - if xmin then - if rxmax then - if xmin then - r=math.max(math.min(x0,xmax),xmin) - else - r=math.min(x0,xmax) - end - end - end - - return r -]] end --- Place markers of the waypoints. Note we assume a very specific number and type of waypoints here. -- @param #RAT self -- @param #table waypoints Table with waypoints. function RAT:_PlaceMarkers(waypoints) + for i=1,#waypoints do + self:_SetMarker(self.waypointdescriptions[i], waypoints[i]) + end +--[[ self:_SetMarker("Takeoff", waypoints[1]) self:_SetMarker("Climb", waypoints[2]) self:_SetMarker("Begin of Cruise", waypoints[3]) @@ -3104,6 +3119,7 @@ function RAT:_PlaceMarkers(waypoints) self:_SetMarker("Holding Point", waypoints[6]) self:_SetMarker("Destination", waypoints[7]) end +]] end @@ -3238,6 +3254,7 @@ function RAT:_ATCInit(airports_map) RAT.ATC.airport[name].queue={} RAT.ATC.airport[name].busy=false RAT.ATC.airport[name].onfinal=nil + RAT.ATC.airport[name].Nonfinal=0 RAT.ATC.airport[name].traffic=0 end SCHEDULER:New(nil, RAT._ATCCheck, {self}, 5, 15) @@ -3251,7 +3268,7 @@ end -- @param #string name Group name of the flight. -- @param #string dest Name of the destination airport. function RAT:_ATCAddFlight(name, dest) - env.info(string.format("%s%s ATC: Adding flight %s with destination %s.", RAT.id, dest, name, dest)) + env.info(string.format("%sATC %s: Adding flight %s with destination %s.", RAT.id, dest, name, dest)) RAT.ATC.flight[name]={} RAT.ATC.flight[name].destination=dest RAT.ATC.flight[name].Tarrive=-1 @@ -3290,6 +3307,7 @@ function RAT:_ATCStatus() 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 @@ -3306,13 +3324,13 @@ function RAT:_ATCStatus() end -- Aircraft is holding. - env.info(string.format("%s%s ATC: Flight %s is holding for %i:%02d. %s.", RAT.id, dest, name, hold/60, hold%60, busy)) + env.info(string.format("%sATC %s: Flight %s is holding for %i:%02d. %s.", RAT.id, dest, name, hold/60, hold%60, busy)) elseif hold==RAT.ATC.onfinal then -- Aircarft is on final approach for landing. local Tfinal=Tnow-RAT.ATC.flight[name].Tonfinal - env.info(string.format("%s%s ATC: Flight %s was cleared for landing. Waiting %i:%02d for landing event.", RAT.id, dest, name, Tfinal/60, Tfinal%60)) + env.info(string.format("%sATC %s: Flight %s was cleared for landing. Waiting %i:%02d for landing event.", RAT.id, dest, name, Tfinal/60, Tfinal%60)) --TODO: Trigger landing for another aircraft when Tfinal > x min? -- After five minutes we set the runway to green. ==> Increase the landing frequency a bit. @@ -3344,7 +3362,6 @@ function RAT:_ATCCheck() for name,_ in pairs(RAT.ATC.airport) do - -- List of flights cleared for landing. local qw={} @@ -3359,8 +3376,8 @@ function RAT:_ATCCheck() RAT.ATC.flight[flight].holding=Tnow-RAT.ATC.flight[flight].Tarrive -- Debug message. - local text=string.format("%s ATC: 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) - env.info(text) + 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) + env.info(RAT.id..text) else @@ -3391,6 +3408,8 @@ function RAT:_ATCClearForLanding(airport, flight) RAT.ATC.airport[airport].busy=true -- Flight which is landing. RAT.ATC.airport[airport].onfinal=flight + -- Number of planes on final approach. + RAT.ATC.airport[airport].Nonfinal=RAT.ATC.airport[airport].Nonfinal+1 -- Current time. RAT.ATC.flight[flight].Tonfinal=timer.getTime() -- Set user flag to 1 ==> stop condition for holding. @@ -3398,9 +3417,9 @@ function RAT:_ATCClearForLanding(airport, flight) local flagvalue=trigger.misc.getUserFlag(flight) -- Debug message. - local text1=string.format("%s%s ATC: Flight %s cleared for final approach (flag=%d).", RAT.id, airport, flight, flagvalue) - local text2=string.format("%s ATC: Flight %s you are cleared for landing.", airport, flight) - env.info(text1) + local text1=string.format("ATC %s: Flight %s cleared for final approach (flag=%d).", airport, flight, flagvalue) + local text2=string.format("ATC %s: Flight %s you are cleared for landing.", airport, flight) + env.info( RAT.id..text1) MESSAGE:New(text2, 10):ToAll() end @@ -3425,18 +3444,27 @@ function RAT:_ATCFlightLanded(name) -- No aircraft on final any more. RAT.ATC.airport[dest].onfinal=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("%s%s ATC: Flight %s landed. Tholding = %i:%02d, Tfinal = %i:%02d.", RAT.id, dest, name, Thold/60, Thold%60, Tfinal/60, Tfinal%60) - local text2=string.format("%s ATC: Flight %s landed. Welcome to %s.", dest, name, dest) - env.info(text1) - env.info(string.format("%s%s ATC: Number of planes landed in total %d.", RAT.id, dest, RAT.ATC.airport[dest].traffic)) - MESSAGE:New(text2, 10):ToAll() + 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.", RAT.ATC.airport[dest].Nonfinal) + local text3=string.format("ATC %s: Traffic report: Number of planes landed in total %d. Flighs / hour = %3.2f.", dest, RAT.ATC.airport[dest].traffic, TrafficPerHour) + local text4=string.format("ATC %s: Flight %s landed. Welcome to %s.", dest, name, dest) + env.info(RAT.id..text1) + env.info(RAT.id..text2) + env.info(RAT.id..text3) + MESSAGE:New(text4, 10):ToAll() end end @@ -3462,7 +3490,7 @@ function RAT:_ATCQueue() end end - -- Sort queue w.r.t holding time in acending order. + -- Sort queue w.r.t holding time in ascending order. local function compare(a,b) return a[2] > b[2] end From f1b7ae76438d90483a2b5bf5c27d6b288c789908 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Wed, 18 Oct 2017 00:47:44 +0200 Subject: [PATCH 08/25] RAT fixes --- Moose Development/Moose/Functional/RAT.lua | 96 ++++++++++++++-------- 1 file changed, 64 insertions(+), 32 deletions(-) diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index ab772a2cb..db6a4b1e6 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -1651,31 +1651,67 @@ function RAT:_SetRoute(takeoff, _departure, _destination) wp[4]=self:_Waypoint(RAT.wp.finalwp, c3, VxCruise, FLcruise) self.waypointdescriptions[4]="Final Destination" + -- Index of the holing point for registering aircraft at ATC. + self.wp_final_index=4 + + else if takeoff==RAT.wp.air then - -- Airstart, simplify climb waypoints. - local c0=Pdeparture - local c1=c0:Translate(d_climb, heading) - local c2=c1:Translate(d_cruise, heading) - local c3=c2:Translate(d_descent/2, heading) - local c4=Pholding - local c5=Pdestination + if d_climb < 1000 or d_cruise < 1000 then - -- Waypoints - wp[1]=self:_Waypoint(takeoff, c0, VxClimb, H_departure, departure) - self.waypointdescriptions[1]="Departure (air)" - wp[2]=self:_Waypoint(RAT.wp.cruise, c1, VxCruise, FLcruise) - self.waypointdescriptions[2]="Begin of Cruise" - wp[3]=self:_Waypoint(RAT.wp.cruise, c2, VxCruise, FLcruise) - self.waypointdescriptions[3]="End of Cruise" - wp[4]=self:_Waypoint(RAT.wp.descent, c3, VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) - self.waypointdescriptions[4]="Descent" - wp[5]=self:_Waypoint(RAT.wp.holding, c4, VxHolding, H_holding+h_holding) - self.waypointdescriptions[5]="Holding Point" - wp[6]=self:_Waypoint(RAT.wp.landing, c5, VxFinal, H_destination, destination) - self.waypointdescriptions[6]="Destination" + -- Airstart, simplify climb waypoints. + local c0=Pdeparture + local c1=c0:Translate(d_climb+d_cruise, heading) + local c2=c1:Translate(d_descent/2, heading) + local c3=Pholding + local c4=Pdestination + + -- Waypoints + wp[1]=self:_Waypoint(takeoff, c0, VxClimb, H_departure, departure) + self.waypointdescriptions[1]="Departure (air)" + wp[2]=self:_Waypoint(RAT.wp.cruise, c1, VxCruise, FLcruise) + self.waypointdescriptions[2]="Cruise" + wp[3]=self:_Waypoint(RAT.wp.descent, c2, VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) + self.waypointdescriptions[3]="Descent" + wp[4]=self:_Waypoint(RAT.wp.holding, c3, VxHolding, H_holding+h_holding) + self.waypointdescriptions[4]="Holding Point" + wp[5]=self:_Waypoint(RAT.wp.landing, c4, VxFinal, H_destination, destination) + self.waypointdescriptions[5]="Destination" + + -- Index of the holing point for registering aircraft at ATC. + self.wp_final_index=4 + + else + + -- Airstart, simplify climb waypoints. + local c0=Pdeparture + local c1=c0:Translate(d_climb, heading) + local c2=c1:Translate(d_cruise, heading) + local c3=c2:Translate(d_descent/2, heading) + local c4=Pholding + local c5=Pdestination + + -- Waypoints + wp[1]=self:_Waypoint(takeoff, c0, VxClimb, H_departure, departure) + self.waypointdescriptions[1]="Departure (air)" + wp[2]=self:_Waypoint(RAT.wp.cruise, c1, VxCruise, FLcruise) + self.waypointdescriptions[2]="Begin of Cruise" + wp[3]=self:_Waypoint(RAT.wp.cruise, c2, VxCruise, FLcruise) + self.waypointdescriptions[3]="End of Cruise" + wp[4]=self:_Waypoint(RAT.wp.descent, c3, VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) + self.waypointdescriptions[4]="Descent" + wp[5]=self:_Waypoint(RAT.wp.holding, c4, VxHolding, H_holding+h_holding) + self.waypointdescriptions[5]="Holding Point" + wp[6]=self:_Waypoint(RAT.wp.landing, c5, VxFinal, H_destination, destination) + self.waypointdescriptions[6]="Destination" + + -- Index of the holing point for registering aircraft at ATC. + self.wp_final_index=5 + + + end else @@ -1703,6 +1739,10 @@ function RAT:_SetRoute(takeoff, _departure, _destination) self.waypointdescriptions[6]="Holding Point" wp[7]=self:_Waypoint(RAT.wp.landing, c6, VxFinal, H_destination, destination) self.waypointdescriptions[7]="Destination" + + -- Index of the holing point for registering aircraft at ATC. + self.wp_final_index=6 + end end @@ -2138,16 +2178,7 @@ function RAT:Status(message, forID) local Ddestination=Pn:Get2DDistance(self.ratcraft[i].destination:GetCoordinate()) -- Distance remaining to holding point or final waypoint - local idx - if self.destinationzone then - idx=4 --final waypoint for destination zone is 4 - else - if self.takeoff==RAT.wp.air then - idx=5 -- holing waypoint for air start is 5 - else - idx=6 -- holing waypoint for normal start is 5 - end - end + local idx=self.wp_final_index local Hp=COORDINATE:New(self.ratcraft[i].waypoints[idx].x, self.ratcraft[i].waypoints[idx].alt, self.ratcraft[i].waypoints[idx].y) local Dholding=Pn:Get2DDistance(Hp) @@ -3335,7 +3366,8 @@ function RAT:_ATCStatus() --TODO: Trigger landing for another aircraft when Tfinal > x min? -- After five minutes we set the runway to green. ==> Increase the landing frequency a bit. if Tfinal>300 then - RAT.ATC.airport[dest].busy=false + --RAT.ATC.airport[dest].busy=false + --self:_ATCCheck() end elseif hold==RAT.ATC.unregistered then @@ -3458,7 +3490,7 @@ function RAT:_ATCFlightLanded(name) -- 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.", RAT.ATC.airport[dest].Nonfinal) + local text2=string.format("ATC %s: Number of flights still on final %d.", dest, RAT.ATC.airport[dest].Nonfinal) local text3=string.format("ATC %s: Traffic report: Number of planes landed in total %d. Flighs / hour = %3.2f.", dest, RAT.ATC.airport[dest].traffic, TrafficPerHour) local text4=string.format("ATC %s: Flight %s landed. Welcome to %s.", dest, name, dest) env.info(RAT.id..text1) From 730cd92d51b04407a8b48179a6e2409a84cf69bf Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Wed, 18 Oct 2017 15:51:01 +0200 Subject: [PATCH 09/25] RAT ATC --- Moose Development/Moose/Functional/RAT.lua | 297 ++++++++++++++++++++- 1 file changed, 293 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index db6a4b1e6..b05e52726 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -1590,7 +1590,9 @@ function RAT:_SetRoute(takeoff, _departure, _destination) -- debug message local text=string.format("\n******************************************************\n") - text=text..string.format("Template = %s\n\n", self.SpawnTemplatePrefix) + text=text..string.format("Template = %s\n", self.SpawnTemplatePrefix) + text=text..string.format("Alias = %s\n", self.alias) + text=text..string.format("Group name = %s\n\n", self:_AnticipatedGroupName()) text=text..string.format("Speeds:\n") text=text..string.format("VxCruiseMin = %6.1f m/s = %5.1f km/h\n", VxCruiseMin, VxCruiseMin*3.6) text=text..string.format("VxCruiseMax = %6.1f m/s = %5.1f km/h\n", VxCruiseMax, VxCruiseMax*3.6) @@ -1709,8 +1711,7 @@ function RAT:_SetRoute(takeoff, _departure, _destination) -- Index of the holing point for registering aircraft at ATC. self.wp_final_index=5 - - + end else @@ -3272,6 +3273,292 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Initializes the ATC arrays and starts schedulers. +-- @param #RAT self +-- @param #table airports_map List of all airports of the map. +function RAT:_ATCInit(airports_map) + if not RAT.ATC.init then + env.info(RAT.id.."Starting RAT ATC.") + RAT.ATC.init=true + for _,name in pairs(airports_map) do + local name=ap:GetName() + RAT.ATC.airport[name]={} + RAT.ATC.airport[name].queue={} + RAT.ATC.airport[name].busy=false + RAT.ATC.airport[name].onfinal={} + RAT.ATC.airport[name].Nonfinal=0 + RAT.ATC.airport[name].traffic=0 + RAT.ATC.airport[name].Tlastclearance=nil + end + SCHEDULER:New(nil, RAT._ATCCheck, {self}, 5, 15) + SCHEDULER:New(nil, RAT._ATCStatus, {self}, 5, 60) + RAT.ATC.T0=timer.getTime() + end +end + +--- Adds andd initializes a new flight after it was spawned. +-- @param #RAT self +-- @param #string name Group name of the flight. +-- @param #string dest Name of the destination airport. +function RAT:_ATCAddFlight(name, dest) + env.info(string.format("%sATC %s: Adding flight %s with destination %s.", RAT.id, dest, name, dest)) + RAT.ATC.flight[name]={} + RAT.ATC.flight[name].destination=dest + RAT.ATC.flight[name].Tarrive=-1 + RAT.ATC.flight[name].holding=-1 + RAT.ATC.flight[name].Tonfinal=-1 +end + +--- Deletes a flight from ATC lists after it landed. +-- @param #RAT self +-- @param #table t Table. +-- @param #string entry Flight name which shall be deleted. +function RAT:_ATCDelFlight(t,entry) + for k,_ in pairs(t) do + if k==entry then + t[entry]=nil + end + end +end + +--- Registers a flight once it is near its holding point at the final destination. +-- @param #RAT self +-- @param #string name Group name of the flight. +-- @param #number time Time the fight first registered. +function RAT:_ATCRegisterFlight(name, time) + RAT.ATC.flight[name].Tarrive=time + RAT.ATC.flight[name].holding=0 +end + + +--- ATC status report about flights. +-- @param #RAT self +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 + busy="Runway is occupied by "..RAT.ATC.airport[dest].Nonfinal + else + busy="Runway is currently clear" + end + + -- Aircraft is holding. + local text=string.format("STATUS: ATC %s: Flight %s is holding for %i:%02d. %s.", dest, name, hold/60, hold%60, busy) + env.info(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("STATUS: ATC %s: Flight %s is on final. Waiting %i:%02d for landing event.", dest, name, Tfinal/60, Tfinal%60) + env.info(RAT.id..text) + + elseif hold==RAT.ATC.unregistered then + + -- Aircraft has not arrived at holding point. + --env.info(string.format("ATC %s: Flight %s is not registered yet (hold %d).", dest, name, hold)) + + else + env.error(RAT.id.."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. +-- @param #RAT self +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 + -- Landing if time is enough and less then two planes are on final. + landing1=(Tnow-RAT.ATC.airport[name].Tlastclearance>90) and RAT.ATC.airport[name].Nonfinal<2 + else + landing1=false + end + -- No other aircraft is on final. + local landing2=RAT.ATC.airport[name].Nonfinal==0 + + + 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("CHECK: 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) + env.info(RAT.id..text) + + else + + local text=string.format("CHECK: 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) + env.info(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. +-- @param #RAT self +-- @param #string airport Name of destination airport. +-- @param #string flight Group name of flight, which gets landing clearence. +function RAT:_ATCClearForLanding(airport, flight) + -- Flight is cleared for landing. + RAT.ATC.flight[flight].holding=RAT.ATC.onfinal + -- Airport runway is busy now. + RAT.ATC.airport[airport].busy=true + -- Flight which is landing. + RAT.ATC.airport[airport].onfinal[flight]=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() + -- 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) + env.info( RAT.id..text1) + MESSAGE:New(text2, 10):ToAll() +end + +--- Takes care of organisational stuff after a plane has landed. +-- @param #RAT self +-- @param #string name Group name of flight. +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) + local text3=string.format("ATC %s: Traffic report: Number of planes landed in total %d. Flights/hour = %3.2f.", dest, RAT.ATC.airport[dest].traffic, TrafficPerHour) + local text4=string.format("ATC %s: Flight %s landed. Welcome to %s.", dest, name, dest) + env.info(RAT.id..text1) + env.info(RAT.id..text2) + env.info(RAT.id..text3) + MESSAGE:New(text4, 10):ToAll() + end + +end + +--- Creates a landing queue for all flights holding at airports. Aircraft with longest holding time gets first permission to land. +-- @param #RAT self +function RAT:_ATCQueue() + + for airport,_ in pairs(RAT.ATC.airport) do + + -- Local airport queue. + local _queue={} + + -- Loop over all flights. + 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 + table.insert(RAT.ATC.airport[airport].queue, v[1]) + end + + --fvh + --for k,v in ipairs(RAT.ATC.airport[airport].queue) do + --print(string.format("queue #%02i flight \"%s\" holding %d seconds",k, v, RAT.ATC.flight[v].holding)) + --end + + end +end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + + +--[[ --- Initializes the ATC arrays and starts schedulers. -- @param #RAT self -- @param #table airports_map List of all airports of the map. @@ -3442,6 +3729,8 @@ function RAT:_ATCClearForLanding(airport, flight) RAT.ATC.airport[airport].onfinal=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() -- Current time. RAT.ATC.flight[flight].Tonfinal=timer.getTime() -- Set user flag to 1 ==> stop condition for holding. @@ -3536,5 +3825,5 @@ function RAT:_ATCQueue() end end - +]] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file From d0107d5cee303714137e1d1b81fda6e5b2d6886d Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Thu, 19 Oct 2017 00:39:40 +0200 Subject: [PATCH 10/25] RAT added radio --- Moose Development/Moose/Functional/RAT.lua | 88 +++++++++++++++++++++- 1 file changed, 84 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index b05e52726..1d4044009 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -120,6 +120,9 @@ -- @field #boolean ATCswitch Enable/disable ATC if set to true/false. -- @field #string parking_id String with a special parking ID for the aircraft. -- @field #number wp_final_index Index of the holding or final waypoint. +-- @field #boolean radio If true/false disables radio messages from the RAT groups. +-- @field #number frequency Radio frequency used by the RAT groups. +-- @field #string modulation Ratio modulation. Either "FM" or "AM". -- @extends Core.Spawn#SPAWN ---# RAT class, extends @{Spawn#SPAWN} @@ -326,6 +329,9 @@ RAT={ ATCswitch=true, -- Enable ATC. parking_id=nil, -- Specific parking ID when aircraft are spawned at airports. wp_final_index=nil, -- Index of the holding for final waypoint. + radio=nil, -- If true/false disables radio messages from the RAT groups. + frequency=nil, -- Radio frequency used by the RAT groups. + modulation=nil, -- Ratio modulation. Either "FM" or "AM". } ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -395,6 +401,8 @@ RAT.ATC={ airport={}, unregistered=-1, onfinal=-100, + Nclearance=2, + delay=180, } --- Running number of placed markers on the F10 map. @@ -567,6 +575,9 @@ function RAT:Spawn(naircraft) text=text..string.format("Create F10 menu : %s\n", tostring(self.f10menu)) text=text..string.format("F10 submenu name: %s\n", self.SubMenuName) text=text..string.format("ATC enabled : %s\n", tostring(self.ATCswitch)) + text=text..string.format("Radio comms : %s\n", tostring(self.radio)) + text=text..string.format("Radio frequency : %s\n", tostring(self.frequency)) + text=text..string.format("Radio modulation : %s\n", tostring(self.frequency)) text=text..string.format("******************************************************\n") env.info(RAT.id..text) @@ -821,7 +832,7 @@ end --- Set livery of aircraft. If more than one livery is specified in a table, the actually used one is chosen randomly from the selection. -- @param #RAT self --- @param #string skins Name of livery or table of names of liveries. +-- @param #table skins Name of livery or table of names of liveries. function RAT:Livery(skins) if type(skins)=="string" then self.livery={skins} @@ -890,6 +901,40 @@ function RAT:SetParkingID(id) env.info(RAT.id.."Setting parking ID to "..self.parking_id) end +--- Enable Radio. Overrules the ME setting. +-- @param #RAT self +function RAT:RadioON() + self.radio=true +end + +--- Disable Radio. Overrules the ME setting. +-- @param #RAT self +function RAT:RadioOFF() + self.radio=false +end + +--- Set radio frequency. +-- @param #RAT self +-- @param #number frequency Radio frequency. +function RAT:RadioFrequency(frequency) + self.frequency=frequency +end + +--- Set radio modulation. Default is AM. +-- @param #RAT self +-- @param #string modulation Either "FM" or "AM". If no value is given, modulation is set to AM. +function RAT:RadioFrequency(modulation) + if modulation=="AM" then + self.modulation=radio.modulation.AM + elseif modulation=="FM" then + self.modulation=radio.modulation.FM + else + self.modulation=radio.modulation.AM + end +end + + + --- Set the time after which inactive groups will be destroyed. Default is 300 seconds. -- @param #RAT self -- @param #number time Time in seconds. @@ -959,6 +1004,20 @@ function RAT:EnableATC(switch) self.ATCswitch=switch end +--- 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 1. +function RAT:ATC_Clearance(n) + RAT.ATC.Nclearance=n +end + +--- 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. +function RAT:ATC_Delay(time) + RAT.ATC.delay=time +end + --- Set minimum distance between departure and destination. Default is 5 km. -- Minimum distance should not be smaller than maybe ~500 meters to ensure that departure and destination are different. -- @param #RAT self @@ -3224,7 +3283,12 @@ function RAT:_ModifySpawnTemplate(waypoints) -- Set (another) livery. if self.livery then - SpawnTemplate.units[UnitID].livery_id = self.livery[math.random(#self.livery)] + for _, skin in pairs(self.livery) do + env.info(RAT.id.."Possible livery: "..skin.." for group "..self:_AnticipatedGroupName()) + end + local skin=self.livery[math.random(#self.livery)] + env.info(RAT.id.."Chosen livery: "..skin.." for group "..self:_AnticipatedGroupName()) + SpawnTemplate.units[UnitID].livery_id = skin end --SpawnTemplate.units[UnitID]["type"] = "Tu-142" @@ -3261,6 +3325,20 @@ function RAT:_ModifySpawnTemplate(waypoints) -- 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 + end + if self.modulation then + SpawnTemplate.modulation=self.modulation + end + --SpawnTemplate.uncontrolled=true -- Update modified template for spawn group. @@ -3279,8 +3357,10 @@ end function RAT:_ATCInit(airports_map) if not RAT.ATC.init then env.info(RAT.id.."Starting RAT ATC.") + env.info(RAT.id.."Simultanious = "..RAT.ATC.Nclearance) + env.info(RAT.id.."Delay = "..RAT.ATC.delay) RAT.ATC.init=true - for _,name in pairs(airports_map) do + for _,ap in pairs(airports_map) do local name=ap:GetName() RAT.ATC.airport[name]={} RAT.ATC.airport[name].queue={} @@ -3399,7 +3479,7 @@ function RAT:_ATCCheck() local landing1 if RAT.ATC.airport[name].Tlastclearance then -- Landing if time is enough and less then two planes are on final. - landing1=(Tnow-RAT.ATC.airport[name].Tlastclearance>90) and RAT.ATC.airport[name].Nonfinal<2 + landing1=(Tnow-RAT.ATC.airport[name].Tlastclearance > RAT.ATC.delay) and RAT.ATC.airport[name].Nonfinal < RAT.ATC.Nclearance else landing1=false end From bce2e3b922f6763156db79590ca15cfc5952e338 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Thu, 19 Oct 2017 23:34:14 +0200 Subject: [PATCH 11/25] RAT waypoints --- Moose Development/Moose/Functional/RAT.lua | 309 ++++++++++++++++----- 1 file changed, 247 insertions(+), 62 deletions(-) diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index 1d4044009..125aeeaae 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -92,6 +92,8 @@ -- @field #table excluded_ports Array containing the names of explicitly excluded airports. -- @field #table destination_zones Array containing the names of the destination zones. -- @field #boolean destinationzone Destination is a zone and not an airport. +-- @field #table return_zones Array containing the names of the return zones. +-- @field #boolean returnzone Zone where aircraft will fly to before returning to their departure airport. -- @field Core.Zone#ZONE departure_Azone Zone containing the departure airports. -- @field Core.Zone#ZONE destination_Azone Zone containing the destination airports. -- @field #table ratcraft Array with the spawned RAT aircraft. @@ -119,7 +121,8 @@ -- @field #string skill Skill of AI. -- @field #boolean ATCswitch Enable/disable ATC if set to true/false. -- @field #string parking_id String with a special parking ID for the aircraft. --- @field #number wp_final_index Index of the holding or final waypoint. +-- @field #number wp_final Index of the final waypoint. +-- @field #number wp_holding Index of the holding waypoint. -- @field #boolean radio If true/false disables radio messages from the RAT groups. -- @field #number frequency Radio frequency used by the RAT groups. -- @field #string modulation Ratio modulation. Either "FM" or "AM". @@ -300,6 +303,8 @@ RAT={ destination_ports={}, -- Array containing the names of the destination airports. destination_zones={}, -- Array containing the names of destination zones. destinationzone=false, -- Destination is a zone and not an airport. + return_zones={}, -- Array containing the names of return zones. + returnzone=false, -- Aircraft will fly to a zone and back. excluded_ports={}, -- Array containing the names of explicitly excluded airports. departure_Azone=nil, -- Zone containing the departure airports. destination_Azone=nil, -- Zone containing the destination airports. @@ -328,7 +333,8 @@ RAT={ skill="High", -- Skill of AI. ATCswitch=true, -- Enable ATC. parking_id=nil, -- Specific parking ID when aircraft are spawned at airports. - wp_final_index=nil, -- Index of the holding for final waypoint. + wp_final=nil, -- Index of the final waypoint. + wp_holding=nil, -- Index of the holding waypoint. radio=nil, -- If true/false disables radio messages from the RAT groups. frequency=nil, -- Radio frequency used by the RAT groups. modulation=nil, -- Ratio modulation. Either "FM" or "AM". @@ -361,6 +367,22 @@ RAT.wp={ finalwp=10, } +--- RAT aircraft status. +-- @list status +RAT.status={ + Birth="Born", + StartingEngines="Starting engines", + TaxiToRunway="Taxiing to runway", + Takeoff="On climb after takeoff", + Climb="Climbing", + CruiseBegin="Cruising", + CruiseEnd="Descending after cruise", + Descent="Descending to holding point", + Holding="Holding", + Final="On final", + Landed="Landed and taxiing to parking", +} + --- RAT friendly coalitions. -- @list coal RAT.coal={ @@ -796,6 +818,33 @@ function RAT:SetDestinationZone(names) end +--- Set name of "return" zones for the AI aircraft. If multiple names are given as a table, one zone is picked randomly as destination. +-- @param #RAT self +-- @param #string names Name or table of names of zones defined in the mission editor. +function RAT:SetReturnZone(names) + + -- Random destination is deactivated now that user specified destination zone(s). + self.random_destination=false + -- Destination is a zone. Needs special care. + self.returnzone=true + + if type(names)=="table" then + + for _,name in pairs(names) do + table.insert(self.return_zones, ZONE:New(name)) + end + + elseif type(names)=="string" then + + table.insert(self.return_zones, ZONE:New(names)) + + else + -- Error message. + env.error("Input parameter must be a string or a table!") + end + +end + --- Include all airports which lie in a zone as possible destinations. -- @param #RAT self @@ -1229,13 +1278,6 @@ function RAT:_SpawnWithRoute(_departure, _destination) RAT:_ATCAddFlight(group:GetName(), destination:GetName()) end - if self.destinationzone then --- env.info(RAT.id.." setstate") --- self:E(self.argkey) --- self:E(self.arg) --- group:SetState(group, self.argkey, self.arg ) - end - -- Set ROE, default is "weapon hold". self:_SetROE(group, self.roe) @@ -1416,7 +1458,6 @@ function RAT:_SetRoute(takeoff, _departure, _destination) if takeoff==RAT.wp.air then -- For an air start, we take a random point within the spawn zone. local vec2=departure:GetRandomVec2() - --Pdeparture=COORDINATE:New(vec2.x, self.aircraft.FLcruise, vec2.y) Pdeparture=COORDINATE:NewFromVec2(vec2) else Pdeparture=departure:GetCoordinate() @@ -1481,26 +1522,43 @@ function RAT:_SetRoute(takeoff, _departure, _destination) -- 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 airport are identical. Airport %s (ID %d).", self.alias, destination:GetName(), destination:GetID()) - MESSAGE:New(text, 120):ToAll() + local text=string.format("%s: Destination and departure airport are identical. Airport %s.", self.alias, destination:GetName()) + MESSAGE:New(text, 30):ToAll() env.error(RAT.id..text) end -- Coordinates of destination airport. - local Pdestination=destination:GetCoordinate() + local Pdestination + if self.destinationzone or self.returnzone then + local vec2=destination:GetRandomVec2() + Pdestination=COORDINATE:NewFromVec2(vec2) + else + Pdestination=destination:GetCoordinate() + end -- Height ASL of destination airport. local H_destination=Pdestination.y -- DESCENT/HOLDING POINT -- Get a random point between 5 and 10 km away from the destination. local Vholding - if self.category==RAT.cat.plane then - Vholding=destination:GetCoordinate():GetRandomVec2InRadius(10000, 5000) - else + local Rhmin=5000 + local Rhmax=10000 + if self.category==RAT.cat.heli then -- For helos we set a distance between 500 to 1000 m. - Vholding=destination:GetCoordinate():GetRandomVec2InRadius(1000, 500) + Rhmin=500 + Rhmax=1000 end + + -- Holing point near destination or departure if we return to the original airport. + local holdingpoint + if self.returnzone then + holdingpoint=departure + else + holdingpoint=destination + end + -- Coordinates of the holding point. y is the land height at that point. + Vholding=holdingpoint:GetCoordinate():GetRandomVec2InRadius(Rhmax, Rhmin) local Pholding=COORDINATE:NewFromVec2(Vholding) -- AGL height of holding point. @@ -1519,11 +1577,22 @@ function RAT:_SetRoute(takeoff, _departure, _destination) local d_holding=Pholding:Get2DDistance(Pdestination) -- GENERAL - -- Heading from departure to holding point of destination. - local heading=self:_Course(Pdeparture, Pholding) + local heading + local d_total + if self.returnzone then + -- Heading from departure to destination in return zone. + heading=self:_Course(Pdeparture, Pdestination) - -- Total distance between departure and holding point near destination. - local d_total=Pdeparture:Get2DDistance(Pholding) + -- Total distance to return zone and back. + d_total=Pdeparture:Get2DDistance(Pdestination)*2 + + 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 if we only would descent to holding point for the given distance if takeoff==RAT.wp.air then @@ -1692,7 +1761,7 @@ function RAT:_SetRoute(takeoff, _departure, _destination) if d_cruise<0 then d_cruise=100 end - + --local waypoints local wp={} if self.destinationzone then @@ -1703,18 +1772,51 @@ function RAT:_SetRoute(takeoff, _departure, _destination) local c2=c1:Translate(d_climb/2, heading) local c3=Pdestination - wp[1]=self:_Waypoint(takeoff, c0, VxClimb, H_departure, departure) + wp[1]=self:_Waypoint(1, takeoff, c0, VxClimb, H_departure, departure) self.waypointdescriptions[1]="Departure" - wp[2]=self:_Waypoint(RAT.wp.climb, c1, VxClimb, H_departure+(FLcruise-H_departure)/2) + wp[2]=self:_Waypoint(2, RAT.wp.climb, c1, VxClimb, H_departure+(FLcruise-H_departure)/2) self.waypointdescriptions[2]="Climb" - wp[3]=self:_Waypoint(RAT.wp.cruise, c2, VxCruise, FLcruise) + wp[3]=self:_Waypoint(3, RAT.wp.cruise, c2, VxCruise, FLcruise) self.waypointdescriptions[3]="Begin of Cruise" - wp[4]=self:_Waypoint(RAT.wp.finalwp, c3, VxCruise, FLcruise) + wp[4]=self:_Waypoint(4, RAT.wp.finalwp, c3, VxCruise, FLcruise) self.waypointdescriptions[4]="Final Destination" - -- Index of the holing point for registering aircraft at ATC. - self.wp_final_index=4 + -- Index of the final waypoint. Here used to despawn and respawn the aircraft. + self.wp_final=4 + + elseif self.returnzone then + + -- We fly from the departure to a zone and back. + local c0=Pdeparture + local c1=c0:Translate(d_climb/2, heading) + local c2=c1:Translate(d_climb/2, heading) + local c3=Pdestination + local c4=c3:Translate(d_cruise/2, heading-180) + local c5=c4:Translate(d_descent/2, heading-180) + local c6=Pholding + local c7=Pdeparture + -- Waypoints + wp[1]=self:_Waypoint(1, takeoff, c0, VxClimb, H_departure, departure) + self.waypointdescriptions[1]="Departure" + wp[2]=self:_Waypoint(2, RAT.wp.climb, c1, VxClimb, H_departure+(FLcruise-H_departure)/2) + self.waypointdescriptions[2]="Climb" + wp[3]=self:_Waypoint(3, RAT.wp.cruise, c2, VxCruise, FLcruise) + self.waypointdescriptions[3]="Begin of Cruise" + wp[4]=self:_Waypoint(4, RAT.wp.cruise, c3, VxCruise, FLcruise) + self.waypointdescriptions[4]="Return Zone" + wp[5]=self:_Waypoint(5, RAT.wp.cruise, c4, VxCruise, FLcruise) + self.waypointdescriptions[5]="End of Cruise" + wp[6]=self:_Waypoint(6, RAT.wp.descent, c5, VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) + self.waypointdescriptions[6]="Descent" + wp[7]=self:_Waypoint(7, RAT.wp.holding, c6, VxHolding, H_holding+h_holding) + self.waypointdescriptions[7]="Holding Point" + wp[8]=self:_Waypoint(8, RAT.wp.landing, c7, VxFinal, H_departure, departure) + self.waypointdescriptions[8]="Destination" + + -- Index of the holding and final waypoint. + self.wp_holding=7 + self.wp_final=8 else @@ -1730,19 +1832,20 @@ function RAT:_SetRoute(takeoff, _departure, _destination) local c4=Pdestination -- Waypoints - wp[1]=self:_Waypoint(takeoff, c0, VxClimb, H_departure, departure) + wp[1]=self:_Waypoint(1, takeoff, c0, VxClimb, H_departure, departure) self.waypointdescriptions[1]="Departure (air)" - wp[2]=self:_Waypoint(RAT.wp.cruise, c1, VxCruise, FLcruise) + wp[2]=self:_Waypoint(2, RAT.wp.cruise, c1, VxCruise, FLcruise) self.waypointdescriptions[2]="Cruise" - wp[3]=self:_Waypoint(RAT.wp.descent, c2, VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) + wp[3]=self:_Waypoint(3, RAT.wp.descent, c2, VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) self.waypointdescriptions[3]="Descent" - wp[4]=self:_Waypoint(RAT.wp.holding, c3, VxHolding, H_holding+h_holding) + wp[4]=self:_Waypoint(4, RAT.wp.holding, c3, VxHolding, H_holding+h_holding) self.waypointdescriptions[4]="Holding Point" - wp[5]=self:_Waypoint(RAT.wp.landing, c4, VxFinal, H_destination, destination) + wp[5]=self:_Waypoint(5, RAT.wp.landing, c4, VxFinal, H_destination, destination) self.waypointdescriptions[5]="Destination" -- Index of the holing point for registering aircraft at ATC. - self.wp_final_index=4 + self.wp_holding=4 + self.wp_final=5 else @@ -1755,21 +1858,22 @@ function RAT:_SetRoute(takeoff, _departure, _destination) local c5=Pdestination -- Waypoints - wp[1]=self:_Waypoint(takeoff, c0, VxClimb, H_departure, departure) + wp[1]=self:_Waypoint(1, takeoff, c0, VxClimb, H_departure, departure) self.waypointdescriptions[1]="Departure (air)" - wp[2]=self:_Waypoint(RAT.wp.cruise, c1, VxCruise, FLcruise) + wp[2]=self:_Waypoint(2, RAT.wp.cruise, c1, VxCruise, FLcruise) self.waypointdescriptions[2]="Begin of Cruise" - wp[3]=self:_Waypoint(RAT.wp.cruise, c2, VxCruise, FLcruise) + wp[3]=self:_Waypoint(3, RAT.wp.cruise, c2, VxCruise, FLcruise) self.waypointdescriptions[3]="End of Cruise" - wp[4]=self:_Waypoint(RAT.wp.descent, c3, VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) + wp[4]=self:_Waypoint(4, RAT.wp.descent, c3, VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) self.waypointdescriptions[4]="Descent" - wp[5]=self:_Waypoint(RAT.wp.holding, c4, VxHolding, H_holding+h_holding) + wp[5]=self:_Waypoint(5, RAT.wp.holding, c4, VxHolding, H_holding+h_holding) self.waypointdescriptions[5]="Holding Point" - wp[6]=self:_Waypoint(RAT.wp.landing, c5, VxFinal, H_destination, destination) + wp[6]=self:_Waypoint(6, RAT.wp.landing, c5, VxFinal, H_destination, destination) self.waypointdescriptions[6]="Destination" -- Index of the holing point for registering aircraft at ATC. - self.wp_final_index=5 + self.wp_holding=5 + self.wp_final=6 end @@ -1785,28 +1889,29 @@ function RAT:_SetRoute(takeoff, _departure, _destination) local c6=Pdestination -- Waypoints - wp[1]=self:_Waypoint(takeoff, c0, VxClimb, H_departure, departure) + wp[1]=self:_Waypoint(1, takeoff, c0, VxClimb, H_departure, departure) self.waypointdescriptions[1]="Departure" - wp[2]=self:_Waypoint(RAT.wp.climb, c1, VxClimb, H_departure+(FLcruise-H_departure)/2) + wp[2]=self:_Waypoint(2, RAT.wp.climb, c1, VxClimb, H_departure+(FLcruise-H_departure)/2) self.waypointdescriptions[2]="Climb" - wp[3]=self:_Waypoint(RAT.wp.cruise, c2, VxCruise, FLcruise) + wp[3]=self:_Waypoint(3, RAT.wp.cruise, c2, VxCruise, FLcruise) self.waypointdescriptions[3]="Begin of Cruise" - wp[4]=self:_Waypoint(RAT.wp.cruise, c3, VxCruise, FLcruise) + wp[4]=self:_Waypoint(4, RAT.wp.cruise, c3, VxCruise, FLcruise) self.waypointdescriptions[4]="End of Cruise" - wp[5]=self:_Waypoint(RAT.wp.descent, c4, VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) + wp[5]=self:_Waypoint(5, RAT.wp.descent, c4, VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) self.waypointdescriptions[5]="Descent" - wp[6]=self:_Waypoint(RAT.wp.holding, c5, VxHolding, H_holding+h_holding) + wp[6]=self:_Waypoint(6, RAT.wp.holding, c5, VxHolding, H_holding+h_holding) self.waypointdescriptions[6]="Holding Point" - wp[7]=self:_Waypoint(RAT.wp.landing, c6, VxFinal, H_destination, destination) + wp[7]=self:_Waypoint(7, RAT.wp.landing, c6, VxFinal, H_destination, destination) self.waypointdescriptions[7]="Destination" -- Index of the holing point for registering aircraft at ATC. - self.wp_final_index=6 + self.wp_holding=6 + self.wp_final= 7 end end - + -- Fill table with waypoints. local waypoints={} for _,p in ipairs(wp) do @@ -1822,7 +1927,11 @@ function RAT:_SetRoute(takeoff, _departure, _destination) self:_Routeinfo(waypoints, "Waypoint info in set_route:") -- return departure, destination and waypoints - return departure, destination, waypoints + if self.returnzone then + return departure, departure, waypoints + else + return departure, destination, waypoints + end end @@ -1928,7 +2037,7 @@ function RAT:_PickDestination(destinations, _random) -- Debug message. local text - if self.destinationzone then + if self.destinationzone or self.returnzone then text="Chosen destination zone: "..destination:GetName() else text="Chosen destination airport: "..destination:GetName().." (ID "..destination:GetID()..")" @@ -1983,10 +2092,17 @@ function RAT:_GetDestinations(departure, q, minrange, maxrange) if self.destinationzone then - -- Zones specified by user. + -- Destination zones specified by user. for _,zone in pairs(self.destination_zones) do table.insert(possible_destinations, zone) end + + elseif self.returnzone then + + -- Return zones specified by user. + for _,zone in pairs(self.return_zones) do + table.insert(possible_destinations, zone) + end else @@ -2238,7 +2354,7 @@ function RAT:Status(message, forID) local Ddestination=Pn:Get2DDistance(self.ratcraft[i].destination:GetCoordinate()) -- Distance remaining to holding point or final waypoint - local idx=self.wp_final_index + local idx=self.wp_final local Hp=COORDINATE:New(self.ratcraft[i].waypoints[idx].x, self.ratcraft[i].waypoints[idx].alt, self.ratcraft[i].waypoints[idx].y) local Dholding=Pn:Get2DDistance(Hp) @@ -2255,8 +2371,8 @@ function RAT:Status(message, forID) -- If distance to holding point is less then X km we register the plane. if self.ATCswitch and Dholding<=DRholding and string.match(status, "On journey") then - RAT:_ATCRegisterFlight(group:GetName(), Tnow) - self.ratcraft[i].status="Holding" + --RAT:_ATCRegisterFlight(group:GetName(), Tnow) + --self.ratcraft[i].status="Holding" end -- Status report. @@ -2637,8 +2753,9 @@ function RAT:_Despawn(group) local index=self:GetSpawnIndexFromGroup(group) env.info("RAT debug index = "..index) - self.ratcraft[index].group:Destroy() + --self.ratcraft[index].group:Destroy() self.ratcraft[index].group=nil + group:Destroy() env.info("RAT debug _despawn 1") -- Decrease group alive counter. self.alive=self.alive-1 @@ -2654,14 +2771,15 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Create a waypoint that can be used with the Route command. --- @param #RAT self +-- @param #RAT self +-- @param #number Running index of waypoints. Starts with 1 which is normally departure/spawn waypoint. -- @param #number Type Type of waypoint. -- @param Core.Point#COORDINATE Coord 3D coordinate of the waypoint. -- @param #number Speed Speed in m/s. -- @param #number Altitude Altitude in m. -- @param Wrapper.Airbase#AIRBASE Airport Airport of object to spawn. -- @return #table Waypoints for DCS task route or spawn template. -function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport) +function RAT:_Waypoint(index, Type, Coord, Speed, Altitude, Airport) -- Altitude of input parameter or y-component of 3D-coordinate. local _Altitude=Altitude or Coord.y @@ -2804,10 +2922,29 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport) ["vangle"] = 0, ["steer"] = 2, } - -- task + -- tasks + local TaskCombo = {} + local TaskHolding = self:_TaskHolding({x=Coord.x, y=Coord.z}, Altitude, Speed, self:_Randomize(90,0.9)) + local TaskRespawn = self:_TaskFunction("RAT._FinalWaypoint", self) + 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 + elseif Type==RAT.wp.finalwp then + --TaskCombo[#TaskCombo+1]=TaskRespawn + end + + RoutePoint.task.params.tasks = TaskCombo + + --[[ if Type==RAT.wp.holding then -- Duration of holing. Between 10 and 170 seconds. - local Duration=self:_Randomize(90,0.9) + local Duration=self:_Randomize(90,0.9) RoutePoint.task=self:_TaskHolding({x=Coord.x, y=Coord.z}, Altitude, Speed, Duration) elseif Type==RAT.wp.finalwp then local TaskRespawn = self:_TaskFunction("RAT._FinalWaypoint", self) @@ -2818,11 +2955,14 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport) RoutePoint.task.params.tasks = TaskCombo self:E(TaskRespawn) else + local PassingWaypoint = self:_TaskFunction("RAT._WaypointFunction", self, index) + local TaskCombo = {PassingWaypoint} RoutePoint.task = {} RoutePoint.task.id = "ComboTask" RoutePoint.task.params = {} - RoutePoint.task.params.tasks = {} + RoutePoint.task.params.tasks = TaskCombo end + ]] -- Return waypoint. return RoutePoint @@ -2943,6 +3083,50 @@ function RAT._FinalWaypoint(group, rat) rat:_Despawn(group) end +--- Function which is called after passing every waypoint. Info on waypoint is given and special functions are executed. +-- @param Core.Group#GROUP group Group of aircraft. +-- @param #RAT rat RAT object. +-- @param #number wp Waypoint index. Running number of the waypoints. Determines the actions to be executed. +function RAT._WaypointFunction(group, rat, wp) + env.info(RAT.id.."Waypointfunction for waypoint "..wp) + env.info("Final waypoint index "..rat.wp_final) + + local Tnow=timer.getTime() + local sdx=rat:GetSpawnIndexFromGroup(group) + local departure=rat.ratcraft[sdx].departure:GetName() + local destination=rat.ratcraft[sdx].departure:GetName() + + local text=string.format("Flight %s passing waypoint #%d %s (%s to %s).", group:GetName(), wp, rat.waypointdescriptions[wp], departure, destination) + env.info(RAT.id..text) + MESSAGE:New(text, 180):ToAll() + + if wp==rat.wp_holding then + MESSAGE:New("Flight "..group:GetName().." holding!", 360):ToAll() + if rat.ATCswitch then + rat:_ATCRegisterFlight(group:GetName(), Tnow) + rat.ratcraft[sdx].status="Holding" + end + end + + if wp==2 then + rat._Despawn(rat, group) + end + + if wp==rat.wp_final then + local text="Final waypoint reached!" + env.info(RAT.id..text) + MESSAGE:New(text, 30):ToAll() + + if rat.destinationzone then + -- Spawn new group. + rat:_Respawn(group) + + -- Despawn old group. + rat:_Despawn(group) + end + end +end + --- Orbit at a specified position at a specified alititude with a specified speed. -- @param #RAT self -- @param #string FunctionString Name of the function to be called. @@ -3406,6 +3590,7 @@ end -- @param #string name Group name of the flight. -- @param #number time Time the fight first registered. function RAT:_ATCRegisterFlight(name, time) + env.info(RAT.id.."Flight ".. name.." registered at ATC for landing clearance.") RAT.ATC.flight[name].Tarrive=time RAT.ATC.flight[name].holding=0 end From 3e16e5fa511c60f7a6e9bb72b448ab6aff4c9b12 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Thu, 19 Oct 2017 23:36:28 +0200 Subject: [PATCH 12/25] RAT --- Moose Development/Moose/Functional/RAT.lua | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index 125aeeaae..de84e13b0 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -3108,10 +3108,6 @@ function RAT._WaypointFunction(group, rat, wp) end end - if wp==2 then - rat._Despawn(rat, group) - end - if wp==rat.wp_final then local text="Final waypoint reached!" env.info(RAT.id..text) From b9231592980aad54c9fc316de719376507781a6f Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Fri, 20 Oct 2017 22:32:45 +0200 Subject: [PATCH 13/25] Changed Suppression Fire to Suppressive Fire --- Moose Development/Moose/Functional/RAT.lua | 350 +++++++++++++----- ...uppressionFire.lua => SuppressiveFire.lua} | 0 Moose Mission Setup/Moose.files | 2 +- 3 files changed, 254 insertions(+), 98 deletions(-) rename Moose Development/Moose/Functional/{SuppressionFire.lua => SuppressiveFire.lua} (100%) diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index de84e13b0..f41c984f7 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -117,6 +117,7 @@ -- @field #number respawn_delay Delay in seconds until repawn happens after landing. -- @field #table markerids Array with marker IDs. -- @field #table waypointdescriptions Table with strings for waypoint descriptions of markers. +-- @field #table waypointstatus Table with strings of waypoint status. -- @field #string livery Livery of the aircraft set by user. -- @field #string skill Skill of AI. -- @field #boolean ATCswitch Enable/disable ATC if set to true/false. @@ -329,6 +330,7 @@ RAT={ respawn_delay=nil, -- Delay in seconds until repawn happens after landing. markerids={}, -- Array with marker IDs. waypointdescriptions={}, -- Array with descriptions for waypoint markers. + waypointstatus={}, -- Array with status info on waypoints. livery=nil, -- Livery of the aircraft. skill="High", -- Skill of AI. ATCswitch=true, -- Enable ATC. @@ -370,17 +372,25 @@ RAT.wp={ --- RAT aircraft status. -- @list status RAT.status={ - Birth="Born", - StartingEngines="Starting engines", - TaxiToRunway="Taxiing to runway", - Takeoff="On climb after takeoff", + -- Waypoint states. + Departure="At departure point", Climb="Climbing", - CruiseBegin="Cruising", - CruiseEnd="Descending after cruise", - Descent="Descending to holding point", + Cruise="Cruising", + Uturn="Flying back home", + Descent="Descending", + DescentHolding="Descend to holding point", Holding="Holding", - Final="On final", - Landed="Landed and taxiing to parking", + Destination="Arrived at destination", + -- Event states. + EventBirthAir="Born in air", + EventBirth="Born on ground", + EventEngineStartAir="Started engines in air", + EventEngineStart="Started engines on ground", + EventTakeoff="Took off", + EventLand="Landed", + EventEngineShutdown="Engines shut down", + EventDead="Dead", + EventCrash="Crashed", } --- RAT friendly coalitions. @@ -632,7 +642,7 @@ function RAT:Spawn(naircraft) self:HandleEvent(EVENTS.Land, self._OnLand) self:HandleEvent(EVENTS.EngineShutdown, self._OnEngineShutdown) self:HandleEvent(EVENTS.Dead, self._OnDead) - --self:HandleEvent(EVENTS.Crash, self._OnCrash) + self:HandleEvent(EVENTS.Crash, self._OnCrash) -- TODO: add hit event? end @@ -1595,6 +1605,7 @@ function RAT:_SetRoute(takeoff, _departure, _destination) end -- max height if we only would descent to holding point for the given distance + -- TODO: Add case for destination zone. We could allow a higher max because no descent is necessary. if takeoff==RAT.wp.air then local H_departure_max = d_total * math.tan(AlphaDescent) + H_holding + h_holding H_departure=math.min(H_departure, H_departure_max) @@ -1693,17 +1704,11 @@ function RAT:_SetRoute(takeoff, _departure, _destination) -- Set cruise altitude. Selected from Gaussian distribution but limited to FLmin and FLmax. local FLcruise=self:_Random_Gaussian(self.aircraft.FLcruise, (FLmax-FLmin)/4, FLmin, FLmax) - + -- Overrule setting if user specified a flight level explicitly. if self.FLuser then FLcruise=self.FLuser end - - -- Cruise alt should not be below departure alt - --FLcruise = math.max(FLcruise, H_departure) - - -- Cruise alt should not be above max FL - --FLcruise = math.min(FLcruise, FLmax) -- Climb and descent heights. local h_climb = (FLcruise-H_departure) --math.abs ? @@ -1747,6 +1752,13 @@ function RAT:_SetRoute(takeoff, _departure, _destination) 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("\nMax heights and distances:\n") + text=text..string.format("d_climb_max = %6.1f km\n", d_climb_max/1000) + text=text..string.format("d_cruise_max = %6.1f km\n", d_cruise_max/1000) + text=text..string.format("d_descent_max = %6.1f km\n", d_descent_max/1000) + text=text..string.format("d_total_max = %6.1f km\n", d_total_max/1000) + text=text..string.format("h_climb_max = %6.1f m\n", h_climb_max) + text=text..string.format("h_descent_max = %6.1f m\n", h_descent_max) text=text..string.format("\nAngles:\n") text=text..string.format("Alpha climb = %6.1f Deg\n", math.deg(AlphaClimb)) text=text..string.format("Alpha descent = %6.1f Deg\n", math.deg(AlphaDescent)) @@ -1762,24 +1774,130 @@ function RAT:_SetRoute(takeoff, _departure, _destination) d_cruise=100 end - --local waypoints + -- Waypoints and coordinates local wp={} + local c={} + + -- Departure/Take-off + c[#c+1]=Pdeparture + wp[#wp+1]=self:_Waypoint(#wp+1, 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 < 1000 or d_cruise < 1000 then + -- We omit the climb phase completely and add it to the cruise part. + d_cruise=d_cruise+d_climb + 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, 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, 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, RAT.wp.cruise, c[#wp+1], VxCruise, FLcruise) + self.waypointdescriptions[#wp]="Begin of Cruise" + self.waypointstatus[#wp]=RAT.status.Cruise + + end + + -- Cruise + if self.destinationzone then + + -- Next waypoint is already the final destination. + c[#c+1]=Pdestination + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.finalwp, c[#wp+1], VxCruise, FLcruise) + self.waypointdescriptions[#wp]="Final Destination" + self.waypointstatus[#wp]=RAT.status.Destination + + elseif self.returnzone then + + c[#c+1]=Pdestination + c[#c+1]=c[#c]:Translate(d_cruise/2, heading-180) + + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.cruise, c[#wp+1], VxCruise, FLcruise) + self.waypointdescriptions[#wp]="Return Zone" + self.waypointstatus[#wp]=RAT.status.Uturn + + wp[#wp+1]=self:_Waypoint(#wp+1, 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, RAT.wp.cruise, c[#wp+1], VxCruise, FLcruise) + self.waypointdescriptions[#wp]="End of Cruise" + self.waypointstatus[#wp]=RAT.status.Descent + end + + -- Descent + if self.destinationzone then + -- Nothing to do. + elseif self.returnzone then + c[#c+1]=c[#c]:Translate(d_descent/2, heading-180) + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.descent, c[#wp+1], VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) + self.waypointdescriptions[#wp]="Descent" + self.waypointstatus[#wp]=RAT.status.DescentHolding + else + c[#c+1]=c[#c]:Translate(d_descent/2, heading) + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.descent, c[#wp+1], VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) + self.waypointdescriptions[#wp]="Descent" + self.waypointstatus[#wp]=RAT.status.DescentHolding + end + + -- Holding and final destination. + if self.destinationzone then + -- Nothing to do. + else + c[#c+1]=Pholding + c[#c+1]=Pdestination + + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.holding, c[#wp+1], VxHolding, H_holding+h_holding) + self.waypointdescriptions[#wp]="Holding Point" + self.waypointstatus[#wp]=RAT.status.Holding + self.wp_holding=#wp + + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.landing, c[#wp+1], VxFinal, H_departure, departure) + self.waypointdescriptions[#wp]="Destination" + self.waypointstatus[#wp]=RAT.status.Destination + end + + -- Final Waypoint + self.wp_final=#wp + +--[[ if self.destinationzone then -- Destination is a zone. No need for holding and landing point. - local c0=Pdeparture - local c1=c0:Translate(d_climb/2, heading) - local c2=c1:Translate(d_climb/2, heading) - local c3=Pdestination + c[#c+1]=Pdeparture + c[#c+1]=c[#c]:Translate(d_climb/2, heading) + c[#c+1]=c[#c]:Translate(d_climb/2, heading) + c[#c+1]=Pdestination - wp[1]=self:_Waypoint(1, takeoff, c0, VxClimb, H_departure, departure) - self.waypointdescriptions[1]="Departure" - wp[2]=self:_Waypoint(2, RAT.wp.climb, c1, VxClimb, H_departure+(FLcruise-H_departure)/2) - self.waypointdescriptions[2]="Climb" - wp[3]=self:_Waypoint(3, RAT.wp.cruise, c2, VxCruise, FLcruise) - self.waypointdescriptions[3]="Begin of Cruise" - wp[4]=self:_Waypoint(4, RAT.wp.finalwp, c3, VxCruise, FLcruise) - self.waypointdescriptions[4]="Final Destination" + wp[#wp+1]=self:_Waypoint(#wp+1, takeoff, c[#wp+1], VxClimb, H_departure, departure) + self.waypointdescriptions[#wp+1]="Departure" + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.climb, c[#wp+1], VxClimb, H_departure+(FLcruise-H_departure)/2) + self.waypointdescriptions[#wp+1]="Climb" + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.cruise, c[#wp+1], VxCruise, FLcruise) + self.waypointdescriptions[#wp+1]="Begin of Cruise" + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.finalwp, c[#wp+1], VxCruise, FLcruise) + self.waypointdescriptions[#wp+1]="Final Destination" -- Index of the final waypoint. Here used to despawn and respawn the aircraft. self.wp_final=4 @@ -1797,22 +1915,22 @@ function RAT:_SetRoute(takeoff, _departure, _destination) local c7=Pdeparture -- Waypoints - wp[1]=self:_Waypoint(1, takeoff, c0, VxClimb, H_departure, departure) - self.waypointdescriptions[1]="Departure" - wp[2]=self:_Waypoint(2, RAT.wp.climb, c1, VxClimb, H_departure+(FLcruise-H_departure)/2) - self.waypointdescriptions[2]="Climb" - wp[3]=self:_Waypoint(3, RAT.wp.cruise, c2, VxCruise, FLcruise) - self.waypointdescriptions[3]="Begin of Cruise" - wp[4]=self:_Waypoint(4, RAT.wp.cruise, c3, VxCruise, FLcruise) - self.waypointdescriptions[4]="Return Zone" - wp[5]=self:_Waypoint(5, RAT.wp.cruise, c4, VxCruise, FLcruise) - self.waypointdescriptions[5]="End of Cruise" - wp[6]=self:_Waypoint(6, RAT.wp.descent, c5, VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) - self.waypointdescriptions[6]="Descent" - wp[7]=self:_Waypoint(7, RAT.wp.holding, c6, VxHolding, H_holding+h_holding) - self.waypointdescriptions[7]="Holding Point" - wp[8]=self:_Waypoint(8, RAT.wp.landing, c7, VxFinal, H_departure, departure) - self.waypointdescriptions[8]="Destination" + wp[#wp+1]=self:_Waypoint(#wp+1, takeoff, c0, VxClimb, H_departure, departure) + self.waypointdescriptions[#wp+1]="Departure" + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.climb, c1, VxClimb, H_departure+(FLcruise-H_departure)/2) + self.waypointdescriptions[#wp+1]="Climb" + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.cruise, c2, VxCruise, FLcruise) + self.waypointdescriptions[#wp+1]="Begin of Cruise" + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.cruise, c3, VxCruise, FLcruise) + self.waypointdescriptions[#wp+1]="Return Zone" + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.cruise, c4, VxCruise, FLcruise) + self.waypointdescriptions[#wp+1]="End of Cruise" + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.descent, c5, VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) + self.waypointdescriptions[#wp+1]="Descent" + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.holding, c6, VxHolding, H_holding+h_holding) + self.waypointdescriptions[#wp+1]="Holding Point" + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.landing, c7, VxFinal, H_departure, departure) + self.waypointdescriptions[#wp+1]="Destination" -- Index of the holding and final waypoint. self.wp_holding=7 @@ -1832,20 +1950,25 @@ function RAT:_SetRoute(takeoff, _departure, _destination) local c4=Pdestination -- Waypoints - wp[1]=self:_Waypoint(1, takeoff, c0, VxClimb, H_departure, departure) - self.waypointdescriptions[1]="Departure (air)" - wp[2]=self:_Waypoint(2, RAT.wp.cruise, c1, VxCruise, FLcruise) - self.waypointdescriptions[2]="Cruise" - wp[3]=self:_Waypoint(3, RAT.wp.descent, c2, VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) - self.waypointdescriptions[3]="Descent" - wp[4]=self:_Waypoint(4, RAT.wp.holding, c3, VxHolding, H_holding+h_holding) - self.waypointdescriptions[4]="Holding Point" - wp[5]=self:_Waypoint(5, RAT.wp.landing, c4, VxFinal, H_destination, destination) - self.waypointdescriptions[5]="Destination" + wp[#wp+1]=self:_Waypoint(#wp+1, takeoff, c0, VxClimb, H_departure, departure) + self.waypointdescriptions[#wp+1]="Departure (air)" + self.waypointstatus[#wp+1]=RAT.status.DepartureAir + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.cruise, c1, VxCruise, FLcruise) + self.waypointdescriptions[#wp+1]="Cruise" + self.waypointstatus[#wp+1]=RAT.status.CruiseBegin + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.descent, c2, VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) + self.waypointdescriptions[#wp+1]="Descent" + self.waypointstatus[#wp+1]=RAT.status.Descent + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.holding, c3, VxHolding, H_holding+h_holding) + self.waypointdescriptions[#wp+1]="Holding Point" + self.waypointstatus[#wp+1]=RAT.status.Holding + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.landing, c4, VxFinal, H_destination, destination) + self.waypointdescriptions[#wp+1]="Destination" + self.waypointstatus[#wp+1]=RAT.status.Destination -- Index of the holing point for registering aircraft at ATC. self.wp_holding=4 - self.wp_final=5 + self.wp_final=5 else @@ -1858,18 +1981,18 @@ function RAT:_SetRoute(takeoff, _departure, _destination) local c5=Pdestination -- Waypoints - wp[1]=self:_Waypoint(1, takeoff, c0, VxClimb, H_departure, departure) - self.waypointdescriptions[1]="Departure (air)" - wp[2]=self:_Waypoint(2, RAT.wp.cruise, c1, VxCruise, FLcruise) - self.waypointdescriptions[2]="Begin of Cruise" - wp[3]=self:_Waypoint(3, RAT.wp.cruise, c2, VxCruise, FLcruise) - self.waypointdescriptions[3]="End of Cruise" - wp[4]=self:_Waypoint(4, RAT.wp.descent, c3, VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) - self.waypointdescriptions[4]="Descent" - wp[5]=self:_Waypoint(5, RAT.wp.holding, c4, VxHolding, H_holding+h_holding) - self.waypointdescriptions[5]="Holding Point" - wp[6]=self:_Waypoint(6, RAT.wp.landing, c5, VxFinal, H_destination, destination) - self.waypointdescriptions[6]="Destination" + wp[#wp+1]=self:_Waypoint(#wp+1, takeoff, c0, VxClimb, H_departure, departure) + self.waypointdescriptions[#wp+1]="Departure (air)" + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.cruise, c1, VxCruise, FLcruise) + self.waypointdescriptions[#wp+1]="Begin of Cruise" + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.cruise, c2, VxCruise, FLcruise) + self.waypointdescriptions[#wp+1]="End of Cruise" + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.descent, c3, VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) + self.waypointdescriptions[#wp+1]="Descent" + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.holding, c4, VxHolding, H_holding+h_holding) + self.waypointdescriptions[#wp+1]="Holding Point" + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.landing, c5, VxFinal, H_destination, destination) + self.waypointdescriptions[#wp+1]="Destination" -- Index of the holing point for registering aircraft at ATC. self.wp_holding=5 @@ -1889,20 +2012,20 @@ function RAT:_SetRoute(takeoff, _departure, _destination) local c6=Pdestination -- Waypoints - wp[1]=self:_Waypoint(1, takeoff, c0, VxClimb, H_departure, departure) - self.waypointdescriptions[1]="Departure" - wp[2]=self:_Waypoint(2, RAT.wp.climb, c1, VxClimb, H_departure+(FLcruise-H_departure)/2) - self.waypointdescriptions[2]="Climb" - wp[3]=self:_Waypoint(3, RAT.wp.cruise, c2, VxCruise, FLcruise) - self.waypointdescriptions[3]="Begin of Cruise" - wp[4]=self:_Waypoint(4, RAT.wp.cruise, c3, VxCruise, FLcruise) - self.waypointdescriptions[4]="End of Cruise" - wp[5]=self:_Waypoint(5, RAT.wp.descent, c4, VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) - self.waypointdescriptions[5]="Descent" - wp[6]=self:_Waypoint(6, RAT.wp.holding, c5, VxHolding, H_holding+h_holding) - self.waypointdescriptions[6]="Holding Point" - wp[7]=self:_Waypoint(7, RAT.wp.landing, c6, VxFinal, H_destination, destination) - self.waypointdescriptions[7]="Destination" + wp[#wp+1]=self:_Waypoint(#wp+1, takeoff, c0, VxClimb, H_departure, departure) + self.waypointdescriptions[#wp+1]="Departure" + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.climb, c1, VxClimb, H_departure+(FLcruise-H_departure)/2) + self.waypointdescriptions[#wp+1]="Climb" + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.cruise, c2, VxCruise, FLcruise) + self.waypointdescriptions[#wp+1]="Begin of Cruise" + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.cruise, c3, VxCruise, FLcruise) + self.waypointdescriptions[#wp+1]="End of Cruise" + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.descent, c4, VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) + self.waypointdescriptions[#wp+1]="Descent" + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.holding, c5, VxHolding, H_holding+h_holding) + self.waypointdescriptions[#wp+1]="Holding Point" + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.landing, c6, VxFinal, H_destination, destination) + self.waypointdescriptions[#wp+1]="Destination" -- Index of the holing point for registering aircraft at ATC. self.wp_holding=6 @@ -1911,6 +2034,7 @@ function RAT:_SetRoute(takeoff, _departure, _destination) end end +]] -- Fill table with waypoints. local waypoints={} @@ -2462,7 +2586,9 @@ end -- @param #RAT self function RAT:_SetStatus(group, status) local index=self:GetSpawnIndexFromGroup(group) - env.info(RAT.id.."Status for group "..group:GetName()..": "..status) + local text=string.format("New status for group %s: %s",group:GetName(), status) + env.info(RAT.id..text) + MESSAGE:New(text, 180):ToAll() self.ratcraft[index].status=status end @@ -2491,8 +2617,10 @@ function RAT:_OnBirth(EventData) local status if SpawnGroup:InAir() then status="Just born (after air start)" + status=RAT.status.EventBirthAir else status="Starting engines (after birth)" + status=RAT.status.EventBirth end self:_SetStatus(SpawnGroup, status) @@ -2528,11 +2656,12 @@ function RAT:_EngineStartup(EventData) local status if SpawnGroup:InAir() then status="On journey (after air start)" + status=RAT.status.EventEngineStartAir else status="Taxiing (after engines started)" + status=RAT.status.EventEngineStart end self:_SetStatus(SpawnGroup, status) - end end @@ -2563,7 +2692,9 @@ function RAT:_OnTakeoff(EventData) env.info(RAT.id..text) -- Set status. - self:_SetStatus(SpawnGroup, "On journey (after takeoff)") + local status=RAT.status.EventTakeoff + --self:_SetStatus(SpawnGroup, "On journey (after takeoff)") + self:_SetStatus(SpawnGroup, status) if self.respawn_after_takeoff then text="Event: Group "..SpawnGroup:GetName().." will be respawned." @@ -2603,7 +2734,9 @@ function RAT:_OnLand(EventData) env.info(RAT.id..text) -- Set status. - self:_SetStatus(SpawnGroup, "Taxiing (after landing)") + --self:_SetStatus(SpawnGroup, "Taxiing (after landing)") + local status=RAT.status.EventLand + self:_SetStatus(SpawnGroup, status) -- ATC plane landed. Take it out of the queue and set runway to free. if self.ATCswitch then @@ -2648,7 +2781,9 @@ function RAT:_OnEngineShutdown(EventData) env.info(RAT.id..text) -- Set status. - self:_SetStatus(SpawnGroup, "Parking (shutting down engines)") + --self:_SetStatus(SpawnGroup, "Parking (shutting down engines)") + local status=RAT.status.EventEngineShutdown + self:_SetStatus(SpawnGroup, status) if not self.respawn_at_landing and not self.norespawn then text="Event: Group "..SpawnGroup:GetName().." will be respawned." @@ -2693,7 +2828,9 @@ function RAT:_OnDead(EventData) env.info(RAT.id..text) -- Set status. - self:_SetStatus(SpawnGroup, "Destroyed (after dead)") + --self:_SetStatus(SpawnGroup, "Destroyed (after dead)") + local status=RAT.status.EventDead + self:_SetStatus(SpawnGroup, status) end end @@ -2727,7 +2864,9 @@ function RAT:_OnCrash(EventData) env.info(RAT.id..text) -- Set status. - self:_SetStatus(SpawnGroup, "Crashed") + --self:_SetStatus(SpawnGroup, "Crashed") + local status=RAT.status.EventCrash + self:_SetStatus(SpawnGroup, status) --TODO: Aircraft are not respawned if they crash. Should they? @@ -3091,20 +3230,34 @@ function RAT._WaypointFunction(group, rat, wp) env.info(RAT.id.."Waypointfunction for waypoint "..wp) env.info("Final waypoint index "..rat.wp_final) - local Tnow=timer.getTime() + -- 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].departure:GetName() - local text=string.format("Flight %s passing waypoint #%d %s (%s to %s).", group:GetName(), wp, rat.waypointdescriptions[wp], departure, destination) + -- Info on passing waypoint. + local text=string.format("Flight %s passing waypoint #%d %s.", group:GetName(), wp, rat.waypointdescriptions[wp]) env.info(RAT.id..text) MESSAGE:New(text, 180):ToAll() + -- Set status + local status=rat.waypointstatus[wp] + rat.ratcraft[sdx].status=status + + -- Radio about the status update. + MESSAGE:New("Flight "..group:GetName().." status "..status, 180):ToAll() + if wp==rat.wp_holding then - MESSAGE:New("Flight "..group:GetName().." holding!", 360):ToAll() + + -- Aircraft arrived at holding point + MESSAGE:New("Flight "..group:GetName().." holding near "..destination.."!", 180):ToAll() + + -- Register aircraft at ATC. if rat.ATCswitch then rat:_ATCRegisterFlight(group:GetName(), Tnow) - rat.ratcraft[sdx].status="Holding" end end @@ -3123,7 +3276,7 @@ function RAT._WaypointFunction(group, rat, wp) end end ---- Orbit at a specified position at a specified alititude with a specified speed. +--- Task function. -- @param #RAT self -- @param #string FunctionString Name of the function to be called. function RAT:_TaskFunction(FunctionString, ... ) @@ -3164,8 +3317,10 @@ 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. -- @return #string Name the group will get after it is spawned. -function RAT:_AnticipatedGroupName() +function RAT:_AnticipatedGroupName(index) + local index=index or self.SpawnIndex+1 return string.format("%s#%03d", self.alias, self.SpawnIndex+1) end @@ -4087,4 +4242,5 @@ function RAT:_ATCQueue() end end ]] -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + diff --git a/Moose Development/Moose/Functional/SuppressionFire.lua b/Moose Development/Moose/Functional/SuppressiveFire.lua similarity index 100% rename from Moose Development/Moose/Functional/SuppressionFire.lua rename to Moose Development/Moose/Functional/SuppressiveFire.lua diff --git a/Moose Mission Setup/Moose.files b/Moose Mission Setup/Moose.files index d5e5b233d..9c6d00043 100644 --- a/Moose Mission Setup/Moose.files +++ b/Moose Mission Setup/Moose.files @@ -47,7 +47,7 @@ Functional/RAT.lua Functional/ZoneGoal.lua Functional/ZoneGoalCoalition.lua Functional/ZoneCaptureCoalition.lua -Functional/SuppressionFire.lua +Functional/SuppressiveFire.lua AI/AI_Balancer.lua AI/AI_A2A.lua From 8ea87021405f0dc09fab9207737983554f491b86 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Fri, 20 Oct 2017 22:39:13 +0200 Subject: [PATCH 14/25] Removed Suppressive Fire --- .../Moose/Functional/SuppressiveFire.lua | 734 ------------------ Moose Mission Setup/Moose.files | 1 - 2 files changed, 735 deletions(-) delete mode 100644 Moose Development/Moose/Functional/SuppressiveFire.lua diff --git a/Moose Development/Moose/Functional/SuppressiveFire.lua b/Moose Development/Moose/Functional/SuppressiveFire.lua deleted file mode 100644 index 5e21a8de0..000000000 --- a/Moose Development/Moose/Functional/SuppressiveFire.lua +++ /dev/null @@ -1,734 +0,0 @@ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- **Functional** - Suppress fire of ground units when they get hit. --- --- ==== --- --- When ground units get hit by (suppressive) enemy fire, they will not be able to shoot back for a certain amount of time. --- --- The implementation is based on an idea and script by MBot. See DCS forum threat https://forums.eagle.ru/showthread.php?t=107635 for details. --- --- ==== --- --- # Demo Missions --- --- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases) --- --- ==== --- --- # YouTube Channel --- --- ### [MOOSE YouTube Channel](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl1jirWIo4t4YxqN-HxjqRkL) --- --- === --- --- ### Author: **[funkyfranky](https://forums.eagle.ru/member.php?u=115026)** --- --- ### Contributions: **Sven van de Velde ([FlightControl](https://forums.eagle.ru/member.php?u=89536))** --- --- ==== --- @module ai_suppression - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- AI_Suppression class --- @type AI_Suppression --- @field #string ClassName Name of the class. --- @field #number Tsuppress_min Minimum time in seconds the group gets suppressed. --- @field #number Tsuppress_max Maximum time in seconds the group gets suppressed. --- @field #number life Relative life in precent of the group. --- @field #number TsuppressionStart Time at which the suppression started. --- @field #number TsuppressionOver Time at which the suppression will be over. --- @field #number Thit Last time the unit was hit. --- @field #number Nhit Number of times the unit was hit since it last was in state "CombatReady". --- @field Core.Zone#ZONE Zone_Retreat Zone into which a group retreats. --- @field #number LifeThreshold Life of group in percent at which the group will be ordered to retreat. --- @field #number IniGroupStrength Number of units in a group at start. --- @field #number GroupStrengthThreshold Threshold of group strength before retreat is ordered. --- @extends Core.Fsm#FSM_CONTROLLABLE --- - ----# AI_Suppression class, extends @{Core.Fsm#FSM_CONTROLLABLE} --- Mimic suppressive fire and make ground units take cover. --- --- ## Some Example... --- --- @field #AI_Suppression -AI_Suppression={ - ClassName = "AI_Suppression", - Tsuppress_min = 5, - Tsuppress_max = 20, - TsuppressStart = nil, - TsuppressOver = nil, - Thit = nil, - Nhit = 0, - Zone_Retreat = nil, - LifeThreshold = 25, - IniGroupStrength = nil, - GroupStrengthThreshold=80, -} - ---- Some ID to identify who we are in output of the DCS.log file. --- @field #string id -AI_Suppression.id="SFX | " - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---TODO: Figure out who was shooting and move away from him. ---TODO: Move behind a scenery building if there is one nearby. ---TODO: Retreat to a given zone or point. - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Creates a new AI_suppression object. --- @param #AI_Suppression self --- @param Wrapper.Group#GROUP Group The GROUP object for which suppression should be applied. --- @return #AI_Suppression -function AI_Suppression:New(Group) - - -- Check that group is present. - if Group then - env.info(AI_Suppression.id.."Suppression fire for group "..Group:GetName()) - else - env.info(AI_Suppression.id.."Suppression fire: Requested group does not exist! (Has to be a MOOSE group.)") - return nil - end - - -- Check that we actually have a GROUND group. - if Group:IsGround()==false then - env.error(AI_Suppression.id.."Suppression fire group "..Group:GetName().." has to be a GROUND group!") - return nil - end - - -- Inherits from FSM_CONTROLLABLE - local self=BASE:Inherit(self, FSM_CONTROLLABLE:New()) -- #AI_Suppression - - - -- Set the controllable for the FSM. - self:SetControllable(Group) - - -- Initial group strength. - self.IniGroupStrength=#Group:GetUnits() - - - -- Get life of group in %. - local life_min, life_max, life_ave, groupstrength=self:_GetLife() - - -- Group is initially in state CombatReady. - self:SetStartState("none") - - -- Transitions: - --------------- - - -- Transition from anything to "Suppressed" after event "Hit". - self:AddTransition("*", "Start", "CombatReady") - - -- Transition from anything to "Suppressed" after event "Hit". - self:AddTransition("*", "Hit", "*") - - -- Transition from "Suppressed" back to "CombatReady after the unit had time to recover. - self:AddTransition("*", "Recovered", "*") - - -- Transition from "Suppressed" back to "CombatReady after the unit had time to recover. - self:AddTransition("*", "Suppress", "Suppressed") - - -- Transition from "Suppressed" to "Hiding" after event "Hit". - self:AddTransition("*", "TakeCover", "Hiding") - - -- Transition from anything to "Retreating" after e.g. being severely damaged. - self:AddTransition("*", "Retreat", "Retreating") - - -- Transition from anything to "Dead" after group died. - self:AddTransition("*", "Died", "Dead") - - -- Check status of the group. - self:AddTransition("*", "Status", "*") - - --self:TakeCover() - - -- return self - return self - -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Set minimum and (optionally) maximum time a unit is suppressed each time it gets hit. --- @param #AI_Suppression self --- @param #number Tmin Minimum time in seconds. --- @param #number Tmax (Optional) Maximum suppression time. If no value is given, the is set to Tmin. -function AI_Suppression:SetSuppressionTime(Tmin, Tmax) - self.Tsuppress_min=Tmin or 1 - self.Tsuppress_max=Tmax or Tmin - env.info(AI_Suppression.id..string.format("Min suppression time %d seconds.", self.Tsuppress_min)) - env.info(AI_Suppression.id..string.format("Max suppression time %d seconds.", self.Tsuppress_max)) -end - ---- Set the zone to which a group retreats after being damaged too much. --- @param #AI_Suppression self --- @param Core.Zone#ZONE zone MOOSE zone object. -function AI_Suppression:SetRetreatZone(zone) - self.Zone_Retreat=zone - env.info(AI_Suppression.id..string.format("Retreat zone for group %s is %s.", self.Controllable:GetName(), self.Zone_Retreat:GetName())) -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- After "Start" event. --- @param #AI_Suppression self -function AI_Suppression:onafterStart(Controlable, From, Event, To) - env.info(AI_Suppression.id..string.format("onafterStart: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) - - -- Handle DCS event hit. - self:HandleEvent(EVENTS.Hit, self._OnHit) - - -- Handle DCS event dead. - self:HandleEvent(EVENTS.Dead, self._OnDead) - -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Before "Status" event. --- @param #AI_Suppression self -function AI_Suppression:OnBeforeStatus(Controlable, From, Event, To) - env.info(AI_Suppression.id..string.format("OnBeforeStatus: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) - local text=string.format("Group %s is in state %s.", Controlable:GetName(), self:GetState()) - MESSAGE:New(text, 10):ToAll() -end - ---- After "Status" event. --- @param #AI_Suppression self -function AI_Suppression:OnAfterStatus(Controlable, From, Event, To) - env.info(AI_Suppression.id..string.format("OnAfterStatus: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) - -- Get new status in 30 sec. - self:__Status(30) -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Before "Hit" event. (Of course, this is not really before the group got hit.) --- @param #AI_Suppression self --- @param Wrapper.Controllable#CONTROLLABLE Controlable Controllable of the group. --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Core.Point#COORDINATE Fallback Fallback coordinates (or nil if no attacker could be found). -function AI_Suppression:OnBeforeHit(Controlable, From, Event, To, Fallback) - env.info(AI_Suppression.id..string.format("OnBeforeHit: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) - - -- Increase Hit counter. - self.Nhit=self.Nhit+1 - - -- Info on hit times. - env.info(AI_Suppression.id..string.format("Group has just been hit %d times.", self.Nhit)) - -end - ---- After "Hit" event. --- @param #AI_Suppression self -function AI_Suppression:OnAfterHit(Controlable, From, Event, To, Fallback) - env.info(AI_Suppression.id..string.format("OnAfterHit: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) - - -- Suppress fire of group. - self:_Suppress() - - -- Get life of group in %. - local life_min, life_max, life_ave, groupstrength=self:_GetLife() - - if self:is("CombatReady") then - env.info(AI_Suppression.id..string.format("Group %s is currently CombatReady.", Controlable:GetName())) - self:Suppress() - elseif self:Is("Suppressed") then - env.info(AI_Suppression.id..string.format("Group %s is currently Suppressed.", Controlable:GetName())) - elseif self:Is("Retreating") then - env.info(AI_Suppression.id..string.format("Group %s is currently Retreating.", Controlable:GetName())) - elseif self:is("Hiding") then - env.info(AI_Suppression.id..string.format("Group %s is currently Hiding.", Controlable:GetName())) - end - - -- After three hits fall back a bit. - local nfallback=3 - if self.Nhit==nfallback then - env.info(AI_Suppression.id..string.format("Group %s is falling back after %d hits.", Controlable:GetName(), nfallback)) - Fallback:SmokeGreen() - local FallbackMarkerID=Fallback:MarkToAll("Fall back position for group "..Controlable:GetName():GetName()) - self:_FallBack(Fallback) - end - - -- If life of one unit is below threshold, the group is ordered to retreat (if a zone has been specified). - if not self:Is("Retreating") then - if groupstrength self.TsuppressionOver then - return true - else - return false - end - -end - ---- After "Recovered" event. --- @param #AI_Suppression self -function AI_Suppression:OnAfterRecovered(Controlable, From, Event, To) - env.info(AI_Suppression.id..string.format("OnAfterRecovered: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) - - -- Send message. - MESSAGE:New(string.format("Group %s has recovered.", Controlable:GetName()), 30):ToAll() - - -- Nothing to do yet. Just monitoring the event. -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Before "Retreat" event. --- @param #AI_Suppression self -function AI_Suppression:OnBeforeRetreat(Controlable, From, Event, To) - env.info(AI_Suppression.id..string.format("OnBeforeRetreat: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) - - -- Retreat is only possible if a zone has been defined by the user. - if self.Zone_Retreat==nil then - env.info(AI_Suppression.id.."Retreat NOT possible! No Zone specified.") - return false - elseif self:Is("Retreating") then - env.info(AI_Suppression.id.."Group is already retreating.") - return false - else - env.info(AI_Suppression.id.."Retreat possible, zone specified.") - return true - end - -end - ---- After "Retreat" event. --- @param #AI_Suppression self -function AI_Suppression:OnAfterRetreat(Controlable, From, Event, To) - env.info(AI_Suppression.id..string.format("OnAfterRetreat: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) - - -- Set the ALARM STATE to GREEN. Then the unit will move even if it is under fire. - Controlable:OptionAlarmStateGreen() - - -- Route the group to a zone. - MESSAGE:New(string.format("Group %s is retreating!", Controlable:GetName()), 30):ToAll() - self:_RetreatToZone(self.Zone_Retreat, 50, "Vee") - -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Before "TakeCover" event. --- @param #AI_Suppression self -function AI_Suppression:OnBeforeTakeCover(Controlable, From, Event, To) - env.info(AI_Suppression.id..string.format("OnBeforeTakeCover: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) - - -- We search objects in a zone with radius 100 m around the group. - -- TODO: Maybe make the zone radius larger for vehicles. - local Zone = ZONE_GROUP:New("Zone_Hiding", Controlable, 500) - - -- Scan for Scenery objects to run/drive to. - Zone:Scan(Object.Category.SCENERY) - - local gothideout=false - for SceneryTypeName, SceneryData in pairs( Zone:GetScannedScenery() ) do - for SceneryName, SceneryObject in pairs( SceneryData ) do - local SceneryObject = SceneryObject -- Wrapper.Scenery#SCENERY - local text=self.Controllable:GetName().. " scenery: " .. SceneryObject:GetTypeName() .. ", Coord LL DMS: " .. SceneryObject:GetCoordinate():ToStringLLDMS() - MESSAGE:New(text, 10):ToAll() - env.info(AI_Suppression.id..text) - -- TODO: Add check if scenery name matches a specific type like tree or building. This might be tricky though! - end - end - - -- Only take cover if we found a hideout. - return gothideout - -end - ---- After "TakeCover" event. --- @param #AI_Suppression self -function AI_Suppression:OnAfterTakeCover(Controlable, From, Event, To) - env.info(AI_Suppression.id..string.format("OnAfterTakeCover: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) - - local text=string.format("Group %s is taking cover!", Controlable:GetName()) - MESSAGE:New(text, 30):ToAll() - - -- Set the ALARM STATE to GREEN. Then the unit will move even if it is under fire. - Controlable:OptionAlarmStateGreen() - - -- Route the group to a zone. - MESSAGE:New(string.format("Group %s would be(!) hiding now!", Controlable:GetName()), 30):ToAll() - - --TODO: Search place to hide. For each unit (disperse) or same for all? - -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Entering "CombatReady" state. The group will be able to fight back. --- @param #AI_Suppression self -function AI_Suppression:OnEnterCombatReady(Controlable, From, Event, To) - env.info(AI_Suppression.id..string.format("OnEnterCombatReady: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) - -- Nothing to do yet. Just monitoring the event -end - ---- Leaving "CombatReady" state. --- @param #AI_Suppression self -function AI_Suppression:OnLeaveCombatReady(Controlable, From, Event, To) - env.info(AI_Suppression.id..string.format("OnLeaveCombatReady: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) - -- Nothing to do yet. Just monitoring the event -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Entering "Suppressed" state. Group will not fight but hold their weapons. --- @param #AI_Suppression self -function AI_Suppression:OnEnterSuppressed(Controlable, From, Event, To) - env.info(AI_Suppression.id..string.format("OnEnterSuppression: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) - -- Nothing to do yet. Just monitoring the event -end - ---- Leaving "Suppressed" state. --- @param #AI_Suppression self -function AI_Suppression:OnLeaveSuppressed(Controlable, From, Event, To) - env.info(AI_Suppression.id..string.format("OnLeaveSuppression: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) - - -- Group can fight again. - self.Controllable:OptionROEOpenFire() - - local text=string.format("Suppression of group %s ended at %f and should have ended at %f.", self.Controllable:GetName(), timer.getTime(), self.TsuppressionOver) - env.info(AI_Suppression.id..text) - -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Entering "Retreating" state. Group will be send to a zone. --- @param #AI_Suppression self --- @param Wrapper.Controllable#CONTROLLABLE Controlable Controllable of the AI group. -function AI_Suppression:OnEnterRetreating(Controlable, From, Event, To) - env.info(AI_Suppression.id..string.format("OnEnterRetreating: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) -end - ---- Leaving "Retreating" state. --- @param #AI_Suppression self --- @param Wrapper.Controllable#CONTROLLABLE Controlable Controllable of the AI group. -function AI_Suppression:OnLeaveRetreating(Controlable, From, Event, To) - env.info(AI_Suppression.id..string.format("OnLeveRetreating: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) - - -- Set the ALARM STATE back to AUTO. - Controlable:OptionAlarmStateAuto() -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Entering "Hiding" state. Group will try to take cover at neargy scenery objects. --- @param #AI_Suppression self --- @param Wrapper.Controllable#CONTROLLABLE Controlable Controllable of the AI group. -function AI_Suppression:OnEnterHiding(Controlable, From, Event, To) - env.info(AI_Suppression.id..string.format("OnEnterHiding: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) -end - ---- Leaving "Hiding" state. --- @param #AI_Suppression self --- @param Wrapper.Controllable#CONTROLLABLE Controlable Controllable of the AI group. -function AI_Suppression:OnLeaveHiding(Controlable, From, Event, To) - env.info(AI_Suppression.id..string.format("OnLeveHiding: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) - - -- Set the ALARM STATE back to AUTO. - Controlable:OptionAlarmStateAuto() -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Handle the DCS event hit. --- @param #AI_Suppression self --- @param Core.Event#EVENTDATA EventData -function AI_Suppression:_OnHit(EventData) - self:E( {AI_Suppression.id.."_OnHit", EventData }) - --env.info(AI_Suppression.id.."Initiator : "..EventData.IniDCSGroupName) - --env.info(AI_Suppression.id.."Target : "..EventData.TgtDCSGroupName) - --env.info(AI_Suppression.id.."Controllable: "..self.Controllable:GetName()) - - if EventData.TgtDCSGroup then - - local TargetGroup=EventData.TgtGroup --Wrapper.Group#GROUP - - if EventData.TgtDCSGroupName==self.Controllable:GetName() then - - -- Figure out who shot. - local InitiatorName="unknown" - local Fallback=nil - if EventData.IniDCSUnit then - - local InitiatorUnit=EventData.IniUnit --Wrapper.Unit#UNIT - InitiatorName=EventData.IniDCSGroupName - - local TC=TargetGroup:GetCoordinate() - local IC=InitiatorUnit:GetCoordinate() - - -- Create a fall back point. - Fallback=self:_FallBackCoord(TC, IC , 200) -- Core.Point#COORDINATE - end - - -- Get life of group in %. - local life_min, life_max, life_ave, groupstrength=self:_GetLife() - - -- Debug message. - local text=string.format("Group %s was hit by %s. Life min=%02d %%, max=%02d %%, average=%02d %%, group=%3.0f", EventData.TgtDCSGroupName, InitiatorName, life_min, life_max, life_ave, groupstrength) - MESSAGE:New(text, 10):ToAll() - env.info(AI_Suppression.id..text) - - -- Trigger Hit event. - self:Hit(Fallback) - end - end -end - ---- Handle the DCS event dead. --- @param #AI_Suppression self --- @param Core.Event#EVENTDATA EventData -function AI_Suppression:_OnDead(EventData) - self:E({AI_Suppression.id.."_OnDead", EventData}) - - if EventData.IniDCSUnit then - if EventData.IniDCSGroupName==self.Controllable:GetName() then - - -- Number of units left in the group. - local nunits=#self.Controllable:GetUnits()-1 - - local text=string.format("A unit from group %s just died! %d units left.", self.Controllable:GetName(), nunits) - MESSAGE:New(text, 10):ToAll() - env.info(AI_Suppression.id..text) - - -- Go to stop state. - if nunits==0 then - self:Stop() - end - - end - end - -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Suppress fire of a unit. --- @param #AI_Suppression self -function AI_Suppression:_Suppress() - - -- Current time. - local Tnow=timer.getTime() - - -- Controllable - local Controlable=self.Controllable - - -- Group will hold their weapons. - Controlable:OptionROEHoldFire() - - -- Get randomized time the unit is suppressed. - local Tsuppress=math.random(self.Tsuppress_min, self.Tsuppress_max) - - -- Time the suppression started - self.TsuppressionStart=Tnow - - -- Time at which the suppression is over. - local renew=true - if self.TsuppressionOver~=nil then - if Tsuppress+Tnow>self.TsuppressionOver then - self.TsuppressionOver=Tnow+Tsuppress - else - renew=false - end - else - self.TsuppressionOver=Tnow+Tsuppress - end - - -- Recovery event will be called in Tsuppress seconds. (We add one second to be sure the time has really passed when recovery is checked.) - if renew then - self:__Recovered(self.TsuppressionOver-Tnow) - end - - -- Debug message. - local text=string.format("Group %s is suppressed for %d seconds.", Controlable:GetName(), Tsuppress) - MESSAGE:New(text, 30):ToAll() - env.info(AI_Suppression.id..text) - text=string.format("Suppression starts at %f and ends at %f.", Tnow, self.TsuppressionOver) - env.info(AI_Suppression.id..text) - -end - - ---- Get (relative) life in percent of a group. Function returns the value of the units with the smallest and largest life. Also the average value of all groups is returned. --- @param #AI_Suppression self --- @param Wrapper.Group#GROUP group Group of unit. --- @return #number Smallest life value of all units. --- @return #number Largest life value of all units. --- @return #number Average life value. -function AI_Suppression:_GetLife() - local group=self.Controllable - if group and group:IsAlive() then - local life_min=100 - local life_max=0 - local life_ave=0 - local n=0 - local units=group:GetUnits() - local groupstrength=#units/self.IniGroupStrength*100 - for _,unit in pairs(units) do - local unit=unit -- Wrapper.Unit#UNIT - if unit and unit:IsActive() then - n=n+1 - local life=unit:GetLife()/(unit:GetLife0()+1)*100 - if life < life_min then - life_min=life - end - if life > life_max then - life_max=life - end - life_ave=life_ave+life - local text=string.format("n=%d: Life = %3.1f, Life0 = %3.1f, min=%3.1f, max=%3.1f, ave=%3.1f, group=%3.1f", n, unit:GetLife(), unit:GetLife0(), life_min, life_max, life_ave/n,groupstrength) - env.info(AI_Suppression.id..text) - end - end - life_ave=life_ave/n - - return life_min, life_max, life_ave, groupstrength - else - return 0, 0, 0 - end -end - - ---- Retreat to a random point within a zone. --- @param #AI_Suppression self --- @param Core.Zone#ZONE zone Zone to which the group retreats. --- @param #number speed Speed of the group. Default max speed the specific group can do. --- @param #string formation Formation of the Group. Default "Vee". -function AI_Suppression:_RetreatToZone(zone, speed, formation) - - -- Set zone, speed and formation if they are not given - zone=zone or self.Zone_Retreat - speed = speed or 999 - formation = formation or "Vee" - - -- - env.info(AI_Suppression.id.."Retreat zone : "..zone:GetName()) - - -- Get a random point in the retreat zone. - local ZoneCoord=zone:GetRandomCoordinate() -- Core.Point#COORDINATE - local ZoneVec2=ZoneCoord:GetVec2() - - -- Debug smoke zone and point. - ZoneCoord:SmokeBlue() - zone:SmokeZone(SMOKECOLOR.Red, 12) - - -- Set task to go to zone. - self.Controllable:TaskRouteToVec2(ZoneVec2, speed, formation) - -end - ---- Determine the coordinate to which a unit should fall back. ---@param #AI_Suppression self ---@param Core.Point#COORDINATE a Coordinate of the defending group. ---@param Core.Point#COORDINATE b Coordinate of the attacking group. ---@return Core.Point#COORDINATE Fallback coordinates. -function AI_Suppression:_FallBackCoord(a, b, distance) - local dx = b.x-a.x - -- take the right value for y-coordinate (if we have "alt" then "y" if not "z") - local ay - if a.alt then - ay=a.y - else - ay=a.z - end - local by - if b.alt then - by=b.y - else - by=b.z - end - local dy = by-ay - local angle = math.deg(math.atan2(dy,dx)) - if angle < 0 then - angle = 360 + angle - end - angle=angle-180 - local fbp=a:Translate(distance, angle) - return fbp -end - - ---- Fall back (move away) from enemy who is shooting on the group. ---@param #AI_Suppression self ---@param Core.Point#COORDINATE coord_fbp Coordinate of the fall back point. -function AI_Suppression:_FallBack(coord_fbp) - - local group=self.Controllable -- Wrapper.Controllable#CONTROLLABLE - - local Waypoints = group:GetTemplateRoutePoints() - - local coord_grp = group:GetCoordinate() - local wp1 = coord_grp:WaypointGround(99, "Vee") - local wp2 = coord_fbp:WaypointGround(99, "Vee") - - table.insert(Waypoints, 1, wp1) - table.insert(Waypoints, 2, wp2) - - -- Condition to wait. - local ConditionWait=group:TaskCondition(nil, nil, nil, nil, 30, nil) - - -- Task to hold. - local TaskHold = group:TaskHold() - - local TaskRoute1 = group:TaskFunction("AI_Suppression._Passing_Waypoint", self, 0) - local TaskCombo2 = {} - TaskCombo2[#TaskCombo2+1] = group:TaskFunction("AI_Suppression._Passing_Waypoint", self, 1) - TaskCombo2[#TaskCombo2+1] = group:TaskControlled(TaskHold, ConditionWait) - local TaskRoute2 = group:TaskCombo(TaskCombo2) - - group:SetTaskWaypoint(Waypoints[1], TaskRoute1) - group:SetTaskWaypoint(Waypoints[2], TaskRoute2) - - group:Route(Waypoints) - -end - - ---- Group has reached a waypoint. ---@param #AI_Suppression self ---@param #number i Waypoint number that has been reached. -function AI_Suppression._Passing_Waypoint(group, Fsm, i) - env.info(AI_Suppression.id.."Passing waypoint") - BASE:E(group) - BASE:E(Fsm) - BASE:E(i) - - MESSAGE:New(string.format("Group %s passing waypoint %d", group:GetName(), i),30):ToAll() - if i==1 then - MESSAGE:New(string.format("Group %s has reached fallback point.", group:GetName(), i),30):ToAll() - end -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/Moose Mission Setup/Moose.files b/Moose Mission Setup/Moose.files index 9c6d00043..657ffe1bd 100644 --- a/Moose Mission Setup/Moose.files +++ b/Moose Mission Setup/Moose.files @@ -47,7 +47,6 @@ Functional/RAT.lua Functional/ZoneGoal.lua Functional/ZoneGoalCoalition.lua Functional/ZoneCaptureCoalition.lua -Functional/SuppressiveFire.lua AI/AI_Balancer.lua AI/AI_A2A.lua From 172e51307cc953a19f37741f615d8bfde936813f Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Sun, 22 Oct 2017 22:54:50 +0200 Subject: [PATCH 15/25] RAT --- Moose Development/Moose/Functional/RAT.lua | 1041 +++++++------------- Moose Mission Setup/Moose.lua | 3 +- 2 files changed, 361 insertions(+), 683 deletions(-) diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index f41c984f7..1c32dbc7e 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -66,6 +66,7 @@ -- @field #boolean debug Turn debug messages on or off. -- @field Core.Group#GROUP templategroup Group serving as template for the RAT aircraft. -- @field #string alias Alias for spawned group. +-- @field #boolean spawninitialized If RAT:Spawn() was already called this RAT object is set to true to prevent users to call it again. -- @field #number spawndelay Delay time in seconds before first spawning happens. -- @field #number spawninterval Interval between spawning units/groups. Note that we add a randomization of 50%. -- @field #number coalition Coalition of spawn group template. @@ -279,6 +280,7 @@ RAT={ debug=false, -- Turn debug messages on or off. templategroup=nil, -- Template group for the RAT aircraft. alias=nil, -- Alias for spawned group. + spawninitialized=false, -- If RAT:Spawn() was already called this is set to true to prevent users to call it again. spawndelay=5, -- Delay time in seconds before first spawning happens. spawninterval=5, -- Interval between spawning units/groups. Note that we add a randomization of 50%. coalition = nil, -- Coalition of spawn group template. @@ -339,7 +341,8 @@ RAT={ wp_holding=nil, -- Index of the holding waypoint. radio=nil, -- If true/false disables radio messages from the RAT groups. frequency=nil, -- Radio frequency used by the RAT groups. - modulation=nil, -- Ratio modulation. Either "FM" or "AM". + modulation=nil, -- Ratio modulation. Either "FM" or "AM". + actype=nil, -- Aircraft type set by user. Changes the type of the template group. } ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -384,8 +387,8 @@ RAT.status={ -- Event states. EventBirthAir="Born in air", EventBirth="Born on ground", - EventEngineStartAir="Started engines in air", - EventEngineStart="Started engines on ground", + EventEngineStartAir="Started engines (in air)", + EventEngineStart="Started engines", EventTakeoff="Took off", EventLand="Landed", EventEngineShutdown="Engines shut down", @@ -539,6 +542,14 @@ end -- @usage yak:Spawn(5) will spawn five aircraft. By default aircraft will spawn at neutral and red airports if the template group is part of the red coaliton. function RAT:Spawn(naircraft) + -- Make sure that this function is only been called once per RAT object. + if self.spawninitialized==true then + env.error("Spawn function should only be called once per RAT object! Exiting and returning nil.") + return nil + else + self.spawninitialized=false + end + -- Number of aircraft to spawn. Default is one. self.ngroups=naircraft or 1 @@ -573,7 +584,33 @@ function RAT:Spawn(naircraft) self.destination_ports=self:_GetAirportsInZone(self.destination_Azone) end - -- debug message + + -- Consistancy checks + -- Destination zone and return zone should not be used together. + if self.destinationzone and self.returnzone then + env.error(RAT.id.."Destination zone _and_ return to zone not possible! Disabling return to zone.") + self.returnzone=false + end + -- Return zone should not be used together with air start. Why actually? + if self.returnzone and self.takeoff==RAT.wp.air then + env.error(RAT.id.."Combination return zone _and_ air start not possible! Falling back to destination zone instead.") + self.returnzone=false + self.destinationzone=true + end + -- Return zone and commute does not make sense. + if self.returnzone and self.continuejourney then + env.error(RAT.id.."Combination return zone _and_ commute does not make sense! Disabling commute.") + self.commute=false + end + + -- Return zone and commute does not make sense. +-- if self.returnzone and self.commute then +-- env.error(RAT.id.."Combination return zone _and_ commute does not make sense! Disabling commute.") +-- self.commute=false +-- end + + + -- Settings info local text=string.format("\n******************************************************\n") text=text..string.format("Spawning %i aircraft from template %s of type %s.\n", self.ngroups, self.SpawnTemplatePrefix, self.aircraft.type) text=text..string.format("Alias: %s\n", self.alias) @@ -586,6 +623,8 @@ function RAT:Spawn(naircraft) text=text..string.format("Takeoff type: %i\n", self.takeoff) text=text..string.format("Commute: %s\n", tostring(self.commute)) text=text..string.format("Journey: %s\n", tostring(self.continuejourney)) + text=text..string.format("Destination Zone: %s\n", tostring(self.destinationzone)) + 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 after landing: %s\n", tostring(self.respawn_at_landing)) @@ -801,8 +840,8 @@ end --- Set name of destination zones for the AI aircraft. If multiple names are given as a table, one zone is picked randomly as destination. -- @param #RAT self --- @param #string names Name or table of names of zones defined in the mission editor. -function RAT:SetDestinationZone(names) +-- @param #string zonenames Name or table of names of zones defined in the mission editor or names of airports on the map. +function RAT:SetDestinationZone(zonenames) -- Random destination is deactivated now that user specified destination zone(s). self.random_destination=false @@ -811,6 +850,44 @@ function RAT:SetDestinationZone(names) -- No ATC required. self.ATCswitch=false + local names + if type(zonenames)=="table" then + names=zonenames + elseif type(zonenames)=="string" then + names={zonenames} + else + -- Error message. + env.error("Input parameter must be a string or a table!") + end + + for _,name in pairs(names) do + if self:_AirportExists(name) then + -- Take airport as zone. + local airportzone=AIRBASE:FindByName(name):GetZone() + table.insert(self.destination_zones, airportzone) + else + -- If it is not an airport, we assume it is a zone. + -- We need the DCS call to determine if it's really a zone. Otherwise code will crash at some point. + local z=trigger.misc.getZone(name) + if z then + table.insert(self.destination_zones, ZONE:New(name)) + end + end + end + +end + +--- Set name of "return" zones for the AI aircraft. If multiple names are given as a table, one zone is picked randomly as destination. +-- @param #RAT self +-- @param #string zonenames Name or table of names of zones defined in the mission editor. +function RAT:SetReturnZone(zonenames) + + -- Random destination is deactivated now that user specified destination zone(s). + self.random_destination=false + -- Destination is a zone. Needs special care. + self.returnzone=true + +--[[ if type(names)=="table" then for _,name in pairs(names) do @@ -825,33 +902,27 @@ function RAT:SetDestinationZone(names) -- Error message. env.error("Input parameter must be a string or a table!") end - -end - ---- Set name of "return" zones for the AI aircraft. If multiple names are given as a table, one zone is picked randomly as destination. --- @param #RAT self --- @param #string names Name or table of names of zones defined in the mission editor. -function RAT:SetReturnZone(names) - - -- Random destination is deactivated now that user specified destination zone(s). - self.random_destination=false - -- Destination is a zone. Needs special care. - self.returnzone=true +]] - if type(names)=="table" then - - for _,name in pairs(names) do - table.insert(self.return_zones, ZONE:New(name)) - end - - elseif type(names)=="string" then - - table.insert(self.return_zones, ZONE:New(names)) - + local names + if type(zonenames)=="table" then + names=zonenames + elseif type(zonenames)=="string" then + names={zonenames} else -- Error message. env.error("Input parameter must be a string or a table!") end + + for _,name in pairs(names) do + if self:_AirportExists(name) then + table.insert(self.destination_zones, AIRBASE:FindByName(name):GetZone()) + elseif self:_ZoneExists(name) then + table.insert(self.destination_zones, ZONE:New(name)) + else + env.error(RAT.id.."Specified airport or zone "..name.." does not exist in mission editor!") + end + end end @@ -900,6 +971,14 @@ function RAT:Livery(skins) end end +--- Change aircraft type. This is a dirty hack which allows to change the aircraft type of the template group. +-- Note that all parameters like cruise speed, climb rate, range etc are still taken from the template group which likely leads to strange behaviour. +-- @param #RAT self +-- @param #string actype Type of aircraft which is spawned independent of the template group. Use with care and expect problems! +function RAT:ChangeAircraft(actype) + self.actype=actype +end + --- Aircraft will continue their journey from their destination. This means they are respawned at their destination and get a new random destination. -- @param #RAT self -- @param #boolean switch Turn journey on=true or off=false. If no value is given switch=true. @@ -992,8 +1071,6 @@ function RAT:RadioFrequency(modulation) end end - - --- Set the time after which inactive groups will be destroyed. Default is 300 seconds. -- @param #RAT self -- @param #number time Time in seconds. @@ -1013,7 +1090,7 @@ end -- @param #RAT self -- @param #number rate Climb rate in ft/min. function RAT:SetClimbRate(rate) - self.Vclimb=rate + self.Vclimb=rate or 1500 end --- Set the angle of descent. Default is 3.6 degrees, which corresponds to 3000 ft descent after one mile of travel. @@ -1060,21 +1137,21 @@ end -- @param #RAT self -- @param #boolean switch true=enable ATC, false=disable ATC. function RAT:EnableATC(switch) - self.ATCswitch=switch + self.ATCswitch=switch or true end --- 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 1. function RAT:ATC_Clearance(n) - RAT.ATC.Nclearance=n + RAT.ATC.Nclearance=n or 1 end --- 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. +-- @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. function RAT:ATC_Delay(time) - RAT.ATC.delay=time + RAT.ATC.delay=time or 240 end --- Set minimum distance between departure and destination. Default is 5 km. @@ -1106,8 +1183,7 @@ end -- @param #RAT self -- @param #boolean switch true=on, false=off. function RAT:StatusReports(switch) - switch = switch or true - self.reportstatus=switch + self.reportstatus=switch or true end --- Place markers of waypoints on the F10 map. Default is off. @@ -1254,22 +1330,21 @@ end -- @param #RAT self -- @param #string _departure (Optional) Name of departure airbase. -- @param #string _destination (Optional) Name of destination airbase. -function RAT:_SpawnWithRoute(_departure, _destination) - - -- Check that we don't already have more groups than we initally wanted. - --if self.alive > self.ngroups then - -- return - --end +-- @param #number _takeoff Takeoff type id. +function RAT:_SpawnWithRoute(_departure, _destination, _takeoff) -- Set takeoff type. - local _takeoff=self.takeoff + local takeoff=self.takeoff if self.takeoff==RAT.wp.coldorhot then local temp={RAT.wp.cold, RAT.wp.hot} - _takeoff=temp[math.random(2)] + takeoff=temp[math.random(2)] + end + if _takeoff then + takeoff=_takeoff end -- Set flight plan. - local departure, destination, waypoints = self:_SetRoute(_takeoff, _departure, _destination) + local departure, destination, waypoints = self:_SetRoute(takeoff, _departure, _destination) -- Return nil if we could not find a departure destination or waypoints if not (departure and destination and waypoints) then @@ -1319,6 +1394,7 @@ function RAT:_SpawnWithRoute(_departure, _destination) -- Each aircraft gets its own takeoff type. unused? self.ratcraft[self.SpawnIndex]["takeoff"]=_takeoff + self.ratcraft[self.SpawnIndex].despawnme=false -- Create submenu for this group. @@ -1338,23 +1414,22 @@ function RAT:_SpawnWithRoute(_departure, _destination) 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) - if self.ATCswitch then - MENU_MISSION_COMMAND:New("Clear for landing", self.Menu[self.SubMenuName].groups[self.SpawnIndex], self.ClearForLanding, self, group:GetName()) - end MENU_MISSION_COMMAND:New("Place markers", self.Menu[self.SubMenuName].groups[self.SpawnIndex], self._PlaceMarkers, self, waypoints) MENU_MISSION_COMMAND:New("Status report", self.Menu[self.SubMenuName].groups[self.SpawnIndex], self.Status, self, true, self.SpawnIndex) end - env.info("RAT debug before end of _SpawnWithRoute") return self.SpawnIndex end + +--- Clear flight for landing. Sets tigger value to 1. +-- @param #RAT self +-- @param #string name Name of flight to be cleared for landing. function RAT:ClearForLanding(name) - env.info("ATC: setting user flag "..name.." to 1.") trigger.action.setUserFlag(name, 1) local flagvalue=trigger.misc.getUserFlag(name) - env.info("ATC: user flag "..name.." ="..flagvalue) + env.info(RAT.id.."ATC: User flag value (landing) for "..name.." set to "..flagvalue) end --- Respawn a group. @@ -1371,10 +1446,14 @@ function RAT:_Respawn(group) local _departure=nil local _destination=nil + local _takeoff=nil if self.continuejourney then -- We continue our journey from the old departure airport. _departure=destination:GetName() + if self.destinationzone then + _takeoff=RAT.wp.air + end elseif self.commute then -- We commute between departure and destination. _departure=destination:GetName() @@ -1383,9 +1462,9 @@ function RAT:_Respawn(group) -- Spawn new group. if self.respawn_delay then - SCHEDULER:New(nil, self._SpawnWithRoute, {self, _departure, _destination}, self.respawn_delay) + SCHEDULER:New(nil, self._SpawnWithRoute, {self, _departure, _destination, _takeoff}, self.respawn_delay) else - self:_SpawnWithRoute(_departure, _destination) + self:_SpawnWithRoute(_departure, _destination, _takeoff) end end @@ -1439,22 +1518,54 @@ function RAT:_SetRoute(takeoff, _departure, _destination) -- Descent angle in rad. local AlphaDescent=math.rad(self.AlphaDescent) - - + + local returnzone=self.returnzone + local destinationzone=self.destinationzone + + -- DEPARTURE AIRPORT -- Departure airport or zone. local departure=nil if _departure then + if self:_AirportExists(_departure) then + -- Check if new departure is an airport. departure=AIRBASE:FindByName(_departure) + elseif self:_ZoneExists(_departure) then + -- If it's not an airport, check whether it's a zone. + departure=ZONE:New(_departure) else local text=string.format("ERROR: Specified departure airport %s does not exist for %s!", _departure, self.alias) env.error(RAT.id..text) end + + if self.commute then + if returnzone then + -- should not be a problem because we flew back to the departure. Departure=destination ==> nothing really changes. + -- Just that the next departure is not random but the same airport we started first. + elseif destinationzone then + -- We initially flew to a zone. + if self.takeoff==RAT.wp.air then + -- We initially came from a zone, i.e. airstart. + if takeoff==RAT.wp.air then + -- Now we also fly to zone. + else + -- Now we fly to an airport where we land + end + else + -- We initally + end + end + elseif self.continuejourney then + + departure=departure:GetZone() + end + + else departure=self:_PickDeparture(takeoff) end - + -- Return nil if no departure could be found. if not departure then local text=string.format("No valid departure airport could be found for %s.", self.alias) @@ -1501,8 +1612,16 @@ function RAT:_SetRoute(takeoff, _departure, _destination) if self:_AirportExists(_destination) then destination=AIRBASE:FindByName(_destination) + if self.destinationzone then + destination=destination:GetZone() + end + elseif self:_ZoneExists(_destination) then + destination=ZONE:New(_destination) + if not self.returnzone then + self.destinationzone=true + end else - local text=string.format("ERROR: Specified destination airport %s does not exist for %s!", _destination, self.alias) + local text=string.format("ERROR: Specified destination airport/zone %s does not exist for %s!", _destination, self.alias) env.error(RAT.id..text) end @@ -1539,9 +1658,17 @@ function RAT:_SetRoute(takeoff, _departure, _destination) -- Coordinates of destination airport. local Pdestination - if self.destinationzone or self.returnzone then + local Preturn + if self.destinationzone then + -- Destination is a zone and we pick a random point within the zone. local vec2=destination:GetRandomVec2() Pdestination=COORDINATE:NewFromVec2(vec2) + elseif self.returnzone then + -- We fly to a random point within a zone and back to the departure airport. + Pdestination=departure:GetCoordinate() + local vec2=destination:GetRandomVec2() + Preturn=COORDINATE:NewFromVec2(vec2) + destination=departure else Pdestination=destination:GetCoordinate() end @@ -1550,7 +1677,6 @@ function RAT:_SetRoute(takeoff, _departure, _destination) -- DESCENT/HOLDING POINT -- Get a random point between 5 and 10 km away from the destination. - local Vholding local Rhmin=5000 local Rhmax=10000 if self.category==RAT.cat.heli then @@ -1559,16 +1685,8 @@ function RAT:_SetRoute(takeoff, _departure, _destination) Rhmax=1000 end - -- Holing point near destination or departure if we return to the original airport. - local holdingpoint - if self.returnzone then - holdingpoint=departure - else - holdingpoint=destination - end - -- Coordinates of the holding point. y is the land height at that point. - Vholding=holdingpoint:GetCoordinate():GetRandomVec2InRadius(Rhmax, Rhmin) + local Vholding=Pdestination:GetRandomVec2InRadius(Rhmax, Rhmin) local Pholding=COORDINATE:NewFromVec2(Vholding) -- AGL height of holding point. @@ -1590,11 +1708,12 @@ function RAT:_SetRoute(takeoff, _departure, _destination) local heading local d_total if self.returnzone then + -- Heading from departure to destination in return zone. - heading=self:_Course(Pdeparture, Pdestination) + heading=self:_Course(Pdeparture, Preturn) -- Total distance to return zone and back. - d_total=Pdeparture:Get2DDistance(Pdestination)*2 + d_total=Pdeparture:Get2DDistance(Preturn) + Preturn:Get2DDistance(Pholding) else -- Heading from departure to holding point of destination. @@ -1607,14 +1726,19 @@ function RAT:_SetRoute(takeoff, _departure, _destination) -- max height if we only would descent to holding point for the given distance -- TODO: Add case for destination zone. We could allow a higher max because no descent is necessary. if takeoff==RAT.wp.air then - local H_departure_max = d_total * math.tan(AlphaDescent) + H_holding + h_holding + local H_departure_max + if self.destinationzone then + H_departure_max = H_departure + else + H_departure_max = d_total * math.tan(AlphaDescent) + H_holding + h_holding + end H_departure=math.min(H_departure, H_departure_max) end -------------------------------------------- -- Height difference between departure and destination. - local deltaH=H_departure-h_holding-H_holding + local deltaH=math.abs(H_departure-h_holding-H_holding) -- Slope between departure and destination. local phi = math.atan(deltaH/d_total) @@ -1631,9 +1755,12 @@ function RAT:_SetRoute(takeoff, _departure, _destination) end -- Total distance including slope. - local D_total = math.sqrt(deltaH*deltaH+d_total*d_total) - --local D_total2 =d_total/math.cos(phi) - --local D_total3 = deltaH/math.sin(phi) + local D_total + if self.returnzone then + D_total = math.sqrt(deltaH*deltaH+d_total/2*d_total/2) + else + D_total = math.sqrt(deltaH*deltaH+d_total*d_total) + end -- SSA triangle for sloped case. local gamma=math.rad(180)-phi_climb-phi_descent @@ -1666,9 +1793,6 @@ function RAT:_SetRoute(takeoff, _departure, _destination) local d_total_max = d_climb_max + d_cruise_max + d_descent_max --CRUISE - -- Max flight level the aircraft can reach if it only climbs and immidiately descents again (i.e. no cruising part). - --local FLmax=self:_FLmax(AlphaClimb, AlphaDescent, d_total, phi, H_departure) - -- Min cruise alt is just above holding point at destination or departure height, whatever is larger. local FLmin=math.max(H_departure, H_holding+h_holding) @@ -1683,8 +1807,8 @@ function RAT:_SetRoute(takeoff, _departure, _destination) FLmax=math.max(H_departure, H_destination)+1000 end - -- Ensure that FLmax not above 90% its service ceiling. - FLmax=math.min(FLmax, self.aircraft.ceiling*0.9) + -- Ensure that FLmax not above 95% its service ceiling. + FLmax=math.min(FLmax, self.aircraft.ceiling*0.95) -- Overrule setting if user specified min/max flight level explicitly. if self.FLminuser then @@ -1711,7 +1835,7 @@ function RAT:_SetRoute(takeoff, _departure, _destination) end -- Climb and descent heights. - local h_climb = (FLcruise-H_departure) --math.abs ? + local h_climb = FLcruise - H_departure local h_descent = FLcruise - (H_holding+h_holding) -- Distances. @@ -1752,20 +1876,32 @@ function RAT:_SetRoute(takeoff, _departure, _destination) 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("\nMax heights and distances:\n") - text=text..string.format("d_climb_max = %6.1f km\n", d_climb_max/1000) - text=text..string.format("d_cruise_max = %6.1f km\n", d_cruise_max/1000) - text=text..string.format("d_descent_max = %6.1f km\n", d_descent_max/1000) - text=text..string.format("d_total_max = %6.1f km\n", d_total_max/1000) - text=text..string.format("h_climb_max = %6.1f m\n", h_climb_max) - text=text..string.format("h_descent_max = %6.1f m\n", h_descent_max) text=text..string.format("\nAngles:\n") - text=text..string.format("Alpha climb = %6.1f Deg\n", math.deg(AlphaClimb)) - text=text..string.format("Alpha descent = %6.1f Deg\n", math.deg(AlphaDescent)) - text=text..string.format("Phi (slope) = %6.1f Deg\n", math.deg(phi)) - text=text..string.format("Phi climb = %6.1f Deg\n", math.deg(phi_climb)) - text=text..string.format("Phi descent = %6.1f Deg\n", math.deg(phi_descent)) - text=text..string.format("Heading = %6.1f Deg\n", heading) + text=text..string.format("Alpha climb = %6.2f Deg\n", math.deg(AlphaClimb)) + text=text..string.format("Alpha descent = %6.2f Deg\n", math.deg(AlphaDescent)) + text=text..string.format("Phi (slope) = %6.2f Deg\n", math.deg(phi)) + text=text..string.format("Phi climb = %6.2f Deg\n", math.deg(phi_climb)) + text=text..string.format("Phi descent = %6.2f Deg\n", math.deg(phi_descent)) + if self.debug then + text=text..string.format("Heading = %6.1f Deg\n", heading) + text=text..string.format("\nSSA triangle:\n") + text=text..string.format("D_total = %6.1f km\n", D_total/1000) + text=text..string.format("gamma = %6.1f Deg\n", math.deg(gamma)) + text=text..string.format("a = %6.1f m\n", a) + text=text..string.format("b = %6.1f m\n", b) + text=text..string.format("hphi_max = %6.1f m\n", hphi_max) + text=text..string.format("hphi_max2 = %6.1f m\n", hphi_max2) + text=text..string.format("h_max1 = %6.1f m\n", h_max1) + text=text..string.format("h_max2 = %6.1f m\n", h_max2) + text=text..string.format("h_max = %6.1f m\n", h_max) + text=text..string.format("\nMax heights and distances:\n") + text=text..string.format("d_climb_max = %6.1f km\n", d_climb_max/1000) + text=text..string.format("d_cruise_max = %6.1f km\n", d_cruise_max/1000) + text=text..string.format("d_descent_max = %6.1f km\n", d_descent_max/1000) + text=text..string.format("d_total_max = %6.1f km\n", d_total_max/1000) + text=text..string.format("h_climb_max = %6.1f m\n", h_climb_max) + text=text..string.format("h_descent_max = %6.1f m\n", h_descent_max) + end text=text..string.format("******************************************************\n") env.info(RAT.id..text) @@ -1827,7 +1963,7 @@ function RAT:_SetRoute(takeoff, _departure, _destination) elseif self.returnzone then - c[#c+1]=Pdestination + c[#c+1]=Preturn c[#c+1]=c[#c]:Translate(d_cruise/2, heading-180) wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.cruise, c[#wp+1], VxCruise, FLcruise) @@ -1873,7 +2009,7 @@ function RAT:_SetRoute(takeoff, _departure, _destination) self.waypointstatus[#wp]=RAT.status.Holding self.wp_holding=#wp - wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.landing, c[#wp+1], VxFinal, H_departure, departure) + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.landing, c[#wp+1], VxFinal, H_destination, destination) self.waypointdescriptions[#wp]="Destination" self.waypointstatus[#wp]=RAT.status.Destination end @@ -1881,161 +2017,6 @@ function RAT:_SetRoute(takeoff, _departure, _destination) -- Final Waypoint self.wp_final=#wp ---[[ - if self.destinationzone then - - -- Destination is a zone. No need for holding and landing point. - c[#c+1]=Pdeparture - c[#c+1]=c[#c]:Translate(d_climb/2, heading) - c[#c+1]=c[#c]:Translate(d_climb/2, heading) - c[#c+1]=Pdestination - - wp[#wp+1]=self:_Waypoint(#wp+1, takeoff, c[#wp+1], VxClimb, H_departure, departure) - self.waypointdescriptions[#wp+1]="Departure" - wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.climb, c[#wp+1], VxClimb, H_departure+(FLcruise-H_departure)/2) - self.waypointdescriptions[#wp+1]="Climb" - wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.cruise, c[#wp+1], VxCruise, FLcruise) - self.waypointdescriptions[#wp+1]="Begin of Cruise" - wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.finalwp, c[#wp+1], VxCruise, FLcruise) - self.waypointdescriptions[#wp+1]="Final Destination" - - -- Index of the final waypoint. Here used to despawn and respawn the aircraft. - self.wp_final=4 - - elseif self.returnzone then - - -- We fly from the departure to a zone and back. - local c0=Pdeparture - local c1=c0:Translate(d_climb/2, heading) - local c2=c1:Translate(d_climb/2, heading) - local c3=Pdestination - local c4=c3:Translate(d_cruise/2, heading-180) - local c5=c4:Translate(d_descent/2, heading-180) - local c6=Pholding - local c7=Pdeparture - - -- Waypoints - wp[#wp+1]=self:_Waypoint(#wp+1, takeoff, c0, VxClimb, H_departure, departure) - self.waypointdescriptions[#wp+1]="Departure" - wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.climb, c1, VxClimb, H_departure+(FLcruise-H_departure)/2) - self.waypointdescriptions[#wp+1]="Climb" - wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.cruise, c2, VxCruise, FLcruise) - self.waypointdescriptions[#wp+1]="Begin of Cruise" - wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.cruise, c3, VxCruise, FLcruise) - self.waypointdescriptions[#wp+1]="Return Zone" - wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.cruise, c4, VxCruise, FLcruise) - self.waypointdescriptions[#wp+1]="End of Cruise" - wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.descent, c5, VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) - self.waypointdescriptions[#wp+1]="Descent" - wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.holding, c6, VxHolding, H_holding+h_holding) - self.waypointdescriptions[#wp+1]="Holding Point" - wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.landing, c7, VxFinal, H_departure, departure) - self.waypointdescriptions[#wp+1]="Destination" - - -- Index of the holding and final waypoint. - self.wp_holding=7 - self.wp_final=8 - - else - - if takeoff==RAT.wp.air then - - if d_climb < 1000 or d_cruise < 1000 then - - -- Airstart, simplify climb waypoints. - local c0=Pdeparture - local c1=c0:Translate(d_climb+d_cruise, heading) - local c2=c1:Translate(d_descent/2, heading) - local c3=Pholding - local c4=Pdestination - - -- Waypoints - wp[#wp+1]=self:_Waypoint(#wp+1, takeoff, c0, VxClimb, H_departure, departure) - self.waypointdescriptions[#wp+1]="Departure (air)" - self.waypointstatus[#wp+1]=RAT.status.DepartureAir - wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.cruise, c1, VxCruise, FLcruise) - self.waypointdescriptions[#wp+1]="Cruise" - self.waypointstatus[#wp+1]=RAT.status.CruiseBegin - wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.descent, c2, VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) - self.waypointdescriptions[#wp+1]="Descent" - self.waypointstatus[#wp+1]=RAT.status.Descent - wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.holding, c3, VxHolding, H_holding+h_holding) - self.waypointdescriptions[#wp+1]="Holding Point" - self.waypointstatus[#wp+1]=RAT.status.Holding - wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.landing, c4, VxFinal, H_destination, destination) - self.waypointdescriptions[#wp+1]="Destination" - self.waypointstatus[#wp+1]=RAT.status.Destination - - -- Index of the holing point for registering aircraft at ATC. - self.wp_holding=4 - self.wp_final=5 - - else - - -- Airstart, simplify climb waypoints. - local c0=Pdeparture - local c1=c0:Translate(d_climb, heading) - local c2=c1:Translate(d_cruise, heading) - local c3=c2:Translate(d_descent/2, heading) - local c4=Pholding - local c5=Pdestination - - -- Waypoints - wp[#wp+1]=self:_Waypoint(#wp+1, takeoff, c0, VxClimb, H_departure, departure) - self.waypointdescriptions[#wp+1]="Departure (air)" - wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.cruise, c1, VxCruise, FLcruise) - self.waypointdescriptions[#wp+1]="Begin of Cruise" - wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.cruise, c2, VxCruise, FLcruise) - self.waypointdescriptions[#wp+1]="End of Cruise" - wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.descent, c3, VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) - self.waypointdescriptions[#wp+1]="Descent" - wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.holding, c4, VxHolding, H_holding+h_holding) - self.waypointdescriptions[#wp+1]="Holding Point" - wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.landing, c5, VxFinal, H_destination, destination) - self.waypointdescriptions[#wp+1]="Destination" - - -- Index of the holing point for registering aircraft at ATC. - self.wp_holding=5 - self.wp_final=6 - - end - - else - - -- Coordinates: departure (0), middle of climb (1), begin of climb (2), end of climb (3), middle of descent (4) holding (5), destination (6) - local c0=Pdeparture - local c1=c0:Translate(d_climb/2, heading) - local c2=c1:Translate(d_climb/2, heading) - local c3=c2:Translate(d_cruise, heading) - local c4=c3:Translate(d_descent/2, heading) - local c5=Pholding - local c6=Pdestination - - -- Waypoints - wp[#wp+1]=self:_Waypoint(#wp+1, takeoff, c0, VxClimb, H_departure, departure) - self.waypointdescriptions[#wp+1]="Departure" - wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.climb, c1, VxClimb, H_departure+(FLcruise-H_departure)/2) - self.waypointdescriptions[#wp+1]="Climb" - wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.cruise, c2, VxCruise, FLcruise) - self.waypointdescriptions[#wp+1]="Begin of Cruise" - wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.cruise, c3, VxCruise, FLcruise) - self.waypointdescriptions[#wp+1]="End of Cruise" - wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.descent, c4, VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) - self.waypointdescriptions[#wp+1]="Descent" - wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.holding, c5, VxHolding, H_holding+h_holding) - self.waypointdescriptions[#wp+1]="Holding Point" - wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.landing, c6, VxFinal, H_destination, destination) - self.waypointdescriptions[#wp+1]="Destination" - - -- Index of the holing point for registering aircraft at ATC. - self.wp_holding=6 - self.wp_final= 7 - - end - - end -]] - -- Fill table with waypoints. local waypoints={} for _,p in ipairs(wp) do @@ -2051,11 +2032,7 @@ function RAT:_SetRoute(takeoff, _departure, _destination) self:_Routeinfo(waypoints, "Waypoint info in set_route:") -- return departure, destination and waypoints - if self.returnzone then - return departure, departure, waypoints - else - return departure, destination, waypoints - end + return departure, destination, waypoints end @@ -2214,20 +2191,22 @@ function RAT:_GetDestinations(departure, q, minrange, maxrange) else - if self.destinationzone then + if self.destinationzone or self.returnzone then - -- Destination zones specified by user. + -- Destination or return zones specified by user. for _,zone in pairs(self.destination_zones) do - table.insert(possible_destinations, zone) + if zone:GetName() ~= departure:GetName() then + + -- Distance from departure to possible destination + local distance=q:Get2DDistance(zone:GetCoordinate()) + + -- Add as possible destination if zone is within range. + if distance>=minrange and distance<=maxrange then + table.insert(possible_destinations, zone) + end + end end - - elseif self.returnzone then - - -- Return zones specified by user. - for _,zone in pairs(self.return_zones) do - table.insert(possible_destinations, zone) - end - + else -- Airports specified by user. @@ -2235,8 +2214,15 @@ function RAT:_GetDestinations(departure, q, minrange, maxrange) --if self:_IsFriendly(name) and not self:_Excluded(name) and name~=departure:GetName() then if name~=departure:GetName() then local airport=AIRBASE:FindByName(name) - --TODO: Maybe here I should check min/max distance as well? But the user explicitly specified the airports... - table.insert(possible_destinations, airport) + + -- Distance from departure to possible destination + local distance=q:Get2DDistance(airport:GetCoordinate()) + + -- Add as possible destination if airport is within range. + if distance>=minrange and distance<=maxrange then + table.insert(possible_destinations, airport) + end + end end end @@ -2345,9 +2331,9 @@ function RAT:_GetAirportsOfMap() if self.debug then local text1="MOOSE: Airport ID = ".._myab:GetID().." and Name = ".._myab:GetName()..", Category = ".._myab:GetCategory()..", TypeName = ".._myab:GetTypeName() - local text2="DCS : Airport ID = "..airbase:getID().." and Name = "..airbase:getName()..", Category = "..airbase:getCategory()..", TypeName = "..airbase:getTypeName() + --local text2="DCS : Airport ID = "..airbase:getID().." and Name = "..airbase:getName()..", Category = "..airbase:getCategory()..", TypeName = "..airbase:getTypeName() env.info(RAT.id..text1) - env.info(RAT.id..text2) + --env.info(RAT.id..text2) end end @@ -2393,12 +2379,6 @@ function RAT:Status(message, forID) -- number of ratcraft spawned. local ngroups=#self.ratcraft - if (message and not forID) or self.reportstatus then - local text=string.format("Alive groups of template %s: %d", self.SpawnTemplatePrefix, self.alive) - env.info(RAT.id..text) - MESSAGE:New(text, 20):ToAll() - end - -- Current time. local Tnow=timer.getTime() @@ -2477,27 +2457,8 @@ function RAT:Status(message, forID) -- Distance remaining to destination. local Ddestination=Pn:Get2DDistance(self.ratcraft[i].destination:GetCoordinate()) - -- Distance remaining to holding point or final waypoint - local idx=self.wp_final - local Hp=COORDINATE:New(self.ratcraft[i].waypoints[idx].x, self.ratcraft[i].waypoints[idx].alt, self.ratcraft[i].waypoints[idx].y) - local Dholding=Pn:Get2DDistance(Hp) - -- Status shortcut. local status=self.ratcraft[i].status - - -- Range from holding point for registering at ATC queue. - local DRholding - if self.category==RAT.cat.plane then - DRholding=8000 - else - DRholding=2000 - end - - -- If distance to holding point is less then X km we register the plane. - if self.ATCswitch and Dholding<=DRholding and string.match(status, "On journey") then - --RAT:_ATCRegisterFlight(group:GetName(), Tnow) - --self.ratcraft[i].status="Holding" - end -- Status report. if (forID and i==forID) or (not forID) then @@ -2520,8 +2481,7 @@ function RAT:Status(message, forID) text=text..string.format("FL%03d = %i m\n", alt/RAT.unit.FL2m, alt) --text=text..string.format("Speed = %i km/h\n", vel) text=text..string.format("Distance travelled = %6.1f km\n", self.ratcraft[i]["Distance"]/1000) - --text=text..string.format("Distance to destination = %6.1f km\n", Ddestination/1000) - text=text..string.format("Distance to destination = %6.1f km", Dholding/1000) + text=text..string.format("Distance to destination = %6.1f km", Ddestination/1000) if not airborne then text=text..string.format("\nTime on ground = %6.0f seconds\n", Tg) text=text..string.format("Position change = %8.1f m since %3.0f seconds.", Dg, dTlast) @@ -2529,25 +2489,33 @@ function RAT:Status(message, forID) if self.debug then env.info(RAT.id..text) end - if self.reportstatus or message then + if message then 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 %4.0f seconds inaktive on ground.", self.SpawnTemplatePrefix, dTlast) + local text=string.format("Group %s is despawned after being %4.0f seconds inaktive on ground.", self.alias, dTlast) env.info(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.SpawnTemplatePrefix, life) + local text=string.format("Damaged group %s is despawned. Life = %3.0f", self.alias, life) self:_Despawn(group) end - + end + + if self.ratcraft[i].despawnme then + local text=string.format("Flight %s will be despawned NOW!", self.alias) + env.info(RAT.id..text) + -- Despawn old group. + self:_Respawn(self.ratcraft[i].group) + self:_Despawn(self.ratcraft[i].group) end end else @@ -2557,6 +2525,13 @@ function RAT:Status(message, forID) end end end + + if (message and not forID) then + local text=string.format("Alive groups of %s: %d", self.alias, self.alive) + env.info(RAT.id..text) + MESSAGE:New(text, 20):ToAll() + end + end --- Get (relative) life of first unit of a group. @@ -2585,11 +2560,24 @@ end --- Set status of group. -- @param #RAT self function RAT:_SetStatus(group, status) + + -- Get index from groupname. local index=self:GetSpawnIndexFromGroup(group) - local text=string.format("New status for group %s: %s",group:GetName(), status) - env.info(RAT.id..text) - MESSAGE:New(text, 180):ToAll() + + -- Set new status. self.ratcraft[index].status=status + + -- No status update message for "first waypoint", "holding" + local noupdate1 = status==RAT.status.Departure + local noupdate2 = status==RAT.status.Holding + + local text=string.format("Flight %s: %s.", group:GetName(), status) + env.info(RAT.id..text) + + if (not (noupdate1 or noupdate2)) then + MESSAGE:New(text, 10):ToAllIf(self.reportstatus) + end + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -2888,14 +2876,11 @@ end -- @param Wrapper.Group#GROUP group Group to be despawned. function RAT:_Despawn(group) - env.info("RAT debug _despawn 0") - local index=self:GetSpawnIndexFromGroup(group) - env.info("RAT debug index = "..index) --self.ratcraft[index].group:Destroy() self.ratcraft[index].group=nil group:Destroy() - env.info("RAT debug _despawn 1") + -- Decrease group alive counter. self.alive=self.alive-1 @@ -2903,7 +2888,7 @@ function RAT:_Despawn(group) if self.f10menu then self.Menu[self.SubMenuName]["groups"][index]:Remove() end - env.info("RAT debug _despawn 2") + --TODO: Maybe here could be some more arrays deleted? end @@ -2930,7 +2915,6 @@ function RAT:_Waypoint(index, Type, Coord, Speed, Altitude, Airport) local _Type=nil local _Action=nil local _alttype="RADIO" - local _AID=nil if Type==RAT.wp.cold then -- take-off with engine off @@ -2938,21 +2922,18 @@ function RAT:_Waypoint(index, Type, Coord, Speed, Altitude, Airport) _Action="From Parking Area" _Altitude = 0 _alttype="RADIO" - _AID = Airport:GetID() elseif Type==RAT.wp.hot then -- take-off with engine on _Type="TakeOffParkingHot" _Action="From Parking Area Hot" _Altitude = 0 _alttype="RADIO" - _AID = Airport:GetID() elseif Type==RAT.wp.runway then -- take-off from runway _Type="TakeOff" _Action="From Parking Area" _Altitude = 0 _alttype="RADIO" - _AID = Airport:GetID() elseif Type==RAT.wp.air then -- air start _Type="Turning Point" @@ -2980,7 +2961,6 @@ function RAT:_Waypoint(index, Type, Coord, Speed, Altitude, Airport) _Action="Landing" _Altitude = 0 _alttype="RADIO" - _AID = Airport:GetID() elseif Type==RAT.wp.finalwp then _Type="Turning Point" _Action="Fly Over Point" @@ -2994,7 +2974,9 @@ function RAT:_Waypoint(index, Type, Coord, Speed, Altitude, Airport) -- some debug info about input parameters local text=string.format("\n******************************************************\n") + text=text..string.format("Waypoint = %d\n", index) text=text..string.format("Template = %s\n", self.SpawnTemplatePrefix) + text=text..string.format("Alias = %s\n", self.alias) text=text..string.format("Type: %i - %s\n", Type, _Type) text=text..string.format("Action: %s\n", _Action) text=text..string.format("Coord: x = %6.1f km, y = %6.1f km, alt = %6.1f m\n", Coord.x/1000, Coord.z/1000, Coord.y) @@ -3005,7 +2987,8 @@ function RAT:_Waypoint(index, Type, Coord, Speed, Altitude, Airport) if Type==RAT.wp.air then text=text..string.format("Zone = %s\n", Airport:GetName()) else - text=text..string.format("Airport = %s with ID %i\n", Airport:GetName(), Airport:GetID()) + --text=text..string.format("Airport = %s with ID %i\n", Airport:GetName(), Airport:GetID()) + text=text..string.format("Airport = %s\n", Airport:GetName()) end else text=text..string.format("No airport/zone specified\n") @@ -3036,6 +3019,8 @@ function RAT:_Waypoint(index, Type, Coord, Speed, Altitude, Airport) RoutePoint.name="RAT waypoint" if (Airport~=nil) and Type~=RAT.wp.air then + env.info(RAT.id.."Airport = "..Airport:GetName()) + env.info(RAT.id.."Type = "..Type) local AirbaseID = Airport:GetID() local AirbaseCategory = Airport:GetDesc().category if AirbaseCategory == Airbase.Category.SHIP then @@ -3074,35 +3059,10 @@ function RAT:_Waypoint(index, Type, Coord, Speed, Altitude, Airport) TaskCombo[#TaskCombo+1]=TaskWaypoint if Type==RAT.wp.holding then TaskCombo[#TaskCombo+1]=TaskHolding - elseif Type==RAT.wp.finalwp then - --TaskCombo[#TaskCombo+1]=TaskRespawn end RoutePoint.task.params.tasks = TaskCombo - --[[ - if Type==RAT.wp.holding then - -- Duration of holing. Between 10 and 170 seconds. - local Duration=self:_Randomize(90,0.9) - RoutePoint.task=self:_TaskHolding({x=Coord.x, y=Coord.z}, Altitude, Speed, Duration) - elseif Type==RAT.wp.finalwp then - local TaskRespawn = self:_TaskFunction("RAT._FinalWaypoint", self) - local TaskCombo = {TaskRespawn} - RoutePoint.task = {} - RoutePoint.task.id = "ComboTask" - RoutePoint.task.params = {} - RoutePoint.task.params.tasks = TaskCombo - self:E(TaskRespawn) - else - local PassingWaypoint = self:_TaskFunction("RAT._WaypointFunction", self, index) - local TaskCombo = {PassingWaypoint} - RoutePoint.task = {} - RoutePoint.task.id = "ComboTask" - RoutePoint.task.params = {} - RoutePoint.task.params.tasks = TaskCombo - end - ]] - -- Return waypoint. return RoutePoint end @@ -3115,7 +3075,6 @@ end -- @param #string comment Some comment to identify the provided information. -- @return #number total Total route length in meters. function RAT:_Routeinfo(waypoints, comment) - local text=string.format("\n******************************************************\n") text=text..string.format("Template = %s\n", self.SpawnTemplatePrefix) if comment then @@ -3125,7 +3084,7 @@ function RAT:_Routeinfo(waypoints, comment) -- info on coordinate and altitude for i=1,#waypoints do local p=waypoints[i] - text=text..string.format("WP #%i: x = %6.1f km, y = %6.1f km, alt = %6.1f m\n", i-1, p.x/1000, p.y/1000, p.alt) + text=text..string.format("WP #%i: x = %6.1f km, y = %6.1f km, alt = %6.1f m %s\n", i-1, p.x/1000, p.y/1000, p.alt, self.waypointdescriptions[i]) end -- info on distance between waypoints local total=0.0 @@ -3139,15 +3098,16 @@ function RAT:_Routeinfo(waypoints, comment) local d=math.sqrt((x1-x2)^2 + (y1-y2)^2) local heading=self:_Course(point1, point2) total=total+d - text=text..string.format("Distance from WP %i-->%i = %6.1f km. Heading = %i.\n", i-1, i, d/1000, heading) + text=text..string.format("Distance from WP %i-->%i = %6.1f km. Heading = %03d: %s - %s\n", i-1, i, d/1000, heading, self.waypointdescriptions[i], self.waypointdescriptions[i+1]) end text=text..string.format("Total distance = %6.1f km\n", total/1000) - local text=string.format("******************************************************\n") + text=text..string.format("******************************************************\n") -- send message if self.debug then - env.info(RAT.id..text) + --env.info(RAT.id..text) end + env.info(RAT.id..text) -- return total route length in meters return total @@ -3206,29 +3166,11 @@ function RAT:_TaskHolding(P1, Altitude, Speed, Duration) return DCSTask end - ---- Function called if aircraft reached its final waypoint. Aircraft gets destroyed and respawned. --- @param Core.Group#GROUP group Group of aircraft. --- @param #RAT rat RAT object. -function RAT._FinalWaypoint(group, rat) - env.info(RAT.id.."FinalWaypoint:") - BASE:E(group) - BASE:E(rat) - - -- Spawn new group. - rat:_Respawn(group) - - -- Despawn old group. - rat:_Despawn(group) -end - --- Function which is called after passing every waypoint. Info on waypoint is given and special functions are executed. -- @param Core.Group#GROUP group Group of aircraft. -- @param #RAT rat RAT object. -- @param #number wp Waypoint index. Running number of the waypoints. Determines the actions to be executed. function RAT._WaypointFunction(group, rat, wp) - env.info(RAT.id.."Waypointfunction for waypoint "..wp) - env.info("Final waypoint index "..rat.wp_final) -- Current time and Spawnindex. local Tnow=timer.getTime() @@ -3236,42 +3178,45 @@ function RAT._WaypointFunction(group, rat, wp) -- Departure and destination names. local departure=rat.ratcraft[sdx].departure:GetName() - local destination=rat.ratcraft[sdx].departure:GetName() + local destination=rat.ratcraft[sdx].destination:GetName() + + -- For messages + local text -- Info on passing waypoint. - local text=string.format("Flight %s passing waypoint #%d %s.", group:GetName(), wp, rat.waypointdescriptions[wp]) + text=string.format("Flight %s passing waypoint #%d %s.", group:GetName(), wp, rat.waypointdescriptions[wp]) env.info(RAT.id..text) - MESSAGE:New(text, 180):ToAll() - - -- Set status + + -- New status. local status=rat.waypointstatus[wp] - rat.ratcraft[sdx].status=status - - -- Radio about the status update. - MESSAGE:New("Flight "..group:GetName().." status "..status, 180):ToAll() - + + --rat.ratcraft[sdx].status=status + rat:_SetStatus(group, status) + if wp==rat.wp_holding then -- Aircraft arrived at holding point - MESSAGE:New("Flight "..group:GetName().." holding near "..destination.."!", 180):ToAll() + 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 + MENU_MISSION_COMMAND:New("Clear for landing", rat.Menu[rat.SubMenuName].groups[sdx], rat.ClearForLanding, rat, group:GetName()) rat:_ATCRegisterFlight(group:GetName(), Tnow) end end if wp==rat.wp_final then - local text="Final waypoint reached!" + text=string.format("Flight %s arrived at final destination %s.", group:GetName(), destination) + MESSAGE:New(text, 10):ToAllIf(rat.reportstatus) env.info(RAT.id..text) - MESSAGE:New(text, 30):ToAll() if rat.destinationzone then - -- Spawn new group. - rat:_Respawn(group) - - -- Despawn old group. - rat:_Despawn(group) + text=string.format("Activating despawn switch for flight %s! Group will be detroyed soon.", group:GetName()) + MESSAGE:New(text, 30):ToAllIf(rat.debug) + env.info(RAT.id..text) + -- Enable despawn switch. Next time the status function is called, the aircraft will be despawned. + rat.ratcraft[sdx].despawnme=true end end end @@ -3285,21 +3230,17 @@ function RAT:_TaskFunction(FunctionString, ... ) local DCSTask local ArgumentKey + -- Templatename and anticipated name the group will get local templatename=self.templategroup:GetName() local groupname=self:_AnticipatedGroupName() - env.info(RAT.id.."template name "..templatename) - env.info(RAT.id.."anticipated name "..groupname) - local DCSScript = {} --DCSScript[#DCSScript+1] = "local MissionControllable = GROUP:Find( ... ) " - DCSScript[#DCSScript+1] = "env.info(\"RAT blabla\") " DCSScript[#DCSScript+1] = "local MissionControllable = GROUP:FindByName(\""..groupname.."\") " DCSScript[#DCSScript+1] = "local RATtemplateControllable = GROUP:FindByName(\""..templatename.."\") " if arg and arg.n > 0 then ArgumentKey = '_' .. tostring(arg):match("table: (.*)") - env.info(RAT.id.."Argumentkey: "..ArgumentKey) self.templategroup:SetState(self.templategroup, ArgumentKey, arg) DCSScript[#DCSScript+1] = "local Arguments = RATtemplateControllable:GetState(RATtemplateControllable, '" .. ArgumentKey .. "' ) " DCSScript[#DCSScript+1] = FunctionString .. "( MissionControllable, unpack( Arguments ) )" @@ -3309,8 +3250,8 @@ function RAT:_TaskFunction(FunctionString, ... ) DCSTask = self.templategroup:TaskWrappedAction(self.templategroup:CommandDoScript(table.concat(DCSScript))) - env.info(RAT.id.."Taskfunction:") - self:E( DCSTask ) + --env.info(RAT.id.."Taskfunction:") + --self:E( DCSTask ) return DCSTask end @@ -3382,6 +3323,17 @@ function RAT:_AirportExists(name) return false end +--- Test if a trigger zone defined in the mission editor exists. +-- @param #RAT self +-- @param #string name +-- @return #boolean True if zone exsits, false otherwise. +function RAT:_ZoneExists(name) + local z=trigger.misc.getZone(name) + if z then + return true + end + return false +end --- Set ROE for a group. -- @param #RAT self @@ -3535,17 +3487,6 @@ function RAT:_PlaceMarkers(waypoints) for i=1,#waypoints do self:_SetMarker(self.waypointdescriptions[i], waypoints[i]) end ---[[ - self:_SetMarker("Takeoff", waypoints[1]) - self:_SetMarker("Climb", waypoints[2]) - self:_SetMarker("Begin of Cruise", waypoints[3]) - self:_SetMarker("End of Cruise", waypoints[4]) - if #waypoints>4 then - self:_SetMarker("Descent", waypoints[5]) - self:_SetMarker("Holding Point", waypoints[6]) - self:_SetMarker("Destination", waypoints[7]) - end -]] end @@ -3591,6 +3532,18 @@ function RAT:_ModifySpawnTemplate(waypoints) -- Heading from first to seconds waypoints to align units in case of air start. local heading = self:_Course(waypoints[1], waypoints[2]) + + -- Set (another) livery. + local skin=nil + if self.livery then + if self.debug then + for _, skin in pairs(self.livery) do + env.info(RAT.id.."Possible livery: "..skin.." for group "..self:_AnticipatedGroupName()) + end + end + skin=self.livery[math.random(#self.livery)] + env.info(RAT.id.."Chosen livery: "..skin.." for group "..self:_AnticipatedGroupName()) + end if self:_GetSpawnIndex(self.SpawnIndex+1) then @@ -3616,17 +3569,15 @@ function RAT:_ModifySpawnTemplate(waypoints) SpawnTemplate.units[UnitID].alt = PointVec3.y SpawnTemplate.units[UnitID].heading = math.rad(heading) - -- Set (another) livery. - if self.livery then - for _, skin in pairs(self.livery) do - env.info(RAT.id.."Possible livery: "..skin.." for group "..self:_AnticipatedGroupName()) - end - local skin=self.livery[math.random(#self.livery)] - env.info(RAT.id.."Chosen livery: "..skin.." for group "..self:_AnticipatedGroupName()) + -- Set livery (will be the same for all units of the group). + if skin then SpawnTemplate.units[UnitID].livery_id = skin end - --SpawnTemplate.units[UnitID]["type"] = "Tu-142" + -- Set type of aircraft. + if self.actype then + SpawnTemplate.units[UnitID]["type"] = self.actype + end -- Set AI skill. SpawnTemplate.units[UnitID]["skill"] = self.skill @@ -3972,275 +3923,3 @@ function RAT:_ATCQueue() end end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - - ---[[ ---- Initializes the ATC arrays and starts schedulers. --- @param #RAT self --- @param #table airports_map List of all airports of the map. -function RAT:_ATCInit(airports_map) - if not RAT.ATC.init then - env.info(RAT.id.."Starting RAT ATC.") - RAT.ATC.init=true - for _,ap in pairs(airports_map) do - local name=ap:GetName() - RAT.ATC.airport[name]={} - RAT.ATC.airport[name].queue={} - RAT.ATC.airport[name].busy=false - RAT.ATC.airport[name].onfinal=nil - RAT.ATC.airport[name].Nonfinal=0 - RAT.ATC.airport[name].traffic=0 - end - SCHEDULER:New(nil, RAT._ATCCheck, {self}, 5, 15) - SCHEDULER:New(nil, RAT._ATCStatus, {self}, 5, 60) - RAT.ATC.T0=timer.getTime() - end -end - ---- Adds andd initializes a new flight after it was spawned. --- @param #RAT self --- @param #string name Group name of the flight. --- @param #string dest Name of the destination airport. -function RAT:_ATCAddFlight(name, dest) - env.info(string.format("%sATC %s: Adding flight %s with destination %s.", RAT.id, dest, name, dest)) - RAT.ATC.flight[name]={} - RAT.ATC.flight[name].destination=dest - RAT.ATC.flight[name].Tarrive=-1 - RAT.ATC.flight[name].holding=-1 - RAT.ATC.flight[name].Tonfinal=-1 -end - ---- Deletes a flight from ATC lists after it landed. --- @param #RAT self --- @param #table t Table. --- @param #string entry Flight name which shall be deleted. -function RAT:_ATCDelFlight(t,entry) - for k,_ in pairs(t) do - if k==entry then - t[entry]=nil - end - end -end - ---- Registers a flight once it is near its holding point at the final destination. --- @param #RAT self --- @param #string name Group name of the flight. --- @param #number time Time the fight first registered. -function RAT:_ATCRegisterFlight(name, time) - RAT.ATC.flight[name].Tarrive=time - RAT.ATC.flight[name].holding=0 -end - - ---- ATC status report about flights. --- @param #RAT self -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 is currently clear" - if RAT.ATC.airport[dest].busy then - if RAT.ATC.airport[dest].onfinal then - busy="Runway is occupied by "..RAT.ATC.airport[dest].onfinal - else - busy="Runway is occupied" - end - end - - -- Aircraft is holding. - env.info(string.format("%sATC %s: Flight %s is holding for %i:%02d. %s.", RAT.id, dest, name, hold/60, hold%60, busy)) - - elseif hold==RAT.ATC.onfinal then - - -- Aircarft is on final approach for landing. - local Tfinal=Tnow-RAT.ATC.flight[name].Tonfinal - env.info(string.format("%sATC %s: Flight %s was cleared for landing. Waiting %i:%02d for landing event.", RAT.id, dest, name, Tfinal/60, Tfinal%60)) - - --TODO: Trigger landing for another aircraft when Tfinal > x min? - -- After five minutes we set the runway to green. ==> Increase the landing frequency a bit. - if Tfinal>300 then - --RAT.ATC.airport[dest].busy=false - --self:_ATCCheck() - end - - elseif hold==RAT.ATC.unregistered then - - -- Aircraft has not arrived at holding point. - --env.info(string.format("%s ATC: Flight %s is not registered yet (hold %d).", dest, name, hold)) - - else - env.error(RAT.id.."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. --- @param #RAT self -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 - - -- List of flights cleared for landing. - local qw={} - - for qID,flight in ipairs(RAT.ATC.airport[name].queue) do - - -- Number of aircraft in queue. - local nqueue=#RAT.ATC.airport[name].queue - - if RAT.ATC.airport[name].busy 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) - env.info(RAT.id..text) - - else - - -- Clear flight for landing. - RAT:_ATCClearForLanding(name, flight) - table.insert(qw, qID) - - end - - end - - -- Remove cleared flights from queue. - for _,i in pairs(qw) do - table.remove(RAT.ATC.airport[name].queue, i) - end - - end -end - ---- Giving landing clearance for aircraft by setting user flag. --- @param #RAT self --- @param #string airport Name of destination airport. --- @param #string flight Group name of flight, which gets landing clearence. -function RAT:_ATCClearForLanding(airport, flight) - -- Flight is cleared for landing. - RAT.ATC.flight[flight].holding=RAT.ATC.onfinal - -- Airport runway is busy now. - RAT.ATC.airport[airport].busy=true - -- Flight which is landing. - RAT.ATC.airport[airport].onfinal=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() - -- 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 final approach (flag=%d).", airport, flight, flagvalue) - local text2=string.format("ATC %s: Flight %s you are cleared for landing.", airport, flight) - env.info( RAT.id..text1) - MESSAGE:New(text2, 10):ToAll() -end - ---- Takes care of organisational stuff after a plane has landed. --- @param #RAT self --- @param #string name Group name of flight. -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=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) - local text3=string.format("ATC %s: Traffic report: Number of planes landed in total %d. Flighs / hour = %3.2f.", dest, RAT.ATC.airport[dest].traffic, TrafficPerHour) - local text4=string.format("ATC %s: Flight %s landed. Welcome to %s.", dest, name, dest) - env.info(RAT.id..text1) - env.info(RAT.id..text2) - env.info(RAT.id..text3) - MESSAGE:New(text4, 10):ToAll() - end - -end - ---- Creates a landing queue for all flights holding at airports. Aircraft with longest holding time gets first permission to land. --- @param #RAT self -function RAT:_ATCQueue() - - for airport,_ in pairs(RAT.ATC.airport) do - - -- Local airport queue. - local _queue={} - - -- Loop over all flights. - for name,_ in pairs(RAT.ATC.flight) do - - 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 - table.insert(RAT.ATC.airport[airport].queue, v[1]) - end - - end -end -]] -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - diff --git a/Moose Mission Setup/Moose.lua b/Moose Mission Setup/Moose.lua index a45a026a8..a020b3125 100644 --- a/Moose Mission Setup/Moose.lua +++ b/Moose Mission Setup/Moose.lua @@ -1,5 +1,5 @@ env.info( '*** MOOSE DYNAMIC INCLUDE START *** ' ) -env.info( 'Moose Generation Timestamp: 20171010_2216' ) +env.info( 'Moose Generation Timestamp: 20171021_1218' ) local base = _G @@ -67,7 +67,6 @@ __Moose.Include( 'Functional/RAT.lua' ) __Moose.Include( 'Functional/ZoneGoal.lua' ) __Moose.Include( 'Functional/ZoneGoalCoalition.lua' ) __Moose.Include( 'Functional/ZoneCaptureCoalition.lua' ) -__Moose.Include( 'Functional/SuppressionFire.lua' ) __Moose.Include( 'AI/AI_Balancer.lua' ) __Moose.Include( 'AI/AI_A2A.lua' ) __Moose.Include( 'AI/AI_A2A_Patrol.lua' ) From 3b3017aa1d356e26dbcd40ee80e34f0d792ce954 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Mon, 23 Oct 2017 23:30:38 +0200 Subject: [PATCH 16/25] RAT improvements. --- Moose Development/Moose/Functional/RAT.lua | 567 +++++++++++---------- 1 file changed, 310 insertions(+), 257 deletions(-) diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index 1c32dbc7e..e439b5174 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -80,7 +80,8 @@ -- @field #number AlphaDescent Default angle of descenti in degrees. A value of 3.6 follows the 3:1 rule of 3 miles of travel and 1000 ft descent. -- @field #string roe ROE of spawned groups, default is weapon hold (this is a peaceful class for civil aircraft or ferry missions). Possible: "hold", "return", "free". -- @field #string rot ROT of spawned groups, default is no reaction. Possible: "noreaction", "passive", "evade". --- @field #string takeoff Takeoff type. 0=coldorhot. +-- @field #number takeoff Takeoff type. 0=coldorhot. +-- @field #number landing Landing type. Determines if we actually land at an airport or treat it as zone. -- @field #number mindist Min distance from departure to destination in meters. Default 5 km. -- @field #number maxdist Max distance from departure to destination in meters. Default 5000 km. -- @field #table airports_map All airports available on current map (Caucasus, Nevada, Normandy, ...). @@ -286,7 +287,7 @@ RAT={ coalition = nil, -- Coalition of spawn group template. country = nil, -- Country of the group template. category = nil, -- Category of aircarft: "plane" or "heli". - friendly = "same", -- Possible departure/destination airport: all=blue+red+neutral, same=spawn+neutral, spawnonly=spawn, blue=blue+neutral, blueonly=blue, red=red+neutral, redonly=red. + friendly = "same", -- Possible departure/destination airport: same=spawn+neutral, spawnonly=spawn, blue=blue+neutral, blueonly=blue, red=red+neutral, redonly=red, neutral. ctable = {}, -- Table with the valid coalitons from choice self.friendly. aircraft = {}, -- Table which holds the basic aircraft properties (speed, range, ...). Vcruisemax=nil, -- Max cruise speed in set by user. @@ -295,6 +296,7 @@ RAT={ roe = "hold", -- ROE of spawned groups, default is weapon hold (this is a peaceful class for civil aircraft or ferry missions). Possible: "hold", "return", "free". rot = "noreaction", -- ROT of spawned groups, default is no reaction. Possible: "noreaction", "passive", "evade". takeoff = 0, -- Takeoff type. 0=coldorhot. + landing = 9, -- Landing type. 9=landing. mindist = 5000, -- Min distance from departure to destination in meters. Default 5 km. maxdist = 500000, -- Max distance from departure to destination in meters. Default 5000 km. airports_map={}, -- All airports available on current map (Caucasus, Nevada, Normandy, ...). @@ -386,12 +388,12 @@ RAT.status={ Destination="Arrived at destination", -- Event states. EventBirthAir="Born in air", - EventBirth="Born on ground", + EventBirth="Ready and starting engines", EventEngineStartAir="Started engines (in air)", - EventEngineStart="Started engines", - EventTakeoff="Took off", - EventLand="Landed", - EventEngineShutdown="Engines shut down", + EventEngineStart="Started engines and taxiing", + EventTakeoff="Airborn after take-off", + EventLand="Landed and taxiing", + EventEngineShutdown="Engines off", EventDead="Dead", EventCrash="Crashed", } @@ -547,7 +549,7 @@ function RAT:Spawn(naircraft) env.error("Spawn function should only be called once per RAT object! Exiting and returning nil.") return nil else - self.spawninitialized=false + self.spawninitialized=true end -- Number of aircraft to spawn. Default is one. @@ -591,23 +593,24 @@ function RAT:Spawn(naircraft) env.error(RAT.id.."Destination zone _and_ return to zone not possible! Disabling return to zone.") self.returnzone=false end - -- Return zone should not be used together with air start. Why actually? + -- If returning to a zone, we set the landing type to the takeoff type. Default landing is ground. + -- But if we start in air we want to end in air. if self.returnzone and self.takeoff==RAT.wp.air then - env.error(RAT.id.."Combination return zone _and_ air start not possible! Falling back to destination zone instead.") - self.returnzone=false - self.destinationzone=true + self.landing=self.takeoff end - -- Return zone and commute does not make sense. + -- Return zone and commute does not make sense. + -- TODO: Actually it makes sense if the departure airport should stay the same. + -- We could achive the same by setting only one departure or destination but this is generally not the case! if self.returnzone and self.continuejourney then - env.error(RAT.id.."Combination return zone _and_ commute does not make sense! Disabling commute.") - self.commute=false + --env.error(RAT.id.."Combination return zone _and_ commute does not make sense! Disabling commute.") + --self.commute=false end -- Return zone and commute does not make sense. --- if self.returnzone and self.commute then + if self.returnzone and self.commute then -- env.error(RAT.id.."Combination return zone _and_ commute does not make sense! Disabling commute.") -- self.commute=false --- end + end -- Settings info @@ -621,6 +624,7 @@ function RAT:Spawn(naircraft) text=text..string.format("Min dist to destination: %4.1f\n", self.mindist) text=text..string.format("Max dist to destination: %4.1f\n", self.maxdist) text=text..string.format("Takeoff type: %i\n", self.takeoff) + text=text..string.format("Landing type: %i\n", self.landing) text=text..string.format("Commute: %s\n", tostring(self.commute)) text=text..string.format("Journey: %s\n", tostring(self.continuejourney)) text=text..string.format("Destination Zone: %s\n", tostring(self.destinationzone)) @@ -757,173 +761,108 @@ end --- Set possible departure ports. This can be an airport or a zone defined in the mission editor. -- @param #RAT self --- @param #string names Name or table of names of departure airports or zones. +-- @param #string departurenames Name or table of names of departure airports or zones. -- @usage RAT:SetDeparture("Sochi-Adler") will spawn RAT objects at Sochi-Adler airport. -- @usage RAT:SetDeparture({"Sochi-Adler", "Gudauta"}) will spawn RAT aircraft radomly at Sochi-Adler or Gudauta airport. -- @usage RAT:SetDeparture({"Zone A", "Gudauta"}) will spawn RAT aircraft in air randomly within Zone A, which has to be defined in the mission editor, or within a zone around Gudauta airport. Note that this also requires RAT:takeoff("air") to be set. -function RAT:SetDeparture(names) +function RAT:SetDeparture(departurenames) -- Random departure is deactivated now that user specified departure ports. self.random_departure=false - if type(names)=="table" then - - -- we did get a table of names - 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) - else - -- If it is not an airport, we assume it is a zone. - -- We need the DCS call to determine if it's really a zone. Otherwise code will crash at some point. - local z=trigger.misc.getZone(name) - if z then - table.insert(self.departure_zones, name) - end - end - - end - - elseif type(names)=="string" then - - if self:_AirportExists(names) then - -- If an airport with this name exists, we put it in the ports array. - table.insert(self.departure_ports, names) - else - -- If it is not an airport, we assume it is a zone. - table.insert(self.departure_zones, names) - end - + -- Convert input to table. + local names + if type(departurenames)=="table" then + names=departurenames + elseif type(departurenames)=="string" then + names={departurenames} else -- error message env.error("Input parameter must be a string or a table!") 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) + elseif self:_ZoneExists(name) then + -- If it is not an airport, we assume it is a zone. + table.insert(self.departure_zones, name) + else + env.error(RAT.id.."ERROR! No departure airport or zone found with name "..name) + end + + end + end ---- Set name of destination airport for the AI aircraft. If no name is given an airport from the friendly coalition(s) is chosen randomly. +--- Set name of destination airports or zones for the AI aircraft. -- @param #RAT self --- @param #string names Name of the destination airport or table of destination airports. +-- @param #string destinationnames Name of the destination airport or table of destination airports. -- @usage RAT:SetDestination("Krymsk") makes all aircraft of this RAT oject fly to Krymsk airport. -function RAT:SetDestination(names) +function RAT:SetDestination(destinationnames) -- Random departure is deactivated now that user specified departure ports. self.random_destination=false - if type(names)=="table" then - - for _,name in pairs(names) do - if self:_AirportExists(name) then - table.insert(self.destination_ports, name) - else - local text=string.format("Airport %s does not exist on map!", name) - env.error(text) - end - end - - elseif type(names)=="string" then - - if self:_AirportExists(names) then - self.destination_ports={names} - else - local text=string.format("Airport %s does not exist on map!", names) - env.error(text) - end - + -- Convert input to table + local names + if type(destinationnames)=="table" then + names=destinationnames + elseif type(destinationnames)=="string" then + names={destinationnames} else -- Error message. env.error("Input parameter must be a string or a table!") 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) + elseif self:_ZoneExists(name) then + -- If it is not an airport, we assume it is a zone. + table.insert(self.destination_ports, name) + else + env.error(RAT.id.."ERROR! No destination airport or zone found with name "..name) + end + + end end ---- Set name of destination zones for the AI aircraft. If multiple names are given as a table, one zone is picked randomly as destination. +--- Destinations are treated as zones. Aircraft will not land but rather be despawned when they reach a random point in the zone. -- @param #RAT self --- @param #string zonenames Name or table of names of zones defined in the mission editor or names of airports on the map. -function RAT:SetDestinationZone(zonenames) +function RAT:DestinationZone() -- Random destination is deactivated now that user specified destination zone(s). self.random_destination=false + -- 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 + -- No ATC required. self.ATCswitch=false - local names - if type(zonenames)=="table" then - names=zonenames - elseif type(zonenames)=="string" then - names={zonenames} - else - -- Error message. - env.error("Input parameter must be a string or a table!") - end - - for _,name in pairs(names) do - if self:_AirportExists(name) then - -- Take airport as zone. - local airportzone=AIRBASE:FindByName(name):GetZone() - table.insert(self.destination_zones, airportzone) - else - -- If it is not an airport, we assume it is a zone. - -- We need the DCS call to determine if it's really a zone. Otherwise code will crash at some point. - local z=trigger.misc.getZone(name) - if z then - table.insert(self.destination_zones, ZONE:New(name)) - end - end - end - end ---- Set name of "return" zones for the AI aircraft. If multiple names are given as a table, one zone is picked randomly as destination. +--- Aircraft will fly to a random point within a zone and then return to its departure airport or zone. -- @param #RAT self --- @param #string zonenames Name or table of names of zones defined in the mission editor. -function RAT:SetReturnZone(zonenames) +function RAT:ReturnZone() -- Random destination is deactivated now that user specified destination zone(s). self.random_destination=false + -- Destination is a zone. Needs special care. self.returnzone=true - ---[[ - if type(names)=="table" then - for _,name in pairs(names) do - table.insert(self.destination_zones, ZONE:New(name)) - end - - elseif type(names)=="string" then - - table.insert(self.destination_zones, ZONE:New(names)) - - else - -- Error message. - env.error("Input parameter must be a string or a table!") - end -]] - - local names - if type(zonenames)=="table" then - names=zonenames - elseif type(zonenames)=="string" then - names={zonenames} - else - -- Error message. - env.error("Input parameter must be a string or a table!") - end - - for _,name in pairs(names) do - if self:_AirportExists(name) then - table.insert(self.destination_zones, AIRBASE:FindByName(name):GetZone()) - elseif self:_ZoneExists(name) then - table.insert(self.destination_zones, ZONE:New(name)) - else - env.error(RAT.id.."Specified airport or zone "..name.." does not exist in mission editor!") - end - end - end @@ -1331,20 +1270,28 @@ end -- @param #string _departure (Optional) Name of departure airbase. -- @param #string _destination (Optional) Name of destination airbase. -- @param #number _takeoff Takeoff type id. -function RAT:_SpawnWithRoute(_departure, _destination, _takeoff) +function RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing) -- Set takeoff type. local takeoff=self.takeoff + local landing=self.landing + + -- Random choice between cold and hot. if self.takeoff==RAT.wp.coldorhot then local temp={RAT.wp.cold, RAT.wp.hot} takeoff=temp[math.random(2)] end + + -- Overrule takeoff/landing by what comes in. if _takeoff then takeoff=_takeoff end + if _landing then + landing=_landing + end -- Set flight plan. - local departure, destination, waypoints = self:_SetRoute(takeoff, _departure, _destination) + local departure, destination, waypoints = self:_SetRoute(takeoff, landing, _departure, _destination) -- Return nil if we could not find a departure destination or waypoints if not (departure and destination and waypoints) then @@ -1358,9 +1305,13 @@ function RAT:_SpawnWithRoute(_departure, _destination, _takeoff) local group=self:SpawnWithIndex(self.SpawnIndex) -- Wrapper.Group#GROUP self.alive=self.alive+1 - -- ATC is monitoring this flight. - if self.ATCswitch then - RAT:_ATCAddFlight(group:GetName(), destination:GetName()) + -- ATC is monitoring this flight (if it supposed to land). + if self.ATCswitch and landing==RAT.wp.landing then + if self.returnzone then + RAT:_ATCAddFlight(group:GetName(), departure:GetName()) + else + RAT:_ATCAddFlight(group:GetName(), destination:GetName()) + end end -- Set ROE, default is "weapon hold". @@ -1392,8 +1343,11 @@ function RAT:_SpawnWithRoute(_departure, _destination, _takeoff) self.ratcraft[self.SpawnIndex]["Pnow"]=group:GetCoordinate() self.ratcraft[self.SpawnIndex]["Distance"]=0 - -- Each aircraft gets its own takeoff type. unused? - self.ratcraft[self.SpawnIndex]["takeoff"]=_takeoff + -- Each aircraft gets its own takeoff type. + self.ratcraft[self.SpawnIndex].takeoff=takeoff + self.ratcraft[self.SpawnIndex].landing=landing + + -- 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 @@ -1443,28 +1397,97 @@ function RAT:_Respawn(group) -- Get departure and destination from previous journey. local departure=self.ratcraft[index].departure local destination=self.ratcraft[index].destination + local takeoff=self.ratcraft[index].takeoff + local landing=self.ratcraft[index].landing local _departure=nil local _destination=nil local _takeoff=nil + local _landing=nil if self.continuejourney then + -- We continue our journey from the old departure airport. _departure=destination:GetName() + if self.destinationzone then + + -- Case: X --> Zone --> Zone --> Zone _takeoff=RAT.wp.air + + -- We should also take case that the destination is set correctly + + elseif self.returnzone then + + -- Case: X --> Zone --> X, X --> Zone --> X + -- We flew to a zone and back. Takeoff type does not + _takeoff=self.takeoff + _landing=self.takeoff + + -- Departure stays the same. + _departure=departure:GetName() + + else + + _takeoff=self.takeoff + _landing=self.landing + end + elseif self.commute then + -- We commute between departure and destination. _departure=destination:GetName() _destination=departure:GetName() - 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 + _landing=RAT.wp.air -- = self.landing + + 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. + _takeoff=self.takeoff -- must be either hot/cold/runway/hotcold + _landing=self.landing -- must be air + else + -- Last takeoff was on ground so we are at a zone now ==> takeoff in air, landing at zone. + _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 + + env.info(RAT.id..string.format("self.takeoff, takeoff, _takeoff = %d, %d, %d", self.takeoff, takeoff, _takeoff)) + env.info(RAT.id..string.format("self.landing, landing, _landing = %d, %d, %d", self.landing, landing, _landing)) + -- Spawn new group. if self.respawn_delay then - SCHEDULER:New(nil, self._SpawnWithRoute, {self, _departure, _destination, _takeoff}, self.respawn_delay) + SCHEDULER:New(nil, self._SpawnWithRoute, {self, _departure, _destination, _takeoff, _landing}, self.respawn_delay) else - self:_SpawnWithRoute(_departure, _destination, _takeoff) + self:_SpawnWithRoute(_departure, _destination, _takeoff, _landing) end end @@ -1473,14 +1496,15 @@ end --- Set the route of the AI plane. Due to DCS landing bug, this has to be done before the unit is spawned. -- @param #RAT self --- @param takeoff #RAT.wp Takeoff type. +-- @param takeoff #RAT.wp Takeoff type. Could also be air start. +-- @param landing #RAT.wp Landing type. Could also be a destination in air. -- @param Wrapper.Airport#AIRBASE _departure (Optional) Departure airbase. -- @param Wrapper.Airport#AIRBASE _destination (Optional) Destination airbase. -- @return Wrapper.Airport#AIRBASE Departure airbase. -- @return Wrapper.Airport#AIRBASE Destination airbase. -- @return #table Table of flight plan waypoints. -- @return #nil If no valid departure or destination airport could be found. -function RAT:_SetRoute(takeoff, _departure, _destination) +function RAT:_SetRoute(takeoff, landing, _departure, _destination) -- Max cruise speed. local VxCruiseMax @@ -1531,36 +1555,17 @@ function RAT:_SetRoute(takeoff, _departure, _destination) if self:_AirportExists(_departure) then -- Check if new departure is an airport. departure=AIRBASE:FindByName(_departure) + -- If we spawn in air, we convert departure to a zone. + if takeoff == RAT.wp.air then + departure=departure:GetZone() + end elseif self:_ZoneExists(_departure) then -- If it's not an airport, check whether it's a zone. departure=ZONE:New(_departure) else local text=string.format("ERROR: Specified departure airport %s does not exist for %s!", _departure, self.alias) env.error(RAT.id..text) - end - - if self.commute then - if returnzone then - -- should not be a problem because we flew back to the departure. Departure=destination ==> nothing really changes. - -- Just that the next departure is not random but the same airport we started first. - elseif destinationzone then - -- We initially flew to a zone. - if self.takeoff==RAT.wp.air then - -- We initially came from a zone, i.e. airstart. - if takeoff==RAT.wp.air then - -- Now we also fly to zone. - else - -- Now we fly to an airport where we land - end - else - -- We initally - end - end - elseif self.continuejourney then - - departure=departure:GetZone() - end - + end else departure=self:_PickDeparture(takeoff) @@ -1611,15 +1616,14 @@ function RAT:_SetRoute(takeoff, _departure, _destination) if _destination then if self:_AirportExists(_destination) then + destination=AIRBASE:FindByName(_destination) - if self.destinationzone then + if landing==RAT.wp.air or self.returnzone then destination=destination:GetZone() end + elseif self:_ZoneExists(_destination) then destination=ZONE:New(_destination) - if not self.returnzone then - self.destinationzone=true - end else local text=string.format("ERROR: Specified destination airport/zone %s does not exist for %s!", _destination, self.alias) env.error(RAT.id..text) @@ -1627,15 +1631,24 @@ function RAT:_SetRoute(takeoff, _departure, _destination) else - -- This handes the case where we have a journey and the first flight is done, i.e. _departure is set. + -- This handles the case where we have a journey and the first flight is done, i.e. _departure is set. -- If a user specified more than two destination airport explicitly, then we will stick to this. -- Otherwise, the route is random from now on. + local random=self.random_destination if self.continuejourney and _departure and #self.destination_ports<3 then - self.random_destination=true + random=true + end + + -- In case of a returnzone the destination (i.e. return point) is always a zone. + local mylanding + if self.returnzone then + mylanding=RAT.wp.air + else + mylanding=landing end -- Get all destination airports within reach. - local destinations=self:_GetDestinations(departure, Pdeparture, self.mindist, math.min(self.aircraft.Reff, self.maxdist)) + local destinations=self:_GetDestinations(departure, Pdeparture, self.mindist, math.min(self.aircraft.Reff, self.maxdist), random, mylanding) -- Pick a destination airport. destination=self:_PickDestination(destinations) @@ -1651,11 +1664,12 @@ function RAT:_SetRoute(takeoff, _departure, _destination) -- 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 airport are identical. Airport %s.", self.alias, destination:GetName()) + local text=string.format("%s: Destination and departure are identical. Airport/zone %s.", self.alias, destination:GetName()) MESSAGE:New(text, 30):ToAll() env.error(RAT.id..text) end + --[[ -- Coordinates of destination airport. local Pdestination local Preturn @@ -1666,13 +1680,40 @@ function RAT:_SetRoute(takeoff, _departure, _destination) elseif self.returnzone then -- We fly to a random point within a zone and back to the departure airport. Pdestination=departure:GetCoordinate() + -- Get a random point inside zone return zone. local vec2=destination:GetRandomVec2() Preturn=COORDINATE:NewFromVec2(vec2) + -- Set departure to destination. destination=departure else Pdestination=destination:GetCoordinate() end - -- Height ASL of destination airport. + ]] + + -- Get a random point inside zone return zone. + local Preturn + local destination_returnzone + if self.returnzone then + -- Get a random point inside zone return zone. + local vec2=destination:GetRandomVec2() + Preturn=COORDINATE:NewFromVec2(vec2) + destination_returnzone=destination + env.info(RAT.id.."Destination r zone = "..destination_returnzone:GetName()) + -- Set departure to destination. + destination=departure + env.info(RAT.id.."Destination r zone = "..destination_returnzone:GetName()) + end + + -- Get destination coordinate. Either in a zone or exactly at the airport. + local Pdestination + if landing==RAT.wp.air then + local vec2=destination:GetRandomVec2() + Pdestination=COORDINATE:NewFromVec2(vec2) + else + Pdestination=destination:GetCoordinate() + end + + -- Height ASL of destination airport/zone. local H_destination=Pdestination.y -- DESCENT/HOLDING POINT @@ -1723,11 +1764,11 @@ function RAT:_SetRoute(takeoff, _departure, _destination) d_total=Pdeparture:Get2DDistance(Pholding) end - -- max height if we only would descent to holding point for the given distance + -- Max height if we only would descent to holding point for the given distance. -- TODO: Add case for destination zone. We could allow a higher max because no descent is necessary. if takeoff==RAT.wp.air then local H_departure_max - if self.destinationzone then + if landing==RAT.wp.air then H_departure_max = H_departure else H_departure_max = d_total * math.tan(AlphaDescent) + H_holding + h_holding @@ -1924,7 +1965,7 @@ function RAT:_SetRoute(takeoff, _departure, _destination) if takeoff==RAT.wp.air then -- Air start. - if d_climb < 1000 or d_cruise < 1000 then + if d_climb < 20000 or d_cruise < 20000 then -- We omit the climb phase completely and add it to the cruise part. d_cruise=d_cruise+d_climb else @@ -1953,23 +1994,28 @@ function RAT:_SetRoute(takeoff, _departure, _destination) end -- Cruise - if self.destinationzone then + + -- First add the little bit from begin of cruise to the return point. + if self.returnzone then + c[#c+1]=Preturn + wp[#wp+1]=self:_Waypoint(#wp+1, 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 + --TODO: change RAT.wp.finalwp to "landing" wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.finalwp, c[#wp+1], VxCruise, FLcruise) self.waypointdescriptions[#wp]="Final Destination" self.waypointstatus[#wp]=RAT.status.Destination elseif self.returnzone then - c[#c+1]=Preturn - c[#c+1]=c[#c]:Translate(d_cruise/2, heading-180) - - wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.cruise, c[#wp+1], VxCruise, FLcruise) - self.waypointdescriptions[#wp]="Return Zone" - self.waypointstatus[#wp]=RAT.status.Uturn - + -- 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, RAT.wp.cruise, c[#wp+1], VxCruise, FLcruise) self.waypointdescriptions[#wp]="End of Cruise" self.waypointstatus[#wp]=RAT.status.Descent @@ -1980,38 +2026,40 @@ function RAT:_SetRoute(takeoff, _departure, _destination) wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.cruise, c[#wp+1], VxCruise, FLcruise) self.waypointdescriptions[#wp]="End of Cruise" self.waypointstatus[#wp]=RAT.status.Descent + end - -- Descent - if self.destinationzone then - -- Nothing to do. - elseif self.returnzone then - c[#c+1]=c[#c]:Translate(d_descent/2, heading-180) - wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.descent, c[#wp+1], VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) - self.waypointdescriptions[#wp]="Descent" - self.waypointstatus[#wp]=RAT.status.DescentHolding - else - c[#c+1]=c[#c]:Translate(d_descent/2, heading) - wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.descent, c[#wp+1], VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) - self.waypointdescriptions[#wp]="Descent" - self.waypointstatus[#wp]=RAT.status.DescentHolding + -- Descent (only if we acually want to land) + if landing==RAT.wp.landing then + if self.returnzone then + c[#c+1]=c[#c]:Translate(d_descent/2, heading-180) + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.descent, c[#wp+1], VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) + self.waypointdescriptions[#wp]="Descent" + self.waypointstatus[#wp]=RAT.status.DescentHolding + else + c[#c+1]=c[#c]:Translate(d_descent/2, heading) + wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.descent, c[#wp+1], VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) + self.waypointdescriptions[#wp]="Descent" + self.waypointstatus[#wp]=RAT.status.DescentHolding + end end -- Holding and final destination. - if self.destinationzone then - -- Nothing to do. - else - c[#c+1]=Pholding - c[#c+1]=Pdestination - + if landing==RAT.wp.landing then + + -- Holding point + c[#c+1]=Pholding wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.holding, c[#wp+1], VxHolding, H_holding+h_holding) self.waypointdescriptions[#wp]="Holding Point" self.waypointstatus[#wp]=RAT.status.Holding self.wp_holding=#wp - - wp[#wp+1]=self:_Waypoint(#wp+1, RAT.wp.landing, c[#wp+1], VxFinal, H_destination, destination) + + -- Final destination. + c[#c+1]=Pdestination + wp[#wp+1]=self:_Waypoint(#wp+1, landing, c[#wp+1], VxFinal, H_destination, destination) self.waypointdescriptions[#wp]="Destination" self.waypointstatus[#wp]=RAT.status.Destination + end -- Final Waypoint @@ -2032,7 +2080,12 @@ function RAT:_SetRoute(takeoff, _departure, _destination) self:_Routeinfo(waypoints, "Waypoint info in set_route:") -- return departure, destination and waypoints - return departure, destination, 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 + else + return departure, destination, waypoints + end end @@ -2125,9 +2178,8 @@ end --- Pick destination airport. If no airport name is given an airport from the coalition is chosen randomly. -- @param #RAT self -- @param #table destinations Table with destination airports. --- @param #boolean _random (Optional) Switch to activate a random selection of airports. -- @return Wrapper.Airbase#AIRBASE Destination airport. -function RAT:_PickDestination(destinations, _random) +function RAT:_PickDestination(destinations) -- Randomly select one possible destination. local destination=nil @@ -2163,16 +2215,17 @@ end -- @param Core.Point#COORDINATE q Coordinate of the departure point. -- @param #number minrange Minimum range to q in meters. -- @param #number maxrange Maximum range to q in meters. --- @return #table Table with possible destination airports. --- @return #nil If no airports could be found. -function RAT:_GetDestinations(departure, q, minrange, maxrange) +-- @param #booleean random Destination is randomly selected from friendly airport (true) or from destinations specified by user input (false). +-- @param #number Number indicating whether we land at a destination or return a zone object. +-- @return #table Table with possible destination airports or zones. +function RAT:_GetDestinations(departure, q, minrange, maxrange, random, landing) -- Min/max range to destination. minrange=minrange or self.mindist maxrange=maxrange or self.maxdist local possible_destinations={} - if self.random_destination then + if random then -- Airports of friendly coalitions. for _,airport in pairs(self.airports) do @@ -2184,49 +2237,48 @@ function RAT:_GetDestinations(departure, q, minrange, maxrange) -- Check if distance form departure to destination is within min/max range. if distance>=minrange and distance<=maxrange then - table.insert(possible_destinations, airport) + if landing==RAT.wp.air then + table.insert(possible_destinations, airport:GetZone()) -- insert zone object. + else + table.insert(possible_destinations, airport) -- insert airport object. + end end end end else - - if self.destinationzone or self.returnzone then - - -- Destination or return zones specified by user. - for _,zone in pairs(self.destination_zones) do - if zone:GetName() ~= departure:GetName() then + + -- Destination airports or zones specified by user. + for _,name in pairs(self.destination_ports) do - -- Distance from departure to possible destination - local distance=q:Get2DDistance(zone:GetCoordinate()) - - -- Add as possible destination if zone is within range. - if distance>=minrange and distance<=maxrange then - table.insert(possible_destinations, zone) + -- Make sure departure and destination are not identical. + if name ~= departure:GetName() then + + local dest + if self:_AirportExists(name) then + if landing==RAT.wp.air then + dest=AIRBASE:FindByName(name):GetZone() + else + dest=AIRBASE:FindByName(name) end + elseif self:_ZoneExists(name) then + dest=ZONE:New(name) + else + env.error(RAT.id.."No airport or zone found with name "..name) end - end - - else - -- Airports specified by user. - for _,name in pairs(self.destination_ports) do - --if self:_IsFriendly(name) and not self:_Excluded(name) and name~=departure:GetName() then - if name~=departure:GetName() then - local airport=AIRBASE:FindByName(name) - - -- Distance from departure to possible destination - local distance=q:Get2DDistance(airport:GetCoordinate()) - - -- Add as possible destination if airport is within range. - if distance>=minrange and distance<=maxrange then - table.insert(possible_destinations, airport) - end + -- 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(possible_destinations, dest) end + end - end + end + end -- Info message. @@ -3018,9 +3070,7 @@ function RAT:_Waypoint(index, Type, Coord, Speed, Altitude, Airport) -- waypoint name (only for the mission editor) RoutePoint.name="RAT waypoint" - if (Airport~=nil) and Type~=RAT.wp.air then - env.info(RAT.id.."Airport = "..Airport:GetName()) - env.info(RAT.id.."Type = "..Type) + if (Airport~=nil) and (Type~=RAT.wp.air) then local AirbaseID = Airport:GetID() local AirbaseCategory = Airport:GetDesc().category if AirbaseCategory == Airbase.Category.SHIP then @@ -3179,6 +3229,7 @@ function RAT._WaypointFunction(group, rat, wp) -- Departure and destination names. local departure=rat.ratcraft[sdx].departure:GetName() local destination=rat.ratcraft[sdx].destination:GetName() + local landing=rat.ratcraft[sdx].landing -- For messages local text @@ -3211,7 +3262,7 @@ function RAT._WaypointFunction(group, rat, wp) MESSAGE:New(text, 10):ToAllIf(rat.reportstatus) env.info(RAT.id..text) - if rat.destinationzone then + 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, 30):ToAllIf(rat.debug) env.info(RAT.id..text) @@ -3923,3 +3974,5 @@ function RAT:_ATCQueue() end end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + + From f4a0b83619fe745b19df35b00ef1a52422bc6e21 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Tue, 24 Oct 2017 23:03:57 +0200 Subject: [PATCH 17/25] RAT --- Moose Development/Moose/Functional/RAT.lua | 476 +++++++++++---------- 1 file changed, 246 insertions(+), 230 deletions(-) diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index e439b5174..eeb5e0c71 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -88,11 +88,9 @@ -- @field #table airports All airports of friedly coalitions. -- @field #boolean random_departure By default a random friendly airport is chosen as departure. -- @field #boolean random_destination By default a random friendly airport is chosen as destination. --- @field #table departure_zones Array containing the names of the departure zones. -- @field #table departure_ports Array containing the names of the destination airports. -- @field #table destination_ports Array containing the names of the destination airports. -- @field #table excluded_ports Array containing the names of explicitly excluded airports. --- @field #table destination_zones Array containing the names of the destination zones. -- @field #boolean destinationzone Destination is a zone and not an airport. -- @field #table return_zones Array containing the names of the return zones. -- @field #boolean returnzone Zone where aircraft will fly to before returning to their departure airport. @@ -303,10 +301,8 @@ RAT={ airports={}, -- All airports of friedly coalitions. random_departure=true, -- By default a random friendly airport is chosen as departure. random_destination=true, -- By default a random friendly airport is chosen as destination. - departure_zones={}, -- Array containing the names of the departure zones. departure_ports={}, -- Array containing the names of the departure airports. destination_ports={}, -- Array containing the names of the destination airports. - destination_zones={}, -- Array containing the names of destination zones. destinationzone=false, -- Destination is a zone and not an airport. return_zones={}, -- Array containing the names of return zones. returnzone=false, -- Aircraft will fly to a zone and back. @@ -391,7 +387,7 @@ RAT.status={ EventBirth="Ready and starting engines", EventEngineStartAir="Started engines (in air)", EventEngineStart="Started engines and taxiing", - EventTakeoff="Airborn after take-off", + EventTakeoff="Airborne after take-off", EventLand="Landed and taxiing", EventEngineShutdown="Engines off", EventDead="Dead", @@ -487,6 +483,8 @@ RAT.id="RAT | " --DONE: Handle the case where more than 10 RAT objects are spawned. Likewise, more than 10 groups of one object. Causes problems with the number of menu items! ==> not now! --DONE: Add custom livery choice if possible. --TODO: When only a destination is set, it should be checked that the departure is within range. Also, that departure and destination are not the same. +--TODO: Add function to include all airports to selected destinations/departures. +--TODO: Find way to respawn aircraft at same position where the last was despawned for commute and journey. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -517,7 +515,7 @@ function RAT:New(groupname, alias) -- Check the group actually exists. if DCSgroup==nil then - env.error("Group with name "..groupname.." does not exist in the mission editor!") + env.error(RAT.id.."Group with name "..groupname.." does not exist in the mission editor!") return nil end @@ -593,25 +591,11 @@ function RAT:Spawn(naircraft) env.error(RAT.id.."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 the takeoff type. Default landing is ground. - -- But if we start in air we want to end 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=self.takeoff + self.landing=RAT.wp.air end - -- Return zone and commute does not make sense. - -- TODO: Actually it makes sense if the departure airport should stay the same. - -- We could achive the same by setting only one departure or destination but this is generally not the case! - if self.returnzone and self.continuejourney then - --env.error(RAT.id.."Combination return zone _and_ commute does not make sense! Disabling commute.") - --self.commute=false - end - - -- Return zone and commute does not make sense. - if self.returnzone and self.commute then --- env.error(RAT.id.."Combination return zone _and_ commute does not make sense! Disabling commute.") --- self.commute=false - end - -- Settings info local text=string.format("\n******************************************************\n") @@ -653,6 +637,12 @@ function RAT:Spawn(naircraft) text=text..string.format("Radio comms : %s\n", tostring(self.radio)) text=text..string.format("Radio frequency : %s\n", tostring(self.frequency)) text=text..string.format("Radio modulation : %s\n", tostring(self.frequency)) + if self.livery then + text=text..string.format("Available liveries:\n") + for _,livery in pairs(self.livery) do + text=text..string.format("- %s\n", livery) + end + end text=text..string.format("******************************************************\n") env.info(RAT.id..text) @@ -778,7 +768,7 @@ function RAT:SetDeparture(departurenames) names={departurenames} else -- error message - env.error("Input parameter must be a string or a table!") + env.error(RAT.id.."Input parameter must be a string or a table in SetDeparture()!") end -- Put names into arrays. @@ -789,7 +779,7 @@ function RAT:SetDeparture(departurenames) table.insert(self.departure_ports, name) elseif self:_ZoneExists(name) then -- If it is not an airport, we assume it is a zone. - table.insert(self.departure_zones, name) + table.insert(self.departure_ports, name) else env.error(RAT.id.."ERROR! No departure airport or zone found with name "..name) end @@ -815,7 +805,7 @@ function RAT:SetDestination(destinationnames) names={destinationnames} else -- Error message. - env.error("Input parameter must be a string or a table!") + env.error(RAT.id.."Input parameter must be a string or a table in SetDestination()!") end -- Put names into arrays. @@ -838,52 +828,41 @@ end --- Destinations are treated as zones. Aircraft will not land but rather be despawned when they reach a random point in the zone. -- @param #RAT self function RAT:DestinationZone() - - -- Random destination is deactivated now that user specified destination zone(s). - self.random_destination=false - -- Destination is a zone. Needs special care. self.destinationzone=true - -- Landing type is "air" because we don't actually land at the airport + -- Landing type is "air" because we don't actually land at the airport. self.landing=RAT.wp.air - - -- No ATC required. - self.ATCswitch=false - end --- Aircraft will fly to a random point within a zone and then return to its departure airport or zone. -- @param #RAT self -function RAT:ReturnZone() - - -- Random destination is deactivated now that user specified destination zone(s). - self.random_destination=false - +function RAT:ReturnZone() -- Destination is a zone. Needs special care. self.returnzone=true - end --- Include all airports which lie in a zone as possible destinations. -- @param #RAT self --- @param Core.Zone#ZONE zone Zone in which the airports lie. +-- @param Core.Zone#ZONE zone Zone in which the departure airports lie. Has to be a MOOSE zone. function RAT:SetDestinationsFromZone(zone) -- Random departure is deactivated now that user specified departure ports. self.random_destination=false + -- Set zone. self.destination_Azone=zone end --- Include all airports which lie in a zone as possible destinations. -- @param #RAT self --- @param Core.Zone#ZONE zone Zone in which the airports lie. +-- @param Core.Zone#ZONE zone Zone in which the destination airports lie. Has to be a MOOSE zone. function RAT:SetDeparturesFromZone(zone) -- Random departure is deactivated now that user specified departure ports. self.random_departure=false + -- Set zone. self.departure_Azone=zone end @@ -1219,8 +1198,8 @@ function RAT:_InitAircraft(DCSgroup) -- operational range in NM converted to m self.aircraft.Rmax = DCSdesc.range*RAT.unit.nm2m - -- effective range taking fuel into accound and a 10% reserve - self.aircraft.Reff = self.aircraft.Rmax*self.aircraft.fuel*0.9 + -- 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 @@ -1251,7 +1230,7 @@ function RAT:_InitAircraft(DCSgroup) text=text..string.format("Max climb speed = %6.1f m/s\n", self.aircraft.Vymax) text=text..string.format("Initial Fuel = %6.1f\n", self.aircraft.fuel*100) text=text..string.format("Max range = %6.1f km\n", self.aircraft.Rmax/1000) - text=text..string.format("Eff range = %6.1f km\n", self.aircraft.Reff/1000) + text=text..string.format("Eff range = %6.1f km (including initial fuel amount)\n", self.aircraft.Reff/1000) text=text..string.format("Ceiling = %6.1f km = FL%3.0f\n", self.aircraft.ceiling/1000, self.aircraft.ceiling/RAT.unit.FL2m) text=text..string.format("FL cruise = %6.1f km = FL%3.0f\n", self.aircraft.FLcruise/1000, self.aircraft.FLcruise/RAT.unit.FL2m) text=text..string.format("******************************************************\n") @@ -1270,7 +1249,7 @@ end -- @param #string _departure (Optional) Name of departure airbase. -- @param #string _destination (Optional) Name of destination airbase. -- @param #number _takeoff Takeoff type id. -function RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing) +function RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing, _livery, _waypoint) -- Set takeoff type. local takeoff=self.takeoff @@ -1291,18 +1270,40 @@ function RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing) end -- Set flight plan. - local departure, destination, waypoints = self:_SetRoute(takeoff, landing, _departure, _destination) + local departure, destination, waypoints = 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 + -- Take livery from previous flight (continue journey). + livery=_livery + elseif self.livery then + -- Choose random livery. + livery=self.livery[math.random(#self.livery)] + local text=string.format("Chosen livery for group %s: %s", self:_AnticipatedGroupName(), livery) + env.info(RAT.id..text) + else + livery=nil + end + + -- Use last waypoint of previous flight as initial wp for this one. + if _waypoint and takeoff==RAT.wp.air and (self.continuejourney or self.commute) then + -- If the other way does not work, we can still try this. + --waypoints[1]=_waypoint + end + -- Modify the spawn template to follow the flight plan. - self:_ModifySpawnTemplate(waypoints) + self:_ModifySpawnTemplate(waypoints, livery) -- Actually spawn the group. local group=self:SpawnWithIndex(self.SpawnIndex) -- Wrapper.Group#GROUP + + -- Increase group counter. self.alive=self.alive+1 -- ATC is monitoring this flight (if it supposed to land). @@ -1347,6 +1348,9 @@ function RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing) self.ratcraft[self.SpawnIndex].takeoff=takeoff self.ratcraft[self.SpawnIndex].landing=landing + -- 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 @@ -1399,36 +1403,49 @@ function RAT:_Respawn(group) local destination=self.ratcraft[index].destination local takeoff=self.ratcraft[index].takeoff local landing=self.ratcraft[index].landing + local livery=self.ratcraft[index].livery + local lastwp=self.ratcraft[index].waypoints[#self.ratcraft[index].waypoints] local _departure=nil local _destination=nil local _takeoff=nil local _landing=nil + local _livery=nil + local _lastwp=nil 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 + if self.destinationzone then -- Case: X --> Zone --> Zone --> Zone _takeoff=RAT.wp.air - - -- We should also take case that the destination is set correctly + _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 + -- We flew to a zone and back. Takeoff type does not change. _takeoff=self.takeoff - _landing=self.takeoff + + -- If we took of in air we also want to land "in air". + if self.takeoff==RAT.wp.air then + _landing=RAT.wp.air + else + _landing=RAT.wp.landing + end - -- Departure stays the same. + -- 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 @@ -1440,6 +1457,9 @@ function RAT:_Respawn(group) _departure=destination:GetName() _destination=departure:GetName() + -- Use the same livery for next aircraft. + _livery=livery + -- Handle takeoff type. if self.destinationzone then -- self.takeoff is either RAT.wp.air or RAT.wp.cold @@ -1448,8 +1468,8 @@ function RAT:_Respawn(group) if self.takeoff==RAT.wp.air then -- Case: Zone <--> Zone (both have takeoff air) - _takeoff=RAT.wp.air -- = self.takeoff - _landing=RAT.wp.air -- = self.landing + _takeoff=RAT.wp.air -- = self.takeoff (because we just checked) + _landing=RAT.wp.air -- = self.landing (because destinationzone) else @@ -1457,9 +1477,9 @@ function RAT:_Respawn(group) if takeoff==RAT.wp.air then -- Last takeoff was air so we are at the airport now, takeoff is from ground. _takeoff=self.takeoff -- must be either hot/cold/runway/hotcold - _landing=self.landing -- must be air + _landing=RAT.wp.air -- must be air = self.landing (because destinationzone) else - -- Last takeoff was on ground so we are at a zone now ==> takeoff in air, landing at zone. + -- Last takeoff was on ground so we are at a zone now ==> takeoff in air, landing at airport. _takeoff=RAT.wp.air _landing=RAT.wp.landing end @@ -1480,14 +1500,19 @@ function RAT:_Respawn(group) end - env.info(RAT.id..string.format("self.takeoff, takeoff, _takeoff = %d, %d, %d", self.takeoff, takeoff, _takeoff)) - env.info(RAT.id..string.format("self.landing, landing, _landing = %d, %d, %d", self.landing, landing, _landing)) + -- 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 + + env.info(RAT.id..string.format("self.takeoff, takeoff, _takeoff = %s, %s, %s", tostring(self.takeoff), tostring(takeoff), tostring(_takeoff))) + env.info(RAT.id..string.format("self.landing, landing, _landing = %s, %s, %s", tostring(self.landing), tostring(landing), tostring(_landing))) -- Spawn new group. if self.respawn_delay then - SCHEDULER:New(nil, self._SpawnWithRoute, {self, _departure, _destination, _takeoff, _landing}, self.respawn_delay) + SCHEDULER:New(nil, self._SpawnWithRoute, {self, _departure, _destination, _takeoff, _landing, _livery, _lastwp}, self.respawn_delay) else - self:_SpawnWithRoute(_departure, _destination, _takeoff, _landing) + self:_SpawnWithRoute(_departure, _destination, _takeoff, _landing, _livery, _lastwp) end end @@ -1504,7 +1529,7 @@ end -- @return Wrapper.Airport#AIRBASE Destination airbase. -- @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) +function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) -- Max cruise speed. local VxCruiseMax @@ -1543,9 +1568,6 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination) -- Descent angle in rad. local AlphaDescent=math.rad(self.AlphaDescent) - local returnzone=self.returnzone - local destinationzone=self.destinationzone - -- DEPARTURE AIRPORT -- Departure airport or zone. @@ -1582,9 +1604,14 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination) -- Coordinates of departure point. local Pdeparture if takeoff==RAT.wp.air then - -- For an air start, we take a random point within the spawn zone. - local vec2=departure:GetRandomVec2() - Pdeparture=COORDINATE:NewFromVec2(vec2) + if _waypoint then + -- Use coordinates of previous flight (commute or journey). + Pdeparture=COORDINATE:New(_waypoint.x, _waypoint.alt, _waypoint.y) + else + -- For an air start, we take a random point within the spawn zone. + local vec2=departure:GetRandomVec2() + Pdeparture=COORDINATE:NewFromVec2(vec2) + end else Pdeparture=departure:GetCoordinate() end @@ -1600,14 +1627,39 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination) Hmin=50 end H_departure=self:_Randomize(self.aircraft.FLcruise*0.7, 0.3, Pdeparture.y+Hmin, self.aircraft.FLcruise) + -- Use alt of last flight. + if _waypoint then + H_departure=_waypoint.alt + end 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 - self.mindist=self:_MinDistance(AlphaClimb, AlphaDescent, self.FLminuser-H_departure) - local text=string.format("Adjusting min distance to %d km (for given min FL%03d)", self.mindist/1000, self.FLminuser/RAT.unit.FL2m) + + -- 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 + mindist=Ddescent -- Takeoff in air. Need only space to descent. + elseif landing==RAT.wp.air then + mindist=Dclimb -- Landing "in air". Need only space to climb. + 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) env.info(RAT.id..text) end @@ -1640,18 +1692,15 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination) end -- In case of a returnzone the destination (i.e. return point) is always a zone. - local mylanding + local mylanding=landing + local acrange=self.aircraft.Reff if self.returnzone then mylanding=RAT.wp.air - else - mylanding=landing + acrange=self.aircraft.Reff/2 -- Aircraft needs to go to zone and back home. end - - -- Get all destination airports within reach. - local destinations=self:_GetDestinations(departure, Pdeparture, self.mindist, math.min(self.aircraft.Reff, self.maxdist), random, mylanding) -- Pick a destination airport. - destination=self:_PickDestination(destinations) + destination=self:_PickDestination(departure, Pdeparture, mindist, math.min(acrange, self.maxdist), random, mylanding) end -- Return nil if no departure could be found. @@ -1669,27 +1718,6 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination) env.error(RAT.id..text) end - --[[ - -- Coordinates of destination airport. - local Pdestination - local Preturn - if self.destinationzone then - -- Destination is a zone and we pick a random point within the zone. - local vec2=destination:GetRandomVec2() - Pdestination=COORDINATE:NewFromVec2(vec2) - elseif self.returnzone then - -- We fly to a random point within a zone and back to the departure airport. - Pdestination=departure:GetCoordinate() - -- Get a random point inside zone return zone. - local vec2=destination:GetRandomVec2() - Preturn=COORDINATE:NewFromVec2(vec2) - -- Set departure to destination. - destination=departure - else - Pdestination=destination:GetCoordinate() - end - ]] - -- Get a random point inside zone return zone. local Preturn local destination_returnzone @@ -1764,12 +1792,11 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination) d_total=Pdeparture:Get2DDistance(Pholding) end - -- Max height if we only would descent to holding point for the given distance. - -- TODO: Add case for destination zone. We could allow a higher max because no descent is necessary. + -- 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 if landing==RAT.wp.air then - H_departure_max = H_departure + H_departure_max = H_departure -- If we fly to a zone, there is no descent necessary. else H_departure_max = d_total * math.tan(AlphaDescent) + H_holding + h_holding end @@ -1779,7 +1806,7 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination) -------------------------------------------- -- Height difference between departure and destination. - local deltaH=math.abs(H_departure-h_holding-H_holding) + local deltaH=math.abs(H_departure-(h_holding+H_holding)) -- Slope between departure and destination. local phi = math.atan(deltaH/d_total) @@ -1839,7 +1866,7 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination) -- If the route is very short we set FLmin a bit lower than FLmax. if FLmin>FLmax then - FLmin=FLmax*0.75 + FLmin=FLmax*0.90 end -- For helicopters we take cruise alt between 50 to 1000 meters above ground. Default cruise alt is ~150 m. @@ -1853,26 +1880,30 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination) -- Overrule setting if user specified min/max flight level explicitly. if self.FLminuser then - FLmin=self.FLminuser + FLmin=math.max(self.FLminuser, FLmin) -- Still take care that we dont fly too high. end if self.FLmaxuser then - FLmax=self.FLmaxuser + FLmax=math.min(self.FLmaxuser, FLmax) -- Still take care that we dont fly too low. end - -- Adjust FLcruise to be at leat FLmin and at most FLmax - if self.aircraft.FLcruiseFLmax then - self.aircraft.FLcruise=FLmax + if FLcruise_expect>FLmax then + FLcruise_expect=FLmax end -- Set cruise altitude. Selected from Gaussian distribution but limited to FLmin and FLmax. - local FLcruise=self:_Random_Gaussian(self.aircraft.FLcruise, (FLmax-FLmin)/4, FLmin, FLmax) + local FLcruise=self:_Random_Gaussian(FLcruise_expect, (FLmax-FLmin)/4, FLmin, FLmax) -- Overrule setting if user specified a flight level explicitly. if self.FLuser then FLcruise=self.FLuser + -- Still cruise alt should be with parameters! + FLcruise=math.max(FLcruise, FLmin) + FLcruise=math.min(FLcruise, FLmax) end -- Climb and descent heights. @@ -1884,7 +1915,6 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination) local d_descent = h_descent/math.tan(AlphaDescent) local d_cruise = d_total-d_climb-d_descent local d_total_cruise = d_climb + d_cruise + d_descent - -------------------------------------------- -- debug message local text=string.format("\n******************************************************\n") @@ -2079,7 +2109,7 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination) -- Some info on the route. self:_Routeinfo(waypoints, "Waypoint info in set_route:") - -- return departure, destination and waypoints + -- 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 @@ -2101,58 +2131,57 @@ function RAT:_PickDeparture(takeoff) -- Array of possible departure airports or zones. local departures={} + + if self.random_departure then - if takeoff==RAT.wp.air then - - if self.random_departure then + -- Airports of friendly coalitions. + for _,airport in pairs(self.airports) do - -- Air start above a random airport. - for _,airport in pairs(self.airports) do - if not self:_Excluded(airport:GetName()) then - table.insert(departures, airport:GetZone()) - end - end - - else - - -- Put all specified zones in table. - for _,name in pairs(self.departure_zones) do - if not self:_Excluded(name) then - table.insert(departures, ZONE:New(name)) - end - end - -- Put all specified airport zones in table. - for _,name in pairs(self.departure_ports) do - if not self:_Excluded(name) then - table.insert(departures, AIRBASE:FindByName(name):GetZone()) + 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 + table.insert(departures, airport) -- insert airport object. end end end else - - if self.random_departure then - - -- All friendly departure airports. - for _,airport in pairs(self.airports) do - if not self:_Excluded(airport:GetName()) then - table.insert(departures, airport) + + -- 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 + dep=AIRBASE:FindByName(name):GetZone() + else + dep=AIRBASE:FindByName(name) end + elseif self:_ZoneExists(name) then + if takeoff==RAT.wp.air then + dep=ZONE:New(name) + else + env.error(RAT.id.."Takeoff is not in air. Cannot use "..name.." as departure!") + end + else + env.error(RAT.id.."No airport or zone found with name "..name) end - else - - -- All airports specified by user - for _,name in pairs(self.departure_ports) do - if self:_IsFriendly(name) and not self:_Excluded(name) then - table.insert(departures, AIRBASE:FindByName(name)) - end + -- Add to departures table. + if dep then + table.insert(departures, dep) end - end + end + end - + + -- Info message. + env.info(RAT.id.."Number of possible departures = "..#departures) + -- Select departure airport or zone. local departure=departures[math.random(#departures)] @@ -2168,63 +2197,31 @@ function RAT:_PickDeparture(takeoff) MESSAGE:New(text, 30):ToAll() end else + env.error(RAT.id.."No departure airport or zone found.") departure=nil end return departure end - ---- Pick destination airport. If no airport name is given an airport from the coalition is chosen randomly. --- @param #RAT self --- @param #table destinations Table with destination airports. --- @return Wrapper.Airbase#AIRBASE Destination airport. -function RAT:_PickDestination(destinations) - - -- Randomly select one possible destination. - local destination=nil - if destinations and #destinations>0 then - - -- Random selection. - destination=destinations[math.random(#destinations)] -- Wrapper.Airbase#AIRBASE - - -- Debug message. - local text - if self.destinationzone or self.returnzone then - text="Chosen destination zone: "..destination:GetName() - else - text="Chosen destination airport: "..destination:GetName().." (ID "..destination:GetID()..")" - end - env.info(RAT.id..text) - if self.debug then - MESSAGE:New(text, 30):ToAll() - end - - else - env.error(RAT.id.."No destination airport found.") - end - - return destination -end - - ---- Get all possible destination airports depending on departure position. --- The list is sorted w.r.t. distance to departure position. +--- Pick destination airport or zone depending on departure position. -- @param #RAT self -- @param Wrapper.Airbase#AIRBASE departure Departure airport or zone. -- @param Core.Point#COORDINATE q Coordinate of the departure point. -- @param #number minrange Minimum range to q in meters. -- @param #number maxrange Maximum range to q in meters. --- @param #booleean random Destination is randomly selected from friendly airport (true) or from destinations specified by user input (false). --- @param #number Number indicating whether we land at a destination or return a zone object. --- @return #table Table with possible destination airports or zones. -function RAT:_GetDestinations(departure, q, minrange, maxrange, random, landing) +-- @param #boolean random Destination is randomly selected from friendly airport (true) or from destinations specified by user input (false). +-- @param #number landing Number indicating whether we land at a destination airport or fly to a zone object. +-- @return Wrapper.Airbase#AIRBASE destination Destination airport or zone. +function RAT:_PickDestination(departure, q, minrange, maxrange, random, landing) -- Min/max range to destination. minrange=minrange or self.mindist maxrange=maxrange or self.maxdist - local possible_destinations={} + -- All possible destinations. + local destinations={} + if random then -- Airports of friendly coalitions. @@ -2238,9 +2235,9 @@ function RAT:_GetDestinations(departure, q, minrange, maxrange, random, landing) -- 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 - table.insert(possible_destinations, airport:GetZone()) -- insert zone object. + table.insert(destinations, airport:GetZone()) -- insert zone object. else - table.insert(possible_destinations, airport) -- insert airport object. + table.insert(destinations, airport) -- insert airport object. end end end @@ -2262,7 +2259,11 @@ function RAT:_GetDestinations(departure, q, minrange, maxrange, random, landing) dest=AIRBASE:FindByName(name) end elseif self:_ZoneExists(name) then - dest=ZONE:New(name) + if landing==RAT.wp.air then + dest=ZONE:New(name) + else + env.error(RAT.id.."Landing is not in air. Cannot use zone "..name.." as destination!") + end else env.error(RAT.id.."No airport or zone found with name "..name) end @@ -2272,7 +2273,7 @@ function RAT:_GetDestinations(departure, q, minrange, maxrange, random, landing) -- Add as possible destination if zone is within range. if distance>=minrange and distance<=maxrange then - table.insert(possible_destinations, dest) + table.insert(destinations, dest) end end @@ -2282,9 +2283,9 @@ function RAT:_GetDestinations(departure, q, minrange, maxrange, random, landing) end -- Info message. - env.info(RAT.id.."Number of possible destination airports = "..#possible_destinations) + env.info(RAT.id.."Number of possible destinations = "..#destinations) - if #possible_destinations > 0 then + if #destinations > 0 then --- Compare distance of destination airports. -- @param Core.Point#COORDINATE a Coordinate of point a. -- @param Core.Point#COORDINATE b Coordinate of point b. @@ -2294,14 +2295,38 @@ function RAT:_GetDestinations(departure, q, minrange, maxrange, random, landing) local qb=q:Get2DDistance(b:GetCoordinate()) return qa < qb end - table.sort(possible_destinations, compare) + table.sort(destinations, compare) else - env.error(RAT.id.."No possible destinations found!") - possible_destinations=nil + destinations=nil end - -- Return table with destination airports. - return possible_destinations + + -- 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 + text=string.format("Chosen destination zone: %s.", destination:GetName()) + else + text=string.format("Chosen destination airport: %s (ID %d).", destination:GetName(), destination:GetID()) + end + env.info(RAT.id..text) + if self.debug then + MESSAGE:New(text, 30):ToAll() + end + + else + env.error(RAT.id.."No destination airport or zone found.") + destination=nil + end + + -- Return the chosen destination. + return destination end @@ -2530,7 +2555,7 @@ function RAT:Status(message, forID) end text=text..string.format("Fuel = %3.0f %%\n", fuel) text=text..string.format("Life = %3.0f %%\n", life) - text=text..string.format("FL%03d = %i m\n", alt/RAT.unit.FL2m, alt) + text=text..string.format("FL%03d = %i m ASL\n", alt/RAT.unit.FL2m, alt) --text=text..string.format("Speed = %i km/h\n", vel) text=text..string.format("Distance travelled = %6.1f km\n", self.ratcraft[i]["Distance"]/1000) text=text..string.format("Distance to destination = %6.1f km", Ddestination/1000) @@ -3206,9 +3231,9 @@ function RAT:_TaskHolding(P1, Altitude, Speed, Duration) DCSTask.params.task=Task if self.ATCswitch then - -- Set stop condition for holding. Either flag=1 or after max. 30 min holding. + -- Set stop condition for holding. Either flag=1 or after max. 50 min holding. local userflagname=string.format("%s#%03d", self.alias, self.SpawnIndex+1) - DCSTask.params.stopCondition={userFlag=userflagname, userFlagValue=1, duration=1800} + DCSTask.params.stopCondition={userFlag=userflagname, userFlagValue=1, duration=3000} else DCSTask.params.stopCondition={duration=Duration} end @@ -3348,16 +3373,19 @@ function RAT:_FLmax(alpha, beta, d, phi, h0) return h3+h0 end ---- Calculate min distance between departure and destination for given minimum flight level and climb/decent rates +--- Calculate minimum distance between departure and destination for given minimum flight level and climb/decent rates. -- @param #RAT self -- @param #number alpha Angle of climb [rad]. -- @param #number beta Angle of descent [rad]. --- @param #number h min height AGL. --- @return #number Minimum distance between departure and destiantion. -function RAT:_MinDistance(alpha, beta, h) - local d1=h/math.tan(alpha) - local d2=h/math.tan(beta) - return d1+d2 +-- @param #number ha Height difference between departure and cruise altiude. +-- @param #number hb Height difference between cruise altitude and destination. +-- @return #number d1 Minimum distance for climb phase to reach cruise altitude. +-- @return #number d2 Minimum distance for descent phase to reach destination height. +-- @return #number dtot Minimum total distance to climb and descent. +function RAT:_MinDistance(alpha, beta, ha, hb) + local d1=ha/math.tan(alpha) + local d2=hb/math.tan(beta) + return d1, d2, d1+d2 end @@ -3576,25 +3604,14 @@ end -- This allows to spawn at airports and also land at other airports, i.e. circumventing the DCS "landing bug". -- @param #RAT self -- @param #table waypoints The waypoints of the AI flight plan. -function RAT:_ModifySpawnTemplate(waypoints) +-- @param #string livery (Optional) Livery of the aircraft. All members of a flight will get the same livery. +function RAT:_ModifySpawnTemplate(waypoints, livery) -- The 3D vector of the first waypoint, i.e. where we actually spawn the template group. local PointVec3 = {x=waypoints[1].x, y=waypoints[1].alt, z=waypoints[1].y} -- Heading from first to seconds waypoints to align units in case of air start. local heading = self:_Course(waypoints[1], waypoints[2]) - - -- Set (another) livery. - local skin=nil - if self.livery then - if self.debug then - for _, skin in pairs(self.livery) do - env.info(RAT.id.."Possible livery: "..skin.." for group "..self:_AnticipatedGroupName()) - end - end - skin=self.livery[math.random(#self.livery)] - env.info(RAT.id.."Chosen livery: "..skin.." for group "..self:_AnticipatedGroupName()) - end if self:_GetSpawnIndex(self.SpawnIndex+1) then @@ -3621,8 +3638,8 @@ function RAT:_ModifySpawnTemplate(waypoints) SpawnTemplate.units[UnitID].heading = math.rad(heading) -- Set livery (will be the same for all units of the group). - if skin then - SpawnTemplate.units[UnitID].livery_id = skin + if livery then + SpawnTemplate.units[UnitID].livery_id = livery end -- Set type of aircraft. @@ -3975,4 +3992,3 @@ function RAT:_ATCQueue() end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - From 06283ad9e32e75839726f44668d5248c4219ee7d Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Tue, 24 Oct 2017 23:06:01 +0200 Subject: [PATCH 18/25] moose --- Moose Mission Setup/Moose.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Moose Mission Setup/Moose.lua b/Moose Mission Setup/Moose.lua index a020b3125..15e738d18 100644 --- a/Moose Mission Setup/Moose.lua +++ b/Moose Mission Setup/Moose.lua @@ -1,5 +1,9 @@ env.info( '*** MOOSE DYNAMIC INCLUDE START *** ' ) +<<<<<<< HEAD env.info( 'Moose Generation Timestamp: 20171021_1218' ) +======= +env.info( 'Moose Generation Timestamp: 20171023_1007' ) +>>>>>>> master local base = _G @@ -36,6 +40,7 @@ __Moose.Include( 'Core/Zone.lua' ) __Moose.Include( 'Core/Database.lua' ) __Moose.Include( 'Core/Set.lua' ) __Moose.Include( 'Core/Point.lua' ) +__Moose.Include( 'Core/Velocity.lua' ) __Moose.Include( 'Core/Message.lua' ) __Moose.Include( 'Core/Fsm.lua' ) __Moose.Include( 'Core/Radio.lua' ) From 6923cfe143c9aa865f4255dac3fd8f9aa35acb93 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Tue, 24 Oct 2017 23:16:04 +0200 Subject: [PATCH 19/25] moose.lua --- Moose Mission Setup/Moose.lua | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Moose Mission Setup/Moose.lua b/Moose Mission Setup/Moose.lua index 15e738d18..4bd75d8e6 100644 --- a/Moose Mission Setup/Moose.lua +++ b/Moose Mission Setup/Moose.lua @@ -1,9 +1,5 @@ env.info( '*** MOOSE DYNAMIC INCLUDE START *** ' ) -<<<<<<< HEAD -env.info( 'Moose Generation Timestamp: 20171021_1218' ) -======= -env.info( 'Moose Generation Timestamp: 20171023_1007' ) ->>>>>>> master +env.info( 'Moose Generation Timestamp: 20171024_2314' ) local base = _G From 7da932e04814fe76805459ab14727f49dd70a828 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Thu, 26 Oct 2017 00:05:40 +0200 Subject: [PATCH 20/25] RAT final touches --- Moose Development/Moose/Functional/RAT.lua | 316 +++++++++++++-------- 1 file changed, 204 insertions(+), 112 deletions(-) diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index eeb5e0c71..e5193dc02 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -101,6 +101,7 @@ -- @field #boolean reportstatus Aircraft report status. -- @field #number statusinterval Intervall between status checks (and reports if enabled). -- @field #boolean placemarkers Place markers of waypoints on F10 map. +-- @field #number FLcruise Cruise altitude of aircraft. Default FL200 for planes and F005 for helos. -- @field #number FLuser Flight level set by users explicitly. -- @field #number FLminuser Minimum flight level set by user. -- @field #number FLmaxuser Maximum flight level set by user. @@ -310,10 +311,11 @@ RAT={ departure_Azone=nil, -- Zone containing the departure airports. destination_Azone=nil, -- Zone containing the destination airports. ratcraft={}, -- Array with the spawned RAT aircraft. - Tinactive=300, -- Time in seconds after which inactive units will be destroyed. Default is 300 seconds. + Tinactive=600, -- Time in seconds after which inactive units will be destroyed. Default is 600 seconds. reportstatus=false, -- Aircraft report status. statusinterval=30, -- Intervall between status checks (and reports if enabled). placemarkers=false, -- Place markers of waypoints on F10 map. + FLcruise=nil, -- Cruise altitude of aircraft. Default FL200 for planes and F005 for helos. FLminuser=nil, -- Minimum flight level set by user. FLmaxuser=nil, -- Maximum flight level set by user. FLuser=nil, -- Flight level set by users explicitly. @@ -385,7 +387,7 @@ RAT.status={ -- Event states. EventBirthAir="Born in air", EventBirth="Ready and starting engines", - EventEngineStartAir="Started engines (in air)", + EventEngineStartAir="On journey", -- Started engines (in air) EventEngineStart="Started engines and taxiing", EventTakeoff="Airborne after take-off", EventLand="Landed and taxiing", @@ -434,8 +436,8 @@ RAT.ATC={ airport={}, unregistered=-1, onfinal=-100, - Nclearance=2, - delay=180, + Nclearance=1, + delay=240, } --- Running number of placed markers on the F10 map. @@ -484,7 +486,7 @@ RAT.id="RAT | " --DONE: Add custom livery choice if possible. --TODO: When only a destination is set, it should be checked that the departure is within range. Also, that departure and destination are not the same. --TODO: Add function to include all airports to selected destinations/departures. ---TODO: Find way to respawn aircraft at same position where the last was despawned for commute and journey. +--DONE: Find way to respawn aircraft at same position where the last was despawned for commute and journey. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -584,8 +586,20 @@ function RAT:Spawn(naircraft) self.destination_ports=self:_GetAirportsInZone(self.destination_Azone) end + -- Setting and possibly correction min/max/cruise flight levels. + if self.FLcruise==nil then + -- Default flight level (ASL). + if self.category==RAT.cat.plane then + -- For planes: FL200 = 20000 ft = 6096 m. + self.FLcruise=200*RAT.unit.FL2m + else + -- For helos: FL005 = 500 ft = 152 m. + self.FLcruise=005*RAT.unit.FL2m + end + end -- Consistancy checks + -- Destination zone and return zone should not be used together. if self.destinationzone and self.returnzone then env.error(RAT.id.."Destination zone _and_ return to zone not possible! Disabling return to zone.") @@ -597,6 +611,41 @@ function RAT:Spawn(naircraft) 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) + end + if self.FLmaxuser then + self.FLmaxuser=math.min(self.FLmaxuser, self.aircraft.ceiling) + end + if self.FLcruise then + self.FLcruise=math.min(self.FLcruise, self.aircraft.ceiling) + end + + -- 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 + 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 + + -- Settings info local text=string.format("\n******************************************************\n") text=text..string.format("Spawning %i aircraft from template %s of type %s.\n", self.ngroups, self.SpawnTemplatePrefix, self.aircraft.type) @@ -624,9 +673,10 @@ function RAT:Spawn(naircraft) text=text..string.format("Vclimb: %4.1f\n", self.Vclimb) text=text..string.format("AlphaDescent: %4.2f\n", self.AlphaDescent) text=text..string.format("Vcruisemax: %s\n", tostring(self.Vcruisemax)) + text=text..string.format("FLcruise = %6.1f km = FL%3.0f\n", self.FLcruise/1000, self.FLcruise/RAT.unit.FL2m) text=text..string.format("FLuser: %s\n", tostring(self.Fluser)) - text=text..string.format("FLminuser: %s\n", tostring(self.Flminuser)) - text=text..string.format("FLmaxuser: %s\n", tostring(self.Flmaxuser)) + text=text..string.format("FLminuser: %s\n", tostring(self.FLminuser)) + text=text..string.format("FLmaxuser: %s\n", tostring(self.FLmaxuser)) text=text..string.format("Place markers: %s\n", tostring(self.placemarkers)) text=text..string.format("Report status: %s\n", tostring(self.reportstatus)) text=text..string.format("Status interval: %4.1f\n", self.statusinterval) @@ -899,41 +949,41 @@ end --- Aircraft will continue their journey from their destination. This means they are respawned at their destination and get a new random destination. -- @param #RAT self --- @param #boolean switch Turn journey on=true or off=false. If no value is given switch=true. -function RAT:ContinueJourney(switch) - switch=switch or true - self.continuejourney=switch +function RAT:ContinueJourney() + self.continuejourney=true + self.commute=false end ---- Aircraft will commute between their departure and destination airports. --- Note, this option is not available if aircraft are spawned in air since they don't have a valid departure airport to fly back to. +--- Aircraft will commute between their departure and destination airports or zones. -- @param #RAT self --- @param #boolean switch Turn commute on=true or off=false. If no value is given switch=true. -function RAT:Commute(switch) - switch=switch or true - self.commute=switch +function RAT:Commute() + self.commute=true + self.continuejourney=false end ---- Set the delay before first group is spawned. Minimum delay is 0.5 seconds. +--- Set the delay before first group is spawned. -- @param #RAT self --- @param #number delay Delay in seconds. +-- @param #number delay Delay in seconds. Default is 5 seconds. Minimum delay is 0.5 seconds. function RAT:SetSpawnDelay(delay) + delay=delay or 5 self.spawndelay=math.max(0.5, delay) end ---- Set the interval between spawnings of the template group. Minimum interval is 0.5 seconds. +--- Set the interval between spawnings of the template group. -- @param #RAT self --- @param #number interval Interval in seconds. +-- @param #number interval Interval in seconds. Default is 5 seconds. Minimum is 0.5 seconds. function RAT:SetSpawnInterval(interval) + interval=interval or 5 self.spawninterval=math.max(0.5, interval) end --- Make aircraft respawn the moment they land rather than at engine shut down. -- @param #RAT self --- @param #number delay (Optional) Delay in seconds until respawn happens after landing. Default is 180 seconds. +-- @param #number delay (Optional) Delay in seconds until respawn happens after landing. Default is 180 seconds. Minimum is 0.5 seconds. function RAT:RespawnAfterLanding(delay) delay = delay or 180 self.respawn_at_landing=true + delay=math.max(0.5, delay) self.respawn_delay=delay end @@ -989,10 +1039,12 @@ function RAT:RadioFrequency(modulation) end end ---- Set the time after which inactive groups will be destroyed. Default is 300 seconds. +--- Set the time after which inactive groups will be destroyed. -- @param #RAT self --- @param #number time Time in seconds. +-- @param #number time Time in seconds. Default is 600 seconds = 10 minutes. Minimum is 60 seconds. function RAT:TimeDestroyInactive(time) + time=time or self.Tinactive + time=math.max(time, 60) self.Tinactive=time end @@ -1004,17 +1056,23 @@ function RAT:SetMaxCruiseSpeed(speed) self.Vcruisemax=speed/3.6 end ---- Set the climb rate. Default is 1500 ft/min. This automatically sets the climb angle. +--- Set the climb rate. This automatically sets the climb angle. -- @param #RAT self --- @param #number rate Climb rate in ft/min. +-- @param #number rate Climb rate in ft/min. Default is 1500 ft/min. Minimum is 100 ft/min. Maximum is 15,000 ft/min. function RAT:SetClimbRate(rate) - self.Vclimb=rate or 1500 + rate=rate or self.Vclimb + rate=math.max(rate, 100) + rate=math.min(rate, 15000) + self.Vclimb=rate end --- Set the angle of descent. Default is 3.6 degrees, which corresponds to 3000 ft descent after one mile of travel. -- @param #RAT self --- @param #number angle Angle of descent in degrees. +-- @param #number angle Angle of descent in degrees. Minimum is 0.5 deg. Maximum 50 deg. function RAT:SetDescentAngle(angle) + angle=angle or self.AlphaDescent + angle=math.max(angle, 0.5) + angle=math.min(angle, 50) self.AlphaDescent=angle end @@ -1053,9 +1111,12 @@ end --- Enable ATC, which manages the landing queue for RAT aircraft if they arrive simultaniously at the same airport. -- @param #RAT self --- @param #boolean switch true=enable ATC, false=disable ATC. +-- @param #boolean switch Enable ATC (true) or Disable ATC (false). No argument means ATC enabled. function RAT:EnableATC(switch) - self.ATCswitch=switch or true + if switch==nil then + switch=true + end + self.ATCswitch=switch end --- Max number of planes that get landing clearance of the RAT ATC. This setting effects all RAT objects and groups! @@ -1091,39 +1152,48 @@ end --- Turn debug messages on or off. Default is off. -- @param #RAT self --- @param #boolean switch true turn messages on, false=off. +-- @param #boolean switch Turn debug on=true or off=false. No argument means on. function RAT:_Debug(switch) - switch = switch or true + if switch==nil then + switch=true + end self.debug=switch end ---- Aircraft report status messages. Default is off. +--- Aircraft report status update messages along the route. -- @param #RAT self --- @param #boolean switch true=on, false=off. +-- @param #boolean switch Swtich reports on (true) or off (false). No argument is on. function RAT:StatusReports(switch) - self.reportstatus=switch or true + if switch==nil then + switch=true + end + self.reportstatus=switch end --- Place markers of waypoints on the F10 map. Default is off. -- @param #RAT self -- @param #boolean switch true=yes, false=no. function RAT:PlaceMarkers(switch) - switch = switch or true + if switch==nil then + switch=true + end self.placemarkers=switch end --- Set flight level. Setting this value will overrule all other logic. Aircraft will try to fly at this height regardless. -- @param #RAT self --- @param #number height FL in hundrets of feet. E.g. FL200 = 20000 ft ASL. -function RAT:SetFL(height) - self.FLuser=height*RAT.unit.FL2m +-- @param #number FL Fight Level in hundrets of feet. E.g. FL200 = 20000 ft ASL. +function RAT:SetFL(FL) + FL=FL or self.FLcruise + FL=math.max(FL,0) + self.FLuser=FL*RAT.unit.FL2m end --- Set max flight level. Setting this value will overrule all other logic. Aircraft will try to fly at less than this FL regardless. -- @param #RAT self --- @param #number height Maximum FL in hundrets of feet. -function RAT:SetFLmax(height) - self.FLmaxuser=height*RAT.unit.FL2m +-- @param #number FL Maximum Fight Level in hundrets of feet. +function RAT:SetFLmax(FL) + self.FLmaxuser=FL*RAT.unit.FL2m end --- Set max cruising altitude above sea level. @@ -1135,9 +1205,9 @@ end --- Set min flight level. Setting this value will overrule all other logic. Aircraft will try to fly at higher than this FL regardless. -- @param #RAT self --- @param #number height Maximum FL in hundrets of feet. -function RAT:SetFLmin(height) - self.FLminuser=height*RAT.unit.FL2m +-- @param #number FL Maximum Fight Level in hundrets of feet. +function RAT:SetFLmin(FL) + self.FLminuser=FL*RAT.unit.FL2m end --- Set min cruising altitude above sea level. @@ -1150,16 +1220,16 @@ end --- Set flight level of cruising part. This is still be checked for consitancy with selected route and prone to radomization. -- Default is FL200 for planes and FL005 for helicopters. -- @param #RAT self --- @param #number height FL in hundrets of feet. E.g. FL200 = 20000 ft ASL. -function RAT:SetFLcruise(height) - self.aircraft.FLcruise=height*RAT.unit.FL2m +-- @param #number FL Flight level in hundrets of feet. E.g. FL200 = 20000 ft ASL. +function RAT:SetFLcruise(FL) + self.FLcruise=FL*RAT.unit.FL2m end --- Set cruising altitude. This is still be checked for consitancy with selected route and prone to radomization. -- @param #RAT self -- @param #number alt Cruising altitude ASL in meters. function RAT:SetCruiseAltitude(alt) - self.aircraft.FLcruise=alt + self.FLcruise=alt end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1210,15 +1280,6 @@ function RAT:_InitAircraft(DCSgroup) -- service ceiling in meters self.aircraft.ceiling=DCSdesc.Hmax - -- Default flight level (ASL). - if self.category==RAT.cat.plane then - -- For planes: FL200 = 20000 ft = 6096 m. - self.aircraft.FLcruise=200*RAT.unit.FL2m - else - -- For helos: FL005 = 500 ft = 152 m. - self.aircraft.FLcruise=005*RAT.unit.FL2m - end - -- info message local text=string.format("\n******************************************************\n") text=text..string.format("Aircraft parameters:\n") @@ -1230,9 +1291,8 @@ function RAT:_InitAircraft(DCSgroup) text=text..string.format("Max climb speed = %6.1f m/s\n", self.aircraft.Vymax) text=text..string.format("Initial Fuel = %6.1f\n", self.aircraft.fuel*100) text=text..string.format("Max range = %6.1f km\n", self.aircraft.Rmax/1000) - text=text..string.format("Eff range = %6.1f km (including initial fuel amount)\n", self.aircraft.Reff/1000) + text=text..string.format("Eff range = %6.1f km (with 95 percent initial fuel amount)\n", self.aircraft.Reff/1000) text=text..string.format("Ceiling = %6.1f km = FL%3.0f\n", self.aircraft.ceiling/1000, self.aircraft.ceiling/RAT.unit.FL2m) - text=text..string.format("FL cruise = %6.1f km = FL%3.0f\n", self.aircraft.FLcruise/1000, self.aircraft.FLcruise/RAT.unit.FL2m) text=text..string.format("******************************************************\n") env.info(RAT.id..text) @@ -1291,11 +1351,13 @@ function RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing, _live livery=nil end +--[[ -- Use last waypoint of previous flight as initial wp for this one. if _waypoint and takeoff==RAT.wp.air and (self.continuejourney or self.commute) then -- If the other way does not work, we can still try this. - --waypoints[1]=_waypoint + waypoints[1]=_waypoint end +]] -- Modify the spawn template to follow the flight plan. self:_ModifySpawnTemplate(waypoints, livery) @@ -1315,6 +1377,11 @@ function RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing, _live end end + -- Place markers of waypoints on F10 map. + if self.placemarkers then + self:_PlaceMarkers(waypoints) + end + -- Set ROE, default is "weapon hold". self:_SetROE(group, self.roe) @@ -1505,8 +1572,10 @@ function RAT:_Respawn(group) _lastwp=lastwp end - env.info(RAT.id..string.format("self.takeoff, takeoff, _takeoff = %s, %s, %s", tostring(self.takeoff), tostring(takeoff), tostring(_takeoff))) - env.info(RAT.id..string.format("self.landing, landing, _landing = %s, %s, %s", tostring(self.landing), tostring(landing), tostring(_landing))) + if self.debug then + env.info(RAT.id..string.format("self.takeoff, takeoff, _takeoff = %s, %s, %s", tostring(self.takeoff), tostring(takeoff), tostring(_takeoff))) + env.info(RAT.id..string.format("self.landing, landing, _landing = %s, %s, %s", tostring(self.landing), tostring(landing), tostring(_landing))) + end -- Spawn new group. if self.respawn_delay then @@ -1568,7 +1637,10 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) -- 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 or zone. local departure=nil @@ -1619,14 +1691,18 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) -- Height ASL of departure point. local H_departure if takeoff==RAT.wp.air then - -- Departure altitude is 70% of default cruise with 30% variation and limited to 1000 m AGL (50 m for helos). + -- Absolute minimum AGL local Hmin if self.category==RAT.cat.plane then Hmin=1000 else Hmin=50 end - H_departure=self:_Randomize(self.aircraft.FLcruise*0.7, 0.3, Pdeparture.y+Hmin, self.aircraft.FLcruise) + -- 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) + end -- Use alt of last flight. if _waypoint then H_departure=_waypoint.alt @@ -1647,7 +1723,7 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) 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. + mindist=0 -- Takeoff and landing are in air. No mindist required. elseif takeoff==RAT.wp.air then mindist=Ddescent -- Takeoff in air. Need only space to descent. elseif landing==RAT.wp.air then @@ -1725,11 +1801,10 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) -- Get a random point inside zone return zone. local vec2=destination:GetRandomVec2() Preturn=COORDINATE:NewFromVec2(vec2) + -- Returnzone becomes destination. destination_returnzone=destination - env.info(RAT.id.."Destination r zone = "..destination_returnzone:GetName()) -- Set departure to destination. destination=departure - env.info(RAT.id.."Destination r zone = "..destination_returnzone:GetName()) end -- Get destination coordinate. Either in a zone or exactly at the airport. @@ -1769,6 +1844,15 @@ 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) @@ -1798,7 +1882,7 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) if landing==RAT.wp.air then H_departure_max = H_departure -- If we fly to a zone, there is no descent necessary. else - H_departure_max = d_total * math.tan(AlphaDescent) + H_holding + h_holding + H_departure_max = d_total * math.tan(AlphaDescent) + Hh_holding end H_departure=math.min(H_departure, H_departure_max) end @@ -1806,7 +1890,7 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) -------------------------------------------- -- Height difference between departure and destination. - local deltaH=math.abs(H_departure-(h_holding+H_holding)) + local deltaH=math.abs(H_departure-Hh_holding) -- Slope between departure and destination. local phi = math.atan(deltaH/d_total) @@ -1814,7 +1898,7 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) -- Adjusted climb/descent angles. local phi_climb local phi_descent - if (H_departure > H_holding+h_holding) then + if (H_departure > Hh_holding) then phi_climb=AlphaClimb+phi phi_descent=AlphaDescent-phi else @@ -1843,7 +1927,7 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) -- Max height relative to departure or destination. local h_max - if (H_departure > H_holding+h_holding) then + if (H_departure > Hh_holding) then h_max=math.min(h_max1, h_max2) else h_max=math.max(h_max1, h_max2) @@ -1851,32 +1935,19 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) -- Max flight level aircraft can reach for given angles and distance. local FLmax = h_max+H_departure - - -- Max heights and distances if we would travel at FLmax. - local h_climb_max = FLmax-H_departure - local h_descent_max = FLmax - (H_holding+h_holding) - local d_climb_max = h_climb_max/math.tan(AlphaClimb) - local d_descent_max = h_descent_max/math.tan(AlphaDescent) - local d_cruise_max = d_total-d_climb_max-d_descent_max - local d_total_max = d_climb_max + d_cruise_max + d_descent_max - + --CRUISE -- Min cruise alt is just above holding point at destination or departure height, whatever is larger. - local FLmin=math.max(H_departure, H_holding+h_holding) - - -- If the route is very short we set FLmin a bit lower than FLmax. - if FLmin>FLmax then - FLmin=FLmax*0.90 - end - + 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 FLmin=math.max(H_departure, H_destination)+50 FLmax=math.max(H_departure, H_destination)+1000 end - -- Ensure that FLmax not above 95% its service ceiling. - FLmax=math.min(FLmax, self.aircraft.ceiling*0.95) + -- 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 @@ -1886,8 +1957,12 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) 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. - local FLcruise_expect=self.aircraft.FLcruise if FLcruise_expect Date: Thu, 26 Oct 2017 17:27:12 +0200 Subject: [PATCH 21/25] RAT small fixes Radio Modulation function named wrong. Markers bug fixed. --- Moose Development/Moose/Functional/RAT.lua | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index e5193dc02..e9f708322 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -1029,7 +1029,7 @@ end --- Set radio modulation. Default is AM. -- @param #RAT self -- @param #string modulation Either "FM" or "AM". If no value is given, modulation is set to AM. -function RAT:RadioFrequency(modulation) +function RAT:RadioModulation(modulation) if modulation=="AM" then self.modulation=radio.modulation.AM elseif modulation=="FM" then @@ -1379,7 +1379,7 @@ function RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing, _live -- Place markers of waypoints on F10 map. if self.placemarkers then - self:_PlaceMarkers(waypoints) + self:_PlaceMarkers(waypoints, self.SpawnIndex) end -- Set ROE, default is "weapon hold". @@ -1439,7 +1439,7 @@ function RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing, _live 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) + 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 @@ -3649,9 +3649,10 @@ end --- Place markers of the waypoints. Note we assume a very specific number and type of waypoints here. -- @param #RAT self -- @param #table waypoints Table with waypoints. -function RAT:_PlaceMarkers(waypoints) +-- @param #number index Spawn index of group. +function RAT:_PlaceMarkers(waypoints, index) for i=1,#waypoints do - self:_SetMarker(self.waypointdescriptions[i], waypoints[i]) + self:_SetMarker(self.waypointdescriptions[i], waypoints[i], index) end end @@ -3660,7 +3661,8 @@ end -- @param #RAT self -- @param #string text Info text displayed at maker. -- @param #table wp Position of marker coming in as waypoint, i.e. has x, y and alt components. -function RAT:_SetMarker(text, wp) +-- @param #number index Spawn index of group. +function RAT:_SetMarker(text, wp, index) RAT.markerid=RAT.markerid+1 self.markerids[#self.markerids+1]=RAT.markerid if self.debug then @@ -3668,7 +3670,7 @@ function RAT:_SetMarker(text, wp) end -- Convert to coordinate. local vec={x=wp.x, y=wp.alt, z=wp.y} - local flight=self:GetGroupFromIndex(self.SpawnIndex):GetName() + local flight=self:GetGroupFromIndex(index):GetName() -- Place maker visible for all on the F10 map. local text1=string.format("%s:\n%s", flight, text) trigger.action.markToAll(RAT.markerid, text1, vec) From c66117464a11f86fdab273ed29716ec6689ca4b9 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Fri, 27 Oct 2017 22:00:47 +0200 Subject: [PATCH 22/25] RAT enhancements Added possibility to add all friendly airports as departure/destination when SetDeparture() is used. Improved consistency check. Added SetAISkill() function. --- Moose Development/Moose/Functional/RAT.lua | 260 ++++++++++++++++----- Moose Mission Setup/Moose.lua | 2 +- 2 files changed, 204 insertions(+), 58 deletions(-) diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index e9f708322..ec8b646f4 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -88,14 +88,20 @@ -- @field #table airports All airports of friedly coalitions. -- @field #boolean random_departure By default a random friendly airport is chosen as departure. -- @field #boolean random_destination By default a random friendly airport is chosen as destination. --- @field #table departure_ports Array containing the names of the destination airports. --- @field #table destination_ports Array containing the names of the destination airports. +-- @field #table departure_ports Array containing the names of the destination airports or zones. +-- @field #table destination_ports Array containing the names of the destination airports or zones. +-- @field #number Ndestination_Airports Number of destination airports set via SetDestination(). +-- @field #number Ndestination_Zones Number of destination zones set via SetDestination(). +-- @field #number Ndeparture_Airports Number of departure airports set via SetDeparture(). +-- @field #number Ndeparture_Zones Number of departure zones set via SetDeparture. -- @field #table excluded_ports Array containing the names of explicitly excluded airports. -- @field #boolean destinationzone Destination is a zone and not an airport. -- @field #table return_zones Array containing the names of the return zones. -- @field #boolean returnzone Zone where aircraft will fly to before returning to their departure airport. -- @field Core.Zone#ZONE departure_Azone Zone containing the departure airports. -- @field Core.Zone#ZONE destination_Azone Zone containing the destination airports. +-- @field #boolean addfriendlydepartures Add all friendly airports to departures. +-- @field #boolean addfriendlydestinations Add all friendly airports to destinations. -- @field #table ratcraft Array with the spawned RAT aircraft. -- @field #number Tinactive Time in seconds after which inactive units will be destroyed. Default is 300 seconds. -- @field #boolean reportstatus Aircraft report status. @@ -302,14 +308,20 @@ RAT={ airports={}, -- All airports of friedly coalitions. random_departure=true, -- By default a random friendly airport is chosen as departure. random_destination=true, -- By default a random friendly airport is chosen as destination. - departure_ports={}, -- Array containing the names of the departure airports. - destination_ports={}, -- Array containing the names of the destination airports. + departure_ports={}, -- Array containing the names of the departure airports or zones. + destination_ports={}, -- Array containing the names of the destination airports or zones. + Ndestination_Airports=0, -- Number of destination airports set via SetDestination(). + Ndestination_Zones=0, -- Number of destination zones set via SetDestination(). + Ndeparture_Airports=0, -- Number of departure airports set via SetDeparture(). + Ndeparture_Zones=0, -- Number of departure zones set via SetDeparture. destinationzone=false, -- Destination is a zone and not an airport. return_zones={}, -- Array containing the names of return zones. returnzone=false, -- Aircraft will fly to a zone and back. excluded_ports={}, -- Array containing the names of explicitly excluded airports. departure_Azone=nil, -- Zone containing the departure airports. destination_Azone=nil, -- Zone containing the destination airports. + addfriendlydepartures=false, -- Add all friendly airports to departures. + addfriendlydestinations=false, -- Add all friendly airports to destinations. ratcraft={}, -- Array with the spawned RAT aircraft. Tinactive=600, -- Time in seconds after which inactive units will be destroyed. Default is 600 seconds. reportstatus=false, -- Aircraft report status. @@ -452,6 +464,10 @@ RAT.MenuF10=nil -- @field #string id RAT.id="RAT | " +--- RAT version. +-- @field #string version +RAT.version="1.0.0" + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --TODO list: @@ -500,6 +516,9 @@ RAT.id="RAT | " -- @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. function RAT:New(groupname, alias) + -- Version info. + env.info(RAT.id.."Version "..RAT.version) + -- Welcome message. env.info(RAT.id.."Creating new RAT object from template: "..groupname) @@ -586,6 +605,14 @@ function RAT:Spawn(naircraft) 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 + -- Setting and possibly correction min/max/cruise flight levels. if self.FLcruise==nil then -- Default flight level (ASL). @@ -598,54 +625,9 @@ function RAT:Spawn(naircraft) end end - -- Consistancy checks + -- Run consistency checks. + self:_CheckConsistency() - -- Destination zone and return zone should not be used together. - if self.destinationzone and self.returnzone then - env.error(RAT.id.."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. - -- 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) - end - if self.FLmaxuser then - self.FLmaxuser=math.min(self.FLmaxuser, self.aircraft.ceiling) - end - if self.FLcruise then - self.FLcruise=math.min(self.FLcruise, self.aircraft.ceiling) - end - - -- 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 - 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 - - -- Settings info local text=string.format("\n******************************************************\n") text=text..string.format("Spawning %i aircraft from template %s of type %s.\n", self.ngroups, self.SpawnTemplatePrefix, self.aircraft.type) @@ -654,6 +636,16 @@ function RAT:Spawn(naircraft) text=text..string.format("Friendly coalitions: %s\n", self.friendly) text=text..string.format("Number of airports on map : %i\n", #self.airports_map) text=text..string.format("Number of friendly airports: %i\n", #self.airports) + text=text..string.format("Totally random departure: %s\n", tostring(self.random_departure)) + if not self.random_departure then + text=text..string.format("Number of departure airports: %d\n", self.Ndeparture_Airports) + text=text..string.format("Number of departure zones : %d\n", self.Ndeparture_Zones) + end + text=text..string.format("Totally random destination: %s\n", tostring(self.random_destination)) + if not self.random_destination then + text=text..string.format("Number of destination airports: %d\n", self.Ndestination_Airports) + text=text..string.format("Number of destination zones : %d\n", self.Ndestination_Zones) + end text=text..string.format("Min dist to destination: %4.1f\n", self.mindist) text=text..string.format("Max dist to destination: %4.1f\n", self.maxdist) text=text..string.format("Takeoff type: %i\n", self.takeoff) @@ -732,6 +724,110 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Function checks consistency of user input and automatically adjusts parameters if necessary. +-- @param #RAT self +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 + self.Ndeparture_Airports=self.Ndeparture_Airports+1 + elseif self:_ZoneExists(name) then + self.Ndeparture_Zones=self.Ndeparture_Zones+1 + end + end + + -- Count destination airports and zones. + for _,name in pairs(self.destination_ports) do + if self:_AirportExists(name) then + self.Ndestination_Airports=self.Ndestination_Airports+1 + elseif self:_ZoneExists(name) then + self.Ndestination_Zones=self.Ndestination_Zones+1 + end + end + + -- What can go wrong? + -- Only zones but not takeoff air == > Enable takeoff air. + if self.Ndeparture_Airports==0 and self.takeoff~=RAT.wp.air then + self.takeoff=RAT.wp.air + env.error(RAT.id.."Only zones (no airports) defined as departure and takeoff is NOT set to air. Enabling air start!") + end + -- No airport and no zone specified. + if self.Ndeparture_Airports==0 and self.Ndeparture_Zone==0 then + self.random_departure=true + local text="No airports or zones found given in SetDeparture(). Enabling random departure airports!" + env.error(RAT.id..text) + MESSAGE:New(text, 30):ToAll() + end + end + + -- User has used SetDestination() + if not self.random_destination then + -- Only zones in list but not landing air ==> 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. + if self.Ndestination_Airports==0 and self.landing~=RAT.wp.air and not self.returnzone then + self.landing=RAT.wp.air + self.destinationzone=true + env.error(RAT.id.."Only zones (no airports) defined as destination and landing is NOT set to air. Enabling destination zone!") + end + -- No specified airport and no zone found at all. + if self.Ndestination_Airports==0 and self.Ndestination_Zones==0 then + self.random_destination=true + local text="No airports or zones found given in SetDestination(). Enabling random destination airports!" + env.error(RAT.id..text) + MESSAGE:New(text, 30):ToAll() + end + end + + -- Destination zone and return zone should not be used together. + if self.destinationzone and self.returnzone then + env.error(RAT.id.."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. + -- 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) + end + if self.FLmaxuser then + self.FLmaxuser=math.min(self.FLmaxuser, self.aircraft.ceiling) + end + if self.FLcruise then + self.FLcruise=math.min(self.FLcruise, self.aircraft.ceiling) + end + + -- 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 + 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 +end + --- Set the friendly coalitions from which the airports can be used as departure and destination. -- @param #RAT self -- @param #string friendly "same"=own coalition+neutral (default), "sameonly"=own coalition only, "neutral"=all neutral airports. @@ -916,6 +1012,17 @@ function RAT:SetDeparturesFromZone(zone) self.departure_Azone=zone end +--- Add all friendly airports to the list of possible departures. +-- @param #RAT self +function RAT:AddFriendlyAirportsToDepartures() + self.addfriendlydepartures=true +end + +--- Add all friendly airports to the list of possible destinations +-- @param #RAT self +function RAT:AddFriendlyAirportsToDestinations() + self.addfriendlydestinations=true +end --- Airports, FARPs and ships explicitly excluded as departures and destinations. -- @param #RAT self @@ -928,6 +1035,23 @@ function RAT:ExcludedAirports(ports) end end +--- 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. +function RAT:SetAISkill(skill) + if skill:lower()=="average" then + self.skill="Average" + elseif skill:lower()=="good" then + self.skill="Good" + elseif skill:lower()=="excellent" then + self.skill="Excellent" + elseif skill:lower()=="random" then + self.skill="Random" + else + self.skill="High" + end +end + --- Set livery of aircraft. If more than one livery is specified in a table, the actually used one is chosen randomly from the selection. -- @param #RAT self -- @param #table skins Name of livery or table of names of liveries. @@ -2351,8 +2475,6 @@ function RAT:_PickDestination(departure, q, minrange, maxrange, random, landing) end end - - end -- Info message. @@ -3114,7 +3236,8 @@ function RAT:_Waypoint(index, Type, Coord, Speed, Altitude, Airport) _alttype="RADIO" elseif Type==RAT.wp.finalwp then _Type="Turning Point" - _Action="Fly Over Point" + --_Action="Fly Over Point" + _Action="Turning Point" _alttype="BARO" else env.error("Unknown waypoint type in RAT:Waypoint() function!") @@ -3246,7 +3369,7 @@ function RAT:_Routeinfo(waypoints, comment) local d=math.sqrt((x1-x2)^2 + (y1-y2)^2) local heading=self:_Course(point1, point2) total=total+d - text=text..string.format("Distance from WP %i-->%i = %6.1f km. Heading = %03d: %s - %s\n", i-1, i, d/1000, heading, self.waypointdescriptions[i], self.waypointdescriptions[i+1]) + text=text..string.format("Distance from WP %i-->%i = %6.1f km. Heading = %03d : %s - %s\n", i-1, i, d/1000, heading, self.waypointdescriptions[i], self.waypointdescriptions[i+1]) end text=text..string.format("Total distance = %6.1f km\n", total/1000) text=text..string.format("******************************************************\n") @@ -3462,6 +3585,31 @@ function RAT:_MinDistance(alpha, beta, ha, hb) end +--- Add names of all friendly airports to possible departure or destination airports if they are not already in the list. +-- @param #RAT self +-- @param #table ports List of departure or destination airports/zones that will be added. +function RAT:_AddFriendlyAirports(ports) + for _,airport in pairs(self.airports) do + if not self:_NameInList(ports, airport:GetName()) then + table.insert(ports, airport:GetName()) + end + end +end + +--- Check if a name/string is in a list or not. +-- @param #RAT self +-- @param #table liste List of names to be checked. +-- @param #string name Name to be checked for. +function RAT:_NameInList(liste, name) + for _,item in pairs(liste) do + if item==name then + return true + end + end + return false +end + + --- Test if an airport exists on the current map. -- @param #RAT self -- @param #string name @@ -4084,5 +4232,3 @@ function RAT:_ATCQueue() end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - - diff --git a/Moose Mission Setup/Moose.lua b/Moose Mission Setup/Moose.lua index 4bd75d8e6..cd942a2ca 100644 --- a/Moose Mission Setup/Moose.lua +++ b/Moose Mission Setup/Moose.lua @@ -1,5 +1,5 @@ env.info( '*** MOOSE DYNAMIC INCLUDE START *** ' ) -env.info( 'Moose Generation Timestamp: 20171024_2314' ) +env.info( 'Moose Generation Timestamp: 20171027_1946' ) local base = _G From 43b5926b74c1c5afab95cb7fb9313c7dc42fcb1a Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Fri, 27 Oct 2017 22:08:16 +0200 Subject: [PATCH 23/25] Moose.lua --- Moose Mission Setup/Moose.lua | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Moose Mission Setup/Moose.lua b/Moose Mission Setup/Moose.lua index e76034f8a..b98fbb753 100644 --- a/Moose Mission Setup/Moose.lua +++ b/Moose Mission Setup/Moose.lua @@ -1,9 +1,5 @@ env.info( '*** MOOSE DYNAMIC INCLUDE START *** ' ) -<<<<<<< HEAD -env.info( 'Moose Generation Timestamp: 20171027_1946' ) -======= env.info( 'Moose Generation Timestamp: 20171026_1116' ) ->>>>>>> master local base = _G From 58a6c43c41cf2fba7d236017ca657ebee927c80d Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Sun, 29 Oct 2017 15:09:17 +0100 Subject: [PATCH 24/25] RAT fixes and adjustments Fixed maxdistance. Was 500 not 5000 km. --- Moose Development/Moose/Functional/RAT.lua | 97 +++++++++++++++------- 1 file changed, 66 insertions(+), 31 deletions(-) diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index ec8b646f4..f1a02ad03 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -133,7 +133,8 @@ -- @field #number wp_holding Index of the holding waypoint. -- @field #boolean radio If true/false disables radio messages from the RAT groups. -- @field #number frequency Radio frequency used by the RAT groups. --- @field #string modulation Ratio modulation. Either "FM" or "AM". +-- @field #string modulation Ratio modulation. Either "FM" or "AM". +-- @field #boolean uncontrolled If true aircraft are spawned in uncontrolled state and will only sit on their parking spots. -- @extends Core.Spawn#SPAWN ---# RAT class, extends @{Spawn#SPAWN} @@ -303,7 +304,7 @@ RAT={ takeoff = 0, -- Takeoff type. 0=coldorhot. landing = 9, -- Landing type. 9=landing. mindist = 5000, -- Min distance from departure to destination in meters. Default 5 km. - maxdist = 500000, -- Max distance from departure to destination in meters. Default 5000 km. + maxdist = 5000000, -- Max distance from departure to destination in meters. Default 5000 km. airports_map={}, -- All airports available on current map (Caucasus, Nevada, Normandy, ...). airports={}, -- All airports of friedly coalitions. random_departure=true, -- By default a random friendly airport is chosen as departure. @@ -355,6 +356,7 @@ RAT={ frequency=nil, -- Radio frequency used by the RAT groups. modulation=nil, -- Ratio modulation. Either "FM" or "AM". actype=nil, -- Aircraft type set by user. Changes the type of the template group. + uncontrolled=false, -- Spawn uncontrolled aircraft. } ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -448,7 +450,7 @@ RAT.ATC={ airport={}, unregistered=-1, onfinal=-100, - Nclearance=1, + Nclearance=2, delay=240, } @@ -654,6 +656,7 @@ function RAT:Spawn(naircraft) text=text..string.format("Journey: %s\n", tostring(self.continuejourney)) text=text..string.format("Destination Zone: %s\n", tostring(self.destinationzone)) text=text..string.format("Return Zone: %s\n", tostring(self.returnzone)) + text=text..string.format("Uncontrolled: %s\n", tostring(self.uncontrolled)) 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 after landing: %s\n", tostring(self.respawn_at_landing)) @@ -766,12 +769,12 @@ function RAT:_CheckConsistency() -- User has used SetDestination() if not self.random_destination then - -- Only zones in list but not landing air ==> Enable destination zone. + -- 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. - if self.Ndestination_Airports==0 and self.landing~=RAT.wp.air and not self.returnzone then + if self.Ndestination_Zones>0 and self.landing~=RAT.wp.air and not self.returnzone then self.landing=RAT.wp.air self.destinationzone=true - env.error(RAT.id.."Only zones (no airports) defined as destination and landing is NOT set to air. Enabling destination zone!") + env.error(RAT.id.."At least one zone defined as destination and landing is NOT set to air. Enabling destination zone!") end -- No specified airport and no zone found at all. if self.Ndestination_Airports==0 and self.Ndestination_Zones==0 then @@ -825,7 +828,13 @@ function RAT:_CheckConsistency() -- Cruise alt > FL max if self.FLmaxuser and self.FLcruise>self.FLmaxuser then self.FLcruise=self.FLmaxuser - end + end + + -- Uncontrolled aircraft must start with engines off. + if self.uncontrolled then + -- TODO: Strangly, it does not work with RAT.wp.cold only with RAT.wp.hot! + self.takeoff=RAT.wp.hot + end end --- Set the friendly coalitions from which the airports can be used as departure and destination. @@ -1150,6 +1159,12 @@ function RAT:RadioFrequency(frequency) self.frequency=frequency end +--- Spawn aircraft in uncontolled state. Aircraft will only sit at their parking spots. Only for populating airfields. +-- @param #RAT self +function RAT:Uncontrolled() + self.uncontrolled=true +end + --- Set radio modulation. Default is AM. -- @param #RAT self -- @param #string modulation Either "FM" or "AM". If no value is given, modulation is set to AM. @@ -1245,14 +1260,14 @@ end --- 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 1. +-- @param #number n Number of aircraft that are allowed to land simultaniously. Default is 2. function RAT:ATC_Clearance(n) - RAT.ATC.Nclearance=n or 1 + RAT.ATC.Nclearance=n or 2 end --- 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. +-- @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. function RAT:ATC_Delay(time) RAT.ATC.delay=time or 240 end @@ -1945,8 +1960,8 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) -- DESCENT/HOLDING POINT -- Get a random point between 5 and 10 km away from the destination. - local Rhmin=5000 - local Rhmax=10000 + local Rhmin=8000 + local Rhmax=20000 if self.category==RAT.cat.heli then -- For helos we set a distance between 500 to 1000 m. Rhmin=500 @@ -2447,8 +2462,8 @@ function RAT:_PickDestination(departure, q, minrange, maxrange, random, landing) -- Make sure departure and destination are not identical. if name ~= departure:GetName() then - - local dest + + local dest=nil if self:_AirportExists(name) then if landing==RAT.wp.air then dest=AIRBASE:FindByName(name):GetZone() @@ -2465,12 +2480,17 @@ function RAT:_PickDestination(departure, q, minrange, maxrange, random, landing) env.error(RAT.id.."No airport or zone found with name "..name) end - -- 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) + 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) + else + local text=string.format("Destination %s is ouside range. Distance = %5.1f km, min = %5.1f km, max = %5.1f km.", name, distance, minrange, maxrange) + env.info(RAT.id..text) + end end end @@ -2701,7 +2721,7 @@ function RAT:Status(message, forID) -- If aircraft did not move more than 50 m since last check, we call it stationary and despawn it. --TODO: add case that the aircraft are currently starting their engines. This should not count as being stationary. --local starting_engines=self.ratcraft[i].status=="" - if Dg<50 then + if Dg<50 and not self.uncontrolled then stationary=true end @@ -2731,6 +2751,11 @@ function RAT:Status(message, forID) -- Status shortcut. local status=self.ratcraft[i].status + + -- Uncontrolled aircraft. + if self.uncontrolled then + status="Uncontrolled" + end -- Status report. if (forID and i==forID) or (not forID) then @@ -2742,7 +2767,7 @@ function RAT:Status(message, forID) else text=text..string.format("%s travelling from %s to %s\n", type, departure, destination) end - text=text..string.format("Status: %s", self.ratcraft[i].status) + text=text..string.format("Status: %s", status) if airborne then text=text.." [airborne]\n" else @@ -3226,8 +3251,8 @@ function RAT:_Waypoint(index, Type, Coord, Speed, Altitude, Airport) _alttype="BARO" elseif Type==RAT.wp.holding then _Type="Turning Point" - --_Action="Turning Point" - _Action="Fly Over Point" + _Action="Turning Point" + --_Action="Fly Over Point" _alttype="BARO" elseif Type==RAT.wp.landing then _Type="Land" @@ -3427,9 +3452,10 @@ function RAT:_TaskHolding(P1, Altitude, Speed, Duration) DCSTask.params.task=Task if self.ATCswitch then - -- Set stop condition for holding. Either flag=1 or after max. 50 min holding. + -- 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) - DCSTask.params.stopCondition={userFlag=userflagname, userFlagValue=1, duration=3000} + local maxholdingduration=60*120 + DCSTask.params.stopCondition={userFlag=userflagname, userFlagValue=1, duration=maxholdingduration} else DCSTask.params.stopCondition={duration=Duration} end @@ -3801,6 +3827,10 @@ end function RAT:_PlaceMarkers(waypoints, index) for i=1,#waypoints do self:_SetMarker(self.waypointdescriptions[i], waypoints[i], index) + if self.debug then + local text=string.format("Marker at waypoint #%d: %s for flight #%d", i, self.waypointdescriptions[i], index) + env.info(RAT.id..text) + end end end @@ -3858,6 +3888,11 @@ function RAT:_ModifySpawnTemplate(waypoints, livery) if SpawnTemplate then self:T(SpawnTemplate) + + -- Spawn aircraft in uncontolled state. + if self.uncontrolled then + SpawnTemplate.uncontrolled=true + end -- Translate the position of the Group Template to the Vec3. for UnitID = 1, #SpawnTemplate.units do @@ -3933,7 +3968,7 @@ function RAT:_ModifySpawnTemplate(waypoints, livery) SpawnTemplate.modulation=self.modulation end - --SpawnTemplate.uncontrolled=true + -- Update modified template for spawn group. --self.SpawnGroups[self.SpawnIndex].SpawnTemplate=SpawnTemplate @@ -4030,7 +4065,7 @@ function RAT:_ATCStatus() end -- Aircraft is holding. - local text=string.format("STATUS: ATC %s: Flight %s is holding for %i:%02d. %s.", dest, name, hold/60, hold%60, busy) + local text=string.format("ATC %s: Flight %s is holding for %i:%02d. %s.", dest, name, hold/60, hold%60, busy) env.info(RAT.id..text) elseif hold==RAT.ATC.onfinal then @@ -4038,7 +4073,7 @@ function RAT:_ATCStatus() -- Aircarft is on final approach for landing. local Tfinal=Tnow-RAT.ATC.flight[name].Tonfinal - local text=string.format("STATUS: ATC %s: Flight %s is on final. Waiting %i:%02d for landing event.", dest, name, Tfinal/60, Tfinal%60) + local text=string.format("ATC %s: Flight %s is on final. Waiting %i:%02d for landing event.", dest, name, Tfinal/60, Tfinal%60) env.info(RAT.id..text) elseif hold==RAT.ATC.unregistered then @@ -4088,12 +4123,12 @@ function RAT:_ATCCheck() RAT.ATC.flight[flight].holding=Tnow-RAT.ATC.flight[flight].Tarrive -- Debug message. - local text=string.format("CHECK: 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) + 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) env.info(RAT.id..text) else - local text=string.format("CHECK: 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) + 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) env.info(RAT.id..text) -- Clear flight for landing. From 97be67bae91988df5fa9b054ff71302494594035 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Sun, 29 Oct 2017 19:27:39 +0100 Subject: [PATCH 25/25] RAT v2 --- Moose Development/Moose/Functional/RAT.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index f1a02ad03..737b60eea 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -468,7 +468,7 @@ RAT.id="RAT | " --- RAT version. -- @field #string version -RAT.version="1.0.0" +RAT.version="2.0.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -754,9 +754,9 @@ function RAT:_CheckConsistency() -- What can go wrong? -- Only zones but not takeoff air == > Enable takeoff air. - if self.Ndeparture_Airports==0 and self.takeoff~=RAT.wp.air then + if self.Ndeparture_Zones>0 and self.takeoff~=RAT.wp.air then self.takeoff=RAT.wp.air - env.error(RAT.id.."Only zones (no airports) defined as departure and takeoff is NOT set to air. Enabling air start!") + env.error(RAT.id.."At least one zone defined as departure and takeoff is NOT set to air. Enabling air start!") end -- No airport and no zone specified. if self.Ndeparture_Airports==0 and self.Ndeparture_Zone==0 then