From 9289e0dac1029682bf00be38485ec56fd28627ad Mon Sep 17 00:00:00 2001
From: funkyfranky
Date: Mon, 28 Aug 2017 00:05:30 +0200
Subject: [PATCH 01/28] First version of RAT
Not even alpha status.
---
Moose Development/Moose/AI/AI_Balancer.lua | 2835 ++++-
Moose Development/Moose/AI/AI_RAT.lua | 1191 ++
Moose Development/Moose/Functional/Spawn.lua | 10809 ++++++++++++++---
Moose Mission Setup/Moose.files | 1 +
Moose Mission Setup/Moose.lua | 3 +-
5 files changed, 12734 insertions(+), 2105 deletions(-)
create mode 100644 Moose Development/Moose/AI/AI_RAT.lua
diff --git a/Moose Development/Moose/AI/AI_Balancer.lua b/Moose Development/Moose/AI/AI_Balancer.lua
index 643dfc5b7..0bfd24570 100644
--- a/Moose Development/Moose/AI/AI_Balancer.lua
+++ b/Moose Development/Moose/AI/AI_Balancer.lua
@@ -1,303 +1,2564 @@
---- **AI** -- **AI Balancing will replace in multi player missions
--- non-occupied human slots with AI groups, in order to provide an engaging simulation environment,
--- even when there are hardly any players in the mission.**
---
--- 
---
--- ====
---
--- # Demo Missions
---
--- ### [AI_BALANCER Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/AIB%20-%20AI%20Balancing)
---
--- ### [AI_BALANCER Demo Missions, only for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AIB%20-%20AI%20Balancing)
---
--- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
---
--- ====
---
--- # YouTube Channel
---
--- ### [AI_BALANCER YouTube Channel](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl2CJVIrL1TdAumuVS8n64B7)
---
--- ====
---
--- ### Author: **Sven Van de Velde (FlightControl)**
--- ### Contributions:
---
--- * **[Dutch_Baron](https://forums.eagle.ru/member.php?u=112075)**: Working together with James has resulted in the creation of the AI_BALANCER class. James has shared his ideas on balancing AI with air units, and together we made a first design which you can use now :-)
---
--- ====
---
--- @module AI_Balancer
-
---- @type AI_BALANCER
--- @field Core.Set#SET_CLIENT SetClient
--- @field Functional.Spawn#SPAWN SpawnAI
--- @field Wrapper.Group#GROUP Test
--- @extends Core.Fsm#FSM_SET
-
-
---- # AI_BALANCER class, extends @{Fsm#FSM_SET}
---
--- The AI_BALANCER class monitors and manages as many replacement AI groups as there are
--- CLIENTS in a SET_CLIENT collection, which are not occupied by human players.
--- In other words, use AI_BALANCER to simulate human behaviour by spawning in replacement AI in multi player missions.
---
--- The parent class @{Fsm#FSM_SET} manages the functionality to control the Finite State Machine (FSM).
--- The mission designer can tailor the behaviour of the AI_BALANCER, by defining event and state transition methods.
--- An explanation about state and event transition methods can be found in the @{FSM} module documentation.
---
--- The mission designer can tailor the AI_BALANCER behaviour, by implementing a state or event handling method for the following:
---
--- * @{#AI_BALANCER.OnAfterSpawned}( AISet, From, Event, To, AIGroup ): Define to add extra logic when an AI is spawned.
---
--- ## 1. AI_BALANCER construction
---
--- Create a new AI_BALANCER object with the @{#AI_BALANCER.New}() method:
---
--- ## 2. AI_BALANCER is a FSM
---
--- 
---
--- ### 2.1. AI_BALANCER States
---
--- * **Monitoring** ( Set ): Monitoring the Set if all AI is spawned for the Clients.
--- * **Spawning** ( Set, ClientName ): There is a new AI group spawned with ClientName as the name of reference.
--- * **Spawned** ( Set, AIGroup ): A new AI has been spawned. You can handle this event to customize the AI behaviour with other AI FSMs or own processes.
--- * **Destroying** ( Set, AIGroup ): The AI is being destroyed.
--- * **Returning** ( Set, AIGroup ): The AI is returning to the airbase specified by the ReturnToAirbase methods. Handle this state to customize the return behaviour of the AI, if any.
---
--- ### 2.2. AI_BALANCER Events
---
--- * **Monitor** ( Set ): Every 10 seconds, the Monitor event is triggered to monitor the Set.
--- * **Spawn** ( Set, ClientName ): Triggers when there is a new AI group to be spawned with ClientName as the name of reference.
--- * **Spawned** ( Set, AIGroup ): Triggers when a new AI has been spawned. You can handle this event to customize the AI behaviour with other AI FSMs or own processes.
--- * **Destroy** ( Set, AIGroup ): The AI is being destroyed.
--- * **Return** ( Set, AIGroup ): The AI is returning to the airbase specified by the ReturnToAirbase methods.
---
--- ## 3. AI_BALANCER spawn interval for replacement AI
---
--- Use the method @{#AI_BALANCER.InitSpawnInterval}() to set the earliest and latest interval in seconds that is waited until a new replacement AI is spawned.
---
--- ## 4. AI_BALANCER returns AI to Airbases
---
--- By default, When a human player joins a slot that is AI_BALANCED, the AI group will be destroyed by default.
--- However, there are 2 additional options that you can use to customize the destroy behaviour.
--- When a human player joins a slot, you can configure to let the AI return to:
---
--- * @{#AI_BALANCER.ReturnToHomeAirbase}: Returns the AI to the **home** @{Airbase#AIRBASE}.
--- * @{#AI_BALANCER.ReturnToNearestAirbases}: Returns the AI to the **nearest friendly** @{Airbase#AIRBASE}.
---
--- Note that when AI returns to an airbase, the AI_BALANCER will trigger the **Return** event and the AI will return,
--- otherwise the AI_BALANCER will trigger a **Destroy** event, and the AI will be destroyed.
---
--- @field #AI_BALANCER
-AI_BALANCER = {
- ClassName = "AI_BALANCER",
- PatrolZones = {},
- AIGroups = {},
- Earliest = 5, -- Earliest a new AI can be spawned is in 5 seconds.
- Latest = 60, -- Latest a new AI can be spawned is in 60 seconds.
-}
---- Creates a new AI_BALANCER object
--- @param #AI_BALANCER self
--- @param Core.Set#SET_CLIENT SetClient A SET\_CLIENT object that will contain the CLIENT objects to be monitored if they are alive or not (joined by a player).
--- @param Functional.Spawn#SPAWN SpawnAI The default Spawn object to spawn new AI Groups when needed.
--- @return #AI_BALANCER
-function AI_BALANCER:New( SetClient, SpawnAI )
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
- -- Inherits from BASE
- local self = BASE:Inherit( self, FSM_SET:New( SET_GROUP:New() ) ) -- AI.AI_Balancer#AI_BALANCER
- -- TODO: Define the OnAfterSpawned event
- self:SetStartState( "None" )
- self:AddTransition( "*", "Monitor", "Monitoring" )
- self:AddTransition( "*", "Spawn", "Spawning" )
- self:AddTransition( "Spawning", "Spawned", "Spawned" )
- self:AddTransition( "*", "Destroy", "Destroying" )
- self:AddTransition( "*", "Return", "Returning" )
- self.SetClient = SetClient
- self.SetClient:FilterOnce()
- self.SpawnAI = SpawnAI
- self.SpawnQueue = {}
- self.ToNearestAirbase = false
- self.ToHomeAirbase = false
+
- self:__Monitor( 1 )
+ MOOSE/AI_Balancer.lua at master · FlightControl-Master/MOOSE
+
+
+
- return self
-end
-
---- Sets the earliest to the latest interval in seconds how long AI_BALANCER will wait to spawn a new AI.
--- Provide 2 identical seconds if the interval should be a fixed amount of seconds.
--- @param #AI_BALANCER self
--- @param #number Earliest The earliest a new AI can be spawned in seconds.
--- @param #number Latest The latest a new AI can be spawned in seconds.
--- @return self
-function AI_BALANCER:InitSpawnInterval( Earliest, Latest )
-
- self.Earliest = Earliest
- self.Latest = Latest
-
- return self
-end
-
---- Returns the AI to the nearest friendly @{Airbase#AIRBASE}.
--- @param #AI_BALANCER self
--- @param Dcs.DCSTypes#Distance ReturnThresholdRange If there is an enemy @{Client#CLIENT} within the ReturnThresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}.
--- @param Core.Set#SET_AIRBASE ReturnAirbaseSet The SET of @{Set#SET_AIRBASE}s to evaluate where to return to.
-function AI_BALANCER:ReturnToNearestAirbases( ReturnThresholdRange, ReturnAirbaseSet )
-
- self.ToNearestAirbase = true
- self.ReturnThresholdRange = ReturnThresholdRange
- self.ReturnAirbaseSet = ReturnAirbaseSet
-end
-
---- Returns the AI to the home @{Airbase#AIRBASE}.
--- @param #AI_BALANCER self
--- @param Dcs.DCSTypes#Distance ReturnThresholdRange If there is an enemy @{Client#CLIENT} within the ReturnThresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}.
-function AI_BALANCER:ReturnToHomeAirbase( ReturnThresholdRange )
-
- self.ToHomeAirbase = true
- self.ReturnThresholdRange = ReturnThresholdRange
-end
-
---- @param #AI_BALANCER self
--- @param Core.Set#SET_GROUP SetGroup
--- @param #string ClientName
--- @param Wrapper.Group#GROUP AIGroup
-function AI_BALANCER:onenterSpawning( SetGroup, From, Event, To, ClientName )
-
- -- OK, Spawn a new group from the default SpawnAI object provided.
- local AIGroup = self.SpawnAI:Spawn() -- Wrapper.Group#GROUP
- if AIGroup then
- AIGroup:E( "Spawning new AIGroup" )
- --TODO: need to rework UnitName thing ...
- SetGroup:Add( ClientName, AIGroup )
- self.SpawnQueue[ClientName] = nil
-
- -- Fire the Spawned event. The first parameter is the AIGroup just Spawned.
- -- Mission designers can catch this event to bind further actions to the AIGroup.
- self:Spawned( AIGroup )
- end
-end
+
---- @param #AI_BALANCER self
--- @param Core.Set#SET_GROUP SetGroup
--- @param Wrapper.Group#GROUP AIGroup
-function AI_BALANCER:onenterDestroying( SetGroup, From, Event, To, ClientName, AIGroup )
-
- AIGroup:Destroy()
- SetGroup:Flush()
- SetGroup:Remove( ClientName )
- SetGroup:Flush()
-end
-
---- @param #AI_BALANCER self
--- @param Core.Set#SET_GROUP SetGroup
--- @param Wrapper.Group#GROUP AIGroup
-function AI_BALANCER:onenterReturning( SetGroup, From, Event, To, AIGroup )
-
- local AIGroupTemplate = AIGroup:GetTemplate()
- if self.ToHomeAirbase == true then
- local WayPointCount = #AIGroupTemplate.route.points
- local SwitchWayPointCommand = AIGroup:CommandSwitchWayPoint( 1, WayPointCount, 1 )
- AIGroup:SetCommand( SwitchWayPointCommand )
- AIGroup:MessageToRed( "Returning to home base ...", 30 )
- else
- -- Okay, we need to send this Group back to the nearest base of the Coalition of the AI.
- --TODO: i need to rework the POINT_VEC2 thing.
- local PointVec2 = POINT_VEC2:New( AIGroup:GetVec2().x, AIGroup:GetVec2().y )
- local ClosestAirbase = self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2( PointVec2 )
- self:T( ClosestAirbase.AirbaseName )
- AIGroup:MessageToRed( "Returning to " .. ClosestAirbase:GetName().. " ...", 30 )
- local RTBRoute = AIGroup:RouteReturnToAirbase( ClosestAirbase )
- AIGroupTemplate.route = RTBRoute
- AIGroup:Respawn( AIGroupTemplate )
- end
-
-end
-
-
---- @param #AI_BALANCER self
-function AI_BALANCER:onenterMonitoring( SetGroup )
-
- self:T2( { self.SetClient:Count() } )
- --self.SetClient:Flush()
-
- self.SetClient:ForEachClient(
- --- @param Wrapper.Client#CLIENT Client
- function( Client )
- self:T3(Client.ClientName)
-
- local AIGroup = self.Set:Get( Client.UnitName ) -- Wrapper.Group#GROUP
- if Client:IsAlive() then
-
- if AIGroup and AIGroup:IsAlive() == true then
-
- if self.ToNearestAirbase == false and self.ToHomeAirbase == false then
- self:Destroy( Client.UnitName, AIGroup )
- else
- -- We test if there is no other CLIENT within the self.ReturnThresholdRange of the first unit of the AI group.
- -- If there is a CLIENT, the AI stays engaged and will not return.
- -- If there is no CLIENT within the self.ReturnThresholdRange, then the unit will return to the Airbase return method selected.
-
- local PlayerInRange = { Value = false }
- local RangeZone = ZONE_RADIUS:New( 'RangeZone', AIGroup:GetVec2(), self.ReturnThresholdRange )
-
- self:T2( RangeZone )
-
- _DATABASE:ForEachPlayer(
- --- @param Wrapper.Unit#UNIT RangeTestUnit
- function( RangeTestUnit, RangeZone, AIGroup, PlayerInRange )
- self:T2( { PlayerInRange, RangeTestUnit.UnitName, RangeZone.ZoneName } )
- if RangeTestUnit:IsInZone( RangeZone ) == true then
- self:T2( "in zone" )
- if RangeTestUnit:GetCoalition() ~= AIGroup:GetCoalition() then
- self:T2( "in range" )
- PlayerInRange.Value = true
- end
- end
- end,
-
- --- @param Core.Zone#ZONE_RADIUS RangeZone
- -- @param Wrapper.Group#GROUP AIGroup
- function( RangeZone, AIGroup, PlayerInRange )
- if PlayerInRange.Value == false then
- self:Return( AIGroup )
- end
- end
- , RangeZone, AIGroup, PlayerInRange
- )
-
- end
- self.Set:Remove( Client.UnitName )
- end
- else
- if not AIGroup or not AIGroup:IsAlive() == true then
- self:T( "Client " .. Client.UnitName .. " not alive." )
- if not self.SpawnQueue[Client.UnitName] then
- -- Spawn a new AI taking into account the spawn interval Earliest, Latest
- self:__Spawn( math.random( self.Earliest, self.Latest ), Client.UnitName )
- self.SpawnQueue[Client.UnitName] = true
- self:E( "New AI Spawned for Client " .. Client.UnitName )
- end
- end
- end
- return true
- end
- )
+
+
+
+
+
- self:__Monitor( 10 )
-end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-- ### Author: **Sven Van de Velde (FlightControl)**
+
+
+
+
-- ### Contributions:
+
+
+
+
--
+
+
+
+
-- * **[Dutch_Baron](https://forums.eagle.ru/member.php?u=112075)**: Working together with James has resulted in the creation of the AI_BALANCER class. James has shared his ideas on balancing AI with air units, and together we made a first design which you can use now :-)
+
+
+
+
--
+
+
+
+
-- ====
+
+
+
+
--
+
+
+
+
-- @module AI_Balancer
+
+
+
+
+
+
+
+
+
--- @type AI_BALANCER
+
+
+
+
-- @field Core.Set#SET_CLIENT SetClient
+
+
+
+
-- @field Functional.Spawn#SPAWN SpawnAI
+
+
+
+
-- @field Wrapper.Group#GROUP Test
+
+
+
+
-- @extends Core.Fsm#FSM_SET
+
+
+
+
+
+
+
+
+
+
+
+
+
+
--- # AI_BALANCER class, extends @{Fsm#FSM_SET}
+
+
+
+
--
+
+
+
+
-- The AI_BALANCER class monitors and manages as many replacement AI groups as there are
+
+
+
+
-- CLIENTS in a SET_CLIENT collection, which are not occupied by human players.
+
+
+
+
-- In other words, use AI_BALANCER to simulate human behaviour by spawning in replacement AI in multi player missions.
+
+
+
+
--
+
+
+
+
-- The parent class @{Fsm#FSM_SET} manages the functionality to control the Finite State Machine (FSM).
+
+
+
+
-- The mission designer can tailor the behaviour of the AI_BALANCER, by defining event and state transition methods.
+
+
+
+
-- An explanation about state and event transition methods can be found in the @{FSM} module documentation.
+
+
+
+
--
+
+
+
+
-- The mission designer can tailor the AI_BALANCER behaviour, by implementing a state or event handling method for the following:
+
+
+
+
--
+
+
+
+
-- * @{#AI_BALANCER.OnAfterSpawned}( AISet, From, Event, To, AIGroup ): Define to add extra logic when an AI is spawned.
+
+
+
+
--
+
+
+
+
-- ## 1. AI_BALANCER construction
+
+
+
+
--
+
+
+
+
-- Create a new AI_BALANCER object with the @{#AI_BALANCER.New}() method:
-- * **Monitoring** ( Set ): Monitoring the Set if all AI is spawned for the Clients.
+
+
+
+
-- * **Spawning** ( Set, ClientName ): There is a new AI group spawned with ClientName as the name of reference.
+
+
+
+
-- * **Spawned** ( Set, AIGroup ): A new AI has been spawned. You can handle this event to customize the AI behaviour with other AI FSMs or own processes.
+
+
+
+
-- * **Destroying** ( Set, AIGroup ): The AI is being destroyed.
+
+
+
+
-- * **Returning** ( Set, AIGroup ): The AI is returning to the airbase specified by the ReturnToAirbase methods. Handle this state to customize the return behaviour of the AI, if any.
+
+
+
+
--
+
+
+
+
-- ### 2.2. AI_BALANCER Events
+
+
+
+
--
+
+
+
+
-- * **Monitor** ( Set ): Every 10 seconds, the Monitor event is triggered to monitor the Set.
+
+
+
+
-- * **Spawn** ( Set, ClientName ): Triggers when there is a new AI group to be spawned with ClientName as the name of reference.
+
+
+
+
-- * **Spawned** ( Set, AIGroup ): Triggers when a new AI has been spawned. You can handle this event to customize the AI behaviour with other AI FSMs or own processes.
+
+
+
+
-- * **Destroy** ( Set, AIGroup ): The AI is being destroyed.
+
+
+
+
-- * **Return** ( Set, AIGroup ): The AI is returning to the airbase specified by the ReturnToAirbase methods.
+
+
+
+
--
+
+
+
+
-- ## 3. AI_BALANCER spawn interval for replacement AI
+
+
+
+
--
+
+
+
+
-- Use the method @{#AI_BALANCER.InitSpawnInterval}() to set the earliest and latest interval in seconds that is waited until a new replacement AI is spawned.
+
+
+
+
--
+
+
+
+
-- ## 4. AI_BALANCER returns AI to Airbases
+
+
+
+
--
+
+
+
+
-- By default, When a human player joins a slot that is AI_BALANCED, the AI group will be destroyed by default.
+
+
+
+
-- However, there are 2 additional options that you can use to customize the destroy behaviour.
+
+
+
+
-- When a human player joins a slot, you can configure to let the AI return to:
+
+
+
+
--
+
+
+
+
-- * @{#AI_BALANCER.ReturnToHomeAirbase}: Returns the AI to the **home** @{Airbase#AIRBASE}.
+
+
+
+
-- * @{#AI_BALANCER.ReturnToNearestAirbases}: Returns the AI to the **nearest friendly** @{Airbase#AIRBASE}.
+
+
+
+
--
+
+
+
+
-- Note that when AI returns to an airbase, the AI_BALANCER will trigger the **Return** event and the AI will return,
+
+
+
+
-- otherwise the AI_BALANCER will trigger a **Destroy** event, and the AI will be destroyed.
+
+
+
+
--
+
+
+
+
-- @field #AI_BALANCER
+
+
+
+
AI_BALANCER = {
+
+
+
+
ClassName ="AI_BALANCER",
+
+
+
+
PatrolZones = {},
+
+
+
+
AIGroups = {},
+
+
+
+
Earliest =5, -- Earliest a new AI can be spawned is in 5 seconds.
+
+
+
+
Latest =60, -- Latest a new AI can be spawned is in 60 seconds.
+
+
+
+
}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
--- Creates a new AI_BALANCER object
+
+
+
+
-- @param #AI_BALANCER self
+
+
+
+
-- @param Core.Set#SET_CLIENT SetClient A SET\_CLIENT object that will contain the CLIENT objects to be monitored if they are alive or not (joined by a player).
+
+
+
+
-- @param Functional.Spawn#SPAWN SpawnAI The default Spawn object to spawn new AI Groups when needed.
--- Sets the earliest to the latest interval in seconds how long AI_BALANCER will wait to spawn a new AI.
+
+
+
+
-- Provide 2 identical seconds if the interval should be a fixed amount of seconds.
+
+
+
+
-- @param #AI_BALANCER self
+
+
+
+
-- @param #number Earliest The earliest a new AI can be spawned in seconds.
+
+
+
+
-- @param #number Latest The latest a new AI can be spawned in seconds.
+
+
+
+
-- @return self
+
+
+
+
function AI_BALANCER:InitSpawnInterval( Earliest, Latest )
+
+
+
+
+
+
+
+
+
self.Earliest= Earliest
+
+
+
+
self.Latest= Latest
+
+
+
+
+
+
+
+
returnself
+
+
+
+
end
+
+
+
+
+
+
+
+
+
--- Returns the AI to the nearest friendly @{Airbase#AIRBASE}.
+
+
+
+
-- @param #AI_BALANCER self
+
+
+
+
-- @param Dcs.DCSTypes#Distance ReturnThresholdRange If there is an enemy @{Client#CLIENT} within the ReturnThresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}.
+
+
+
+
-- @param Core.Set#SET_AIRBASE ReturnAirbaseSet The SET of @{Set#SET_AIRBASE}s to evaluate where to return to.
+
+
+
+
function AI_BALANCER:ReturnToNearestAirbases( ReturnThresholdRange, ReturnAirbaseSet )
+
+
+
+
+
+
+
+
+
self.ToNearestAirbase=true
+
+
+
+
self.ReturnThresholdRange= ReturnThresholdRange
+
+
+
+
self.ReturnAirbaseSet= ReturnAirbaseSet
+
+
+
+
end
+
+
+
+
+
+
+
+
+
--- Returns the AI to the home @{Airbase#AIRBASE}.
+
+
+
+
-- @param #AI_BALANCER self
+
+
+
+
-- @param Dcs.DCSTypes#Distance ReturnThresholdRange If there is an enemy @{Client#CLIENT} within the ReturnThresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}.
+
+
+
+
function AI_BALANCER:ReturnToHomeAirbase( ReturnThresholdRange )
+
+
+
+
+
+
+
+
+
self.ToHomeAirbase=true
+
+
+
+
self.ReturnThresholdRange= ReturnThresholdRange
+
+
+
+
end
+
+
+
+
+
+
+
+
+
--- @param #AI_BALANCER self
+
+
+
+
-- @param Core.Set#SET_GROUP SetGroup
+
+
+
+
-- @param #string ClientName
+
+
+
+
-- @param Wrapper.Group#GROUP AIGroup
+
+
+
+
function AI_BALANCER:onenterSpawning( SetGroup, From, Event, To, ClientName )
+
+
+
+
+
+
+
+
+
-- OK, Spawn a new group from the default SpawnAI object provided.
+
+
+
+
local AIGroup =self.SpawnAI:Spawn() -- Wrapper.Group#GROUP
+
+
+
+
if AIGroup then
+
+
+
+
AIGroup:E( "Spawning new AIGroup" )
+
+
+
+
--TODO: need to rework UnitName thing ...
+
+
+
+
+
+
+
+
SetGroup:Add( ClientName, AIGroup )
+
+
+
+
self.SpawnQueue[ClientName] =nil
+
+
+
+
+
+
+
+
-- Fire the Spawned event. The first parameter is the AIGroup just Spawned.
+
+
+
+
-- Mission designers can catch this event to bind further actions to the AIGroup.
+
+
+
+
self:Spawned( AIGroup )
+
+
+
+
end
+
+
+
+
end
+
+
+
+
+
+
+
+
+
--- @param #AI_BALANCER self
+
+
+
+
-- @param Core.Set#SET_GROUP SetGroup
+
+
+
+
-- @param Wrapper.Group#GROUP AIGroup
+
+
+
+
function AI_BALANCER:onenterDestroying( SetGroup, From, Event, To, ClientName, AIGroup )
+
+
+
+
+
+
+
+
+
AIGroup:Destroy()
+
+
+
+
SetGroup:Flush()
+
+
+
+
SetGroup:Remove( ClientName )
+
+
+
+
SetGroup:Flush()
+
+
+
+
end
+
+
+
+
+
+
+
+
+
--- @param #AI_BALANCER self
+
+
+
+
-- @param Core.Set#SET_GROUP SetGroup
+
+
+
+
-- @param Wrapper.Group#GROUP AIGroup
+
+
+
+
function AI_BALANCER:onenterReturning( SetGroup, From, Event, To, AIGroup )
+
+
+
+
+
+
+
+
+
local AIGroupTemplate = AIGroup:GetTemplate()
+
+
+
+
ifself.ToHomeAirbase==truethen
+
+
+
+
local WayPointCount =#AIGroupTemplate.route.points
+
+
+
+
local SwitchWayPointCommand = AIGroup:CommandSwitchWayPoint( 1, WayPointCount, 1 )
+
+
+
+
AIGroup:SetCommand( SwitchWayPointCommand )
+
+
+
+
AIGroup:MessageToRed( "Returning to home base ...", 30 )
+
+
+
+
else
+
+
+
+
-- Okay, we need to send this Group back to the nearest base of the Coalition of the AI.
+
+
+
+
--TODO: i need to rework the POINT_VEC2 thing.
+
+
+
+
local PointVec2 = POINT_VEC2:New( AIGroup:GetVec2().x, AIGroup:GetVec2().y )
+
+
+
+
local ClosestAirbase =self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2( PointVec2 )
+
+
+
+
self:T( ClosestAirbase.AirbaseName )
+
+
+
+
AIGroup:MessageToRed( "Returning to ".. ClosestAirbase:GetName().." ...", 30 )
+
+
+
+
local RTBRoute = AIGroup:RouteReturnToAirbase( ClosestAirbase )
+
+
+
+
AIGroupTemplate.route= RTBRoute
+
+
+
+
AIGroup:Respawn( AIGroupTemplate )
+
+
+
+
end
+
+
+
+
+
+
+
+
+
end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
--- @param #AI_BALANCER self
+
+
+
+
function AI_BALANCER:onenterMonitoring( SetGroup )
+
+
+
+
+
+
+
+
+
self:T2( { self.SetClient:Count() } )
+
+
+
+
--self.SetClient:Flush()
+
+
+
+
+
+
+
+
+
self.SetClient:ForEachClient(
+
+
+
+
--- @param Wrapper.Client#CLIENT Client
+
+
+
+
function( Client )
+
+
+
+
self:T3(Client.ClientName)
+
+
+
+
+
+
+
+
+
local AIGroup =self.Set:Get( Client.UnitName ) -- Wrapper.Group#GROUP
+
+
+ You can't perform that action at this time.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ You signed in with another tab or window. Reload to refresh your session.
+ You signed out in another tab or window. Reload to refresh your session.
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Moose Development/Moose/AI/AI_RAT.lua b/Moose Development/Moose/AI/AI_RAT.lua
new file mode 100644
index 000000000..c2787f805
--- /dev/null
+++ b/Moose Development/Moose/AI/AI_RAT.lua
@@ -0,0 +1,1191 @@
+--- ** AI **
+-- @module AI_RAT
+
+--- Some ID to identify where we are
+-- #string myid
+myid="RAT | "
+
+--- RAT class
+-- @type RAT
+-- @field ClassName
+-- @field #string prefix
+-- @field #RAT
+-- @extends #SPAWN
+
+--- Table of RAT
+-- @field #RAT RAT
+-- @param #string ClassName Name of class.
+RAT={
+ ClassName = "RAT", -- Name of class: RAT = Random Air Traffic.
+ debug=false, -- Turn debug messages on or off.
+ prefix=nil, -- Prefix of the template group defined in the mission editor.
+ spawndelay=5, -- Delay time in seconds before first spawning happens.
+ spawninterval=5, -- Interval between spawning units/groups. note that we add a randomization of 10%
+ coalition = nil, -- Coalition of spawn 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.
+ ctable = {}, -- Table with the valid coalitons from choice self.friendly.
+ aircraft = {}, -- Table which holds the basic aircraft properties (speed, range, ...).
+ Vcruisemax=250, -- Max cruise speed in m/s (250 m/s = 900 km/h = 486 kt)
+ Vclimb=1500, -- Default climb rate in ft/min.
+ AlphaDescent=3.6, -- 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.
+ FLcruise=200, -- Default cruise flight level in. FL200 = 20000 ft.
+ roe = "hold", -- ROE of spawned groups, default is weapon hold (this is a peaceful class for civil aircraft or ferry missions).
+ takeoff = "hot", -- Takeoff type: "hot", "cold", "runway", "air", "random".
+ mindist = 10000, -- Min distance from departure to destination in meters.
+ airports_map={}, -- All airports available on current map (Caucasus, Nevada, Normandy, ...).
+ airports={}, -- All airports of friedly coalitions.
+ airports_departure={}, -- Possible departure airports if unit/group is spawned at airport, spawnpoint=air or spawnpoint=airport.
+ airports_destination={}, -- Possible destination airports if unit does not fly "overseas", destpoint=overseas or destpoint=airport.
+ departure_name="random", -- Name of the departure airport. Default is "random" for a randomly chosen one of the coalition airports.
+ destination_name="random",-- Name of the destination airport. Default is "random" for a randomly chosen one of the coalition airports.
+ zones_departure={}, -- Departure zones for air start.
+ zones_radius=5000, -- Radius of departure zones in meters.
+ ratcraft={}, -- Array with the spawned RAT aircraft.
+}
+
+--TODO list:
+--TODO: Add scheduled spawn and corresponding user functions.
+--TODO: Add possibility to spawn in air.
+--TODO: Add from zones and to zones for aircraft to fly from/to.
+--TODO: Make more functions to adjust/set parameters.
+--TODO: Clean up debug messages.
+--DONE: Improve flight plan. Especially check FL against route length.
+--TODO: Integrate RAT:DB? No, not now.
+--DONE: Add event handlers.
+--DONE: Respawn units when they have landed.
+--DONE: Change ROE state.
+--TODO: Make ROE state user function
+--TODO: Add status reports.
+--TODO: Check compatibility with #SPAWN functions.
+--TODO: Add possibility to continue journey at destination. Need "place" in event data for that.
+--DONE: Check that FARPS are not used as airbases for planes. Don't know if they appear in list of airports.
+--DONE: Add cases for helicopters.
+--TODO: Lots of other little things...
+
+
+--- Creates a new RAT object.
+-- @param #RAT self
+-- @param #string prefix Prefix of the (template) group name defined in the mission editor.
+-- @param #string friendly Friendly coalitions from which airports can be used.
+-- "all"=neutral+red+blue, "same"=spawn coalition+neutral, "sameonly"=spawn coalition, "blue"=blue+neutral, "blueonly"=blue, "red"=red+neutral, "redonly"=red, "neutral"=neutral.
+-- Default is "same", so aircraft will use airports of the coalition their spawn template has plus all neutral airports.
+-- @return #RAT self Object of RAT class.
+-- @return #nil Nil if the group does not exists in the mission editor.
+function RAT:New(prefix, friendly)
+
+ -- Inherit SPAWN clase.
+ local self=BASE:Inherit(self, SPAWN:New(prefix)) -- #RAT
+
+ -- Set prefix.
+ --TODO: Replace this by SpawnTemplatePrefix.
+ self.prefix=prefix
+
+ -- Set friendly coalitions. Default is "same", i.e. same coalition as template group plus neutrals.
+ self.friendly = friendly or "same"
+
+ -- Get template group defined in the mission editor.
+ local DCSgroup=Group.getByName(prefix)
+
+ -- Check the group actually exists.
+ if DCSgroup==nil then
+ error("Group with name "..prefix.." does not exist in the mission editor!")
+ return nil
+ end
+
+ -- Set own coalition.
+ self.coalition=DCSgroup:getCoalition()
+
+ -- Initialize aircraft parameters based on ME group template.
+ self:_InitAircraft(DCSgroup)
+
+ -- Get all airports of this map.
+ self:_GetAirportsOfMap()
+
+ -- Set the coalition table based on choice of self.coalition and self.friendly.
+ self:_SetCoalitionTable()
+
+ -- Get all airports of this map beloning to "our" coalition.
+ self:_GetAirportsOfCoalition()
+
+ return self
+end
+
+--- Initialize basic parameters of the aircraft based on its (template) group in the mission editor.
+-- @param #RAT self
+-- @param #DCSgroup DCSgroup Group of the aircraft in the mission editor.
+function RAT:_InitAircraft(DCSgroup)
+
+ local DCSunit=DCSgroup:getUnit(1)
+ local DCSdesc=DCSunit:getDesc()
+ local DCScategory=DCSgroup:getCategory()
+ local DCStype=DCSunit:getTypeName()
+
+ -- print descriptors table of unit
+ if self.debug then
+ self:E({"DCSdesc", DCSdesc})
+ end
+
+ -- unit conversions
+ local ft2meter=0.305
+ local kmh2ms=0.278
+ local FL2m=30.48
+ local nm2km=1.852
+ local nm2m=1852
+
+ -- set category
+ if DCScategory==Group.Category.AIRPLANE then
+ self.category="plane"
+ elseif DCScategory==Group.Category.HELICOPTER then
+ self.category="heli"
+ else
+ self.category="other"
+ error(myid.."Group of RAT is neither airplane nor helicopter!")
+ end
+
+ -- Get type of aircraft.
+ self.aircraft.type=DCStype
+
+ -- inital fuel in %
+ self.aircraft.fuel=DCSunit:getFuel()
+
+ -- operational range in NM converted to m
+ self.aircraft.Rmax = DCSdesc.range*nm2m
+
+ -- effective range taking fuel into accound and a 10% reserve
+ self.aircraft.Reff = self.aircraft.Rmax*self.aircraft.fuel*0.9
+
+ -- max airspeed from group
+ self.aircraft.Vmax = DCSdesc.speedMax
+
+ -- min cruise airspeed = 75% of max
+ self.aircraft.Vmin = self.aircraft.Vmax*0.75
+
+ -- actual travel speed (random between ASmin and ASmax)
+ --TODO: this needs to be placed somewhere else!
+ self.aircraft.Vcruise = math.random(self.aircraft.Vmin, self.aircraft.Vmax)
+ -- limit travel speed to ~900 km/h
+ self.aircraft.Vcruise = math.min(self.aircraft.Vcruise,self.Vcruisemax)
+
+ -- max climb speed in m/s
+ self.aircraft.Vymax=DCSdesc.VyMax
+
+ -- reasonably civil climb speed Vy=1500 ft/min but max aircraft specific climb rate
+ self.aircraft.Vclimb=math.min(self.Vclimb*ft2meter/60, self.aircraft.Vymax)
+
+ -- climb angle
+ self.aircraft.AlphaClimb=math.asin(self.aircraft.Vclimb/self.aircraft.Vmax)
+
+ -- descent angle 3:1 rule of descent, i.e. 3 miles of travel and 1000 ft of descent.
+ self.aircraft.AlphaDescent=math.rad(self.AlphaDescent)
+
+ -- service ceiling in meters
+ self.aircraft.ceiling=DCSdesc.Hmax
+
+ -- default flight levels (ASL)
+ if self.category=="plane" then
+ --TODO: need to fine tune and text these default parameters (also for different maps)
+ self.aircraft.FLmin=100*FL2m
+ self.aircraft.FLmax=self.aircraft.ceiling*0.9
+ self.aircraft.FLcruise=self.FLcruise*FL2m
+ else
+ --TODO: need to check these parameters for helos
+ self.aircraft.FLmin=001*FL2m
+ self.aircraft.FLmax=self.aircraft.ceiling*0.9
+ self.aircraft.FLcruise=005*FL2m
+ end
+
+ -- send debug message
+ local text=string.format("Aircraft parameters:\n")
+ text=text..string.format("Category = %s\n", self.category)
+ text=text..string.format("Max Speed = %6.1f m/s.\n", self.aircraft.Vmax)
+ text=text..string.format("Cruise Speed = %6.1f m/s.\n", self.aircraft.Vcruise)
+ text=text..string.format("Climb Speed Max = %6.1f m/s.\n", self.aircraft.Vymax)
+ text=text..string.format("Climb Speed = %6.1f m/s.\n", self.aircraft.Vclimb)
+ text=text..string.format("Angle of climb = %6.1f Deg.\n", math.deg(self.aircraft.AlphaClimb))
+ text=text..string.format("Angle of descent = %6.1f Deg.\n", math.deg(self.aircraft.AlphaDescent))
+ 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("Ceiling = %3.0f = %6.1f km.\n", self.aircraft.ceiling/FL2m, self.aircraft.ceiling/1000)
+ text=text..string.format("FL min = %3.0f = %6.1f km.\n", self.aircraft.FLmin/FL2m, self.aircraft.FLmin/1000)
+ text=text..string.format("FL max = %3.0f = %6.1f km.\n", self.aircraft.FLmax/FL2m, self.aircraft.FLmax/1000)
+ text=text..string.format("FL cruise = %3.0f = %6.1f km.", self.aircraft.FLcruise/FL2m, self.aircraft.FLcruise/1000)
+ env.info(myid..text)
+ if self.debug then
+ MESSAGE:New(text, 60):ToAll()
+ end
+
+end
+
+
+--- Spawn the AI aircraft.
+-- @param #RAT self
+-- @param #number naircraft (Optional) Number of aircraft to spawn. Default is one aircraft.
+-- @param #string name (Optional) Name of the spawn group (for debugging only).
+function RAT:Spawn(naircraft, name)
+
+ -- Number of aircraft to spawn. Default is one.
+ naircraft=naircraft or 1
+
+ -- some of group for debugging
+ --TODO: remove name from input parameter and make better unique RAT AI name
+ name=name or "RAT AI "..self.aircraft.type
+
+ -- debug message
+ local text="Spawning "..naircraft.." aircraft of group "..self.prefix.." with name "..name.." of type "..self.aircraft.type..".\n"
+ text=text.."Takeoff type: "..self.takeoff.."\n"
+ text=text.."Friendly airports: "..self.friendly
+ env.info(myid..text)
+ if self.debug then
+ MESSAGE:New(text, 60, "Info"):ToAll()
+ end
+
+ -- schedule the spawning
+ --TODO: make dt user input self.SpawnInterval
+ --TODO: check the interval is > 180 seconds if spawn at runway is chosen.
+ local tstart=5
+ local dt=5
+ local tstop=tstart+dt*(naircraft-1)
+ SCHEDULER:New(nil, self._SpawnWithRoute, {self}, tstart, dt, 0.1, tstop)
+
+ -- Status report scheduler
+ SCHEDULER:New(nil, self.Status, {self}, 30, 10, 0.0)
+
+end
+
+
+--- Spawn the AI aircraft with a route.
+-- @param #RAT self
+function RAT:_SpawnWithRoute()
+
+ -- Set flight plan.
+ local departure, destination, waypoints = self:_SetRoute()
+
+ -- Modify the spawn template to follow the flight plan.
+ self:_ModifySpawnTemplate(departure, waypoints)
+
+ -- Actually spawn the group.
+ local group=self:SpawnWithIndex(self.SpawnIndex) -- Core.Group#GROUP
+
+ -- set ROE to "weapon hold" and ROT to "no reaction"
+ -- TODO: make user function to set this
+ group:OptionROEReturnFire()
+ --group:OptionROEHoldFire()
+ group:OptionROTNoReaction()
+ --group:OptionROTPassiveDefense()
+
+ self.ratcraft[self.SpawnIndex]={}
+ self.ratcraft[self.SpawnIndex]["group"]=group
+ self.ratcraft[self.SpawnIndex]["destination"]=destination
+ self.ratcraft[self.SpawnIndex]["departure"]=departure
+ self.ratcraft[self.SpawnIndex]["status"]="spawned"
+
+ -- Handle events.
+ -- TODO: add hit event?
+ self:HandleEvent(EVENTS.Birth, self._OnBirthDay)
+ self:HandleEvent(EVENTS.EngineStartup, self._EngineStartup)
+ self:HandleEvent(EVENTS.Takeoff, self._OnTakeoff)
+ self:HandleEvent(EVENTS.Land, self._OnLand)
+ self:HandleEvent(EVENTS.EngineShutdown, self._OnEngineShutdown)
+ self:HandleEvent(EVENTS.Dead, self._OnDead)
+ self:HandleEvent(EVENTS.Crash, self._OnCrash)
+end
+
+
+--- Report status of RAT groups.
+-- @param #RAT self
+function RAT:Status()
+ local ngroups=#self.SpawnGroups
+ MESSAGE:New("Number of groups spawned = "..ngroups, 60):ToAll()
+ for i=1, ngroups do
+ local group=self.SpawnGroups[i].Group
+ local prefix=self:_GetPrefixFromGroup(group)
+ local life=self:_GetLife(group)
+ local text=string.format("Group %s ID %i:", prefix, i)
+ text=text..string.format("Life = %3.0f\n", life)
+ text=text..string.format("Status = %s\n", self.ratcraft[i].status)
+ text=text..string.format("Flying from %s to %s.",self.ratcraft[i].departure:GetName(), self.ratcraft[i].destination:GetName())
+ MESSAGE:New(text, 60):ToAll()
+ env.info(myid..text)
+ end
+end
+
+--- Get life of unit.
+-- @param #RAT self
+-- @return #number Life of unit in percent.
+function RAT:_GetLife(group)
+ if group then
+ local unit=group:GetUnit(1)
+ local life=unit:GetLife()/unit:GetLife0()*100
+ return life
+ else
+ error("Group does not exists in RAT_Getlife(). Returning zero.")
+ return 0.0
+ end
+end
+
+--- Set status of group.
+-- @param #RAT self
+function RAT:_SetStatus(group, status)
+ local index=self:GetSpawnIndexFromGroup(group)
+ env.info(myid.."Index for group "..group:GetName().." "..index.." status: "..status)
+ self.ratcraft[index].status=status
+end
+
+--- Function is executed when a unit is spawned.
+-- @param #RAT self
+function RAT:_OnBirthDay(EventData)
+ env.info(myid.."It's a birthday")
+ local SpawnGroup = EventData.IniGroup
+ if SpawnGroup then
+ local index=self:GetSpawnIndexFromGroup(SpawnGroup)
+ local EventPrefix = self:_GetPrefixFromGroup(SpawnGroup)
+ local text="Event: Group "..SpawnGroup:GetName().." was born."
+ env.info(myid..text)
+ --MESSAGE:New(text, 180):ToAll()
+ self:_SetStatus(SpawnGroup, "starting engines (born)")
+ else
+ error("Group does not exist in RAT:_EngineStartup().")
+ end
+end
+
+--- Function is executed when a unit starts its engines.
+-- @param #RAT self
+function RAT:_EngineStartup(EventData)
+ local SpawnGroup = EventData.IniGroup
+ if SpawnGroup then
+ local text="Event: Group "..SpawnGroup:GetName().." started engines. Life="..self:_GetLife(SpawnGroup)
+ env.info(myid..text)
+ --MESSAGE:New(text, 180):ToAll()
+ self:_SetStatus(SpawnGroup, "taxi (engines started)")
+ else
+ error("Group does not exist in RAT:_EngineStartup().")
+ end
+end
+
+--- Function is executed when a unit takes off.
+-- @param #RAT self
+function RAT:_OnTakeoff(EventData)
+ local SpawnGroup = EventData.IniGroup
+ if SpawnGroup then
+ local text="Event: Group "..SpawnGroup:GetName().." took off. Life="..self:_GetLife(SpawnGroup)
+ env.info(myid..text)
+ --MESSAGE:New(text, 180):ToAll()
+ self:_SetStatus(SpawnGroup, "airborn (took off)")
+ else
+ error("Group does not exist in RAT:_OnTakeoff().")
+ end
+end
+
+--- Function is executed when a unit lands.
+-- @param #RAT self
+function RAT:_OnLand(EventData)
+ local SpawnGroup = EventData.IniGroup
+ if SpawnGroup then
+ local text="Event: Group "..SpawnGroup:GetName().." landed. Life="..self:_GetLife(SpawnGroup)
+ env.info(myid..text)
+ --MESSAGE:New(text, 180):ToAll()
+ self:_SetStatus(SpawnGroup, "landed")
+ text="Event: Group "..SpawnGroup:GetName().." will be respawned."
+ env.info(myid..text)
+ --MESSAGE:New(text, 180):ToAll()
+ self:_SpawnWithRoute()
+ else
+ error("Group does not exist in RAT:_OnLand().")
+ end
+end
+
+--- Function is executed when a unit shuts down its engines.
+-- @param #RAT self
+function RAT:_OnEngineShutdown(EventData)
+ local SpawnGroup = EventData.IniGroup
+ if SpawnGroup then
+ local text="Event: Group "..SpawnGroup:GetName().." shut down its engines. Life="..self:_GetLife(SpawnGroup)
+ env.info(myid..text)
+ --MESSAGE:New(text, 180):ToAll()
+ self:_SetStatus(SpawnGroup, "arrived (engines shut down)")
+ text="Event: Group "..SpawnGroup:GetName().." will be destroyed now."
+ env.info(myid..text)
+ --MESSAGE:New(text, 180):ToAll()
+ SpawnGroup:Destroy()
+ else
+ error("Group does not exist in RAT:_OnEngineShutdown().")
+ end
+end
+
+--- Function is executed when a unit is dead.
+-- @param #RAT self
+function RAT:_OnDead(EventData)
+ local SpawnGroup = EventData.IniGroup
+ if SpawnGroup then
+ local text="Event: Group "..SpawnGroup:GetName().." was died. Life="..self:_GetLife(SpawnGroup)
+ env.info(myid..text)
+ self:_SetStatus(SpawnGroup, "dead (died)")
+ --MESSAGE:New(text, 180):ToAll()
+ else
+ error("Group does not exist in RAT:_OnDedad().")
+ end
+end
+
+--- Function is executed when a unit crashes.
+-- @param #RAT self
+function RAT:_OnCrash(EventData)
+ local SpawnGroup = EventData.IniGroup
+ if SpawnGroup then
+ local text="Event: Group "..SpawnGroup:GetName().." crashed. Life="..self:_GetLife(SpawnGroup)
+ env.info(myid..text)
+ --MESSAGE:New(text, 180):ToAll()
+ self:_SetStatus(SpawnGroup, "crashed")
+ --TODO: maybe spawn some people at the crash site and send a distress call. And define them as cargo which can be rescued.
+ else
+ error("Group does not exist in RAT:_OnCrash().")
+ end
+end
+
+
+--- Set name of departure airport for the AI aircraft. If no name is given an airport from the coalition is chosen randomly.
+-- @param #RAT self
+-- @param #string name Name of the departure airport or "random" for a randomly chosen one of the coalition.
+function RAT:SetDeparture(name)
+ if name and AIRBASE:FindByName(name) then
+ self.departure_name=name
+ else
+ self.departure_name="random"
+ end
+end
+
+--- Set name of destination airport for the AI aircraft. If no name is given an airport from the coalition is chosen randomly.
+-- @param #RAT self
+-- @param #string name Name of the destination airport or "random" for a randomly chosen one of the coalition.
+function RAT:SetDestination(name)
+ if name and AIRBASE:FindByName(name) then
+ self.destination_name=name
+ else
+ self.destination_name="random"
+ end
+end
+
+
+--- Set the departure airport of the AI. If no airport name is given an airport from the coalition is chosen randomly.
+-- @param #RAT self
+-- @return Wrapper.Airbase#AIRBASE Departure airport
+function RAT:_SetDeparture()
+ local departure -- Wrapper.Airbase#AIRBASE
+ if self.departure_name=="random" then
+ -- Get a random departure airport from all friendly coalition airports.
+ departure=self.airports[math.random(1, #self.airports)]
+ elseif AIRBASE:FindByName(self.departure_name) then
+ -- Take the explicit airport provided.
+ departure=AIRBASE:FindByName(self.departure_name)
+ else
+ -- If nothing else works, we randomly choose from frindly coalition airports.
+ departure=self.airports[math.random(1, #self.airports)]
+ end
+ local text="Chosen departure airport: "..departure:GetName().." with ID "..departure:GetID()
+ self:E(departure:GetDesc())
+ env.info(myid..text)
+ MESSAGE:New(text, 60):ToAll()
+ return departure
+end
+
+
+--- Set the destination airport of the AI. If no airport name is given an airport from the coalition is chosen randomly.
+-- @param #RAT self
+-- @return Wrapper.Airbase#AIRBASE Destination airport.
+function RAT:_SetDestination()
+ local destination -- Wrapper.Airbase#AIRBASE
+ if self.destination_name=="random" then
+ -- Get random destination from all friendly airports within range.
+ destination=self.airports_destination[math.random(1, #self.airports_destination)]
+ elseif self.destination_name and AIRBASE:FindByName(self.destination_name) then
+ -- Take the explicit airport provided.
+ destination=AIRBASE:FindByName(self.destination_name)
+ else
+ -- If nothing else works, we randomly choose from frindly coalition airports.
+ destination=self.airports_destination[math.random(1, #self.airports_destination)]
+ end
+ local text="Chosen destination airport: "..destination:GetName().." with ID "..destination:GetID()
+ self:E(destination:GetDesc())
+ env.info(myid..text)
+ MESSAGE:New(text, 60):ToAll()
+ return destination
+end
+
+
+--- Get all possible destination airports depending on departure position.
+-- The list is sorted w.r.t. distance to departure position.
+-- @param #RAT self
+-- @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.
+function RAT:_GetDestinations(q, minrange, maxrange)
+ local absolutemin=10000 -- Absolute minimum is 10 km.
+ minrange=minrange or absolutemin -- Default min is absolute min.
+ maxrange=maxrange or 10000000 -- Default max 10,000 km.
+ -- Ensure that minrange is always > 10 km to ensure the destination != departure.
+ minrange=math.max(absolutemin, minrange)
+ -- loop over all friendly airports
+ for _,airport in pairs(self.airports) do
+ local p=airport:GetCoordinate()
+ local distance=q:Get2DDistance(p)
+ -- check if distance form departure to destination is within min/max range
+ if distance>=minrange and distance<=maxrange then
+ table.insert(self.airports_destination, airport)
+ end
+ end
+ env.info(myid.."Number of possible destination airports = "..#self.airports_destination)
+ if #self.airports_destination > 1 then
+ --- Compare distance of destination airports.
+ -- @param Core.Point#COORDINATE a Coordinate of point a.
+ -- @param Core.Point#COORDINATE b Coordinate of point b.
+ -- @return #list Table sorted by distance.
+ local function compare(a,b)
+ local qa=q:Get2DDistance(a:GetCoordinate())
+ local qb=q:Get2DDistance(b:GetCoordinate())
+ return qa < qb
+ end
+ table.sort(self.airports_destination, compare)
+ end
+end
+
+
+--- Get all airports of the current map.
+-- @param #RAT self
+function RAT:_GetAirportsOfMap()
+ local _coalition
+
+ for i=0,2 do -- cycle coalition.side 0=NEUTRAL, 1=RED, 2=BLUE
+
+ -- set coalition
+ if i==0 then
+ _coalition=coalition.side.NEUTRAL
+ elseif i==1 then
+ _coalition=coalition.side.RED
+ elseif i==2 then
+ _coalition=coalition.side.BLUE
+ end
+
+ -- get airbases of coalition
+ local ab=coalition.getAirbases(i)
+
+ -- loop over airbases and put them in a table
+ for _,airbase in pairs(ab) do -- loop over airbases
+ local _id=airbase:getID()
+ local _p=airbase:getPosition().p
+ local _name=airbase:getName()
+ local _myab=AIRBASE:FindByName(_name)
+ local text="Airport ID = ".._myab:GetID().." and Name = ".._myab:GetName()..", Category = ".._myab:GetCategory()..", TypeName = ".._myab:GetTypeName()
+ env.info(myid..text)
+ table.insert(self.airports_map, _myab)
+ end
+
+ end
+end
+
+
+--- Get all "friendly" airports of the current map.
+-- @param #RAT self
+function RAT:_GetAirportsOfCoalition()
+ for _,coalition in pairs(self.ctable) do
+ for _,airport in pairs(self.airports_map) do
+ if airport:GetCoalition()==coalition then
+ airport:GetTypeName()
+ -- remember that planes cannot land on FARPs.
+ if not (self.category=="plane" and airport:GetTypeName()=="FARP") then
+ table.insert(self.airports, airport)
+ end
+ end
+ end
+ end
+
+ if #self.airports==0 then
+ local text="No possible departure/destination airports found!"
+ MESSAGE:New(text, 180):ToAll()
+ error(myid..text)
+ end
+end
+
+
+--- Create a waypoint that can be used with the Route command.
+-- @param #RAT self
+-- @param #string Type Type of waypoint. takeoff-cold, takeoff-hot, takeoff-runway, climb, cruise, descent, holding, land, landing.
+-- @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 #list RoutePoint Waypoint for DCS task route.
+function RAT:Waypoint(Type, Coord, Speed, Altitude, Airport)
+
+ -- set altitude to input parameter or take y of 3D-coordinate
+ local _Altitude=Altitude or Coord.y
+
+ --TODO: _Type should be generalized to Grouptemplate.Type
+ --TODO: Only use _alttype="BARO" and add landheight for _alttype="RADIO". Don't know if "BARO" really works atm.
+
+ -- convert type and action in DCS format
+ local _Action=nil
+ local _AID=nil
+ local _hold=nil
+ local _Type=nil
+ local _alttype="RADIO"
+ if Type:lower()=="takeoff-cold" or Type:lower()=="cold" then
+ -- take-off with engine off
+ _Type="TakeOffParking"
+ _Altitude = 2
+ _alttype="RADIO"
+ _AID = Airport:GetID()
+ elseif Type:lower()=="takeoff-hot" or Type:lower()=="hot" then
+ -- take-off with engine on
+ _Type="TakeOffParkingHot"
+ _AID = Airport:GetID()
+ _Altitude = 2
+ _alttype="RADIO"
+ elseif Type:lower()=="takeoff-runway" or Type:lower()=="runway" then
+ -- take-off from runway
+ _Type="TakeOff"
+ _AID = Airport:GetID()
+ _Altitude = 2
+ _alttype="RADIO"
+ elseif Type:lower()=="climb" or Type:lower()=="cruise" then
+ _Type="Turning Point"
+ _Action="Turning Point"
+ _alttype="BARO"
+ elseif Type:lower()=="descent" then
+ _Type="Turning Point"
+ _Action="Fly Over Point"
+ _alttype="RADIO"
+ elseif Type:lower()=="holding" then
+ _Type="Turning Point"
+ _Action="Fly Over Point"
+ _alttype="RADIO"
+ local P1= {x=Coord.x, y=Coord.z}
+ _hold=self:_TaskHolding(P1, Altitude, Speed)
+ elseif Type:lower()=="landing" or Type:lower()=="land" then
+ _Type="Land"
+ _Action="Landing"
+ _alttype="RADIO"
+ _Altitude = 2
+ _AID = Airport:GetID()
+ else
+ error("Unknown waypoint type in RAT:Waypoint function!")
+ _Type="Turning Point"
+ _Action="Turning Point"
+ _alttype="RADIO"
+ end
+
+ -- some debug info about input parameters
+ if self.debug then
+ local at="unknown (oops!)"
+ if _alttype=="BARO" then
+ at="ASL"
+ elseif _alttype=="RADIO" then
+ at="AGL"
+ end
+ local text=string.format("\nType: %s.\n", Type)
+ if Action then
+ text=text..string.format("Action: %s.\n", tostring(Action))
+ end
+ 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)
+ text=text..string.format("Speed = %6.1f m/s = %6.1f km/h = %6.1f knots.\n", Speed, Speed*3.6, Speed*1.94384)
+ text=text..string.format("Altitude = %6.1f m "..at..".\n", Altitude)
+ if Airport then
+ text=text..string.format("Destination airport = %s with ID %i.", Airport:GetName(), Airport:GetID())
+ else
+ text=text..string.format("No airport specified.")
+ end
+ local debugmessage=false
+ if debugmessage then
+ MESSAGE:New(text, 30, "RAT Waypoint Debug"):ToAll()
+ end
+ env.info(myid..text)
+ end
+
+ -- define waypoint
+ local RoutePoint = {}
+ -- coordinates and altitude
+ RoutePoint.x = Coord.x
+ RoutePoint.y = Coord.z
+ RoutePoint.alt = _Altitude
+ -- altitude type: BARO=ASL or RADIO=AGL
+ RoutePoint.alt_type = _alttype
+ -- type
+ RoutePoint.type = _Type or nil
+ RoutePoint.action = _Action or nil
+ -- speed in m/s
+ RoutePoint.speed = Speed
+ RoutePoint.speed_locked = true
+ -- ETA (not used)
+ --TODO: ETA check if this makes the DCS bug go away
+ --RoutePoint.ETA=nil
+ RoutePoint.ETA_locked=true
+ -- waypoint name (only for the mission editor)
+ RoutePoint.name="RAT waypoint"
+ if _AID then
+ RoutePoint.airdromeId=_AID
+ end
+ -- properties
+ RoutePoint.properties = {
+ ["vnav"] = 1,
+ ["scale"] = 0,
+ ["angle"] = 0,
+ ["vangle"] = 0,
+ ["steer"] = 2,
+ }
+ -- task
+ if Type:lower()=="holding" then
+ RoutePoint.task=_hold
+ else
+ RoutePoint.task = {}
+ RoutePoint.task.id = "ComboTask"
+ RoutePoint.task.params = {}
+ RoutePoint.task.params.tasks = {}
+ end
+ -- return the waypoint
+ return RoutePoint
+end
+
+
+--- Set takeoff type. Starting cold at airport, starting hot at airport, starting at runway, starting in the air or randomly select one of the previous.
+-- Default is "takeoff-hot" for a start at airport with engines already running.
+-- @param #RAT self
+-- @param #string type Type can be "takeoff-cold" or "cold", "takeoff-hot" or "hot", "takeoff-runway" or "runway", "air", "random".
+function RAT:SetTakeoff(type)
+ -- all possible types
+ local types={"takeoff-cold", "takeoff-hot", "takeoff-runway"}
+ local _Type
+ if type:lower()=="takeoff-cold" or type:lower()=="cold" then
+ _Type="takeoff-cold"
+ elseif type:lower()=="takeoff-hot" or type:lower()=="hot" then
+ _Type="takeoff-hot"
+ elseif type:lower()=="takeoff-runway" or type:lower()=="runway" then
+ _Type="takeoff-runway"
+ elseif type:lower()=="air" then
+ --TODO: not implemented yet
+ _Type="air"
+ elseif type:lower()=="random" then
+ _Type=types[math.random(1, #types)]
+ else
+ _Type="takeoff-hot"
+ end
+ self.takeoff=_Type
+end
+
+--- Orbit at a specified position at a specified alititude with a specified speed.
+-- @param #RAT self
+-- @param Dcs.DCSTypes#Vec2 P1 The point to hold the position.
+-- @param #number Altitude The altitude AGL to hold the position.
+-- @param #number Speed The speed flying when holding the position in m/s.
+-- @return Dcs.DCSTasking.Task#Task
+function RAT:_TaskHolding(P1, Altitude, Speed)
+ local LandHeight = land.getHeight(P1)
+
+ --TODO: Add duration of holding. Otherwise it will hold until fuel is emtpy.
+
+ -- second point is 10 km north of P1
+ --TODO: randomize P1
+ local P2={}
+ P2.x=P1.x
+ P2.y=P1.y+10000
+ local DCSTask = {
+ id = 'Orbit',
+ params = {
+ pattern = AI.Task.OrbitPattern.RACE_TRACK,
+ point = P1,
+ point2 = P2,
+ speed = Speed,
+ altitude = Altitude + LandHeight
+ }
+ }
+
+ return DCSTask
+end
+
+
+--- Provide information about the assigned flightplan.
+-- @param #RAT self
+-- @param #list waypoints Waypoints of the flight plan.
+-- @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=""
+ if comment then
+ text=comment.."\n"
+ end
+ text=text..string.format("Number of waypoints = %i\n", #waypoints)
+ -- 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)
+ end
+ -- info on distance between waypoints
+ local total=0.0
+ for i=1,#waypoints-1 do
+ local point1=waypoints[i]
+ local point2=waypoints[i+1]
+ local x1=point1.x
+ local y1=point1.y
+ local x2=point2.x
+ local y2=point2.y
+ 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)
+ end
+ text=text..string.format("Total distance = %6.1f km", total/1000)
+
+ -- send message
+ env.info(text)
+ if self.debug then
+ MESSAGE:New(text, 60):ToAll()
+ end
+
+ -- return total route length in meters
+ return total
+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
+-- @return Wrapper.Airport#AIRBASE Departure airbase.
+-- @return Wrapper.Airport#AIRBASE Destination airbase.
+-- @return #table Table of flight plan waypoints.
+function RAT:_SetRoute()
+
+ -- unit conversions
+ local ft2meter=0.305
+ local kmh2ms=0.278
+ local FL2m=30.48
+ local nm2km=1.852
+
+ --TODO: Add case where we don't have a departure airport but rather a zone!
+
+ -- DEPARTURE AIRPORT
+ -- set departure airport
+ local departure=self:_SetDeparture()
+
+ -- coordinates
+ local Pdeparture=departure:GetCoordinate()
+
+ -- height ASL if departure
+ local H_departure=Pdeparture.y
+
+ -- DESTINATION AIRPORT
+ -- get all destination airports in reach and at least 10 km away from departure
+ self:_GetDestinations(Pdeparture, self.mindist, self.range)
+ local destination=self:_SetDestination()
+
+ if destination:GetName()==departure:GetName() then
+ local text="Destination and departure aiport are identical: "..destination:GetName().." with ID "..destination:GetID()
+ MESSAGE:New(text, 120):ToAll()
+ error(myid..text)
+ end
+
+ -- coordinates of destination
+ local Pdestination=destination:GetCoordinate()
+ -- height ASL of destination
+ local H_destination=Pdestination.y
+
+ -- DESCENT/HOLDING POINT
+ -- get a random point between 10 and 20 km away from the destination
+ local Vholding
+ if self.category=="plane" then
+ Vholding=destination:GetCoordinate():GetRandomVec2InRadius(20000, 10000)
+ else
+ -- for helos we set 500 to 1000 m
+ Vholding=destination:GetCoordinate():GetRandomVec2InRadius(1000, 500)
+ end
+ local Pholding=COORDINATE:NewFromVec2(Vholding)
+
+ -- holding point altitude
+ local h_holding
+ if self.category=="plane" then
+ h_holding=1000
+ else
+ h_holding=100
+ end
+ env.info(myid.."h_holding before = "..h_holding)
+ h_holding=self:_Randomize(h_holding, 0.2)
+ env.info(myid.."h_holding after = "..h_holding)
+
+ -- distance from holding point to destination
+ local d_holding=Pholding:Get2DDistance(Pdestination)
+
+ -- GENERAL
+ -- heading from departure to holding point of destination
+ local heading=self:_Course(Pdeparture, Pholding) -- heading from departure to destination
+
+ -- total distance between departure and holding point + last bit to destination
+ local d_total=Pdeparture:Get2DDistance(Pholding)
+
+ -- CLIMB and DESCENT angles
+ local AlphaClimb=self.aircraft.AlphaClimb --=self:_Randomize(self.aircraft.AlphaClimb, 0.1)
+ local AlphaDescent=self.aircraft.AlphaDescent --self:_Randomize(self.aircraft.AlphaDescent, 0.1)
+
+ --CRUISE
+ -- set min/max cruise altitudes
+ local FLmax
+ local FLmin
+ local FLcruise=self.aircraft.FLcruise
+ if self.category=="plane" then
+ -- min cruise alt is above 100 meter above holding point
+ FLmin=math.max(H_departure, H_destination+h_holding)
+ -- quick check if the distance between the two airports is large enough to
+ -- actually reach the desired FL and then descent again at the given climb/descent rates.
+ FLmax=self:_FLmax(AlphaClimb, AlphaDescent, d_total, H_departure)
+ if FLmin>FLmax then
+ FLmin=FLmax*0.8
+ end
+ if FLcruise>FLmax then
+ FLcruise=FLmax*0.9
+ end
+ else
+ -- for helicopters we take cruise alt between 50 to 1000 meters above ground
+ FLmin=math.max(H_departure, H_destination)+50
+ FLmax=math.max(H_departure, H_destination)+1000
+ end
+ -- set randomized cruise altitude: default +-50% but limited
+ FLcruise=self:_Randomize(FLcruise, 0.5, FLmin, FLmax)
+ -- check that we are not above 90% of serice ceiling
+ FLcruise=math.min(FLcruise, self.aircraft.ceiling*0.9)
+
+
+ --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(AlphaClimb)
+ -- time of climb in seconds
+ local t_climb=h_climb/self.aircraft.Vclimb
+
+ -- DESCENT
+ -- height difference for descent form cruise alt to holding point
+ local h_descent=FLcruise-H_destination-h_holding
+ -- x-distance of descent part
+ local d_descent=math.abs(h_descent/math.tan(AlphaDescent))
+
+ -- CRUISE
+ local d_cruise=d_total-d_climb-d_descent
+
+ -- debug message
+ local text=string.format("Route distances:\n")
+ text=text..string.format("d_climb = %6.1f km\n", d_climb/1000)
+ text=text..string.format("d_cruise = %6.1f km\n", d_cruise/1000)
+ text=text..string.format("d_descent = %6.1f km\n", d_descent/1000)
+ text=text..string.format("d_holding = %6.1f km\n", d_holding/1000)
+ text=text..string.format("d_total = %6.1f km\n", d_total/1000)
+ text=text..string.format("Route heights:\n")
+ text=text..string.format("H_departure = %6.1f m ASL\n", H_departure)
+ text=text..string.format("H_destination = %6.1f m ASL\n", H_destination)
+ text=text..string.format("h_climb = %6.1f m AGL\n", h_climb)
+ 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("FLmin = %6.1f m ASL\n", FLmin)
+ text=text..string.format("FLmax = %6.1f m ASL\n", FLmax)
+ text=text..string.format("FLcruise = %6.1f m ASL\n", FLcruise)
+ text=text..string.format("h_descent = %6.1f m\n", h_descent)
+ text=text..string.format("h_holding = %6.1f m AGL\n", h_holding)
+ text=text..string.format("Heading = %6.1f Degrees", heading)
+ env.info(myid..text)
+ if self.debug then
+ MESSAGE:New(text, 180):ToAll()
+ end
+
+ -- coordinates of route from depature (0) to cruise (1) to descent (2) to holing (3) to destination (4)
+ local c0=Pdeparture
+ local c1=c0:Translate(d_climb, heading)
+ local c2=c1:Translate(d_cruise, heading)
+ local c3=c2:Translate(d_descent, heading)
+ local c3=Pholding
+ local c4=Pdestination
+
+ -- convert coordinates into route waypoints
+ --TODO: modify this for air start
+ local wp0=self:Waypoint(self.takeoff, c0, self.aircraft.Vmin, 2, departure)
+ local wp1=self:Waypoint("climb", c1, self.aircraft.Vmax, FLcruise)
+ local wp2=self:Waypoint("cruise", c2, self.aircraft.Vcruise, FLcruise)
+ --TODO: add the possibility for a holing point, i.e. we circle a bit before final approach.
+ local wp3=self:Waypoint("descent", c3, self.aircraft.Vmin, h_holding)
+ --local wp3=self:Waypoint("holding", c3, self.aircraft.Vmin, h_holding)
+ local wp4=self:Waypoint("landing", c4, self.aircraft.Vmin, 2, destination)
+
+ -- set waypoints
+ local waypoints = {wp0, wp1, wp2, wp3, wp4}
+
+ -- some info on the route as message
+ self:_Routeinfo(waypoints, "Waypoint info in set_route:")
+
+ -- return departure, destination and waypoints
+ return departure, destination, waypoints
+
+end
+
+--- Calculate the max flight level for a given distance and fixed climb and descent rates.
+-- In other words we have a distance between two airports and want to know how high we
+-- can climb before we must descent again to arrive at the destination without any level/cruising part.
+-- @param #RAT self
+-- @param #number alpha Angle of climb [rad].
+-- @param #number beta Angle of descent [rad].
+-- @param #number d Distance between the two airports [m].
+-- @param #number h0 Height [m] of departure airport. Note we implicitly assume that the height difference between departure and destination is negligible.
+-- @return #number Maximal flight level in meters.
+function RAT:_FLmax(alpha, beta, d, h0)
+-- solve ASA triangle for one side (d) and two adjacent angles (alpha, beta) given
+ local gamma=math.rad(180)-alpha-beta
+ local a=d*math.sin(alpha)/math.sin(gamma)
+ local b=d*math.sin(beta)/math.sin(gamma)
+ local h1=b*math.sin(alpha)
+ local h2=a*math.sin(beta)
+ local FL2m=30.48
+ local text=string.format("FLmax = FL%3.0f = %6.1f m.\n", h1/FL2m, h1)
+ text=text..string.format("FLmax = FL%3.0f = %6.1f m.", h2/FL2m, h2)
+ env.info(myid..text)
+ return b*math.sin(alpha)+h0
+end
+
+
+--- Modifies the template of the group to be spawned.
+-- In particular a template can be spawned at an airbase or in the air at a certain point.
+-- Also adds the waypoints to the template, which circumvents the DCS "landing bug".
+-- @param #RAT self
+-- @param Wrapper.Airbase#AIRBASE Airbase The @{Airbase} where to spawn the group.
+-- @param #number TakeoffAltitude (optional) The altitude above the ground.
+function RAT:_ModifySpawnTemplate(Airbase, waypoints)
+
+ -- point of Airbase
+ local PointVec3 = Airbase:GetPointVec3()
+
+
+ if self:_GetSpawnIndex( self.SpawnIndex + 1 ) then
+ local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate
+
+ if SpawnTemplate then
+ self:E(SpawnTemplate)
+
+ -- Translate the position of the Group Template to the Vec3.
+ for UnitID = 1, #SpawnTemplate.units do
+ self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
+ local UnitTemplate = SpawnTemplate.units[UnitID]
+ local SX = UnitTemplate.x
+ local SY = UnitTemplate.y
+ local BX = SpawnTemplate.route.points[1].x
+ local BY = SpawnTemplate.route.points[1].y
+ local TX = PointVec3.x + ( SX - BX )
+ local TY = PointVec3.z + ( SY - BY )
+ SpawnTemplate.units[UnitID].x = TX
+ SpawnTemplate.units[UnitID].y = TY
+ SpawnTemplate.units[UnitID].alt = PointVec3.y + 2
+ self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
+ end
+
+ -- Copy waypoints into spawntemplate.
+ -- this way we avoid the "landing bug" DCS currently has :)
+ for i,wp in ipairs(waypoints) do
+ SpawnTemplate.route.points[i]=wp
+ end
+
+ --TODO: Is this really necessary. Should already be defined in _Waypoints() function
+ SpawnTemplate.route.points[1].x = PointVec3.x
+ SpawnTemplate.route.points[1].y = PointVec3.z
+ SpawnTemplate.route.points[1].alt = PointVec3.y + 2
+ --SpawnTemplate.route.points[1].airdromeId = Airbase:GetID()
+ --SpawnTemplate.route.points[1].type = GROUPTEMPLATE.Takeoff[Takeoff]
+
+ SpawnTemplate.x = PointVec3.x
+ SpawnTemplate.y = PointVec3.z
+
+ -- Update modified template for spawn group.
+ self.SpawnGroups[self.SpawnIndex].SpawnTemplate=SpawnTemplate
+ self:E(SpawnTemplate)
+ end
+ end
+end
+
+
+--- Create a table with the valid coalitions for departure and destination airports.
+-- @param #RAT self
+function RAT:_SetCoalitionTable()
+ -- get all possible departures/destinations depending on coalition
+ if self.friendly=="all" then
+ self.ctable={coalition.side.BLUE, coalition.side.RED, coalition.side.NEUTRAL}
+ elseif self.friendly=="blue" then
+ self.ctable={coalition.side.BLUE, coalition.side.NEUTRAL}
+ elseif self.friendly=="blueonly" then
+ self.ctable={coalition.side.BLUE}
+ elseif self.friendly=="red" then
+ self.ctable={coalition.side.RED, coalition.side.NEUTRAL}
+ elseif self.friendly=="redonly" then
+ self.ctable={coalition.side.RED}
+ elseif self.friendly=="neutral" then
+ self.ctable={coalition.side.NEUTRAL}
+ elseif self.friendly=="same" then
+ self.ctable={self.coalition, coalition.side.NEUTRAL}
+ elseif self.friendly=="sameonly" then
+ self.ctable={self.coalition}
+ else
+ self.ctable={self.coalition, coalition.side.NEUTRAL}
+ end
+ -- debug info
+ self:E({"Coalition table: ", self.ctable})
+end
+
+
+--- Convert 3D waypoint to 3D coordinate. x==>x, alt==>y, y==>z
+-- @param #RAT self
+-- @param #table wp Containing .x, .y and .alt
+-- @return Core.Point#COORDINATE Coordinates of the waypoint.
+function RAT:_WP2COORD(wp)
+ local _coord = COORDINATE:New(wp.x, wp.alt, wp.y) -- Core.Point#COORDINATE
+ return _coord
+end
+
+
+---Determine the heading from point a to point b.
+--@param #RAT self
+--@param Core.Point#COORDINATE a Point from.
+--@param Core.Point#COORDINATE b Point to.
+--@return #number Heading/angle in degrees.
+function RAT:_Course(a,b)
+ 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
+ return angle
+end
+
+
+--- Randomize a value by a certain amount.
+-- @param #RAT self
+-- @param #number value The value which should be randomized
+-- @param #number fac Randomization factor.
+-- @param #number lower (Optional) Lower limit of the returned value.
+-- @param #number upper (Optional) Upper limit of the returned value.
+-- @return #number Randomized value.
+-- @usage _Randomize(100, 0.1) returns a value between 90 and 110, i.e. a plus/minus ten percent variation.
+-- @usage _Randomize(100, 0.5, nil, 120) returns a value between 50 and 120, i.e. a plus/minus fivty percent variation with upper bound 120.
+function RAT:_Randomize(value, fac, lower, upper)
+ local r=math.random(value-value*fac,value+value*fac)
+ if upper and r>upper then
+ r=upper
+ end
+ if lower and r
+
+
+
+
+
+
+
+
+
+
---- @type SPAWN.SpawnZoneTable
--- @list SpawnZone
-
---- Creates the main object to spawn a @{Group} defined in the DCS ME.
--- @param #SPAWN self
--- @param #string SpawnTemplatePrefix is the name of the Group in the ME that defines the Template. Each new group will have the name starting with SpawnTemplatePrefix.
--- @return #SPAWN
--- @usage
--- -- NATO helicopters engaging in the battle field.
--- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' )
--- @usage local Plane = SPAWN:New( "Plane" ) -- Creates a new local variable that can initiate new planes with the name "Plane#ddd" using the template "Plane" as defined within the ME.
-function SPAWN:New( SpawnTemplatePrefix )
- local self = BASE:Inherit( self, BASE:New() ) -- #SPAWN
- self:F( { SpawnTemplatePrefix } )
+
+
- local TemplateGroup = Group.getByName( SpawnTemplatePrefix )
- if TemplateGroup then
- self.SpawnTemplatePrefix = SpawnTemplatePrefix
- self.SpawnIndex = 0
- self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart.
- self.AliveUnits = 0 -- Contains the counter how many units are currently alive
- self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not.
- self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!!
- self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning.
- self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts.
- self.SpawnInitLimit = false -- By default, no InitLimit
- self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time.
- self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned.
- self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false.
- self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned.
- self.AIOnOff = true -- The AI is on by default when spawning a group.
- self.SpawnUnControlled = false
- self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name.
- self.DelayOnOff = false -- No intial delay when spawning the first group.
- self.Grouping = nil -- No grouping
-
- self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned.
- else
- error( "SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" )
- end
-
- self:SetEventPriority( 5 )
-
- return self
-end
-
---- Creates a new SPAWN instance to create new groups based on the defined template and using a new alias for each new group.
--- @param #SPAWN self
--- @param #string SpawnTemplatePrefix is the name of the Group in the ME that defines the Template.
--- @param #string SpawnAliasPrefix is the name that will be given to the Group at runtime.
--- @return #SPAWN
--- @usage
--- -- NATO helicopters engaging in the battle field.
--- Spawn_BE_KA50 = SPAWN:NewWithAlias( 'BE KA-50@RAMP-Ground Defense', 'Helicopter Attacking a City' )
--- @usage local PlaneWithAlias = SPAWN:NewWithAlias( "Plane", "Bomber" ) -- Creates a new local variable that can instantiate new planes with the name "Bomber#ddd" using the template "Plane" as defined within the ME.
-function SPAWN:NewWithAlias( SpawnTemplatePrefix, SpawnAliasPrefix )
- local self = BASE:Inherit( self, BASE:New() )
- self:F( { SpawnTemplatePrefix, SpawnAliasPrefix } )
- local TemplateGroup = Group.getByName( SpawnTemplatePrefix )
- if TemplateGroup then
- self.SpawnTemplatePrefix = SpawnTemplatePrefix
- self.SpawnAliasPrefix = SpawnAliasPrefix
- self.SpawnIndex = 0
- self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart.
- self.AliveUnits = 0 -- Contains the counter how many units are currently alive
- self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not.
- self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!!
- self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning.
- self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts.
- self.SpawnInitLimit = false -- By default, no InitLimit
- self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time.
- self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned.
- self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false.
- self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned.
- self.AIOnOff = true -- The AI is on by default when spawning a group.
- self.SpawnUnControlled = false
- self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name.
- self.DelayOnOff = false -- No intial delay when spawning the first group.
- self.Grouping = nil
-
- self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned.
- else
- error( "SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" )
- end
-
- self:SetEventPriority( 5 )
-
- return self
-end
-
-
---- Limits the Maximum amount of Units that can be alive at the same time, and the maximum amount of groups that can be spawned.
--- Note that this method is exceptionally important to balance the performance of the mission. Depending on the machine etc, a mission can only process a maximum amount of units.
--- If the time interval must be short, but there should not be more Units or Groups alive than a maximum amount of units, then this method should be used...
--- When a @{#SPAWN.New} is executed and the limit of the amount of units alive is reached, then no new spawn will happen of the group, until some of these units of the spawn object will be destroyed.
--- @param #SPAWN self
--- @param #number SpawnMaxUnitsAlive The maximum amount of units that can be alive at runtime.
--- @param #number SpawnMaxGroups The maximum amount of groups that can be spawned. When the limit is reached, then no more actual spawns will happen of the group.
--- This parameter is useful to define a maximum amount of airplanes, ground troops, helicopters, ships etc within a supply area.
--- This parameter accepts the value 0, which defines that there are no maximum group limits, but there are limits on the maximum of units that can be alive at the same time.
--- @return #SPAWN self
--- @usage
--- -- NATO helicopters engaging in the battle field.
--- -- This helicopter group consists of one Unit. So, this group will SPAWN maximum 2 groups simultaneously within the DCSRTE.
--- -- There will be maximum 24 groups spawned during the whole mission lifetime.
--- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitLimit( 2, 24 )
-function SPAWN:InitLimit( SpawnMaxUnitsAlive, SpawnMaxGroups )
- self:F( { self.SpawnTemplatePrefix, SpawnMaxUnitsAlive, SpawnMaxGroups } )
-
- self.SpawnInitLimit = true
- self.SpawnMaxUnitsAlive = SpawnMaxUnitsAlive -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time.
- self.SpawnMaxGroups = SpawnMaxGroups -- The maximum amount of groups that can be spawned.
-
- for SpawnGroupID = 1, self.SpawnMaxGroups do
- self:_InitializeSpawnGroups( SpawnGroupID )
- end
-
- return self
-end
-
---- Keeps the unit names as defined within the mission editor,
--- but note that anything after a # mark is ignored,
--- and any spaces before and after the resulting name are removed.
--- IMPORTANT! This method MUST be the first used after :New !!!
--- @param #SPAWN self
--- @return #SPAWN self
-function SPAWN:InitKeepUnitNames()
- self:F( )
-
- self.SpawnInitKeepUnitNames = true
- return self
-end
-
-
---- Randomizes the defined route of the SpawnTemplatePrefix group in the ME. This is very useful to define extra variation of the behaviour of groups.
--- @param #SPAWN self
--- @param #number SpawnStartPoint is the waypoint where the randomization begins.
--- Note that the StartPoint = 0 equaling the point where the group is spawned.
--- @param #number SpawnEndPoint is the waypoint where the randomization ends counting backwards.
--- This parameter is useful to avoid randomization to end at a waypoint earlier than the last waypoint on the route.
--- @param #number SpawnRadius is the radius in meters in which the randomization of the new waypoints, with the original waypoint of the original template located in the middle ...
--- @param #number SpawnHeight (optional) Specifies the **additional** height in meters that can be added to the base height specified at each waypoint in the ME.
--- @return #SPAWN
--- @usage
--- -- NATO helicopters engaging in the battle field.
--- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP).
--- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter.
--- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters.
--- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 )
-function SPAWN:InitRandomizeRoute( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight )
- self:F( { self.SpawnTemplatePrefix, SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight } )
-
- self.SpawnRandomizeRoute = true
- self.SpawnRandomizeRouteStartPoint = SpawnStartPoint
- self.SpawnRandomizeRouteEndPoint = SpawnEndPoint
- self.SpawnRandomizeRouteRadius = SpawnRadius
- self.SpawnRandomizeRouteHeight = SpawnHeight
-
- for GroupID = 1, self.SpawnMaxGroups do
- self:_RandomizeRoute( GroupID )
- end
-
- return self
-end
-
---- Randomizes the position of @{Group}s that are spawned within a **radius band**, given an Outer and Inner radius, from the point that the spawn happens.
--- @param #SPAWN self
--- @param #boolean RandomizePosition If true, SPAWN will perform the randomization of the @{Group}s position between a given outer and inner radius.
--- @param Dcs.DCSTypes#Distance OuterRadius (optional) The outer radius in meters where the new group will be spawned.
--- @param Dcs.DCSTypes#Distance InnerRadius (optional) The inner radius in meters where the new group will NOT be spawned.
--- @return #SPAWN
-function SPAWN:InitRandomizePosition( RandomizePosition, OuterRadius, InnerRadius )
- self:F( { self.SpawnTemplatePrefix, RandomizePosition, OuterRadius, InnerRadius } )
-
- self.SpawnRandomizePosition = RandomizePosition or false
- self.SpawnRandomizePositionOuterRadius = OuterRadius or 0
- self.SpawnRandomizePositionInnerRadius = InnerRadius or 0
-
- for GroupID = 1, self.SpawnMaxGroups do
- self:_RandomizeRoute( GroupID )
- end
- return self
-end
-
---- Randomizes the UNITs that are spawned within a radius band given an Outer and Inner radius.
--- @param #SPAWN self
--- @param #boolean RandomizeUnits If true, SPAWN will perform the randomization of the @{UNIT}s position within the group between a given outer and inner radius.
--- @param Dcs.DCSTypes#Distance OuterRadius (optional) The outer radius in meters where the new group will be spawned.
--- @param Dcs.DCSTypes#Distance InnerRadius (optional) The inner radius in meters where the new group will NOT be spawned.
--- @return #SPAWN
--- @usage
--- -- NATO helicopters engaging in the battle field.
--- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP).
--- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter.
--- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters.
--- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 )
-function SPAWN:InitRandomizeUnits( RandomizeUnits, OuterRadius, InnerRadius )
- self:F( { self.SpawnTemplatePrefix, RandomizeUnits, OuterRadius, InnerRadius } )
-
- self.SpawnRandomizeUnits = RandomizeUnits or false
- self.SpawnOuterRadius = OuterRadius or 0
- self.SpawnInnerRadius = InnerRadius or 0
-
- for GroupID = 1, self.SpawnMaxGroups do
- self:_RandomizeRoute( GroupID )
- end
+
- return self
-end
+ MOOSE/Spawn.lua at master · FlightControl-Master/MOOSE
+
+
+
---- This method is rather complicated to understand. But I'll try to explain.
--- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor,
--- but they will all follow the same Template route and have the same prefix name.
--- In other words, this method randomizes between a defined set of groups the template to be used for each new spawn of a group.
--- @param #SPAWN self
--- @param #string SpawnTemplatePrefixTable A table with the names of the groups defined within the mission editor, from which one will be choosen when a new group will be spawned.
--- @return #SPAWN
--- @usage
--- -- NATO Tank Platoons invading Gori.
--- -- Choose between 13 different 'US Tank Platoon' configurations for each new SPAWN the Group to be spawned for the
--- -- 'US Tank Platoon Left', 'US Tank Platoon Middle' and 'US Tank Platoon Right' SpawnTemplatePrefixes.
--- -- Each new SPAWN will randomize the route, with a defined time interval of 200 seconds with 40% time variation (randomization) and
--- -- with a limit set of maximum 12 Units alive simulteneously and 150 Groups to be spawned during the whole mission.
--- Spawn_US_Platoon = { 'US Tank Platoon 1', 'US Tank Platoon 2', 'US Tank Platoon 3', 'US Tank Platoon 4', 'US Tank Platoon 5',
--- 'US Tank Platoon 6', 'US Tank Platoon 7', 'US Tank Platoon 8', 'US Tank Platoon 9', 'US Tank Platoon 10',
--- 'US Tank Platoon 11', 'US Tank Platoon 12', 'US Tank Platoon 13' }
--- Spawn_US_Platoon_Left = SPAWN:New( 'US Tank Platoon Left' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 )
--- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 )
--- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 )
-function SPAWN:InitRandomizeTemplate( SpawnTemplatePrefixTable )
- self:F( { self.SpawnTemplatePrefix, SpawnTemplatePrefixTable } )
+
+
- self.SpawnTemplatePrefixTable = SpawnTemplatePrefixTable
- self.SpawnRandomizeTemplate = true
-
- for SpawnGroupID = 1, self.SpawnMaxGroups do
- self:_RandomizeTemplate( SpawnGroupID )
- end
-
- return self
-end
-
---- When spawning a new group, make the grouping of the units according the InitGrouping setting.
--- @param #SPAWN self
--- @param #number Grouping Indicates the maximum amount of units in the group.
--- @return #SPAWN
-function SPAWN:InitGrouping( Grouping ) -- R2.2
- self:F( { self.SpawnTemplatePrefix, Grouping } )
-
- self.SpawnGrouping = Grouping
-
- return self
-end
-
-
-
---TODO: Add example.
---- This method provides the functionality to randomize the spawning of the Groups at a given list of zones of different types.
--- @param #SPAWN self
--- @param #table SpawnZoneTable A table with @{Zone} objects. If this table is given, then each spawn will be executed within the given list of @{Zone}s objects.
--- @return #SPAWN
--- @usage
--- -- NATO Tank Platoons invading Gori.
--- -- Choose between 3 different zones for each new SPAWN the Group to be executed, regardless of the zone type.
-function SPAWN:InitRandomizeZones( SpawnZoneTable )
- self:F( { self.SpawnTemplatePrefix, SpawnZoneTable } )
-
- self.SpawnZoneTable = SpawnZoneTable
- self.SpawnRandomizeZones = true
-
- for SpawnGroupID = 1, self.SpawnMaxGroups do
- self:_RandomizeZones( SpawnGroupID )
- end
+
+
+
+
+
- return self
-end
+
+
+
+
+
+
+
+
+
-
---- For planes and helicopters, when these groups go home and land on their home airbases and farps, they normally would taxi to the parking spot, shut-down their engines and wait forever until the Group is removed by the runtime environment.
--- This method is used to re-spawn automatically (so no extra call is needed anymore) the same group after it has landed.
--- This will enable a spawned group to be re-spawned after it lands, until it is destroyed...
--- Note: When the group is respawned, it will re-spawn from the original airbase where it took off.
--- So ensure that the routes for groups that respawn, always return to the original airbase, or players may get confused ...
--- @param #SPAWN self
--- @return #SPAWN self
--- @usage
--- -- RU Su-34 - AI Ship Attack
--- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically.
--- SpawnRU_SU34 = SPAWN:New( 'TF1 RU Su-34 Krymsk@AI - Attack Ships' ):Schedule( 2, 3, 1800, 0.4 ):SpawnUncontrolled():InitRandomizeRoute( 1, 1, 3000 ):RepeatOnEngineShutDown()
-function SPAWN:InitRepeat()
- self:F( { self.SpawnTemplatePrefix, self.SpawnIndex } )
-
- self.Repeat = true
- self.RepeatOnEngineShutDown = false
- self.RepeatOnLanding = true
-
- return self
-end
-
---- Respawn group after landing.
--- @param #SPAWN self
--- @return #SPAWN self
-function SPAWN:InitRepeatOnLanding()
- self:F( { self.SpawnTemplatePrefix } )
-
- self:InitRepeat()
- self.RepeatOnEngineShutDown = false
- self.RepeatOnLanding = true
-
- return self
-end
+
---- Respawn after landing when its engines have shut down.
--- @param #SPAWN self
--- @return #SPAWN self
-function SPAWN:InitRepeatOnEngineShutDown()
- self:F( { self.SpawnTemplatePrefix } )
-
- self:InitRepeat()
- self.RepeatOnEngineShutDown = true
- self.RepeatOnLanding = false
-
- return self
-end
-
-
---- CleanUp groups when they are still alive, but inactive.
--- When groups are still alive and have become inactive due to damage and are unable to contribute anything, then this group will be removed at defined intervals in seconds.
--- @param #SPAWN self
--- @param #string SpawnCleanUpInterval The interval to check for inactive groups within seconds.
--- @return #SPAWN self
--- @usage Spawn_Helicopter:CleanUp( 20 ) -- CleanUp the spawning of the helicopters every 20 seconds when they become inactive.
-function SPAWN:InitCleanUp( SpawnCleanUpInterval )
- self:F( { self.SpawnTemplatePrefix, SpawnCleanUpInterval } )
-
- self.SpawnCleanUpInterval = SpawnCleanUpInterval
- self.SpawnCleanUpTimeStamps = {}
-
- local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup()
- self:T( { "CleanUp Scheduler:", SpawnGroup } )
-
- --self.CleanUpFunction = routines.scheduleFunction( self._SpawnCleanUpScheduler, { self }, timer.getTime() + 1, SpawnCleanUpInterval )
- self.CleanUpScheduler = SCHEDULER:New( self, self._SpawnCleanUpScheduler, {}, 1, SpawnCleanUpInterval, 0.2 )
- return self
-end
-
-
-
---- Makes the groups visible before start (like a batallion).
--- The method will take the position of the group as the first position in the array.
--- @param #SPAWN self
--- @param #number SpawnAngle The angle in degrees how the groups and each unit of the group will be positioned.
--- @param #number SpawnWidth The amount of Groups that will be positioned on the X axis.
--- @param #number SpawnDeltaX The space between each Group on the X-axis.
--- @param #number SpawnDeltaY The space between each Group on the Y-axis.
--- @return #SPAWN self
--- @usage
--- -- Define an array of Groups.
--- Spawn_BE_Ground = SPAWN:New( 'BE Ground' ):InitLimit( 2, 24 ):InitArray( 90, "Diamond", 10, 100, 50 )
-function SPAWN:InitArray( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY )
- self:F( { self.SpawnTemplatePrefix, SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY } )
-
- self.SpawnVisible = true -- When the first Spawn executes, all the Groups need to be made visible before start.
-
- local SpawnX = 0
- local SpawnY = 0
- local SpawnXIndex = 0
- local SpawnYIndex = 0
-
- for SpawnGroupID = 1, self.SpawnMaxGroups do
- self:T( { SpawnX, SpawnY, SpawnXIndex, SpawnYIndex } )
-
- self.SpawnGroups[SpawnGroupID].Visible = true
- self.SpawnGroups[SpawnGroupID].Spawned = false
-
- SpawnXIndex = SpawnXIndex + 1
- if SpawnWidth and SpawnWidth ~= 0 then
- if SpawnXIndex >= SpawnWidth then
- SpawnXIndex = 0
- SpawnYIndex = SpawnYIndex + 1
- end
- end
-
- local SpawnRootX = self.SpawnGroups[SpawnGroupID].SpawnTemplate.x
- local SpawnRootY = self.SpawnGroups[SpawnGroupID].SpawnTemplate.y
-
- self:_TranslateRotate( SpawnGroupID, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle )
-
- self.SpawnGroups[SpawnGroupID].SpawnTemplate.lateActivation = true
- self.SpawnGroups[SpawnGroupID].SpawnTemplate.visible = true
-
- self.SpawnGroups[SpawnGroupID].Visible = true
-
- self:HandleEvent( EVENTS.Birth, self._OnBirth )
- self:HandleEvent( EVENTS.Dead, self._OnDeadOrCrash )
- self:HandleEvent( EVENTS.Crash, self._OnDeadOrCrash )
- if self.Repeat then
- self:HandleEvent( EVENTS.Takeoff, self._OnTakeOff )
- self:HandleEvent( EVENTS.Land, self._OnLand )
- end
- if self.RepeatOnEngineShutDown then
- self:HandleEvent( EVENTS.EngineShutdown, self._OnEngineShutDown )
- end
-
- self.SpawnGroups[SpawnGroupID].Group = _DATABASE:Spawn( self.SpawnGroups[SpawnGroupID].SpawnTemplate )
-
- SpawnX = SpawnXIndex * SpawnDeltaX
- SpawnY = SpawnYIndex * SpawnDeltaY
- end
-
- return self
-end
-
-do -- AI methods
- --- Turns the AI On or Off for the @{Group} when spawning.
- -- @param #SPAWN self
- -- @param #boolean AIOnOff A value of true sets the AI On, a value of false sets the AI Off.
- -- @return #SPAWN The SPAWN object
- function SPAWN:InitAIOnOff( AIOnOff )
- self.AIOnOff = AIOnOff
- return self
- end
+
+
+
+
+
+
+
+
+
+
+
+
- --- Turns the AI On for the @{Group} when spawning.
- -- @param #SPAWN self
- -- @return #SPAWN The SPAWN object
- function SPAWN:InitAIOn()
-
- return self:InitAIOnOff( true )
- end
-
- --- Turns the AI Off for the @{Group} when spawning.
- -- @param #SPAWN self
- -- @return #SPAWN The SPAWN object
- function SPAWN:InitAIOff()
-
- return self:InitAIOnOff( false )
- end
-end -- AI methods
+
-do -- Delay methods
- --- Turns the Delay On or Off for the first @{Group} scheduled spawning.
- -- The default value is that for scheduled spawning, there is an initial delay when spawning the first @{Group}.
- -- @param #SPAWN self
- -- @param #boolean DelayOnOff A value of true sets the Delay On, a value of false sets the Delay Off.
- -- @return #SPAWN The SPAWN object
- function SPAWN:InitDelayOnOff( DelayOnOff )
-
- self.DelayOnOff = DelayOnOff
- return self
- end
-
- --- Turns the Delay On for the @{Group} when spawning.
- -- @param #SPAWN self
- -- @return #SPAWN The SPAWN object
- function SPAWN:InitDelayOn()
-
- return self:InitDelayOnOff( true )
- end
-
- --- Turns the Delay Off for the @{Group} when spawning.
- -- @param #SPAWN self
- -- @return #SPAWN The SPAWN object
- function SPAWN:InitDelayOff()
-
- return self:InitDelayOnOff( false )
- end
+
+
-end -- Delay methods
+
---- Will spawn a group based on the internal index.
--- Note: Uses @{DATABASE} module defined in MOOSE.
--- @param #SPAWN self
--- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions.
-function SPAWN:Spawn()
- self:F( { self.SpawnTemplatePrefix, self.SpawnIndex, self.AliveUnits } )
- return self:SpawnWithIndex( self.SpawnIndex + 1 )
-end
+
---- Will re-spawn a group based on a given index.
--- Note: Uses @{DATABASE} module defined in MOOSE.
--- @param #SPAWN self
--- @param #string SpawnIndex The index of the group to be spawned.
--- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions.
-function SPAWN:ReSpawn( SpawnIndex )
- self:F( { self.SpawnTemplatePrefix, SpawnIndex } )
-
- if not SpawnIndex then
- SpawnIndex = 1
- end
--- TODO: This logic makes DCS crash and i don't know why (yet).
- local SpawnGroup = self:GetGroupFromIndex( SpawnIndex )
- local WayPoints = SpawnGroup and SpawnGroup.WayPoints or nil
- if SpawnGroup then
- local SpawnDCSGroup = SpawnGroup:GetDCSObject()
- if SpawnDCSGroup then
- SpawnGroup:Destroy()
- end
- end
+
- local SpawnGroup = self:SpawnWithIndex( SpawnIndex )
- if SpawnGroup and WayPoints then
- -- If there were WayPoints set, then Re-Execute those WayPoints!
- SpawnGroup:WayPointInitialize( WayPoints )
- SpawnGroup:WayPointExecute( 1, 5 )
- end
-
- if SpawnGroup.ReSpawnFunction then
- SpawnGroup:ReSpawnFunction()
- end
-
- SpawnGroup:ResetEvents()
-
- return SpawnGroup
-end
+
---- Will spawn a group with a specified index number.
--- Uses @{DATABASE} global object defined in MOOSE.
--- @param #SPAWN self
--- @param #string SpawnIndex The index of the group to be spawned.
--- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions.
-function SPAWN:SpawnWithIndex( SpawnIndex )
- self:F2( { SpawnTemplatePrefix = self.SpawnTemplatePrefix, SpawnIndex = SpawnIndex, AliveUnits = self.AliveUnits, SpawnMaxGroups = self.SpawnMaxGroups } )
-
- if self:_GetSpawnIndex( SpawnIndex ) then
-
- if self.SpawnGroups[self.SpawnIndex].Visible then
- self.SpawnGroups[self.SpawnIndex].Group:Activate()
- else
+
+
- local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate
- self:T( SpawnTemplate.name )
+
+
+
+
+
+
+
+
+
+
+
- if SpawnTemplate.CategoryID == Group.Category.HELICOPTER or SpawnTemplate.CategoryID == Group.Category.AIRPLANE then
- if SpawnTemplate.route.points[1].type == "TakeOffParking" then
- SpawnTemplate.uncontrolled = self.SpawnUnControlled
- end
- end
- end
-
- self:HandleEvent( EVENTS.Birth, self._OnBirth )
- self:HandleEvent( EVENTS.Dead, self._OnDeadOrCrash )
- self:HandleEvent( EVENTS.Crash, self._OnDeadOrCrash )
- if self.Repeat then
- self:HandleEvent( EVENTS.Takeoff, self._OnTakeOff )
- self:HandleEvent( EVENTS.Land, self._OnLand )
- end
- if self.RepeatOnEngineShutDown then
- self:HandleEvent( EVENTS.EngineShutdown, self._OnEngineShutDown )
- end
-
- self.SpawnGroups[self.SpawnIndex].Group = _DATABASE:Spawn( SpawnTemplate )
-
- local SpawnGroup = self.SpawnGroups[self.SpawnIndex].Group -- Wrapper.Group#GROUP
-
- --TODO: Need to check if this function doesn't need to be scheduled, as the group may not be immediately there!
- if SpawnGroup then
+
+
+
- SpawnGroup:SetAIOnOff( self.AIOnOff )
- end
- self:T3( SpawnTemplate.name )
-
- -- If there is a SpawnFunction hook defined, call it.
- if self.SpawnFunctionHook then
- -- delay calling this for .1 seconds so that it hopefully comes after the BIRTH event of the group.
- self.SpawnHookScheduler = SCHEDULER:New()
- self.SpawnHookScheduler:Schedule( nil, self.SpawnFunctionHook, { self.SpawnGroups[self.SpawnIndex].Group, unpack( self.SpawnFunctionArguments)}, 0.1 )
- -- self.SpawnFunctionHook( self.SpawnGroups[self.SpawnIndex].Group, unpack( self.SpawnFunctionArguments ) )
- end
- -- TODO: Need to fix this by putting an "R" in the name of the group when the group repeats.
- --if self.Repeat then
- -- _DATABASE:SetStatusGroup( SpawnTemplate.name, "ReSpawn" )
- --end
- end
-
- self.SpawnGroups[self.SpawnIndex].Spawned = true
- return self.SpawnGroups[self.SpawnIndex].Group
- else
- --self:E( { self.SpawnTemplatePrefix, "No more Groups to Spawn:", SpawnIndex, self.SpawnMaxGroups } )
- end
+
+
- return nil
-end
+
+
+
+
+
---- Spawns new groups at varying time intervals.
--- This is useful if you want to have continuity within your missions of certain (AI) groups to be present (alive) within your missions.
--- @param #SPAWN self
--- @param #number SpawnTime The time interval defined in seconds between each new spawn of new groups.
--- @param #number SpawnTimeVariation The variation to be applied on the defined time interval between each new spawn.
--- The variation is a number between 0 and 1, representing the %-tage of variation to be applied on the time interval.
--- @return #SPAWN self
--- @usage
--- -- NATO helicopters engaging in the battle field.
--- -- The time interval is set to SPAWN new helicopters between each 600 seconds, with a time variation of 50%.
--- -- The time variation in this case will be between 450 seconds and 750 seconds.
--- -- This is calculated as follows:
--- -- Low limit: 600 * ( 1 - 0.5 / 2 ) = 450
--- -- High limit: 600 * ( 1 + 0.5 / 2 ) = 750
--- -- Between these two values, a random amount of seconds will be choosen for each new spawn of the helicopters.
--- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):Schedule( 600, 0.5 )
-function SPAWN:SpawnScheduled( SpawnTime, SpawnTimeVariation )
- self:F( { SpawnTime, SpawnTimeVariation } )
+
+
+
+
+ New repository
+
- if SpawnTime ~= nil and SpawnTimeVariation ~= nil then
- local InitialDelay = 0
- if self.DelayOnOff == true then
- InitialDelay = math.random( SpawnTime - SpawnTime * SpawnTimeVariation, SpawnTime + SpawnTime * SpawnTimeVariation )
- end
- self.SpawnScheduler = SCHEDULER:New( self, self._Scheduler, {}, InitialDelay, SpawnTime, SpawnTimeVariation )
- end
+
+ Import repository
+
- return self
-end
+
+ New gist
+
---- Will re-start the spawning scheduler.
--- Note: This method is only required to be called when the schedule was stopped.
--- @param #SPAWN self
--- @return #SPAWN
-function SPAWN:SpawnScheduleStart()
- self:F( { self.SpawnTemplatePrefix } )
-
- self.SpawnScheduler:Start()
- return self
-end
-
---- Will stop the scheduled spawning scheduler.
--- @param #SPAWN self
--- @return #SPAWN
-function SPAWN:SpawnScheduleStop()
- self:F( { self.SpawnTemplatePrefix } )
-
- self.SpawnScheduler:Stop()
- return self
-end
+
+ New organization
+
---- Allows to place a CallFunction hook when a new group spawns.
--- The provided method will be called when a new group is spawned, including its given parameters.
--- The first parameter of the SpawnFunction is the @{Group#GROUP} that was spawned.
--- @param #SPAWN self
--- @param #function SpawnCallBackFunction The function to be called when a group spawns.
--- @param SpawnFunctionArguments A random amount of arguments to be provided to the function when the group spawns.
--- @return #SPAWN
--- @usage
--- -- Declare SpawnObject and call a function when a new Group is spawned.
--- local SpawnObject = SPAWN
--- :New( "SpawnObject" )
--- :InitLimit( 2, 10 )
--- :OnSpawnGroup(
--- function( SpawnGroup )
--- SpawnGroup:E( "I am spawned" )
--- end
--- )
--- :SpawnScheduled( 300, 0.3 )
-function SPAWN:OnSpawnGroup( SpawnCallBackFunction, ... )
- self:F( "OnSpawnGroup" )
- self.SpawnFunctionHook = SpawnCallBackFunction
- self.SpawnFunctionArguments = {}
- if arg then
- self.SpawnFunctionArguments = arg
- end
+
+
---- Will spawn a group at an airbase.
--- This method is mostly advisable to be used if you want to simulate spawning units at an airbase.
--- Note that each point in the route assigned to the spawning group is reset to the point of the spawn.
--- You can use the returned group to further define the route to be followed.
--- @param #SPAWN self
--- @param Wrapper.Airbase#AIRBASE Airbase The @{Airbase} where to spawn the group.
--- @param #SPAWN.Takeoff Takeoff (optional) The location and takeoff method. Default is Hot.
--- @param #number TakeoffAltitude (optional) The altitude above the ground.
--- @return Wrapper.Group#GROUP that was spawned.
--- @return #nil Nothing was spawned.
-function SPAWN:SpawnAtAirbase( Airbase, Takeoff, TakeoffAltitude ) -- R2.2
- self:E( { self.SpawnTemplatePrefix, Airbase, Takeoff, TakeoffAltitude } )
+
- local PointVec3 = Airbase:GetPointVec3()
- self:T2(PointVec3)
+
+
+
+
+
- Takeoff = Takeoff or SPAWN.Takeoff.Hot
-
- if self:_GetSpawnIndex( self.SpawnIndex + 1 ) then
-
- local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate
-
- if SpawnTemplate then
+
+
- self:T( { "Current point of ", self.SpawnTemplatePrefix, Airbase } )
- -- Translate the position of the Group Template to the Vec3.
- for UnitID = 1, #SpawnTemplate.units do
- self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
- local UnitTemplate = SpawnTemplate.units[UnitID]
- local SX = UnitTemplate.x
- local SY = UnitTemplate.y
- local BX = SpawnTemplate.route.points[1].x
- local BY = SpawnTemplate.route.points[1].y
- local TX = PointVec3.x + ( SX - BX )
- local TY = PointVec3.z + ( SY - BY )
- SpawnTemplate.units[UnitID].x = TX
- SpawnTemplate.units[UnitID].y = TY
- if Takeoff == GROUP.Takeoff.Air then
- SpawnTemplate.units[UnitID].alt = PointVec3.y + ( TakeoffAltitude or 200 )
- else
- SpawnTemplate.units[UnitID].alt = PointVec3.y + 10
- end
- self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
- end
- SpawnTemplate.route.points[1].x = PointVec3.x
- SpawnTemplate.route.points[1].y = PointVec3.z
- if Takeoff == GROUP.Takeoff.Air then
- SpawnTemplate.route.points[1].alt = PointVec3.y + ( TakeoffAltitude or 200 )
- else
- SpawnTemplate.route.points[1].alt = PointVec3.y + 10
- SpawnTemplate.route.points[1].airdromeId = Airbase:GetID()
- end
- SpawnTemplate.route.points[1].type = GROUPTEMPLATE.Takeoff[Takeoff]
+
+
+
+
+
+
+
+
+
+
+
+
+
- SpawnTemplate.x = PointVec3.x
- SpawnTemplate.y = PointVec3.z
-
- return self:SpawnWithIndex( self.SpawnIndex )
- end
- end
-
- return nil
-end
---- Will spawn a group from a Vec3 in 3D space.
--- This method is mostly advisable to be used if you want to simulate spawning units in the air, like helicopters or airplanes.
--- Note that each point in the route assigned to the spawning group is reset to the point of the spawn.
--- You can use the returned group to further define the route to be followed.
--- @param #SPAWN self
--- @param Dcs.DCSTypes#Vec3 Vec3 The Vec3 coordinates where to spawn the group.
--- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
--- @return Wrapper.Group#GROUP that was spawned.
--- @return #nil Nothing was spawned.
-function SPAWN:SpawnFromVec3( Vec3, SpawnIndex )
- self:F( { self.SpawnTemplatePrefix, Vec3, SpawnIndex } )
- local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 )
- self:T2(PointVec3)
- if SpawnIndex then
- else
- SpawnIndex = self.SpawnIndex + 1
- end
-
- if self:_GetSpawnIndex( SpawnIndex ) then
+
+
+
+
+
+
+
+
+
- local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate
-
- if SpawnTemplate then
+
+
+
- self:T( { "Current point of ", self.SpawnTemplatePrefix, Vec3 } )
+
-- * **Aaron**: Posed the idea for Group position randomization at SpawnInZone and make the Unit randomization separate from the Group randomization.
+
+
+
+
-- * [**Entropy**](https://forums.eagle.ru/member.php?u=111471), **Afinegan**: Came up with the requirement for AIOnOff().
+
+
+
+
--
+
+
+
+
-- ### Authors:
+
+
+
+
--
+
+
+
+
-- * **FlightControl**: Design & Programming
+
+
+
+
--
+
+
+
+
-- @module Spawn
+
+
+
+
+
+
+
+
+
----BASE:TraceClass("SPAWN")
+
+
+
+
+
+
+
+
+
+
+
+
+
+
--- SPAWN Class
+
+
+
+
-- @type SPAWN
+
+
+
+
-- @field ClassName
+
+
+
+
-- @field #string SpawnTemplatePrefix
+
+
+
+
-- @field #string SpawnAliasPrefix
+
+
+
+
-- @field #number AliveUnits
+
+
+
+
-- @field #number MaxAliveUnits
+
+
+
+
-- @field #number SpawnIndex
+
+
+
+
-- @field #number MaxAliveGroups
+
+
+
+
-- @field #SPAWN.SpawnZoneTable SpawnZoneTable
+
+
+
+
-- @extends Core.Base#BASE
+
+
+
+
+
+
+
+
+
+
+
+
+
+
--- # SPAWN class, extends @{Base#BASE}
+
+
+
+
--
+
+
+
+
-- The SPAWN class allows to spawn dynamically new groups.
+
+
+
+
-- Each SPAWN object needs to be have a related **template group** setup in the Mission Editor (ME),
+
+
+
+
-- which is a normal group with the **Late Activation** flag set.
+
+
+
+
-- This template group will never be activated in your mission.
+
+
+
+
-- SPAWN uses that **template group** to reference to all the characteristics
+
+
+
+
-- (air, ground, livery, unit composition, formation, skill level etc) of each new group to be spawned.
+
+
+
+
--
+
+
+
+
-- Therefore, when creating a SPAWN object, the @{#SPAWN.New} and @{#SPAWN.NewWithAlias} require
+
+
+
+
-- **the name of the template group** to be given as a string to those constructor methods.
+
+
+
+
--
+
+
+
+
-- Initialization settings can be applied on the SPAWN object,
+
+
+
+
-- which modify the behaviour or the way groups are spawned.
+
+
+
+
-- These initialization methods have the prefix **Init**.
+
+
+
+
-- There are also spawn methods with the prefix **Spawn** and will spawn new groups in various ways.
+
+
+
+
--
+
+
+
+
-- ### IMPORTANT! The methods with prefix **Init** must be used before any methods with prefix **Spawn** method are used, or unexpected results may appear!!!
+
+
+
+
--
+
+
+
+
-- Because SPAWN can spawn multiple groups of a template group,
+
+
+
+
-- SPAWN has an **internal index** that keeps track
+
+
+
+
-- which was the latest group that was spawned.
+
+
+
+
--
+
+
+
+
-- **Limits** can be set on how many groups can be spawn in each SPAWN object,
+
+
+
+
-- using the method @{#SPAWN.InitLimit}. SPAWN has 2 kind of limits:
+
+
+
+
--
+
+
+
+
-- * The maximum amount of @{Unit}s that can be **alive** at the same time...
+
+
+
+
-- * The maximum amount of @{Group}s that can be **spawned**... This is more of a **resource**-type of limit.
+
+
+
+
--
+
+
+
+
-- When new groups get spawned using the **Spawn** methods,
+
+
+
+
-- it will be evaluated whether any limits have been reached.
+
+
+
+
-- When no spawn limit is reached, a new group will be created by the spawning methods,
+
+
+
+
-- and the internal index will be increased with 1.
+
+
+
+
--
+
+
+
+
-- These limits ensure that your mission does not accidentally get flooded with spawned groups.
+
+
+
+
-- Additionally, it also guarantees that independent of the group composition,
+
+
+
+
-- at any time, the most optimal amount of groups are alive in your mission.
+
+
+
+
-- For example, if your template group has a group composition of 10 units, and you specify a limit of 100 units alive at the same time,
+
+
+
+
-- with unlimited resources = :InitLimit( 100, 0 ) and 10 groups are alive, but two groups have only one unit alive in the group,
+
+
+
+
-- then a sequent Spawn(Scheduled) will allow a new group to be spawned!!!
+
+
+
+
--
+
+
+
+
-- ### IMPORTANT!! If a limit has been reached, it is possible that a **Spawn** method returns **nil**, meaning, no @{Group} had been spawned!!!
+
+
+
+
--
+
+
+
+
-- Spawned groups get **the same name** as the name of the template group.
+
+
+
+
-- Spawned units in those groups keep _by default_ **the same name** as the name of the template group.
+
+
+
+
-- However, because multiple groups and units are created from the template group,
+
+
+
+
-- a suffix is added to each spawned group and unit.
+
+
+
+
--
+
+
+
+
-- Newly spawned groups will get the following naming structure at run-time:
+
+
+
+
--
+
+
+
+
-- 1. Spawned groups will have the name _GroupName_#_nnn_, where _GroupName_ is the name of the **template group**,
+
+
+
+
-- and _nnn_ is a **counter from 0 to 999**.
+
+
+
+
-- 2. Spawned units will have the name _GroupName_#_nnn_-_uu_,
+
+
+
+
-- where _uu_ is a **counter from 0 to 99** for each new spawned unit belonging to the group.
+
+
+
+
--
+
+
+
+
-- That being said, there is a way to keep the same unit names!
+
+
+
+
-- The method @{#SPAWN.InitKeepUnitNames}() will keep the same unit names as defined within the template group, thus:
+
+
+
+
--
+
+
+
+
-- 3. Spawned units will have the name _UnitName_#_nnn_-_uu_,
+
+
+
+
-- where _UnitName_ is the **unit name as defined in the template group*,
+
+
+
+
-- and _uu_ is a **counter from 0 to 99** for each new spawned unit belonging to the group.
+
+
+
+
--
+
+
+
+
-- Some **additional notes that need to be considered!!**:
+
+
+
+
--
+
+
+
+
-- * templates are actually groups defined within the mission editor, with the flag "Late Activation" set.
+
+
+
+
-- As such, these groups are never used within the mission, but are used by the @{#SPAWN} module.
+
+
+
+
-- * It is important to defined BEFORE you spawn new groups,
+
+
+
+
-- a proper initialization of the SPAWN instance is done with the options you want to use.
+
+
+
+
-- * When designing a mission, NEVER name groups using a "#" within the name of the group Spawn template(s),
+
+
+
+
-- or the SPAWN module logic won't work anymore.
+
+
+
+
--
+
+
+
+
-- ## SPAWN construction methods
+
+
+
+
--
+
+
+
+
-- Create a new SPAWN object with the @{#SPAWN.New}() or the @{#SPAWN.NewWithAlias}() methods:
+
+
+
+
--
+
+
+
+
-- * @{#SPAWN.New}(): Creates a new SPAWN object taking the name of the group that represents the GROUP template (definition).
+
+
+
+
-- * @{#SPAWN.NewWithAlias}(): Creates a new SPAWN object taking the name of the group that represents the GROUP template (definition), and gives each spawned @{Group} an different name.
+
+
+
+
--
+
+
+
+
-- It is important to understand how the SPAWN class works internally. The SPAWN object created will contain internally a list of groups that will be spawned and that are already spawned.
+
+
+
+
-- The initialization methods will modify this list of groups so that when a group gets spawned, ALL information is already prepared when spawning. This is done for performance reasons.
+
+
+
+
-- So in principle, the group list will contain all parameters and configurations after initialization, and when groups get actually spawned, this spawning can be done quickly and efficient.
+
+
+
+
--
+
+
+
+
-- ## SPAWN **Init**ialization methods
+
+
+
+
--
+
+
+
+
-- A spawn object will behave differently based on the usage of **initialization** methods, which all start with the **Init** prefix:
+
+
+
+
--
+
+
+
+
-- ### Unit Names
+
+
+
+
--
+
+
+
+
-- * @{#SPAWN.InitKeepUnitNames}(): Keeps the unit names as defined within the mission editor, but note that anything after a # mark is ignored, and any spaces before and after the resulting name are removed. IMPORTANT! This method MUST be the first used after :New !!!
+
+
+
+
--
+
+
+
+
-- ### Route randomization
+
+
+
+
--
+
+
+
+
-- * @{#SPAWN.InitRandomizeRoute}(): Randomize the routes of spawned groups, and for air groups also optionally the height.
+
+
+
+
--
+
+
+
+
-- ### Group composition randomization
+
+
+
+
--
+
+
+
+
-- * @{#SPAWN.InitRandomizeTemplate}(): Randomize the group templates so that when a new group is spawned, a random group template is selected from one of the templates defined.
+
+
+
+
--
+
+
+
+
-- ### Uncontrolled
+
+
+
+
--
+
+
+
+
-- * @{#SPAWN.InitUnControlled}(): Spawn plane groups uncontrolled.
+
+
+
+
--
+
+
+
+
-- ### Array formation
+
+
+
+
--
+
+
+
+
-- * @{#SPAWN.InitArray}(): Make groups visible before they are actually activated, and order these groups like a batallion in an array.
+
+
+
+
--
+
+
+
+
-- ### Position randomization
+
+
+
+
--
+
+
+
+
-- * @{#SPAWN.InitRandomizePosition}(): Randomizes the position of @{Group}s that are spawned within a **radius band**, given an Outer and Inner radius, from the point that the spawn happens.
+
+
+
+
-- * @{#SPAWN.InitRandomizeUnits}(): Randomizes the @{Unit}s in the @{Group} that is spawned within a **radius band**, given an Outer and Inner radius.
+
+
+
+
-- * @{#SPAWN.InitRandomizeZones}(): Randomizes the spawning between a predefined list of @{Zone}s that are declared using this function. Each zone can be given a probability factor.
+
+
+
+
--
+
+
+
+
-- ### Enable / Disable AI when spawning a new @{Group}
+
+
+
+
--
+
+
+
+
-- * @{#SPAWN.InitAIOn}(): Turns the AI On when spawning the new @{Group} object.
+
+
+
+
-- * @{#SPAWN.InitAIOff}(): Turns the AI Off when spawning the new @{Group} object.
+
+
+
+
-- * @{#SPAWN.InitAIOnOff}(): Turns the AI On or Off when spawning the new @{Group} object.
+
+
+
+
--
+
+
+
+
-- ### Limit scheduled spawning
+
+
+
+
--
+
+
+
+
-- * @{#SPAWN.InitLimit}(): Limits the amount of groups that can be alive at the same time and that can be dynamically spawned.
+
+
+
+
--
+
+
+
+
-- ### Delay initial scheduled spawn
+
+
+
+
--
+
+
+
+
-- * @{#SPAWN.InitDelayOnOff}(): Turns the inital delay On/Off when scheduled spawning the first @{Group} object.
+
+
+
+
-- * @{#SPAWN.InitDelayOn}(): Turns the inital delay On when scheduled spawning the first @{Group} object.
+
+
+
+
-- * @{#SPAWN.InitDelayOff}(): Turns the inital delay Off when scheduled spawning the first @{Group} object.
+
+
+
+
--
+
+
+
+
-- ### Repeat spawned @{Group}s upon landing
+
+
+
+
--
+
+
+
+
-- * @{#SPAWN.InitRepeat}() or @{#SPAWN.InitRepeatOnLanding}(): This method is used to re-spawn automatically the same group after it has landed.
+
+
+
+
-- * @{#SPAWN.InitRepeatOnEngineShutDown}(): This method is used to re-spawn automatically the same group after it has landed and it shuts down the engines at the ramp.
+
+
+
+
--
+
+
+
+
--
+
+
+
+
-- ## SPAWN **Spawn** methods
+
+
+
+
--
+
+
+
+
-- Groups can be spawned at different times and methods:
+
+
+
+
--
+
+
+
+
-- ### **Single** spawning methods
+
+
+
+
--
+
+
+
+
-- * @{#SPAWN.Spawn}(): Spawn one new group based on the last spawned index.
+
+
+
+
-- * @{#SPAWN.ReSpawn}(): Re-spawn a group based on a given index.
+
+
+
+
-- * @{#SPAWN.SpawnFromVec3}(): Spawn a new group from a Vec3 coordinate. (The group will can be spawned at a point in the air).
+
+
+
+
-- * @{#SPAWN.SpawnFromVec2}(): Spawn a new group from a Vec2 coordinate. (The group will be spawned at land height ).
+
+
+
+
-- * @{#SPAWN.SpawnFromStatic}(): Spawn a new group from a structure, taking the position of a @{Static}.
+
+
+
+
-- * @{#SPAWN.SpawnFromUnit}(): Spawn a new group taking the position of a @{Unit}.
+
+
+
+
-- * @{#SPAWN.SpawnInZone}(): Spawn a new group in a @{Zone}.
+
+
+
+
--
+
+
+
+
-- Note that @{#SPAWN.Spawn} and @{#SPAWN.ReSpawn} return a @{GROUP#GROUP.New} object, that contains a reference to the DCSGroup object.
+
+
+
+
-- You can use the @{GROUP} object to do further actions with the DCSGroup.
+
+
+
+
--
+
+
+
+
-- ### **Scheduled** spawning methods
+
+
+
+
--
+
+
+
+
-- * @{#SPAWN.SpawnScheduled}(): Spawn groups at scheduled but randomized intervals.
+
+
+
+
-- * @{#SPAWN.SpawnScheduledStart}(): Start or continue to spawn groups at scheduled time intervals.
+
+
+
+
-- * @{#SPAWN.SpawnScheduledStop}(): Stop the spawning of groups at scheduled time intervals.
+
+
+
+
--
+
+
+
+
--
+
+
+
+
--
+
+
+
+
-- ## Retrieve alive GROUPs spawned by the SPAWN object
+
+
+
+
--
+
+
+
+
-- The SPAWN class administers which GROUPS it has reserved (in stock) or has created during mission execution.
+
+
+
+
-- Every time a SPAWN object spawns a new GROUP object, a reference to the GROUP object is added to an internal table of GROUPS.
+
+
+
+
-- SPAWN provides methods to iterate through that internal GROUP object reference table:
+
+
+
+
--
+
+
+
+
-- * @{#SPAWN.GetFirstAliveGroup}(): Will find the first alive GROUP it has spawned, and return the alive GROUP object and the first Index where the first alive GROUP object has been found.
+
+
+
+
-- * @{#SPAWN.GetNextAliveGroup}(): Will find the next alive GROUP object from a given Index, and return a reference to the alive GROUP object and the next Index where the alive GROUP has been found.
+
+
+
+
-- * @{#SPAWN.GetLastAliveGroup}(): Will find the last alive GROUP object, and will return a reference to the last live GROUP object and the last Index where the last alive GROUP object has been found.
+
+
+
+
--
+
+
+
+
-- You can use the methods @{#SPAWN.GetFirstAliveGroup}() and sequently @{#SPAWN.GetNextAliveGroup}() to iterate through the alive GROUPS within the SPAWN object, and to actions... See the respective methods for an example.
+
+
+
+
-- The method @{#SPAWN.GetGroupFromIndex}() will return the GROUP object reference from the given Index, dead or alive...
+
+
+
+
--
+
+
+
+
-- ## Spawned cleaning of inactive groups
+
+
+
+
--
+
+
+
+
-- Sometimes, it will occur during a mission run-time, that ground or especially air objects get damaged, and will while being damged stop their activities, while remaining alive.
+
+
+
+
-- In such cases, the SPAWN object will just sit there and wait until that group gets destroyed, but most of the time it won't,
+
+
+
+
-- and it may occur that no new groups are or can be spawned as limits are reached.
+
+
+
+
-- To prevent this, a @{#SPAWN.InitCleanUp}() initialization method has been defined that will silently monitor the status of each spawned group.
+
+
+
+
-- Once a group has a velocity = 0, and has been waiting for a defined interval, that group will be cleaned or removed from run-time.
+
+
+
+
-- There is a catch however :-) If a damaged group has returned to an airbase within the coalition, that group will not be considered as "lost"...
+
+
+
+
-- In such a case, when the inactive group is cleaned, a new group will Re-spawned automatically.
+
+
+
+
-- This models AI that has succesfully returned to their airbase, to restart their combat activities.
+
+
+
+
-- Check the @{#SPAWN.InitCleanUp}() for further info.
+
+
+
+
--
+
+
+
+
-- ## Catch the @{Group} Spawn Event in a callback function!
+
+
+
+
--
+
+
+
+
-- When using the @{#SPAWN.SpawnScheduled)() method, new @{Group}s are created following the spawn time interval parameters.
+
+
+
+
-- When a new @{Group} is spawned, you maybe want to execute actions with that group spawned at the spawn event.
+
+
+
+
-- The SPAWN class supports this functionality through the method @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ),
+
+
+
+
-- which takes a function as a parameter that you can define locally.
+
+
+
+
-- Whenever a new @{Group} is spawned, the given function is called, and the @{Group} that was just spawned, is given as a parameter.
+
+
+
+
-- As a result, your spawn event handling function requires one parameter to be declared, which will contain the spawned @{Group} object.
+
+
+
+
-- A coding example is provided at the description of the @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ) method.
+
+
+
+
--
+
+
+
+
-- ## Delay the initial spawning
+
+
+
+
--
+
+
+
+
-- When using the @{#SPAWN.SpawnScheduled)() method, the default behaviour of this method will be that it will spawn the initial (first) @{Group}
+
+
+
+
-- immediately when :SpawnScheduled() is initiated. The methods @{#SPAWN.InitDelayOnOff}() and @{#SPAWN.InitDelayOn}() can be used to
+
+
+
+
-- activate a delay before the first @{Group} is spawned. For completeness, a method @{#SPAWN.InitDelayOff}() is also available, that
+
+
+
+
-- can be used to switch off the initial delay. Because there is no delay by default, this method would only be used when a
+
+
+
+
-- @{#SPAWN.SpawnScheduledStop}() ; @{#SPAWN.SpawnScheduledStart}() sequence would have been used.
+
+
+
+
--
+
+
+
+
--
+
+
+
+
-- @field #SPAWN SPAWN
+
+
+
+
--
+
+
+
+
SPAWN = {
+
+
+
+
ClassName ="SPAWN",
+
+
+
+
SpawnTemplatePrefix =nil,
+
+
+
+
SpawnAliasPrefix =nil,
+
+
+
+
}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
--- Enumerator for spawns at airbases
+
+
+
+
-- @type SPAWN.Takeoff
+
+
+
+
-- @extends Wrapper.Group#GROUP.Takeoff
+
+
+
+
+
+
+
+
+
--- @field #SPAWN.Takeoff Takeoff
+
+
+
+
SPAWN.Takeoff= GROUP.Takeoff
+
+
+
+
+
+
+
+
+
+
+
+
+
+
--- @type SPAWN.SpawnZoneTable
+
+
+
+
-- @list <Core.Zone#ZONE_BASE> SpawnZone
+
+
+
+
+
+
+
+
+
+
+
+
+
+
--- Creates the main object to spawn a @{Group} defined in the DCS ME.
+
+
+
+
-- @param #SPAWN self
+
+
+
+
-- @param #string SpawnTemplatePrefix is the name of the Group in the ME that defines the Template. Each new group will have the name starting with SpawnTemplatePrefix.
+
+
+
+
-- @return #SPAWN
+
+
+
+
-- @usage
+
+
+
+
-- -- NATO helicopters engaging in the battle field.
-- @usage local Plane = SPAWN:New( "Plane" ) -- Creates a new local variable that can initiate new planes with the name "Plane#ddd" using the template "Plane" as defined within the ME.
local TemplateGroup = Group.getByName( SpawnTemplatePrefix )
+
+
+
+
if TemplateGroup then
+
+
+
+
self.SpawnTemplatePrefix= SpawnTemplatePrefix
+
+
+
+
self.SpawnIndex=0
+
+
+
+
self.SpawnCount=0-- The internal counter of the amount of spawning the has happened since SpawnStart.
+
+
+
+
self.AliveUnits=0-- Contains the counter how many units are currently alive
+
+
+
+
self.SpawnIsScheduled=false-- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not.
+
+
+
+
self.SpawnTemplate=self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!!
+
+
+
+
self.Repeat=false-- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning.
+
+
+
+
self.UnControlled=false-- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts.
+
+
+
+
self.SpawnInitLimit=false-- By default, no InitLimit
+
+
+
+
self.SpawnMaxUnitsAlive=0-- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time.
+
+
+
+
self.SpawnMaxGroups=0-- The maximum amount of groups that can be spawned.
+
+
+
+
self.SpawnRandomize=false-- Sets the randomization flag of new Spawned units to false.
+
+
+
+
self.SpawnVisible=false-- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned.
+
+
+
+
self.AIOnOff=true-- The AI is on by default when spawning a group.
+
+
+
+
self.SpawnUnControlled=false
+
+
+
+
self.SpawnInitKeepUnitNames=false-- Overwrite unit names by default with group name.
+
+
+
+
self.DelayOnOff=false-- No intial delay when spawning the first group.
+
+
+
+
self.Grouping=nil-- No grouping
+
+
+
+
+
+
+
+
+
self.SpawnGroups= {} -- Array containing the descriptions of each Group to be Spawned.
+
+
+
+
else
+
+
+
+
error( "SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '".. SpawnTemplatePrefix .."'" )
+
+
+
+
end
+
+
+
+
+
+
+
+
+
self:SetEventPriority( 5 )
+
+
+
+
+
+
+
+
+
returnself
+
+
+
+
end
+
+
+
+
+
+
+
+
+
--- Creates a new SPAWN instance to create new groups based on the defined template and using a new alias for each new group.
+
+
+
+
-- @param #SPAWN self
+
+
+
+
-- @param #string SpawnTemplatePrefix is the name of the Group in the ME that defines the Template.
+
+
+
+
-- @param #string SpawnAliasPrefix is the name that will be given to the Group at runtime.
+
+
+
+
-- @return #SPAWN
+
+
+
+
-- @usage
+
+
+
+
-- -- NATO helicopters engaging in the battle field.
-- @usage local PlaneWithAlias = SPAWN:NewWithAlias( "Plane", "Bomber" ) -- Creates a new local variable that can instantiate new planes with the name "Bomber#ddd" using the template "Plane" as defined within the ME.
+
+
+
+
function SPAWN:NewWithAlias( SpawnTemplatePrefix, SpawnAliasPrefix )
local TemplateGroup = Group.getByName( SpawnTemplatePrefix )
+
+
+
+
if TemplateGroup then
+
+
+
+
self.SpawnTemplatePrefix= SpawnTemplatePrefix
+
+
+
+
self.SpawnAliasPrefix= SpawnAliasPrefix
+
+
+
+
self.SpawnIndex=0
+
+
+
+
self.SpawnCount=0-- The internal counter of the amount of spawning the has happened since SpawnStart.
+
+
+
+
self.AliveUnits=0-- Contains the counter how many units are currently alive
+
+
+
+
self.SpawnIsScheduled=false-- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not.
+
+
+
+
self.SpawnTemplate=self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!!
+
+
+
+
self.Repeat=false-- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning.
+
+
+
+
self.UnControlled=false-- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts.
+
+
+
+
self.SpawnInitLimit=false-- By default, no InitLimit
+
+
+
+
self.SpawnMaxUnitsAlive=0-- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time.
+
+
+
+
self.SpawnMaxGroups=0-- The maximum amount of groups that can be spawned.
+
+
+
+
self.SpawnRandomize=false-- Sets the randomization flag of new Spawned units to false.
+
+
+
+
self.SpawnVisible=false-- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned.
+
+
+
+
self.AIOnOff=true-- The AI is on by default when spawning a group.
+
+
+
+
self.SpawnUnControlled=false
+
+
+
+
self.SpawnInitKeepUnitNames=false-- Overwrite unit names by default with group name.
+
+
+
+
self.DelayOnOff=false-- No intial delay when spawning the first group.
+
+
+
+
self.Grouping=nil
+
+
+
+
+
+
+
+
+
self.SpawnGroups= {} -- Array containing the descriptions of each Group to be Spawned.
+
+
+
+
else
+
+
+
+
error( "SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '".. SpawnTemplatePrefix .."'" )
+
+
+
+
end
+
+
+
+
+
+
+
+
self:SetEventPriority( 5 )
+
+
+
+
+
+
+
+
returnself
+
+
+
+
end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
--- Limits the Maximum amount of Units that can be alive at the same time, and the maximum amount of groups that can be spawned.
+
+
+
+
-- Note that this method is exceptionally important to balance the performance of the mission. Depending on the machine etc, a mission can only process a maximum amount of units.
+
+
+
+
-- If the time interval must be short, but there should not be more Units or Groups alive than a maximum amount of units, then this method should be used...
+
+
+
+
-- When a @{#SPAWN.New} is executed and the limit of the amount of units alive is reached, then no new spawn will happen of the group, until some of these units of the spawn object will be destroyed.
+
+
+
+
-- @param #SPAWN self
+
+
+
+
-- @param #number SpawnMaxUnitsAlive The maximum amount of units that can be alive at runtime.
+
+
+
+
-- @param #number SpawnMaxGroups The maximum amount of groups that can be spawned. When the limit is reached, then no more actual spawns will happen of the group.
+
+
+
+
-- This parameter is useful to define a maximum amount of airplanes, ground troops, helicopters, ships etc within a supply area.
+
+
+
+
-- This parameter accepts the value 0, which defines that there are no maximum group limits, but there are limits on the maximum of units that can be alive at the same time.
+
+
+
+
-- @return #SPAWN self
+
+
+
+
-- @usage
+
+
+
+
-- -- NATO helicopters engaging in the battle field.
+
+
+
+
-- -- This helicopter group consists of one Unit. So, this group will SPAWN maximum 2 groups simultaneously within the DCSRTE.
+
+
+
+
-- -- There will be maximum 24 groups spawned during the whole mission lifetime.
self.SpawnMaxUnitsAlive= SpawnMaxUnitsAlive -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time.
+
+
+
+
self.SpawnMaxGroups= SpawnMaxGroups -- The maximum amount of groups that can be spawned.
+
+
+
+
+
+
+
+
for SpawnGroupID =1, self.SpawnMaxGroupsdo
+
+
+
+
self:_InitializeSpawnGroups( SpawnGroupID )
+
+
+
+
end
+
+
+
+
+
+
+
+
+
returnself
+
+
+
+
end
+
+
+
+
+
+
+
+
+
--- Keeps the unit names as defined within the mission editor,
+
+
+
+
-- but note that anything after a # mark is ignored,
+
+
+
+
-- and any spaces before and after the resulting name are removed.
+
+
+
+
-- IMPORTANT! This method MUST be the first used after :New !!!
+
+
+
+
-- @param #SPAWN self
+
+
+
+
-- @return #SPAWN self
+
+
+
+
function SPAWN:InitKeepUnitNames()
+
+
+
+
self:F( )
+
+
+
+
+
+
+
+
+
self.SpawnInitKeepUnitNames=true
+
+
+
+
+
+
+
+
returnself
+
+
+
+
end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
--- Randomizes the defined route of the SpawnTemplatePrefix group in the ME. This is very useful to define extra variation of the behaviour of groups.
+
+
+
+
-- @param #SPAWN self
+
+
+
+
-- @param #number SpawnStartPoint is the waypoint where the randomization begins.
+
+
+
+
-- Note that the StartPoint = 0 equaling the point where the group is spawned.
+
+
+
+
-- @param #number SpawnEndPoint is the waypoint where the randomization ends counting backwards.
+
+
+
+
-- This parameter is useful to avoid randomization to end at a waypoint earlier than the last waypoint on the route.
+
+
+
+
-- @param #number SpawnRadius is the radius in meters in which the randomization of the new waypoints, with the original waypoint of the original template located in the middle ...
+
+
+
+
-- @param #number SpawnHeight (optional) Specifies the **additional** height in meters that can be added to the base height specified at each waypoint in the ME.
+
+
+
+
-- @return #SPAWN
+
+
+
+
-- @usage
+
+
+
+
-- -- NATO helicopters engaging in the battle field.
+
+
+
+
-- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP).
+
+
+
+
-- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter.
+
+
+
+
-- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters.
--- Randomizes the position of @{Group}s that are spawned within a **radius band**, given an Outer and Inner radius, from the point that the spawn happens.
+
+
+
+
-- @param #SPAWN self
+
+
+
+
-- @param #boolean RandomizePosition If true, SPAWN will perform the randomization of the @{Group}s position between a given outer and inner radius.
+
+
+
+
-- @param Dcs.DCSTypes#Distance OuterRadius (optional) The outer radius in meters where the new group will be spawned.
+
+
+
+
-- @param Dcs.DCSTypes#Distance InnerRadius (optional) The inner radius in meters where the new group will NOT be spawned.
+
+
+
+
-- @return #SPAWN
+
+
+
+
function SPAWN:InitRandomizePosition( RandomizePosition, OuterRadius, InnerRadius )
--- Randomizes the UNITs that are spawned within a radius band given an Outer and Inner radius.
+
+
+
+
-- @param #SPAWN self
+
+
+
+
-- @param #boolean RandomizeUnits If true, SPAWN will perform the randomization of the @{UNIT}s position within the group between a given outer and inner radius.
+
+
+
+
-- @param Dcs.DCSTypes#Distance OuterRadius (optional) The outer radius in meters where the new group will be spawned.
+
+
+
+
-- @param Dcs.DCSTypes#Distance InnerRadius (optional) The inner radius in meters where the new group will NOT be spawned.
+
+
+
+
-- @return #SPAWN
+
+
+
+
-- @usage
+
+
+
+
-- -- NATO helicopters engaging in the battle field.
+
+
+
+
-- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP).
+
+
+
+
-- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter.
+
+
+
+
-- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters.
--- This method is rather complicated to understand. But I'll try to explain.
+
+
+
+
-- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor,
+
+
+
+
-- but they will all follow the same Template route and have the same prefix name.
+
+
+
+
-- In other words, this method randomizes between a defined set of groups the template to be used for each new spawn of a group.
+
+
+
+
-- @param #SPAWN self
+
+
+
+
-- @param #string SpawnTemplatePrefixTable A table with the names of the groups defined within the mission editor, from which one will be choosen when a new group will be spawned.
+
+
+
+
-- @return #SPAWN
+
+
+
+
-- @usage
+
+
+
+
-- -- NATO Tank Platoons invading Gori.
+
+
+
+
-- -- Choose between 13 different 'US Tank Platoon' configurations for each new SPAWN the Group to be spawned for the
+
+
+
+
-- -- 'US Tank Platoon Left', 'US Tank Platoon Middle' and 'US Tank Platoon Right' SpawnTemplatePrefixes.
+
+
+
+
-- -- Each new SPAWN will randomize the route, with a defined time interval of 200 seconds with 40% time variation (randomization) and
+
+
+
+
-- -- with a limit set of maximum 12 Units alive simulteneously and 150 Groups to be spawned during the whole mission.
+
+
+
+
-- Spawn_US_Platoon = { 'US Tank Platoon 1', 'US Tank Platoon 2', 'US Tank Platoon 3', 'US Tank Platoon 4', 'US Tank Platoon 5',
+
+
+
+
-- 'US Tank Platoon 6', 'US Tank Platoon 7', 'US Tank Platoon 8', 'US Tank Platoon 9', 'US Tank Platoon 10',
+
+
+
+
-- 'US Tank Platoon 11', 'US Tank Platoon 12', 'US Tank Platoon 13' }
--- When spawning a new group, make the grouping of the units according the InitGrouping setting.
+
+
+
+
-- @param #SPAWN self
+
+
+
+
-- @param #number Grouping Indicates the maximum amount of units in the group.
+
+
+
+
-- @return #SPAWN
+
+
+
+
function SPAWN:InitGrouping( Grouping ) -- R2.2
+
+
+
+
self:F( { self.SpawnTemplatePrefix, Grouping } )
+
+
+
+
+
+
+
+
+
self.SpawnGrouping= Grouping
+
+
+
+
+
+
+
+
+
returnself
+
+
+
+
end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
--TODO: Add example.
+
+
+
+
--- This method provides the functionality to randomize the spawning of the Groups at a given list of zones of different types.
+
+
+
+
-- @param #SPAWN self
+
+
+
+
-- @param #table SpawnZoneTable A table with @{Zone} objects. If this table is given, then each spawn will be executed within the given list of @{Zone}s objects.
+
+
+
+
-- @return #SPAWN
+
+
+
+
-- @usage
+
+
+
+
-- -- NATO Tank Platoons invading Gori.
+
+
+
+
-- -- Choose between 3 different zones for each new SPAWN the Group to be executed, regardless of the zone type.
+
+
+
+
function SPAWN:InitRandomizeZones( SpawnZoneTable )
--- For planes and helicopters, when these groups go home and land on their home airbases and farps, they normally would taxi to the parking spot, shut-down their engines and wait forever until the Group is removed by the runtime environment.
+
+
+
+
-- This method is used to re-spawn automatically (so no extra call is needed anymore) the same group after it has landed.
+
+
+
+
-- This will enable a spawned group to be re-spawned after it lands, until it is destroyed...
+
+
+
+
-- Note: When the group is respawned, it will re-spawn from the original airbase where it took off.
+
+
+
+
-- So ensure that the routes for groups that respawn, always return to the original airbase, or players may get confused ...
+
+
+
+
-- @param #SPAWN self
+
+
+
+
-- @return #SPAWN self
+
+
+
+
-- @usage
+
+
+
+
-- -- RU Su-34 - AI Ship Attack
+
+
+
+
-- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically.
--- Respawn after landing when its engines have shut down.
+
+
+
+
-- @param #SPAWN self
+
+
+
+
-- @return #SPAWN self
+
+
+
+
function SPAWN:InitRepeatOnEngineShutDown()
+
+
+
+
self:F( { self.SpawnTemplatePrefix } )
+
+
+
+
+
+
+
+
+
self:InitRepeat()
+
+
+
+
self.RepeatOnEngineShutDown=true
+
+
+
+
self.RepeatOnLanding=false
+
+
+
+
+
+
+
+
returnself
+
+
+
+
end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
--- CleanUp groups when they are still alive, but inactive.
+
+
+
+
-- When groups are still alive and have become inactive due to damage and are unable to contribute anything, then this group will be removed at defined intervals in seconds.
+
+
+
+
-- @param #SPAWN self
+
+
+
+
-- @param #string SpawnCleanUpInterval The interval to check for inactive groups within seconds.
+
+
+
+
-- @return #SPAWN self
+
+
+
+
-- @usage Spawn_Helicopter:CleanUp( 20 ) -- CleanUp the spawning of the helicopters every 20 seconds when they become inactive.
+
+
+
+
function SPAWN:InitCleanUp( SpawnCleanUpInterval )
--- Will spawn a group from a hosting unit. This method is mostly advisable to be used if you want to simulate spawning from air units, like helicopters, which are dropping infantry into a defined Landing Zone.
+
+
+
+
-- Note that each point in the route assigned to the spawning group is reset to the point of the spawn.
+
+
+
+
-- You can use the returned group to further define the route to be followed.
+
+
+
+
-- @param #SPAWN self
+
+
+
+
-- @param Wrapper.Unit#UNIT HostUnit The air or ground unit dropping or unloading the group.
+
+
+
+
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
+
+
+
+
-- @return Wrapper.Group#GROUP that was spawned.
+
+
+
+
-- @return #nil Nothing was spawned.
+
+
+
+
function SPAWN:SpawnFromUnit( HostUnit, SpawnIndex )
--- Will spawn a group from a hosting static. This method is mostly advisable to be used if you want to simulate spawning from buldings and structures (static buildings).
+
+
+
+
-- You can use the returned group to further define the route to be followed.
+
+
+
+
-- @param #SPAWN self
+
+
+
+
-- @param Wrapper.Static#STATIC HostStatic The static dropping or unloading the group.
+
+
+
+
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
+
+
+
+
-- @return Wrapper.Group#GROUP that was spawned.
+
+
+
+
-- @return #nil Nothing was spawned.
+
+
+
+
function SPAWN:SpawnFromStatic( HostStatic, SpawnIndex )
local SpawnName =string.format( '%s#%03d', SpawnPrefix, SpawnIndex )
+
+
+
+
self:T( SpawnName )
+
+
+
+
return SpawnName
+
+
+
+
else
+
+
+
+
self:T( SpawnPrefix )
+
+
+
+
return SpawnPrefix
+
+
+
+
end
+
+
+
+
+
+
+
+
end
+
+
+
+
+
+
+
+
+
--- Will find the first alive @{Group} it has spawned, and return the alive @{Group} object and the first Index where the first alive @{Group} object has been found.
+
+
+
+
-- @param #SPAWN self
+
+
+
+
-- @return Wrapper.Group#GROUP, #number The @{Group} object found, the new Index where the group was found.
+
+
+
+
-- @return #nil, #nil When no group is found, #nil is returned.
+
+
+
+
-- @usage
+
+
+
+
-- -- Find the first alive @{Group} object of the SpawnPlanes SPAWN object @{Group} collection that it has spawned during the mission.
+
+
+
+
-- local GroupPlane, Index = SpawnPlanes:GetFirstAliveGroup()
+
+
+
+
-- while GroupPlane ~= nil do
+
+
+
+
-- -- Do actions with the GroupPlane object.
+
+
+
+
-- GroupPlane, Index = SpawnPlanes:GetNextAliveGroup( Index )
local SpawnGroup =self:GetGroupFromIndex( SpawnIndex )
+
+
+
+
if SpawnGroup and SpawnGroup:IsAlive() then
+
+
+
+
return SpawnGroup, SpawnIndex
+
+
+
+
end
+
+
+
+
end
+
+
+
+
+
+
+
+
returnnil, nil
+
+
+
+
end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
--- Will find the next alive @{Group} object from a given Index, and return a reference to the alive @{Group} object and the next Index where the alive @{Group} has been found.
+
+
+
+
-- @param #SPAWN self
+
+
+
+
-- @param #number SpawnIndexStart A Index holding the start position to search from. This method can also be used to find the first alive @{Group} object from the given Index.
+
+
+
+
-- @return Wrapper.Group#GROUP, #number The next alive @{Group} object found, the next Index where the next alive @{Group} object was found.
+
+
+
+
-- @return #nil, #nil When no alive @{Group} object is found from the start Index position, #nil is returned.
+
+
+
+
-- @usage
+
+
+
+
-- -- Find the first alive @{Group} object of the SpawnPlanes SPAWN object @{Group} collection that it has spawned during the mission.
+
+
+
+
-- local GroupPlane, Index = SpawnPlanes:GetFirstAliveGroup()
+
+
+
+
-- while GroupPlane ~= nil do
+
+
+
+
-- -- Do actions with the GroupPlane object.
+
+
+
+
-- GroupPlane, Index = SpawnPlanes:GetNextAliveGroup( Index )
+
+
+
+
-- end
+
+
+
+
function SPAWN:GetNextAliveGroup( SpawnIndexStart )
for SpawnIndex = SpawnIndexStart, self.SpawnCountdo
+
+
+
+
local SpawnGroup =self:GetGroupFromIndex( SpawnIndex )
+
+
+
+
if SpawnGroup and SpawnGroup:IsAlive() then
+
+
+
+
return SpawnGroup, SpawnIndex
+
+
+
+
end
+
+
+
+
end
+
+
+
+
+
+
+
+
returnnil, nil
+
+
+
+
end
+
+
+
+
+
+
+
+
+
--- Will find the last alive @{Group} object, and will return a reference to the last live @{Group} object and the last Index where the last alive @{Group} object has been found.
+
+
+
+
-- @param #SPAWN self
+
+
+
+
-- @return Wrapper.Group#GROUP, #number The last alive @{Group} object found, the last Index where the last alive @{Group} object was found.
+
+
+
+
-- @return #nil, #nil When no alive @{Group} object is found, #nil is returned.
+
+
+
+
-- @usage
+
+
+
+
-- -- Find the last alive @{Group} object of the SpawnPlanes SPAWN object @{Group} collection that it has spawned during the mission.
+
+
+
+
-- local GroupPlane, Index = SpawnPlanes:GetLastAliveGroup()
- -- Translate the position of the Group Template to the Vec3.
- for UnitID = 1, #SpawnTemplate.units do
- self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
- local UnitTemplate = SpawnTemplate.units[UnitID]
- local SX = UnitTemplate.x
- local SY = UnitTemplate.y
- local BX = SpawnTemplate.route.points[1].x
- local BY = SpawnTemplate.route.points[1].y
- local TX = Vec3.x + ( SX - BX )
- local TY = Vec3.z + ( SY - BY )
- SpawnTemplate.units[UnitID].x = TX
- SpawnTemplate.units[UnitID].y = TY
- SpawnTemplate.units[UnitID].alt = Vec3.y
- self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
- end
- SpawnTemplate.route.points[1].x = Vec3.x
- SpawnTemplate.route.points[1].y = Vec3.z
- SpawnTemplate.route.points[1].alt = Vec3.y
-
- SpawnTemplate.x = Vec3.x
- SpawnTemplate.y = Vec3.z
-
- return self:SpawnWithIndex( self.SpawnIndex )
- end
- end
-
- return nil
-end
+
---- Get the group from an index.
--- Returns the group from the SpawnGroups list.
--- If no index is given, it will return the first group in the list.
--- @param #SPAWN self
--- @param #number SpawnIndex The index of the group to return.
--- @return Wrapper.Group#GROUP self
-function SPAWN:GetGroupFromIndex( SpawnIndex )
- self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndex } )
-
- if not SpawnIndex then
- SpawnIndex = 1
- end
-
- if self.SpawnGroups and self.SpawnGroups[SpawnIndex] then
- local SpawnGroup = self.SpawnGroups[SpawnIndex].Group
- return SpawnGroup
- else
- return nil
- end
-end
+
+
+
+ You can't perform that action at this time.
+
---- Return the prefix of a SpawnUnit.
--- The method will search for a #-mark, and will return the text before the #-mark.
--- It will return nil of no prefix was found.
--- @param #SPAWN self
--- @param Dcs.DCSWrapper.Unit#UNIT DCSUnit The @{DCSUnit} to be searched.
--- @return #string The prefix
--- @return #nil Nothing found
-function SPAWN:_GetPrefixFromGroup( SpawnGroup )
- self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnGroup } )
-
- local GroupName = SpawnGroup:GetName()
- if GroupName then
- local SpawnPrefix = string.match( GroupName, ".*#" )
- if SpawnPrefix then
- SpawnPrefix = SpawnPrefix:sub( 1, -2 )
- end
- return SpawnPrefix
- end
-
- return nil
-end
-
-
---- Get the index from a given group.
--- The function will search the name of the group for a #, and will return the number behind the #-mark.
-function SPAWN:GetSpawnIndexFromGroup( SpawnGroup )
- self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnGroup } )
-
- local IndexString = string.match( SpawnGroup:GetName(), "#(%d*)$" ):sub( 2 )
- local Index = tonumber( IndexString )
-
- self:T3( IndexString, Index )
- return Index
-
-end
-
---- Return the last maximum index that can be used.
-function SPAWN:_GetLastIndex()
- self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } )
-
- return self.SpawnMaxGroups
-end
-
---- Initalize the SpawnGroups collection.
--- @param #SPAWN self
-function SPAWN:_InitializeSpawnGroups( SpawnIndex )
- self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndex } )
-
- if not self.SpawnGroups[SpawnIndex] then
- self.SpawnGroups[SpawnIndex] = {}
- self.SpawnGroups[SpawnIndex].Visible = false
- self.SpawnGroups[SpawnIndex].Spawned = false
- self.SpawnGroups[SpawnIndex].UnControlled = false
- self.SpawnGroups[SpawnIndex].SpawnTime = 0
-
- self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix = self.SpawnTemplatePrefix
- self.SpawnGroups[SpawnIndex].SpawnTemplate = self:_Prepare( self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix, SpawnIndex )
- end
-
- self:_RandomizeTemplate( SpawnIndex )
- self:_RandomizeRoute( SpawnIndex )
- --self:_TranslateRotate( SpawnIndex )
-
- return self.SpawnGroups[SpawnIndex]
-end
-
-
-
---- Gets the CategoryID of the Group with the given SpawnPrefix
-function SPAWN:_GetGroupCategoryID( SpawnPrefix )
- local TemplateGroup = Group.getByName( SpawnPrefix )
-
- if TemplateGroup then
- return TemplateGroup:getCategory()
- else
- return nil
- end
-end
-
---- Gets the CoalitionID of the Group with the given SpawnPrefix
-function SPAWN:_GetGroupCoalitionID( SpawnPrefix )
- local TemplateGroup = Group.getByName( SpawnPrefix )
-
- if TemplateGroup then
- return TemplateGroup:getCoalition()
- else
- return nil
- end
-end
-
---- Gets the CountryID of the Group with the given SpawnPrefix
-function SPAWN:_GetGroupCountryID( SpawnPrefix )
- self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnPrefix } )
-
- local TemplateGroup = Group.getByName( SpawnPrefix )
-
- if TemplateGroup then
- local TemplateUnits = TemplateGroup:getUnits()
- return TemplateUnits[1]:getCountry()
- else
- return nil
- end
-end
-
---- Gets the Group Template from the ME environment definition.
--- This method used the @{DATABASE} object, which contains ALL initial and new spawned object in MOOSE.
--- @param #SPAWN self
--- @param #string SpawnTemplatePrefix
--- @return @SPAWN self
-function SPAWN:_GetTemplate( SpawnTemplatePrefix )
- self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnTemplatePrefix } )
-
- local SpawnTemplate = nil
-
- SpawnTemplate = routines.utils.deepCopy( _DATABASE.Templates.Groups[SpawnTemplatePrefix].Template )
-
- if SpawnTemplate == nil then
- error( 'No Template returned for SpawnTemplatePrefix = ' .. SpawnTemplatePrefix )
- end
-
- --SpawnTemplate.SpawnCoalitionID = self:_GetGroupCoalitionID( SpawnTemplatePrefix )
- --SpawnTemplate.SpawnCategoryID = self:_GetGroupCategoryID( SpawnTemplatePrefix )
- --SpawnTemplate.SpawnCountryID = self:_GetGroupCountryID( SpawnTemplatePrefix )
-
- self:T3( { SpawnTemplate } )
- return SpawnTemplate
-end
-
---- Prepares the new Group Template.
--- @param #SPAWN self
--- @param #string SpawnTemplatePrefix
--- @param #number SpawnIndex
--- @return #SPAWN self
-function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) --R2.2
- self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } )
-
- local SpawnTemplate = self:_GetTemplate( SpawnTemplatePrefix )
- SpawnTemplate.name = self:SpawnGroupName( SpawnIndex )
-
- SpawnTemplate.groupId = nil
- --SpawnTemplate.lateActivation = false
- SpawnTemplate.lateActivation = false
-
- if SpawnTemplate.CategoryID == Group.Category.GROUND then
- self:T3( "For ground units, visible needs to be false..." )
- SpawnTemplate.visible = false
- end
-
- if self.SpawnGrouping then
- local UnitAmount = #SpawnTemplate.units
- self:F( { UnitAmount = UnitAmount, SpawnGrouping = self.SpawnGrouping } )
- if UnitAmount > self.SpawnGrouping then
- for UnitID = self.SpawnGrouping + 1, UnitAmount do
- SpawnTemplate.units[UnitID] = nil
- end
- else
- if UnitAmount < self.SpawnGrouping then
- for UnitID = UnitAmount + 1, self.SpawnGrouping do
- SpawnTemplate.units[UnitID] = UTILS.DeepCopy( SpawnTemplate.units[1] )
- SpawnTemplate.units[UnitID].unitId = nil
- end
- end
- end
- end
-
- if self.SpawnInitKeepUnitNames == false then
- for UnitID = 1, #SpawnTemplate.units do
- SpawnTemplate.units[UnitID].name = string.format( SpawnTemplate.name .. '-%02d', UnitID )
- SpawnTemplate.units[UnitID].unitId = nil
- end
- else
- for UnitID = 1, #SpawnTemplate.units do
- local UnitPrefix, Rest = string.match( SpawnTemplate.units[UnitID].name, "^([^#]+)#?" ):gsub( "^%s*(.-)%s*$", "%1" )
- self:T( { UnitPrefix, Rest } )
-
- SpawnTemplate.units[UnitID].name = string.format( '%s#%03d-%02d', UnitPrefix, SpawnIndex, UnitID )
- SpawnTemplate.units[UnitID].unitId = nil
- end
- end
-
- self:T3( { "Template:", SpawnTemplate } )
- return SpawnTemplate
-
-end
-
---- Private method randomizing the routes.
--- @param #SPAWN self
--- @param #number SpawnIndex The index of the group to be spawned.
--- @return #SPAWN
-function SPAWN:_RandomizeRoute( SpawnIndex )
- self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeRoute, self.SpawnRandomizeRouteStartPoint, self.SpawnRandomizeRouteEndPoint, self.SpawnRandomizeRouteRadius } )
-
- if self.SpawnRandomizeRoute then
- local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate
- local RouteCount = #SpawnTemplate.route.points
- for t = self.SpawnRandomizeRouteStartPoint + 1, ( RouteCount - self.SpawnRandomizeRouteEndPoint ) do
-
- SpawnTemplate.route.points[t].x = SpawnTemplate.route.points[t].x + math.random( self.SpawnRandomizeRouteRadius * -1, self.SpawnRandomizeRouteRadius )
- SpawnTemplate.route.points[t].y = SpawnTemplate.route.points[t].y + math.random( self.SpawnRandomizeRouteRadius * -1, self.SpawnRandomizeRouteRadius )
-
- -- Manage randomization of altitude for airborne units ...
- if SpawnTemplate.CategoryID == Group.Category.AIRPLANE or SpawnTemplate.CategoryID == Group.Category.HELICOPTER then
- if SpawnTemplate.route.points[t].alt and self.SpawnRandomizeRouteHeight then
- SpawnTemplate.route.points[t].alt = SpawnTemplate.route.points[t].alt + math.random( 1, self.SpawnRandomizeRouteHeight )
- end
- else
- SpawnTemplate.route.points[t].alt = nil
- end
-
- self:T( 'SpawnTemplate.route.points[' .. t .. '].x = ' .. SpawnTemplate.route.points[t].x .. ', SpawnTemplate.route.points[' .. t .. '].y = ' .. SpawnTemplate.route.points[t].y )
- end
- end
-
- self:_RandomizeZones( SpawnIndex )
-
- return self
-end
-
---- Private method that randomizes the template of the group.
--- @param #SPAWN self
--- @param #number SpawnIndex
--- @return #SPAWN self
-function SPAWN:_RandomizeTemplate( SpawnIndex )
- self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeTemplate } )
-
- if self.SpawnRandomizeTemplate then
- self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix = self.SpawnTemplatePrefixTable[ math.random( 1, #self.SpawnTemplatePrefixTable ) ]
- self.SpawnGroups[SpawnIndex].SpawnTemplate = self:_Prepare( self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix, SpawnIndex )
- self.SpawnGroups[SpawnIndex].SpawnTemplate.route = routines.utils.deepCopy( self.SpawnTemplate.route )
- self.SpawnGroups[SpawnIndex].SpawnTemplate.x = self.SpawnTemplate.x
- self.SpawnGroups[SpawnIndex].SpawnTemplate.y = self.SpawnTemplate.y
- self.SpawnGroups[SpawnIndex].SpawnTemplate.start_time = self.SpawnTemplate.start_time
- local OldX = self.SpawnGroups[SpawnIndex].SpawnTemplate.units[1].x
- local OldY = self.SpawnGroups[SpawnIndex].SpawnTemplate.units[1].y
- for UnitID = 1, #self.SpawnGroups[SpawnIndex].SpawnTemplate.units do
- self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].heading = self.SpawnTemplate.units[1].heading
- self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].x = self.SpawnTemplate.units[1].x + ( self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].x - OldX )
- self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y = self.SpawnTemplate.units[1].y + ( self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y - OldY )
- self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].alt = self.SpawnTemplate.units[1].alt
- end
- end
-
- self:_RandomizeRoute( SpawnIndex )
-
- return self
-end
-
---- Private method that randomizes the @{Zone}s where the Group will be spawned.
--- @param #SPAWN self
--- @param #number SpawnIndex
--- @return #SPAWN self
-function SPAWN:_RandomizeZones( SpawnIndex )
- self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeZones } )
-
- if self.SpawnRandomizeZones then
- local SpawnZone = nil -- Core.Zone#ZONE_BASE
- while not SpawnZone do
- self:T( { SpawnZoneTableCount = #self.SpawnZoneTable, self.SpawnZoneTable } )
- local ZoneID = math.random( #self.SpawnZoneTable )
- self:T( ZoneID )
- SpawnZone = self.SpawnZoneTable[ ZoneID ]:GetZoneMaybe()
- end
+
- self:T( "Preparing Spawn in Zone", SpawnZone:GetName() )
+
- local SpawnVec2 = SpawnZone:GetRandomVec2()
- self:T( { SpawnVec2 = SpawnVec2 } )
- local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate
- self:T( { Route = SpawnTemplate.route } )
-
- for UnitID = 1, #SpawnTemplate.units do
- local UnitTemplate = SpawnTemplate.units[UnitID]
- self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y )
- local SX = UnitTemplate.x
- local SY = UnitTemplate.y
- local BX = SpawnTemplate.route.points[1].x
- local BY = SpawnTemplate.route.points[1].y
- local TX = SpawnVec2.x + ( SX - BX )
- local TY = SpawnVec2.y + ( SY - BY )
- UnitTemplate.x = TX
- UnitTemplate.y = TY
- -- TODO: Manage altitude based on landheight...
- --SpawnTemplate.units[UnitID].alt = SpawnVec2:
- self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y )
- end
- SpawnTemplate.x = SpawnVec2.x
- SpawnTemplate.y = SpawnVec2.y
- SpawnTemplate.route.points[1].x = SpawnVec2.x
- SpawnTemplate.route.points[1].y = SpawnVec2.y
- end
-
- return self
-
-end
-
-function SPAWN:_TranslateRotate( SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle )
- self:F( { self.SpawnTemplatePrefix, SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle } )
-
- -- Translate
- local TranslatedX = SpawnX
- local TranslatedY = SpawnY
-
- -- Rotate
- -- From Wikipedia: https://en.wikipedia.org/wiki/Rotation_matrix#Common_rotations
- -- x' = x \cos \theta - y \sin \theta\
- -- y' = x \sin \theta + y \cos \theta\
- local RotatedX = - TranslatedX * math.cos( math.rad( SpawnAngle ) )
- + TranslatedY * math.sin( math.rad( SpawnAngle ) )
- local RotatedY = TranslatedX * math.sin( math.rad( SpawnAngle ) )
- + TranslatedY * math.cos( math.rad( SpawnAngle ) )
-
- -- Assign
- self.SpawnGroups[SpawnIndex].SpawnTemplate.x = SpawnRootX - RotatedX
- self.SpawnGroups[SpawnIndex].SpawnTemplate.y = SpawnRootY + RotatedY
-
-
- local SpawnUnitCount = table.getn( self.SpawnGroups[SpawnIndex].SpawnTemplate.units )
- for u = 1, SpawnUnitCount do
-
- -- Translate
- local TranslatedX = SpawnX
- local TranslatedY = SpawnY - 10 * ( u - 1 )
-
- -- Rotate
- local RotatedX = - TranslatedX * math.cos( math.rad( SpawnAngle ) )
- + TranslatedY * math.sin( math.rad( SpawnAngle ) )
- local RotatedY = TranslatedX * math.sin( math.rad( SpawnAngle ) )
- + TranslatedY * math.cos( math.rad( SpawnAngle ) )
-
- -- Assign
- self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].x = SpawnRootX - RotatedX
- self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].y = SpawnRootY + RotatedY
- self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].heading = self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].heading + math.rad( SpawnAngle )
- end
-
- return self
-end
-
---- Get the next index of the groups to be spawned. This method is complicated, as it is used at several spaces.
-function SPAWN:_GetSpawnIndex( SpawnIndex )
- self:F2( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive, self.AliveUnits, #self.SpawnTemplate.units } )
-
- if ( self.SpawnMaxGroups == 0 ) or ( SpawnIndex <= self.SpawnMaxGroups ) then
- if ( self.SpawnMaxUnitsAlive == 0 ) or ( self.AliveUnits + #self.SpawnTemplate.units <= self.SpawnMaxUnitsAlive ) or self.UnControlled == true then
- if SpawnIndex and SpawnIndex >= self.SpawnCount + 1 then
- self.SpawnCount = self.SpawnCount + 1
- SpawnIndex = self.SpawnCount
- end
- self.SpawnIndex = SpawnIndex
- if not self.SpawnGroups[self.SpawnIndex] then
- self:_InitializeSpawnGroups( self.SpawnIndex )
- end
- else
- return nil
- end
- else
- return nil
- end
-
- return self.SpawnIndex
-end
+
+
+ You signed in with another tab or window. Reload to refresh your session.
+ You signed out in another tab or window. Reload to refresh your session.
+
+
+
+
+
+
+
+
--- TODO Need to delete this... _DATABASE does this now ...
+
+
---- @param #SPAWN self
--- @param Core.Event#EVENTDATA EventData
-function SPAWN:_OnBirth( EventData )
- self:F( self.SpawnTemplatePrefix )
-
- local SpawnGroup = EventData.IniGroup
-
- if SpawnGroup then
- local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup )
- if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group!
- self:T( { "Birth Event:", EventPrefix, self.SpawnTemplatePrefix } )
- if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then
- self.AliveUnits = self.AliveUnits + 1
- self:T( "Alive Units: " .. self.AliveUnits )
- end
- end
- end
-
-end
-
---- Obscolete
--- @todo Need to delete this... _DATABASE does this now ...
-
---- @param #SPAWN self
--- @param Core.Event#EVENTDATA EventData
-function SPAWN:_OnDeadOrCrash( EventData )
- self:F( self.SpawnTemplatePrefix )
-
- local SpawnGroup = EventData.IniGroup
-
- if SpawnGroup then
- local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup )
- if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group!
- self:T( { "Dead event: " .. EventPrefix } )
- if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then
- self.AliveUnits = self.AliveUnits - 1
- self:T( "Alive Units: " .. self.AliveUnits )
- end
- end
- end
-end
-
---- Will detect AIR Units taking off... When the event takes place, the spawned Group is registered as airborne...
--- This is needed to ensure that Re-SPAWNing only is done for landed AIR Groups.
--- @param #SPAWN self
--- @param Core.Event#EVENTDATA EventData
-function SPAWN:_OnTakeOff( EventData )
- self:F( self.SpawnTemplatePrefix )
-
- local SpawnGroup = EventData.IniGroup
- if SpawnGroup then
- local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup )
- if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group!
- self:T( { "TakeOff event: " .. EventPrefix } )
- if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then
- self:T( "self.Landed = false" )
- SpawnGroup:SetState( SpawnGroup, "Spawn_Landed", false )
- end
- end
- end
-end
-
---- Will detect AIR Units landing... When the event takes place, the spawned Group is registered as landed.
--- This is needed to ensure that Re-SPAWNing is only done for landed AIR Groups.
--- @param #SPAWN self
--- @param Core.Event#EVENTDATA EventData
-function SPAWN:_OnLand( EventData )
- self:F( self.SpawnTemplatePrefix )
-
- local SpawnGroup = EventData.IniGroup
- if SpawnGroup then
- local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup )
- if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group!
- self:T( { "Land event: " .. EventPrefix } )
- if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then
- -- TODO: Check if this is the last unit of the group that lands.
- SpawnGroup:SetState( SpawnGroup, "Spawn_Landed", true )
- if self.RepeatOnLanding then
- local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup )
- self:T( { "Landed:", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } )
- self:ReSpawn( SpawnGroupIndex )
- end
- end
- end
- end
-end
-
---- Will detect AIR Units shutting down their engines ...
--- When the event takes place, and the method @{RepeatOnEngineShutDown} was called, the spawned Group will Re-SPAWN.
--- But only when the Unit was registered to have landed.
--- @param #SPAWN self
--- @param Core.Event#EVENTDATA EventData
-function SPAWN:_OnEngineShutDown( EventData )
- self:F( self.SpawnTemplatePrefix )
-
- local SpawnGroup = EventData.IniGroup
- if SpawnGroup then
- local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup )
- if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group!
- self:T( { "EngineShutdown event: " .. EventPrefix } )
- if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then
- -- todo: test if on the runway
- local Landed = SpawnGroup:GetState( SpawnGroup, "Spawn_Landed" )
- if Landed and self.RepeatOnEngineShutDown then
- local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup )
- self:T( { "EngineShutDown: ", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } )
- self:ReSpawn( SpawnGroupIndex )
- end
- end
- end
- end
-end
-
---- This function is called automatically by the Spawning scheduler.
--- It is the internal worker method SPAWNing new Groups on the defined time intervals.
-function SPAWN:_Scheduler()
- self:F2( { "_Scheduler", self.SpawnTemplatePrefix, self.SpawnAliasPrefix, self.SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive } )
-
- -- Validate if there are still groups left in the batch...
- self:Spawn()
-
- return true
-end
-
---- Schedules the CleanUp of Groups
--- @param #SPAWN self
--- @return #boolean True = Continue Scheduler
-function SPAWN:_SpawnCleanUpScheduler()
- self:F( { "CleanUp Scheduler:", self.SpawnTemplatePrefix } )
-
- local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup()
- self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } )
-
- while SpawnGroup do
-
- local SpawnUnits = SpawnGroup:GetUnits()
-
- for UnitID, UnitData in pairs( SpawnUnits ) do
-
- local SpawnUnit = UnitData -- Wrapper.Unit#UNIT
- local SpawnUnitName = SpawnUnit:GetName()
-
-
- self.SpawnCleanUpTimeStamps[SpawnUnitName] = self.SpawnCleanUpTimeStamps[SpawnUnitName] or {}
- local Stamp = self.SpawnCleanUpTimeStamps[SpawnUnitName]
- self:T( { SpawnUnitName, Stamp } )
-
- if Stamp.Vec2 then
- if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1 then
- local NewVec2 = SpawnUnit:GetVec2()
- if Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y then
- -- If the plane is not moving, and is on the ground, assign it with a timestamp...
- if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then
- self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } )
- self:ReSpawn( SpawnCursor )
- Stamp.Vec2 = nil
- Stamp.Time = nil
- end
- else
- Stamp.Time = timer.getTime()
- Stamp.Vec2 = SpawnUnit:GetVec2()
- end
- else
- Stamp.Vec2 = nil
- Stamp.Time = nil
- end
- else
- if SpawnUnit:InAir() == false then
- Stamp.Vec2 = SpawnUnit:GetVec2()
- if SpawnUnit:GetVelocityKMH() < 1 then
- Stamp.Time = timer.getTime()
- end
- else
- Stamp.Time = nil
- Stamp.Vec2 = nil
- end
- end
- end
-
- SpawnGroup, SpawnCursor = self:GetNextAliveGroup( SpawnCursor )
-
- self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } )
-
- end
-
- return true -- Repeat
-
-end
diff --git a/Moose Mission Setup/Moose.files b/Moose Mission Setup/Moose.files
index 1f686de8b..1794a4ff2 100644
--- a/Moose Mission Setup/Moose.files
+++ b/Moose Mission Setup/Moose.files
@@ -52,6 +52,7 @@ AI/AI_Cap.lua
AI/AI_Cas.lua
AI/AI_Bai.lua
AI/AI_Formation.lua
+AI/AI_RAT.lua
Actions/Act_Assign.lua
Actions/Act_Route.lua
diff --git a/Moose Mission Setup/Moose.lua b/Moose Mission Setup/Moose.lua
index 1cd7ba934..0365ba7b0 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: 20170811_0800' )
+env.info( 'Moose Generation Timestamp: 20170811_0800 funkyfranky' )
local base = _G
@@ -70,6 +70,7 @@ __Moose.Include( 'AI/AI_Patrol.lua' )
__Moose.Include( 'AI/AI_Cap.lua' )
__Moose.Include( 'AI/AI_Cas.lua' )
__Moose.Include( 'AI/AI_Bai.lua' )
+__Moose.Include( 'AI/AI_RAT.lua' )
__Moose.Include( 'AI/AI_Formation.lua' )
__Moose.Include( 'Actions/Act_Assign.lua' )
__Moose.Include( 'Actions/Act_Route.lua' )
From 6a2739da5ebc355b94c482f415334122907e4ae8 Mon Sep 17 00:00:00 2001
From: funkyfranky
Date: Mon, 28 Aug 2017 00:16:55 +0200
Subject: [PATCH 02/28] Fixed Spawn & AI_Balancer mistake.
---
Moose Development/Moose/AI/AI_Balancer.lua | 4341 +++----
Moose Development/Moose/Functional/Spawn.lua | 10889 +++--------------
2 files changed, 3696 insertions(+), 11534 deletions(-)
diff --git a/Moose Development/Moose/AI/AI_Balancer.lua b/Moose Development/Moose/AI/AI_Balancer.lua
index 0bfd24570..14059f200 100644
--- a/Moose Development/Moose/AI/AI_Balancer.lua
+++ b/Moose Development/Moose/AI/AI_Balancer.lua
@@ -1,2564 +1,1901 @@
+--- **Functional** -- Spawn dynamically new GROUPs in your missions.
+--
+-- 
+--
+-- ====
+--
+-- The documentation of the SPAWN class can be found further in this document.
+--
+-- ====
+--
+-- # Demo Missions
+--
+-- ### [SPAWN Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/SPA%20-%20Spawning)
+--
+-- ### [SPAWN Demo Missions, only for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SPA%20-%20Spawning)
+--
+-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
+--
+-- ====
+--
+-- # YouTube Channel
+--
+-- ### [SPAWN YouTube Channel](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl1jirWIo4t4YxqN-HxjqRkL)
+--
+-- ===
+--
+-- # **AUTHORS and CONTRIBUTIONS**
+--
+-- ### Contributions:
+--
+-- * **Aaron**: Posed the idea for Group position randomization at SpawnInZone and make the Unit randomization separate from the Group randomization.
+-- * [**Entropy**](https://forums.eagle.ru/member.php?u=111471), **Afinegan**: Came up with the requirement for AIOnOff().
+--
+-- ### Authors:
+--
+-- * **FlightControl**: Design & Programming
+--
+-- @module Spawn
+
+----BASE:TraceClass("SPAWN")
+--- SPAWN Class
+-- @type SPAWN
+-- @field ClassName
+-- @field #string SpawnTemplatePrefix
+-- @field #string SpawnAliasPrefix
+-- @field #number AliveUnits
+-- @field #number MaxAliveUnits
+-- @field #number SpawnIndex
+-- @field #number MaxAliveGroups
+-- @field #SPAWN.SpawnZoneTable SpawnZoneTable
+-- @extends Core.Base#BASE
+--- # SPAWN class, extends @{Base#BASE}
+--
+-- The SPAWN class allows to spawn dynamically new groups.
+-- Each SPAWN object needs to be have a related **template group** setup in the Mission Editor (ME),
+-- which is a normal group with the **Late Activation** flag set.
+-- This template group will never be activated in your mission.
+-- SPAWN uses that **template group** to reference to all the characteristics
+-- (air, ground, livery, unit composition, formation, skill level etc) of each new group to be spawned.
+--
+-- Therefore, when creating a SPAWN object, the @{#SPAWN.New} and @{#SPAWN.NewWithAlias} require
+-- **the name of the template group** to be given as a string to those constructor methods.
+--
+-- Initialization settings can be applied on the SPAWN object,
+-- which modify the behaviour or the way groups are spawned.
+-- These initialization methods have the prefix **Init**.
+-- There are also spawn methods with the prefix **Spawn** and will spawn new groups in various ways.
+--
+-- ### IMPORTANT! The methods with prefix **Init** must be used before any methods with prefix **Spawn** method are used, or unexpected results may appear!!!
+--
+-- Because SPAWN can spawn multiple groups of a template group,
+-- SPAWN has an **internal index** that keeps track
+-- which was the latest group that was spawned.
+--
+-- **Limits** can be set on how many groups can be spawn in each SPAWN object,
+-- using the method @{#SPAWN.InitLimit}. SPAWN has 2 kind of limits:
+--
+-- * The maximum amount of @{Unit}s that can be **alive** at the same time...
+-- * The maximum amount of @{Group}s that can be **spawned**... This is more of a **resource**-type of limit.
+--
+-- When new groups get spawned using the **Spawn** methods,
+-- it will be evaluated whether any limits have been reached.
+-- When no spawn limit is reached, a new group will be created by the spawning methods,
+-- and the internal index will be increased with 1.
+--
+-- These limits ensure that your mission does not accidentally get flooded with spawned groups.
+-- Additionally, it also guarantees that independent of the group composition,
+-- at any time, the most optimal amount of groups are alive in your mission.
+-- For example, if your template group has a group composition of 10 units, and you specify a limit of 100 units alive at the same time,
+-- with unlimited resources = :InitLimit( 100, 0 ) and 10 groups are alive, but two groups have only one unit alive in the group,
+-- then a sequent Spawn(Scheduled) will allow a new group to be spawned!!!
+--
+-- ### IMPORTANT!! If a limit has been reached, it is possible that a **Spawn** method returns **nil**, meaning, no @{Group} had been spawned!!!
+--
+-- Spawned groups get **the same name** as the name of the template group.
+-- Spawned units in those groups keep _by default_ **the same name** as the name of the template group.
+-- However, because multiple groups and units are created from the template group,
+-- a suffix is added to each spawned group and unit.
+--
+-- Newly spawned groups will get the following naming structure at run-time:
+--
+-- 1. Spawned groups will have the name _GroupName_#_nnn_, where _GroupName_ is the name of the **template group**,
+-- and _nnn_ is a **counter from 0 to 999**.
+-- 2. Spawned units will have the name _GroupName_#_nnn_-_uu_,
+-- where _uu_ is a **counter from 0 to 99** for each new spawned unit belonging to the group.
+--
+-- That being said, there is a way to keep the same unit names!
+-- The method @{#SPAWN.InitKeepUnitNames}() will keep the same unit names as defined within the template group, thus:
+--
+-- 3. Spawned units will have the name _UnitName_#_nnn_-_uu_,
+-- where _UnitName_ is the **unit name as defined in the template group*,
+-- and _uu_ is a **counter from 0 to 99** for each new spawned unit belonging to the group.
+--
+-- Some **additional notes that need to be considered!!**:
+--
+-- * templates are actually groups defined within the mission editor, with the flag "Late Activation" set.
+-- As such, these groups are never used within the mission, but are used by the @{#SPAWN} module.
+-- * It is important to defined BEFORE you spawn new groups,
+-- a proper initialization of the SPAWN instance is done with the options you want to use.
+-- * When designing a mission, NEVER name groups using a "#" within the name of the group Spawn template(s),
+-- or the SPAWN module logic won't work anymore.
+--
+-- ## SPAWN construction methods
+--
+-- Create a new SPAWN object with the @{#SPAWN.New}() or the @{#SPAWN.NewWithAlias}() methods:
+--
+-- * @{#SPAWN.New}(): Creates a new SPAWN object taking the name of the group that represents the GROUP template (definition).
+-- * @{#SPAWN.NewWithAlias}(): Creates a new SPAWN object taking the name of the group that represents the GROUP template (definition), and gives each spawned @{Group} an different name.
+--
+-- It is important to understand how the SPAWN class works internally. The SPAWN object created will contain internally a list of groups that will be spawned and that are already spawned.
+-- The initialization methods will modify this list of groups so that when a group gets spawned, ALL information is already prepared when spawning. This is done for performance reasons.
+-- So in principle, the group list will contain all parameters and configurations after initialization, and when groups get actually spawned, this spawning can be done quickly and efficient.
+--
+-- ## SPAWN **Init**ialization methods
+--
+-- A spawn object will behave differently based on the usage of **initialization** methods, which all start with the **Init** prefix:
+--
+-- ### Unit Names
+--
+-- * @{#SPAWN.InitKeepUnitNames}(): Keeps the unit names as defined within the mission editor, but note that anything after a # mark is ignored, and any spaces before and after the resulting name are removed. IMPORTANT! This method MUST be the first used after :New !!!
+--
+-- ### Route randomization
+--
+-- * @{#SPAWN.InitRandomizeRoute}(): Randomize the routes of spawned groups, and for air groups also optionally the height.
+--
+-- ### Group composition randomization
+--
+-- * @{#SPAWN.InitRandomizeTemplate}(): Randomize the group templates so that when a new group is spawned, a random group template is selected from one of the templates defined.
+--
+-- ### Uncontrolled
+--
+-- * @{#SPAWN.InitUnControlled}(): Spawn plane groups uncontrolled.
+--
+-- ### Array formation
+--
+-- * @{#SPAWN.InitArray}(): Make groups visible before they are actually activated, and order these groups like a batallion in an array.
+--
+-- ### Position randomization
+--
+-- * @{#SPAWN.InitRandomizePosition}(): Randomizes the position of @{Group}s that are spawned within a **radius band**, given an Outer and Inner radius, from the point that the spawn happens.
+-- * @{#SPAWN.InitRandomizeUnits}(): Randomizes the @{Unit}s in the @{Group} that is spawned within a **radius band**, given an Outer and Inner radius.
+-- * @{#SPAWN.InitRandomizeZones}(): Randomizes the spawning between a predefined list of @{Zone}s that are declared using this function. Each zone can be given a probability factor.
+--
+-- ### Enable / Disable AI when spawning a new @{Group}
+--
+-- * @{#SPAWN.InitAIOn}(): Turns the AI On when spawning the new @{Group} object.
+-- * @{#SPAWN.InitAIOff}(): Turns the AI Off when spawning the new @{Group} object.
+-- * @{#SPAWN.InitAIOnOff}(): Turns the AI On or Off when spawning the new @{Group} object.
+--
+-- ### Limit scheduled spawning
+--
+-- * @{#SPAWN.InitLimit}(): Limits the amount of groups that can be alive at the same time and that can be dynamically spawned.
+--
+-- ### Delay initial scheduled spawn
+--
+-- * @{#SPAWN.InitDelayOnOff}(): Turns the inital delay On/Off when scheduled spawning the first @{Group} object.
+-- * @{#SPAWN.InitDelayOn}(): Turns the inital delay On when scheduled spawning the first @{Group} object.
+-- * @{#SPAWN.InitDelayOff}(): Turns the inital delay Off when scheduled spawning the first @{Group} object.
+--
+-- ### Repeat spawned @{Group}s upon landing
+--
+-- * @{#SPAWN.InitRepeat}() or @{#SPAWN.InitRepeatOnLanding}(): This method is used to re-spawn automatically the same group after it has landed.
+-- * @{#SPAWN.InitRepeatOnEngineShutDown}(): This method is used to re-spawn automatically the same group after it has landed and it shuts down the engines at the ramp.
+--
+--
+-- ## SPAWN **Spawn** methods
+--
+-- Groups can be spawned at different times and methods:
+--
+-- ### **Single** spawning methods
+--
+-- * @{#SPAWN.Spawn}(): Spawn one new group based on the last spawned index.
+-- * @{#SPAWN.ReSpawn}(): Re-spawn a group based on a given index.
+-- * @{#SPAWN.SpawnFromVec3}(): Spawn a new group from a Vec3 coordinate. (The group will can be spawned at a point in the air).
+-- * @{#SPAWN.SpawnFromVec2}(): Spawn a new group from a Vec2 coordinate. (The group will be spawned at land height ).
+-- * @{#SPAWN.SpawnFromStatic}(): Spawn a new group from a structure, taking the position of a @{Static}.
+-- * @{#SPAWN.SpawnFromUnit}(): Spawn a new group taking the position of a @{Unit}.
+-- * @{#SPAWN.SpawnInZone}(): Spawn a new group in a @{Zone}.
+--
+-- Note that @{#SPAWN.Spawn} and @{#SPAWN.ReSpawn} return a @{GROUP#GROUP.New} object, that contains a reference to the DCSGroup object.
+-- You can use the @{GROUP} object to do further actions with the DCSGroup.
+--
+-- ### **Scheduled** spawning methods
+--
+-- * @{#SPAWN.SpawnScheduled}(): Spawn groups at scheduled but randomized intervals.
+-- * @{#SPAWN.SpawnScheduledStart}(): Start or continue to spawn groups at scheduled time intervals.
+-- * @{#SPAWN.SpawnScheduledStop}(): Stop the spawning of groups at scheduled time intervals.
+--
+--
+--
+-- ## Retrieve alive GROUPs spawned by the SPAWN object
+--
+-- The SPAWN class administers which GROUPS it has reserved (in stock) or has created during mission execution.
+-- Every time a SPAWN object spawns a new GROUP object, a reference to the GROUP object is added to an internal table of GROUPS.
+-- SPAWN provides methods to iterate through that internal GROUP object reference table:
+--
+-- * @{#SPAWN.GetFirstAliveGroup}(): Will find the first alive GROUP it has spawned, and return the alive GROUP object and the first Index where the first alive GROUP object has been found.
+-- * @{#SPAWN.GetNextAliveGroup}(): Will find the next alive GROUP object from a given Index, and return a reference to the alive GROUP object and the next Index where the alive GROUP has been found.
+-- * @{#SPAWN.GetLastAliveGroup}(): Will find the last alive GROUP object, and will return a reference to the last live GROUP object and the last Index where the last alive GROUP object has been found.
+--
+-- You can use the methods @{#SPAWN.GetFirstAliveGroup}() and sequently @{#SPAWN.GetNextAliveGroup}() to iterate through the alive GROUPS within the SPAWN object, and to actions... See the respective methods for an example.
+-- The method @{#SPAWN.GetGroupFromIndex}() will return the GROUP object reference from the given Index, dead or alive...
+--
+-- ## Spawned cleaning of inactive groups
+--
+-- Sometimes, it will occur during a mission run-time, that ground or especially air objects get damaged, and will while being damged stop their activities, while remaining alive.
+-- In such cases, the SPAWN object will just sit there and wait until that group gets destroyed, but most of the time it won't,
+-- and it may occur that no new groups are or can be spawned as limits are reached.
+-- To prevent this, a @{#SPAWN.InitCleanUp}() initialization method has been defined that will silently monitor the status of each spawned group.
+-- Once a group has a velocity = 0, and has been waiting for a defined interval, that group will be cleaned or removed from run-time.
+-- There is a catch however :-) If a damaged group has returned to an airbase within the coalition, that group will not be considered as "lost"...
+-- In such a case, when the inactive group is cleaned, a new group will Re-spawned automatically.
+-- This models AI that has succesfully returned to their airbase, to restart their combat activities.
+-- Check the @{#SPAWN.InitCleanUp}() for further info.
+--
+-- ## Catch the @{Group} Spawn Event in a callback function!
+--
+-- When using the @{#SPAWN.SpawnScheduled)() method, new @{Group}s are created following the spawn time interval parameters.
+-- When a new @{Group} is spawned, you maybe want to execute actions with that group spawned at the spawn event.
+-- The SPAWN class supports this functionality through the method @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ),
+-- which takes a function as a parameter that you can define locally.
+-- Whenever a new @{Group} is spawned, the given function is called, and the @{Group} that was just spawned, is given as a parameter.
+-- As a result, your spawn event handling function requires one parameter to be declared, which will contain the spawned @{Group} object.
+-- A coding example is provided at the description of the @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ) method.
+--
+-- ## Delay the initial spawning
+--
+-- When using the @{#SPAWN.SpawnScheduled)() method, the default behaviour of this method will be that it will spawn the initial (first) @{Group}
+-- immediately when :SpawnScheduled() is initiated. The methods @{#SPAWN.InitDelayOnOff}() and @{#SPAWN.InitDelayOn}() can be used to
+-- activate a delay before the first @{Group} is spawned. For completeness, a method @{#SPAWN.InitDelayOff}() is also available, that
+-- can be used to switch off the initial delay. Because there is no delay by default, this method would only be used when a
+-- @{#SPAWN.SpawnScheduledStop}() ; @{#SPAWN.SpawnScheduledStart}() sequence would have been used.
+--
+--
+-- @field #SPAWN SPAWN
+--
+SPAWN = {
+ ClassName = "SPAWN",
+ SpawnTemplatePrefix = nil,
+ SpawnAliasPrefix = nil,
+}
-
-
-
-
-
-
-
-
-
-
-
+--- Enumerator for spawns at airbases
+-- @type SPAWN.Takeoff
+-- @extends Wrapper.Group#GROUP.Takeoff
+
+--- @field #SPAWN.Takeoff Takeoff
+SPAWN.Takeoff = GROUP.Takeoff
+--- @type SPAWN.SpawnZoneTable
+-- @list SpawnZone
-
-
-
-
-
+
+--- Creates the main object to spawn a @{Group} defined in the DCS ME.
+-- @param #SPAWN self
+-- @param #string SpawnTemplatePrefix is the name of the Group in the ME that defines the Template. Each new group will have the name starting with SpawnTemplatePrefix.
+-- @return #SPAWN
+-- @usage
+-- -- NATO helicopters engaging in the battle field.
+-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' )
+-- @usage local Plane = SPAWN:New( "Plane" ) -- Creates a new local variable that can initiate new planes with the name "Plane#ddd" using the template "Plane" as defined within the ME.
+function SPAWN:New( SpawnTemplatePrefix )
+ local self = BASE:Inherit( self, BASE:New() ) -- #SPAWN
+ self:F( { SpawnTemplatePrefix } )
+ local TemplateGroup = Group.getByName( SpawnTemplatePrefix )
+ if TemplateGroup then
+ self.SpawnTemplatePrefix = SpawnTemplatePrefix
+ self.SpawnIndex = 0
+ self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart.
+ self.AliveUnits = 0 -- Contains the counter how many units are currently alive
+ self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not.
+ self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!!
+ self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning.
+ self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts.
+ self.SpawnInitLimit = false -- By default, no InitLimit
+ self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time.
+ self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned.
+ self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false.
+ self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned.
+ self.AIOnOff = true -- The AI is on by default when spawning a group.
+ self.SpawnUnControlled = false
+ self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name.
+ self.DelayOnOff = false -- No intial delay when spawning the first group.
+ self.Grouping = nil -- No grouping
-
-
- MOOSE/AI_Balancer.lua at master · FlightControl-Master/MOOSE
-
-
-
+ self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned.
+ else
+ error( "SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" )
+ end
+ self:SetEventPriority( 5 )
+
+ return self
+end
+
+--- Creates a new SPAWN instance to create new groups based on the defined template and using a new alias for each new group.
+-- @param #SPAWN self
+-- @param #string SpawnTemplatePrefix is the name of the Group in the ME that defines the Template.
+-- @param #string SpawnAliasPrefix is the name that will be given to the Group at runtime.
+-- @return #SPAWN
+-- @usage
+-- -- NATO helicopters engaging in the battle field.
+-- Spawn_BE_KA50 = SPAWN:NewWithAlias( 'BE KA-50@RAMP-Ground Defense', 'Helicopter Attacking a City' )
+-- @usage local PlaneWithAlias = SPAWN:NewWithAlias( "Plane", "Bomber" ) -- Creates a new local variable that can instantiate new planes with the name "Bomber#ddd" using the template "Plane" as defined within the ME.
+function SPAWN:NewWithAlias( SpawnTemplatePrefix, SpawnAliasPrefix )
+ local self = BASE:Inherit( self, BASE:New() )
+ self:F( { SpawnTemplatePrefix, SpawnAliasPrefix } )
+
+ local TemplateGroup = Group.getByName( SpawnTemplatePrefix )
+ if TemplateGroup then
+ self.SpawnTemplatePrefix = SpawnTemplatePrefix
+ self.SpawnAliasPrefix = SpawnAliasPrefix
+ self.SpawnIndex = 0
+ self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart.
+ self.AliveUnits = 0 -- Contains the counter how many units are currently alive
+ self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not.
+ self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!!
+ self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning.
+ self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts.
+ self.SpawnInitLimit = false -- By default, no InitLimit
+ self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time.
+ self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned.
+ self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false.
+ self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned.
+ self.AIOnOff = true -- The AI is on by default when spawning a group.
+ self.SpawnUnControlled = false
+ self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name.
+ self.DelayOnOff = false -- No intial delay when spawning the first group.
+ self.Grouping = nil
+
+ self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned.
+ else
+ error( "SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" )
+ end
+
+ self:SetEventPriority( 5 )
+
+ return self
+end
+
+
+--- Limits the Maximum amount of Units that can be alive at the same time, and the maximum amount of groups that can be spawned.
+-- Note that this method is exceptionally important to balance the performance of the mission. Depending on the machine etc, a mission can only process a maximum amount of units.
+-- If the time interval must be short, but there should not be more Units or Groups alive than a maximum amount of units, then this method should be used...
+-- When a @{#SPAWN.New} is executed and the limit of the amount of units alive is reached, then no new spawn will happen of the group, until some of these units of the spawn object will be destroyed.
+-- @param #SPAWN self
+-- @param #number SpawnMaxUnitsAlive The maximum amount of units that can be alive at runtime.
+-- @param #number SpawnMaxGroups The maximum amount of groups that can be spawned. When the limit is reached, then no more actual spawns will happen of the group.
+-- This parameter is useful to define a maximum amount of airplanes, ground troops, helicopters, ships etc within a supply area.
+-- This parameter accepts the value 0, which defines that there are no maximum group limits, but there are limits on the maximum of units that can be alive at the same time.
+-- @return #SPAWN self
+-- @usage
+-- -- NATO helicopters engaging in the battle field.
+-- -- This helicopter group consists of one Unit. So, this group will SPAWN maximum 2 groups simultaneously within the DCSRTE.
+-- -- There will be maximum 24 groups spawned during the whole mission lifetime.
+-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitLimit( 2, 24 )
+function SPAWN:InitLimit( SpawnMaxUnitsAlive, SpawnMaxGroups )
+ self:F( { self.SpawnTemplatePrefix, SpawnMaxUnitsAlive, SpawnMaxGroups } )
+
+ self.SpawnInitLimit = true
+ self.SpawnMaxUnitsAlive = SpawnMaxUnitsAlive -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time.
+ self.SpawnMaxGroups = SpawnMaxGroups -- The maximum amount of groups that can be spawned.
+
+ for SpawnGroupID = 1, self.SpawnMaxGroups do
+ self:_InitializeSpawnGroups( SpawnGroupID )
+ end
+
+ return self
+end
+
+--- Keeps the unit names as defined within the mission editor,
+-- but note that anything after a # mark is ignored,
+-- and any spaces before and after the resulting name are removed.
+-- IMPORTANT! This method MUST be the first used after :New !!!
+-- @param #SPAWN self
+-- @return #SPAWN self
+function SPAWN:InitKeepUnitNames()
+ self:F( )
+
+ self.SpawnInitKeepUnitNames = true
+
+ return self
+end
+
+
+--- Randomizes the defined route of the SpawnTemplatePrefix group in the ME. This is very useful to define extra variation of the behaviour of groups.
+-- @param #SPAWN self
+-- @param #number SpawnStartPoint is the waypoint where the randomization begins.
+-- Note that the StartPoint = 0 equaling the point where the group is spawned.
+-- @param #number SpawnEndPoint is the waypoint where the randomization ends counting backwards.
+-- This parameter is useful to avoid randomization to end at a waypoint earlier than the last waypoint on the route.
+-- @param #number SpawnRadius is the radius in meters in which the randomization of the new waypoints, with the original waypoint of the original template located in the middle ...
+-- @param #number SpawnHeight (optional) Specifies the **additional** height in meters that can be added to the base height specified at each waypoint in the ME.
+-- @return #SPAWN
+-- @usage
+-- -- NATO helicopters engaging in the battle field.
+-- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP).
+-- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter.
+-- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters.
+-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 )
+function SPAWN:InitRandomizeRoute( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight )
+ self:F( { self.SpawnTemplatePrefix, SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight } )
+
+ self.SpawnRandomizeRoute = true
+ self.SpawnRandomizeRouteStartPoint = SpawnStartPoint
+ self.SpawnRandomizeRouteEndPoint = SpawnEndPoint
+ self.SpawnRandomizeRouteRadius = SpawnRadius
+ self.SpawnRandomizeRouteHeight = SpawnHeight
+
+ for GroupID = 1, self.SpawnMaxGroups do
+ self:_RandomizeRoute( GroupID )
+ end
+
+ return self
+end
+
+--- Randomizes the position of @{Group}s that are spawned within a **radius band**, given an Outer and Inner radius, from the point that the spawn happens.
+-- @param #SPAWN self
+-- @param #boolean RandomizePosition If true, SPAWN will perform the randomization of the @{Group}s position between a given outer and inner radius.
+-- @param Dcs.DCSTypes#Distance OuterRadius (optional) The outer radius in meters where the new group will be spawned.
+-- @param Dcs.DCSTypes#Distance InnerRadius (optional) The inner radius in meters where the new group will NOT be spawned.
+-- @return #SPAWN
+function SPAWN:InitRandomizePosition( RandomizePosition, OuterRadius, InnerRadius )
+ self:F( { self.SpawnTemplatePrefix, RandomizePosition, OuterRadius, InnerRadius } )
+
+ self.SpawnRandomizePosition = RandomizePosition or false
+ self.SpawnRandomizePositionOuterRadius = OuterRadius or 0
+ self.SpawnRandomizePositionInnerRadius = InnerRadius or 0
+
+ for GroupID = 1, self.SpawnMaxGroups do
+ self:_RandomizeRoute( GroupID )
+ end
+
+ return self
+end
+
+
+--- Randomizes the UNITs that are spawned within a radius band given an Outer and Inner radius.
+-- @param #SPAWN self
+-- @param #boolean RandomizeUnits If true, SPAWN will perform the randomization of the @{UNIT}s position within the group between a given outer and inner radius.
+-- @param Dcs.DCSTypes#Distance OuterRadius (optional) The outer radius in meters where the new group will be spawned.
+-- @param Dcs.DCSTypes#Distance InnerRadius (optional) The inner radius in meters where the new group will NOT be spawned.
+-- @return #SPAWN
+-- @usage
+-- -- NATO helicopters engaging in the battle field.
+-- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP).
+-- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter.
+-- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters.
+-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 )
+function SPAWN:InitRandomizeUnits( RandomizeUnits, OuterRadius, InnerRadius )
+ self:F( { self.SpawnTemplatePrefix, RandomizeUnits, OuterRadius, InnerRadius } )
+
+ self.SpawnRandomizeUnits = RandomizeUnits or false
+ self.SpawnOuterRadius = OuterRadius or 0
+ self.SpawnInnerRadius = InnerRadius or 0
+
+ for GroupID = 1, self.SpawnMaxGroups do
+ self:_RandomizeRoute( GroupID )
+ end
+
+ return self
+end
+
+--- This method is rather complicated to understand. But I'll try to explain.
+-- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor,
+-- but they will all follow the same Template route and have the same prefix name.
+-- In other words, this method randomizes between a defined set of groups the template to be used for each new spawn of a group.
+-- @param #SPAWN self
+-- @param #string SpawnTemplatePrefixTable A table with the names of the groups defined within the mission editor, from which one will be choosen when a new group will be spawned.
+-- @return #SPAWN
+-- @usage
+-- -- NATO Tank Platoons invading Gori.
+-- -- Choose between 13 different 'US Tank Platoon' configurations for each new SPAWN the Group to be spawned for the
+-- -- 'US Tank Platoon Left', 'US Tank Platoon Middle' and 'US Tank Platoon Right' SpawnTemplatePrefixes.
+-- -- Each new SPAWN will randomize the route, with a defined time interval of 200 seconds with 40% time variation (randomization) and
+-- -- with a limit set of maximum 12 Units alive simulteneously and 150 Groups to be spawned during the whole mission.
+-- Spawn_US_Platoon = { 'US Tank Platoon 1', 'US Tank Platoon 2', 'US Tank Platoon 3', 'US Tank Platoon 4', 'US Tank Platoon 5',
+-- 'US Tank Platoon 6', 'US Tank Platoon 7', 'US Tank Platoon 8', 'US Tank Platoon 9', 'US Tank Platoon 10',
+-- 'US Tank Platoon 11', 'US Tank Platoon 12', 'US Tank Platoon 13' }
+-- Spawn_US_Platoon_Left = SPAWN:New( 'US Tank Platoon Left' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 )
+-- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 )
+-- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 )
+function SPAWN:InitRandomizeTemplate( SpawnTemplatePrefixTable )
+ self:F( { self.SpawnTemplatePrefix, SpawnTemplatePrefixTable } )
+
+ self.SpawnTemplatePrefixTable = SpawnTemplatePrefixTable
+ self.SpawnRandomizeTemplate = true
+
+ for SpawnGroupID = 1, self.SpawnMaxGroups do
+ self:_RandomizeTemplate( SpawnGroupID )
+ end
+
+ return self
+end
+
+--- When spawning a new group, make the grouping of the units according the InitGrouping setting.
+-- @param #SPAWN self
+-- @param #number Grouping Indicates the maximum amount of units in the group.
+-- @return #SPAWN
+function SPAWN:InitGrouping( Grouping ) -- R2.2
+ self:F( { self.SpawnTemplatePrefix, Grouping } )
+
+ self.SpawnGrouping = Grouping
+
+ return self
+end
+
+
+
+--TODO: Add example.
+--- This method provides the functionality to randomize the spawning of the Groups at a given list of zones of different types.
+-- @param #SPAWN self
+-- @param #table SpawnZoneTable A table with @{Zone} objects. If this table is given, then each spawn will be executed within the given list of @{Zone}s objects.
+-- @return #SPAWN
+-- @usage
+-- -- NATO Tank Platoons invading Gori.
+-- -- Choose between 3 different zones for each new SPAWN the Group to be executed, regardless of the zone type.
+function SPAWN:InitRandomizeZones( SpawnZoneTable )
+ self:F( { self.SpawnTemplatePrefix, SpawnZoneTable } )
+
+ self.SpawnZoneTable = SpawnZoneTable
+ self.SpawnRandomizeZones = true
+
+ for SpawnGroupID = 1, self.SpawnMaxGroups do
+ self:_RandomizeZones( SpawnGroupID )
+ end
+
+ return self
+end
+
+
+
+
+
+--- For planes and helicopters, when these groups go home and land on their home airbases and farps, they normally would taxi to the parking spot, shut-down their engines and wait forever until the Group is removed by the runtime environment.
+-- This method is used to re-spawn automatically (so no extra call is needed anymore) the same group after it has landed.
+-- This will enable a spawned group to be re-spawned after it lands, until it is destroyed...
+-- Note: When the group is respawned, it will re-spawn from the original airbase where it took off.
+-- So ensure that the routes for groups that respawn, always return to the original airbase, or players may get confused ...
+-- @param #SPAWN self
+-- @return #SPAWN self
+-- @usage
+-- -- RU Su-34 - AI Ship Attack
+-- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically.
+-- SpawnRU_SU34 = SPAWN:New( 'TF1 RU Su-34 Krymsk@AI - Attack Ships' ):Schedule( 2, 3, 1800, 0.4 ):SpawnUncontrolled():InitRandomizeRoute( 1, 1, 3000 ):RepeatOnEngineShutDown()
+function SPAWN:InitRepeat()
+ self:F( { self.SpawnTemplatePrefix, self.SpawnIndex } )
+
+ self.Repeat = true
+ self.RepeatOnEngineShutDown = false
+ self.RepeatOnLanding = true
+
+ return self
+end
+
+--- Respawn group after landing.
+-- @param #SPAWN self
+-- @return #SPAWN self
+function SPAWN:InitRepeatOnLanding()
+ self:F( { self.SpawnTemplatePrefix } )
+
+ self:InitRepeat()
+ self.RepeatOnEngineShutDown = false
+ self.RepeatOnLanding = true
+
+ return self
+end
+
+
+--- Respawn after landing when its engines have shut down.
+-- @param #SPAWN self
+-- @return #SPAWN self
+function SPAWN:InitRepeatOnEngineShutDown()
+ self:F( { self.SpawnTemplatePrefix } )
+
+ self:InitRepeat()
+ self.RepeatOnEngineShutDown = true
+ self.RepeatOnLanding = false
+
+ return self
+end
+
+
+--- CleanUp groups when they are still alive, but inactive.
+-- When groups are still alive and have become inactive due to damage and are unable to contribute anything, then this group will be removed at defined intervals in seconds.
+-- @param #SPAWN self
+-- @param #string SpawnCleanUpInterval The interval to check for inactive groups within seconds.
+-- @return #SPAWN self
+-- @usage Spawn_Helicopter:CleanUp( 20 ) -- CleanUp the spawning of the helicopters every 20 seconds when they become inactive.
+function SPAWN:InitCleanUp( SpawnCleanUpInterval )
+ self:F( { self.SpawnTemplatePrefix, SpawnCleanUpInterval } )
+
+ self.SpawnCleanUpInterval = SpawnCleanUpInterval
+ self.SpawnCleanUpTimeStamps = {}
+
+ local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup()
+ self:T( { "CleanUp Scheduler:", SpawnGroup } )
+
+ --self.CleanUpFunction = routines.scheduleFunction( self._SpawnCleanUpScheduler, { self }, timer.getTime() + 1, SpawnCleanUpInterval )
+ self.CleanUpScheduler = SCHEDULER:New( self, self._SpawnCleanUpScheduler, {}, 1, SpawnCleanUpInterval, 0.2 )
+ return self
+end
+
+
+
+--- Makes the groups visible before start (like a batallion).
+-- The method will take the position of the group as the first position in the array.
+-- @param #SPAWN self
+-- @param #number SpawnAngle The angle in degrees how the groups and each unit of the group will be positioned.
+-- @param #number SpawnWidth The amount of Groups that will be positioned on the X axis.
+-- @param #number SpawnDeltaX The space between each Group on the X-axis.
+-- @param #number SpawnDeltaY The space between each Group on the Y-axis.
+-- @return #SPAWN self
+-- @usage
+-- -- Define an array of Groups.
+-- Spawn_BE_Ground = SPAWN:New( 'BE Ground' ):InitLimit( 2, 24 ):InitArray( 90, "Diamond", 10, 100, 50 )
+function SPAWN:InitArray( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY )
+ self:F( { self.SpawnTemplatePrefix, SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY } )
+
+ self.SpawnVisible = true -- When the first Spawn executes, all the Groups need to be made visible before start.
+
+ local SpawnX = 0
+ local SpawnY = 0
+ local SpawnXIndex = 0
+ local SpawnYIndex = 0
+
+ for SpawnGroupID = 1, self.SpawnMaxGroups do
+ self:T( { SpawnX, SpawnY, SpawnXIndex, SpawnYIndex } )
+
+ self.SpawnGroups[SpawnGroupID].Visible = true
+ self.SpawnGroups[SpawnGroupID].Spawned = false
-
+ SpawnXIndex = SpawnXIndex + 1
+ if SpawnWidth and SpawnWidth ~= 0 then
+ if SpawnXIndex >= SpawnWidth then
+ SpawnXIndex = 0
+ SpawnYIndex = SpawnYIndex + 1
+ end
+ end
-
-
-
-
-
+ local SpawnRootX = self.SpawnGroups[SpawnGroupID].SpawnTemplate.x
+ local SpawnRootY = self.SpawnGroups[SpawnGroupID].SpawnTemplate.y
+
+ self:_TranslateRotate( SpawnGroupID, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle )
+
+ self.SpawnGroups[SpawnGroupID].SpawnTemplate.lateActivation = true
+ self.SpawnGroups[SpawnGroupID].SpawnTemplate.visible = true
+
+ self.SpawnGroups[SpawnGroupID].Visible = true
+
+ self:HandleEvent( EVENTS.Birth, self._OnBirth )
+ self:HandleEvent( EVENTS.Dead, self._OnDeadOrCrash )
+ self:HandleEvent( EVENTS.Crash, self._OnDeadOrCrash )
+ if self.Repeat then
+ self:HandleEvent( EVENTS.Takeoff, self._OnTakeOff )
+ self:HandleEvent( EVENTS.Land, self._OnLand )
+ end
+ if self.RepeatOnEngineShutDown then
+ self:HandleEvent( EVENTS.EngineShutdown, self._OnEngineShutDown )
+ end
+
+ self.SpawnGroups[SpawnGroupID].Group = _DATABASE:Spawn( self.SpawnGroups[SpawnGroupID].SpawnTemplate )
+
+ SpawnX = SpawnXIndex * SpawnDeltaX
+ SpawnY = SpawnYIndex * SpawnDeltaY
+ end
+ return self
+end
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+do -- AI methods
+ --- Turns the AI On or Off for the @{Group} when spawning.
+ -- @param #SPAWN self
+ -- @param #boolean AIOnOff A value of true sets the AI On, a value of false sets the AI Off.
+ -- @return #SPAWN The SPAWN object
+ function SPAWN:InitAIOnOff( AIOnOff )
-
-
-
-
-
-
-
-
-
-
-
-
+ self.AIOnOff = AIOnOff
+ return self
+ end
+ --- Turns the AI On for the @{Group} when spawning.
+ -- @param #SPAWN self
+ -- @return #SPAWN The SPAWN object
+ function SPAWN:InitAIOn()
+
+ return self:InitAIOnOff( true )
+ end
+
+ --- Turns the AI Off for the @{Group} when spawning.
+ -- @param #SPAWN self
+ -- @return #SPAWN The SPAWN object
+ function SPAWN:InitAIOff()
+
+ return self:InitAIOnOff( false )
+ end
-
+end -- AI methods
-
-
+do -- Delay methods
+ --- Turns the Delay On or Off for the first @{Group} scheduled spawning.
+ -- The default value is that for scheduled spawning, there is an initial delay when spawning the first @{Group}.
+ -- @param #SPAWN self
+ -- @param #boolean DelayOnOff A value of true sets the Delay On, a value of false sets the Delay Off.
+ -- @return #SPAWN The SPAWN object
+ function SPAWN:InitDelayOnOff( DelayOnOff )
+
+ self.DelayOnOff = DelayOnOff
+ return self
+ end
+
+ --- Turns the Delay On for the @{Group} when spawning.
+ -- @param #SPAWN self
+ -- @return #SPAWN The SPAWN object
+ function SPAWN:InitDelayOn()
+
+ return self:InitDelayOnOff( true )
+ end
+
+ --- Turns the Delay Off for the @{Group} when spawning.
+ -- @param #SPAWN self
+ -- @return #SPAWN The SPAWN object
+ function SPAWN:InitDelayOff()
+
+ return self:InitDelayOnOff( false )
+ end
-
+end -- Delay methods
+--- Will spawn a group based on the internal index.
+-- Note: Uses @{DATABASE} module defined in MOOSE.
+-- @param #SPAWN self
+-- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions.
+function SPAWN:Spawn()
+ self:F( { self.SpawnTemplatePrefix, self.SpawnIndex, self.AliveUnits } )
-
+ return self:SpawnWithIndex( self.SpawnIndex + 1 )
+end
+--- Will re-spawn a group based on a given index.
+-- Note: Uses @{DATABASE} module defined in MOOSE.
+-- @param #SPAWN self
+-- @param #string SpawnIndex The index of the group to be spawned.
+-- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions.
+function SPAWN:ReSpawn( SpawnIndex )
+ self:F( { self.SpawnTemplatePrefix, SpawnIndex } )
+
+ if not SpawnIndex then
+ SpawnIndex = 1
+ end
-
+-- TODO: This logic makes DCS crash and i don't know why (yet).
+ local SpawnGroup = self:GetGroupFromIndex( SpawnIndex )
+ local WayPoints = SpawnGroup and SpawnGroup.WayPoints or nil
+ if SpawnGroup then
+ local SpawnDCSGroup = SpawnGroup:GetDCSObject()
+ if SpawnDCSGroup then
+ SpawnGroup:Destroy()
+ end
+ end
-
+ local SpawnGroup = self:SpawnWithIndex( SpawnIndex )
+ if SpawnGroup and WayPoints then
+ -- If there were WayPoints set, then Re-Execute those WayPoints!
+ SpawnGroup:WayPointInitialize( WayPoints )
+ SpawnGroup:WayPointExecute( 1, 5 )
+ end
+
+ if SpawnGroup.ReSpawnFunction then
+ SpawnGroup:ReSpawnFunction()
+ end
+
+ SpawnGroup:ResetEvents()
+
+ return SpawnGroup
+end
-
-
-
-
-
-
-
-
-
-
-
+--- Will spawn a group with a specified index number.
+-- Uses @{DATABASE} global object defined in MOOSE.
+-- @param #SPAWN self
+-- @param #string SpawnIndex The index of the group to be spawned.
+-- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions.
+function SPAWN:SpawnWithIndex( SpawnIndex )
+ self:F2( { SpawnTemplatePrefix = self.SpawnTemplatePrefix, SpawnIndex = SpawnIndex, AliveUnits = self.AliveUnits, SpawnMaxGroups = self.SpawnMaxGroups } )
+
+ if self:_GetSpawnIndex( SpawnIndex ) then
+ if self.SpawnGroups[self.SpawnIndex].Visible then
+ self.SpawnGroups[self.SpawnIndex].Group:Activate()
+ else
-
+ -- If RandomizeUnits, then Randomize the formation at the start point.
+ if self.SpawnRandomizeUnits then
+ for UnitID = 1, #SpawnTemplate.units do
+ local RandomVec2 = PointVec3:GetRandomVec2InRadius( self.SpawnOuterRadius, self.SpawnInnerRadius )
+ SpawnTemplate.units[UnitID].x = RandomVec2.x
+ SpawnTemplate.units[UnitID].y = RandomVec2.y
+ self:T( 'SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
+ end
+ end
-
-
-
+ if SpawnTemplate.CategoryID == Group.Category.HELICOPTER or SpawnTemplate.CategoryID == Group.Category.AIRPLANE then
+ if SpawnTemplate.route.points[1].type == "TakeOffParking" then
+ SpawnTemplate.uncontrolled = self.SpawnUnControlled
+ end
+ end
+ end
+ self:HandleEvent( EVENTS.Birth, self._OnBirth )
+ self:HandleEvent( EVENTS.Dead, self._OnDeadOrCrash )
+ self:HandleEvent( EVENTS.Crash, self._OnDeadOrCrash )
+ if self.Repeat then
+ self:HandleEvent( EVENTS.Takeoff, self._OnTakeOff )
+ self:HandleEvent( EVENTS.Land, self._OnLand )
+ end
+ if self.RepeatOnEngineShutDown then
+ self:HandleEvent( EVENTS.EngineShutdown, self._OnEngineShutDown )
+ end
-
-
+ local SpawnGroup = self.SpawnGroups[self.SpawnIndex].Group -- Wrapper.Group#GROUP
+ --TODO: Need to check if this function doesn't need to be scheduled, as the group may not be immediately there!
+ if SpawnGroup then
+
+ SpawnGroup:SetAIOnOff( self.AIOnOff )
+ end
-
-
-
-
-
-
-
-
-
-
-
-
-
+ self:T3( SpawnTemplate.name )
+
+ -- If there is a SpawnFunction hook defined, call it.
+ if self.SpawnFunctionHook then
+ -- delay calling this for .1 seconds so that it hopefully comes after the BIRTH event of the group.
+ self.SpawnHookScheduler = SCHEDULER:New()
+ self.SpawnHookScheduler:Schedule( nil, self.SpawnFunctionHook, { self.SpawnGroups[self.SpawnIndex].Group, unpack( self.SpawnFunctionArguments)}, 0.1 )
+ -- self.SpawnFunctionHook( self.SpawnGroups[self.SpawnIndex].Group, unpack( self.SpawnFunctionArguments ) )
+ end
+ -- TODO: Need to fix this by putting an "R" in the name of the group when the group repeats.
+ --if self.Repeat then
+ -- _DATABASE:SetStatusGroup( SpawnTemplate.name, "ReSpawn" )
+ --end
+ end
-
-
-
+ self.SpawnGroups[self.SpawnIndex].Spawned = true
+ return self.SpawnGroups[self.SpawnIndex].Group
+ else
+ --self:E( { self.SpawnTemplatePrefix, "No more Groups to Spawn:", SpawnIndex, self.SpawnMaxGroups } )
+ end
-
+--- Spawns new groups at varying time intervals.
+-- This is useful if you want to have continuity within your missions of certain (AI) groups to be present (alive) within your missions.
+-- @param #SPAWN self
+-- @param #number SpawnTime The time interval defined in seconds between each new spawn of new groups.
+-- @param #number SpawnTimeVariation The variation to be applied on the defined time interval between each new spawn.
+-- The variation is a number between 0 and 1, representing the %-tage of variation to be applied on the time interval.
+-- @return #SPAWN self
+-- @usage
+-- -- NATO helicopters engaging in the battle field.
+-- -- The time interval is set to SPAWN new helicopters between each 600 seconds, with a time variation of 50%.
+-- -- The time variation in this case will be between 450 seconds and 750 seconds.
+-- -- This is calculated as follows:
+-- -- Low limit: 600 * ( 1 - 0.5 / 2 ) = 450
+-- -- High limit: 600 * ( 1 + 0.5 / 2 ) = 750
+-- -- Between these two values, a random amount of seconds will be choosen for each new spawn of the helicopters.
+-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):Schedule( 600, 0.5 )
+function SPAWN:SpawnScheduled( SpawnTime, SpawnTimeVariation )
+ self:F( { SpawnTime, SpawnTimeVariation } )
-
-
- FlightControl-Master/MOOSE
+ if SpawnTime ~= nil and SpawnTimeVariation ~= nil then
+ local InitialDelay = 0
+ if self.DelayOnOff == true then
+ InitialDelay = math.random( SpawnTime - SpawnTime * SpawnTimeVariation, SpawnTime + SpawnTime * SpawnTimeVariation )
+ end
+ self.SpawnScheduler = SCHEDULER:New( self, self._Scheduler, {}, InitialDelay, SpawnTime, SpawnTimeVariation )
+ end
-
+--- Will re-start the spawning scheduler.
+-- Note: This method is only required to be called when the schedule was stopped.
+-- @param #SPAWN self
+-- @return #SPAWN
+function SPAWN:SpawnScheduleStart()
+ self:F( { self.SpawnTemplatePrefix } )
+ self.SpawnScheduler:Start()
+ return self
+end
+--- Will stop the scheduled spawning scheduler.
+-- @param #SPAWN self
+-- @return #SPAWN
+function SPAWN:SpawnScheduleStop()
+ self:F( { self.SpawnTemplatePrefix } )
-
+--- Allows to place a CallFunction hook when a new group spawns.
+-- The provided method will be called when a new group is spawned, including its given parameters.
+-- The first parameter of the SpawnFunction is the @{Group#GROUP} that was spawned.
+-- @param #SPAWN self
+-- @param #function SpawnCallBackFunction The function to be called when a group spawns.
+-- @param SpawnFunctionArguments A random amount of arguments to be provided to the function when the group spawns.
+-- @return #SPAWN
+-- @usage
+-- -- Declare SpawnObject and call a function when a new Group is spawned.
+-- local SpawnObject = SPAWN
+-- :New( "SpawnObject" )
+-- :InitLimit( 2, 10 )
+-- :OnSpawnGroup(
+-- function( SpawnGroup )
+-- SpawnGroup:E( "I am spawned" )
+-- end
+-- )
+-- :SpawnScheduled( 300, 0.3 )
+function SPAWN:OnSpawnGroup( SpawnCallBackFunction, ... )
+ self:F( "OnSpawnGroup" )
-
-
+ self.SpawnFunctionHook = SpawnCallBackFunction
+ self.SpawnFunctionArguments = {}
+ if arg then
+ self.SpawnFunctionArguments = arg
+ end
+
+ return self
+end
+
+--- Will spawn a group at an airbase.
+-- This method is mostly advisable to be used if you want to simulate spawning units at an airbase.
+-- Note that each point in the route assigned to the spawning group is reset to the point of the spawn.
+-- You can use the returned group to further define the route to be followed.
+-- @param #SPAWN self
+-- @param Wrapper.Airbase#AIRBASE Airbase The @{Airbase} where to spawn the group.
+-- @param #SPAWN.Takeoff Takeoff (optional) The location and takeoff method. Default is Hot.
+-- @param #number TakeoffAltitude (optional) The altitude above the ground.
+-- @return Wrapper.Group#GROUP that was spawned.
+-- @return #nil Nothing was spawned.
+function SPAWN:SpawnAtAirbase( Airbase, Takeoff, TakeoffAltitude ) -- R2.2
+ self:E( { self.SpawnTemplatePrefix, Airbase, Takeoff, TakeoffAltitude } )
+
+ local PointVec3 = Airbase:GetPointVec3()
+ self:T2(PointVec3)
+
+ Takeoff = Takeoff or SPAWN.Takeoff.Hot
+
+ if self:_GetSpawnIndex( self.SpawnIndex + 1 ) then
+
+ local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate
+
+ if SpawnTemplate then
+
+ self:T( { "Current point of ", self.SpawnTemplatePrefix, Airbase } )
+
+ -- Translate the position of the Group Template to the Vec3.
+ for UnitID = 1, #SpawnTemplate.units do
+ self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
+ local UnitTemplate = SpawnTemplate.units[UnitID]
+ local SX = UnitTemplate.x
+ local SY = UnitTemplate.y
+ local BX = SpawnTemplate.route.points[1].x
+ local BY = SpawnTemplate.route.points[1].y
+ local TX = PointVec3.x + ( SX - BX )
+ local TY = PointVec3.z + ( SY - BY )
+ SpawnTemplate.units[UnitID].x = TX
+ SpawnTemplate.units[UnitID].y = TY
+ if Takeoff == GROUP.Takeoff.Air then
+ SpawnTemplate.units[UnitID].alt = PointVec3.y + ( TakeoffAltitude or 200 )
+ else
+ SpawnTemplate.units[UnitID].alt = PointVec3.y + 10
+ end
+ self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
+ end
-
-- ### Author: **Sven Van de Velde (FlightControl)**
-
-
-
-
-- ### Contributions:
-
-
-
-
--
-
-
-
-
-- * **[Dutch_Baron](https://forums.eagle.ru/member.php?u=112075)**: Working together with James has resulted in the creation of the AI_BALANCER class. James has shared his ideas on balancing AI with air units, and together we made a first design which you can use now :-)
-
-
-
-
--
-
-
-
-
-- ====
-
-
-
-
--
-
-
-
-
-- @module AI_Balancer
-
-
-
-
-
-
-
-
-
--- @type AI_BALANCER
-
-
-
-
-- @field Core.Set#SET_CLIENT SetClient
-
-
-
-
-- @field Functional.Spawn#SPAWN SpawnAI
-
-
-
-
-- @field Wrapper.Group#GROUP Test
-
-
-
-
-- @extends Core.Fsm#FSM_SET
-
-
-
-
-
-
-
-
-
-
-
-
-
-
--- # AI_BALANCER class, extends @{Fsm#FSM_SET}
-
-
-
-
--
-
-
-
-
-- The AI_BALANCER class monitors and manages as many replacement AI groups as there are
-
-
-
-
-- CLIENTS in a SET_CLIENT collection, which are not occupied by human players.
-
-
-
-
-- In other words, use AI_BALANCER to simulate human behaviour by spawning in replacement AI in multi player missions.
-
-
-
-
--
-
-
-
-
-- The parent class @{Fsm#FSM_SET} manages the functionality to control the Finite State Machine (FSM).
-
-
-
-
-- The mission designer can tailor the behaviour of the AI_BALANCER, by defining event and state transition methods.
-
-
-
-
-- An explanation about state and event transition methods can be found in the @{FSM} module documentation.
-
-
-
-
--
-
-
-
-
-- The mission designer can tailor the AI_BALANCER behaviour, by implementing a state or event handling method for the following:
-
-
-
-
--
-
-
-
-
-- * @{#AI_BALANCER.OnAfterSpawned}( AISet, From, Event, To, AIGroup ): Define to add extra logic when an AI is spawned.
-
-
-
-
--
-
-
-
-
-- ## 1. AI_BALANCER construction
-
-
-
-
--
-
-
-
-
-- Create a new AI_BALANCER object with the @{#AI_BALANCER.New}() method:
-- * **Monitoring** ( Set ): Monitoring the Set if all AI is spawned for the Clients.
-
-
-
-
-- * **Spawning** ( Set, ClientName ): There is a new AI group spawned with ClientName as the name of reference.
-
-
-
-
-- * **Spawned** ( Set, AIGroup ): A new AI has been spawned. You can handle this event to customize the AI behaviour with other AI FSMs or own processes.
-
-
-
-
-- * **Destroying** ( Set, AIGroup ): The AI is being destroyed.
-
-
-
-
-- * **Returning** ( Set, AIGroup ): The AI is returning to the airbase specified by the ReturnToAirbase methods. Handle this state to customize the return behaviour of the AI, if any.
-
-
-
-
--
-
-
-
-
-- ### 2.2. AI_BALANCER Events
-
-
-
-
--
-
-
-
-
-- * **Monitor** ( Set ): Every 10 seconds, the Monitor event is triggered to monitor the Set.
-
-
-
-
-- * **Spawn** ( Set, ClientName ): Triggers when there is a new AI group to be spawned with ClientName as the name of reference.
-
-
-
-
-- * **Spawned** ( Set, AIGroup ): Triggers when a new AI has been spawned. You can handle this event to customize the AI behaviour with other AI FSMs or own processes.
-
-
-
-
-- * **Destroy** ( Set, AIGroup ): The AI is being destroyed.
-
-
-
-
-- * **Return** ( Set, AIGroup ): The AI is returning to the airbase specified by the ReturnToAirbase methods.
-
-
-
-
--
-
-
-
-
-- ## 3. AI_BALANCER spawn interval for replacement AI
-
-
-
-
--
-
-
-
-
-- Use the method @{#AI_BALANCER.InitSpawnInterval}() to set the earliest and latest interval in seconds that is waited until a new replacement AI is spawned.
-
-
-
-
--
-
-
-
-
-- ## 4. AI_BALANCER returns AI to Airbases
-
-
-
-
--
-
-
-
-
-- By default, When a human player joins a slot that is AI_BALANCED, the AI group will be destroyed by default.
-
-
-
-
-- However, there are 2 additional options that you can use to customize the destroy behaviour.
-
-
-
-
-- When a human player joins a slot, you can configure to let the AI return to:
-
-
-
-
--
-
-
-
-
-- * @{#AI_BALANCER.ReturnToHomeAirbase}: Returns the AI to the **home** @{Airbase#AIRBASE}.
-
-
-
-
-- * @{#AI_BALANCER.ReturnToNearestAirbases}: Returns the AI to the **nearest friendly** @{Airbase#AIRBASE}.
-
-
-
-
--
-
-
-
-
-- Note that when AI returns to an airbase, the AI_BALANCER will trigger the **Return** event and the AI will return,
-
-
-
-
-- otherwise the AI_BALANCER will trigger a **Destroy** event, and the AI will be destroyed.
-
-
-
-
--
-
-
-
-
-- @field #AI_BALANCER
-
-
-
-
AI_BALANCER = {
-
-
-
-
ClassName ="AI_BALANCER",
-
-
-
-
PatrolZones = {},
-
-
-
-
AIGroups = {},
-
-
-
-
Earliest =5, -- Earliest a new AI can be spawned is in 5 seconds.
-
-
-
-
Latest =60, -- Latest a new AI can be spawned is in 60 seconds.
-
-
-
-
}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
--- Creates a new AI_BALANCER object
-
-
-
-
-- @param #AI_BALANCER self
-
-
-
-
-- @param Core.Set#SET_CLIENT SetClient A SET\_CLIENT object that will contain the CLIENT objects to be monitored if they are alive or not (joined by a player).
-
-
-
-
-- @param Functional.Spawn#SPAWN SpawnAI The default Spawn object to spawn new AI Groups when needed.
--- Sets the earliest to the latest interval in seconds how long AI_BALANCER will wait to spawn a new AI.
-
-
-
-
-- Provide 2 identical seconds if the interval should be a fixed amount of seconds.
-
-
-
-
-- @param #AI_BALANCER self
-
-
-
-
-- @param #number Earliest The earliest a new AI can be spawned in seconds.
-
-
-
-
-- @param #number Latest The latest a new AI can be spawned in seconds.
-
-
-
-
-- @return self
-
-
-
-
function AI_BALANCER:InitSpawnInterval( Earliest, Latest )
-
-
-
-
-
-
-
-
-
self.Earliest= Earliest
-
-
-
-
self.Latest= Latest
-
-
-
-
-
-
-
-
returnself
-
-
-
-
end
-
-
-
-
-
-
-
-
-
--- Returns the AI to the nearest friendly @{Airbase#AIRBASE}.
-
-
-
-
-- @param #AI_BALANCER self
-
-
-
-
-- @param Dcs.DCSTypes#Distance ReturnThresholdRange If there is an enemy @{Client#CLIENT} within the ReturnThresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}.
-
-
-
-
-- @param Core.Set#SET_AIRBASE ReturnAirbaseSet The SET of @{Set#SET_AIRBASE}s to evaluate where to return to.
-
-
-
-
function AI_BALANCER:ReturnToNearestAirbases( ReturnThresholdRange, ReturnAirbaseSet )
-
-
-
-
-
-
-
-
-
self.ToNearestAirbase=true
-
-
-
-
self.ReturnThresholdRange= ReturnThresholdRange
-
-
-
-
self.ReturnAirbaseSet= ReturnAirbaseSet
-
-
-
-
end
-
-
-
-
-
-
-
-
-
--- Returns the AI to the home @{Airbase#AIRBASE}.
-
-
-
-
-- @param #AI_BALANCER self
-
-
-
-
-- @param Dcs.DCSTypes#Distance ReturnThresholdRange If there is an enemy @{Client#CLIENT} within the ReturnThresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}.
-
-
-
-
function AI_BALANCER:ReturnToHomeAirbase( ReturnThresholdRange )
-
-
-
-
-
-
-
-
-
self.ToHomeAirbase=true
-
-
-
-
self.ReturnThresholdRange= ReturnThresholdRange
-
-
-
-
end
-
-
-
-
-
-
-
-
-
--- @param #AI_BALANCER self
-
-
-
-
-- @param Core.Set#SET_GROUP SetGroup
-
-
-
-
-- @param #string ClientName
-
-
-
-
-- @param Wrapper.Group#GROUP AIGroup
-
-
-
-
function AI_BALANCER:onenterSpawning( SetGroup, From, Event, To, ClientName )
-
-
-
-
-
-
-
-
-
-- OK, Spawn a new group from the default SpawnAI object provided.
-
-
-
-
local AIGroup =self.SpawnAI:Spawn() -- Wrapper.Group#GROUP
-
-
-
-
if AIGroup then
-
-
-
-
AIGroup:E( "Spawning new AIGroup" )
-
-
-
-
--TODO: need to rework UnitName thing ...
-
-
-
-
-
-
-
-
SetGroup:Add( ClientName, AIGroup )
-
-
-
-
self.SpawnQueue[ClientName] =nil
-
-
-
-
-
-
-
-
-- Fire the Spawned event. The first parameter is the AIGroup just Spawned.
-
-
-
-
-- Mission designers can catch this event to bind further actions to the AIGroup.
-
-
-
-
self:Spawned( AIGroup )
-
-
-
-
end
-
-
-
-
end
-
-
-
-
-
-
-
-
-
--- @param #AI_BALANCER self
-
-
-
-
-- @param Core.Set#SET_GROUP SetGroup
-
-
-
-
-- @param Wrapper.Group#GROUP AIGroup
-
-
-
-
function AI_BALANCER:onenterDestroying( SetGroup, From, Event, To, ClientName, AIGroup )
-
-
-
-
-
-
-
-
-
AIGroup:Destroy()
-
-
-
-
SetGroup:Flush()
-
-
-
-
SetGroup:Remove( ClientName )
-
-
-
-
SetGroup:Flush()
-
-
-
-
end
-
-
-
-
-
-
-
-
-
--- @param #AI_BALANCER self
-
-
-
-
-- @param Core.Set#SET_GROUP SetGroup
-
-
-
-
-- @param Wrapper.Group#GROUP AIGroup
-
-
-
-
function AI_BALANCER:onenterReturning( SetGroup, From, Event, To, AIGroup )
-
-
-
-
-
-
-
-
-
local AIGroupTemplate = AIGroup:GetTemplate()
-
-
-
-
ifself.ToHomeAirbase==truethen
-
-
-
-
local WayPointCount =#AIGroupTemplate.route.points
-
-
-
-
local SwitchWayPointCommand = AIGroup:CommandSwitchWayPoint( 1, WayPointCount, 1 )
-
-
-
-
AIGroup:SetCommand( SwitchWayPointCommand )
-
-
-
-
AIGroup:MessageToRed( "Returning to home base ...", 30 )
-
-
-
-
else
-
-
-
-
-- Okay, we need to send this Group back to the nearest base of the Coalition of the AI.
-
-
-
-
--TODO: i need to rework the POINT_VEC2 thing.
-
-
-
-
local PointVec2 = POINT_VEC2:New( AIGroup:GetVec2().x, AIGroup:GetVec2().y )
-
-
-
-
local ClosestAirbase =self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2( PointVec2 )
-
-
-
-
self:T( ClosestAirbase.AirbaseName )
-
-
-
-
AIGroup:MessageToRed( "Returning to ".. ClosestAirbase:GetName().." ...", 30 )
-
-
-
-
local RTBRoute = AIGroup:RouteReturnToAirbase( ClosestAirbase )
-
-
-
-
AIGroupTemplate.route= RTBRoute
-
-
-
-
AIGroup:Respawn( AIGroupTemplate )
-
-
-
-
end
-
-
-
-
-
-
-
-
-
end
-
-
-
-
-
-
-
-
-
-
-
-
-
-
--- @param #AI_BALANCER self
-
-
-
-
function AI_BALANCER:onenterMonitoring( SetGroup )
-
-
-
-
-
-
-
-
-
self:T2( { self.SetClient:Count() } )
-
-
-
-
--self.SetClient:Flush()
-
-
-
-
-
-
-
-
-
self.SetClient:ForEachClient(
-
-
-
-
--- @param Wrapper.Client#CLIENT Client
-
-
-
-
function( Client )
-
-
-
-
self:T3(Client.ClientName)
-
-
-
-
-
-
-
-
-
local AIGroup =self.Set:Get( Client.UnitName ) -- Wrapper.Group#GROUP
-
-
- You can't perform that action at this time.
-
+--- Will spawn a group from a Vec3 in 3D space.
+-- This method is mostly advisable to be used if you want to simulate spawning units in the air, like helicopters or airplanes.
+-- Note that each point in the route assigned to the spawning group is reset to the point of the spawn.
+-- You can use the returned group to further define the route to be followed.
+-- @param #SPAWN self
+-- @param Dcs.DCSTypes#Vec3 Vec3 The Vec3 coordinates where to spawn the group.
+-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
+-- @return Wrapper.Group#GROUP that was spawned.
+-- @return #nil Nothing was spawned.
+function SPAWN:SpawnFromVec3( Vec3, SpawnIndex )
+ self:F( { self.SpawnTemplatePrefix, Vec3, SpawnIndex } )
+ local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 )
+ self:T2(PointVec3)
+ if SpawnIndex then
+ else
+ SpawnIndex = self.SpawnIndex + 1
+ end
+
+ if self:_GetSpawnIndex( SpawnIndex ) then
-
-
-
-
-
-
-
-
-
- You signed in with another tab or window. Reload to refresh your session.
- You signed out in another tab or window. Reload to refresh your session.
-
-
-
-
-
-
-
-
+ local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate
+
+ if SpawnTemplate then
+
+ self:T( { "Current point of ", self.SpawnTemplatePrefix, Vec3 } )
+
+ -- Translate the position of the Group Template to the Vec3.
+ for UnitID = 1, #SpawnTemplate.units do
+ self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
+ local UnitTemplate = SpawnTemplate.units[UnitID]
+ local SX = UnitTemplate.x
+ local SY = UnitTemplate.y
+ local BX = SpawnTemplate.route.points[1].x
+ local BY = SpawnTemplate.route.points[1].y
+ local TX = Vec3.x + ( SX - BX )
+ local TY = Vec3.z + ( SY - BY )
+ SpawnTemplate.units[UnitID].x = TX
+ SpawnTemplate.units[UnitID].y = TY
+ SpawnTemplate.units[UnitID].alt = Vec3.y
+ self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
+ end
+
+ SpawnTemplate.route.points[1].x = Vec3.x
+ SpawnTemplate.route.points[1].y = Vec3.z
+ SpawnTemplate.route.points[1].alt = Vec3.y
+
+ SpawnTemplate.x = Vec3.x
+ SpawnTemplate.y = Vec3.z
+
+ return self:SpawnWithIndex( self.SpawnIndex )
+ end
+ end
+
+ return nil
+end
+
+--- Will spawn a group from a Vec2 in 3D space.
+-- This method is mostly advisable to be used if you want to simulate spawning groups on the ground from air units, like vehicles.
+-- Note that each point in the route assigned to the spawning group is reset to the point of the spawn.
+-- You can use the returned group to further define the route to be followed.
+-- @param #SPAWN self
+-- @param Dcs.DCSTypes#Vec2 Vec2 The Vec2 coordinates where to spawn the group.
+-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
+-- @return Wrapper.Group#GROUP that was spawned.
+-- @return #nil Nothing was spawned.
+function SPAWN:SpawnFromVec2( Vec2, SpawnIndex )
+ self:F( { self.SpawnTemplatePrefix, Vec2, SpawnIndex } )
+
+ local PointVec2 = POINT_VEC2:NewFromVec2( Vec2 )
+ return self:SpawnFromVec3( PointVec2:GetVec3(), SpawnIndex )
+end
-
-
+--- Will spawn a group from a hosting unit. This method is mostly advisable to be used if you want to simulate spawning from air units, like helicopters, which are dropping infantry into a defined Landing Zone.
+-- Note that each point in the route assigned to the spawning group is reset to the point of the spawn.
+-- You can use the returned group to further define the route to be followed.
+-- @param #SPAWN self
+-- @param Wrapper.Unit#UNIT HostUnit The air or ground unit dropping or unloading the group.
+-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
+-- @return Wrapper.Group#GROUP that was spawned.
+-- @return #nil Nothing was spawned.
+function SPAWN:SpawnFromUnit( HostUnit, SpawnIndex )
+ self:F( { self.SpawnTemplatePrefix, HostUnit, SpawnIndex } )
+ if HostUnit and HostUnit:IsAlive() ~= nil then -- and HostUnit:getUnit(1):inAir() == false then
+ return self:SpawnFromVec3( HostUnit:GetVec3(), SpawnIndex )
+ end
+
+ return nil
+end
+
+--- Will spawn a group from a hosting static. This method is mostly advisable to be used if you want to simulate spawning from buldings and structures (static buildings).
+-- You can use the returned group to further define the route to be followed.
+-- @param #SPAWN self
+-- @param Wrapper.Static#STATIC HostStatic The static dropping or unloading the group.
+-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
+-- @return Wrapper.Group#GROUP that was spawned.
+-- @return #nil Nothing was spawned.
+function SPAWN:SpawnFromStatic( HostStatic, SpawnIndex )
+ self:F( { self.SpawnTemplatePrefix, HostStatic, SpawnIndex } )
+
+ if HostStatic and HostStatic:IsAlive() then
+ return self:SpawnFromVec3( HostStatic:GetVec3(), SpawnIndex )
+ end
+
+ return nil
+end
+
+--- Will spawn a Group within a given @{Zone}.
+-- The @{Zone} can be of any type derived from @{Zone#ZONE_BASE}.
+-- Once the @{Group} is spawned within the zone, the @{Group} will continue on its route.
+-- The **first waypoint** (where the group is spawned) is replaced with the zone location coordinates.
+-- @param #SPAWN self
+-- @param Core.Zone#ZONE Zone The zone where the group is to be spawned.
+-- @param #boolean RandomizeGroup (optional) Randomization of the @{Group} position in the zone.
+-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
+-- @return Wrapper.Group#GROUP that was spawned.
+-- @return #nil when nothing was spawned.
+function SPAWN:SpawnInZone( Zone, RandomizeGroup, SpawnIndex )
+ self:F( { self.SpawnTemplatePrefix, Zone, RandomizeGroup, SpawnIndex } )
+
+ if Zone then
+ if RandomizeGroup then
+ return self:SpawnFromVec2( Zone:GetRandomVec2(), SpawnIndex )
+ else
+ return self:SpawnFromVec2( Zone:GetVec2(), SpawnIndex )
+ end
+ end
+
+ return nil
+end
+
+--- (**AIR**) Will spawn a plane group in UnControlled or Controlled mode...
+-- This will be similar to the uncontrolled flag setting in the ME.
+-- You can use UnControlled mode to simulate planes startup and ready for take-off but aren't moving (yet).
+-- ReSpawn the plane in Controlled mode, and the plane will move...
+-- @param #SPAWN self
+-- @param #boolean UnControlled true if UnControlled, false if Controlled.
+-- @return #SPAWN self
+function SPAWN:InitUnControlled( UnControlled )
+ self:F2( { self.SpawnTemplatePrefix, UnControlled } )
+
+ self.SpawnUnControlled = UnControlled
+
+ for SpawnGroupID = 1, self.SpawnMaxGroups do
+ self.SpawnGroups[SpawnGroupID].UnControlled = UnControlled
+ end
+
+ return self
+end
+
+
+--- Get the Coordinate of the Group that is Late Activated as the template for the SPAWN object.
+-- @param #SPAWN self
+-- @return Core.Point#COORDINATE The Coordinate
+function SPAWN:GetCoordinate()
+
+ local LateGroup = GROUP:FindByName( self.SpawnTemplatePrefix )
+ if LateGroup then
+ return LateGroup:GetCoordinate()
+ end
+
+ return nil
+end
+
+
+--- Will return the SpawnGroupName either with with a specific count number or without any count.
+-- @param #SPAWN self
+-- @param #number SpawnIndex Is the number of the Group that is to be spawned.
+-- @return #string SpawnGroupName
+function SPAWN:SpawnGroupName( SpawnIndex )
+ self:F( { self.SpawnTemplatePrefix, SpawnIndex } )
+
+ local SpawnPrefix = self.SpawnTemplatePrefix
+ if self.SpawnAliasPrefix then
+ SpawnPrefix = self.SpawnAliasPrefix
+ end
+
+ if SpawnIndex then
+ local SpawnName = string.format( '%s#%03d', SpawnPrefix, SpawnIndex )
+ self:T( SpawnName )
+ return SpawnName
+ else
+ self:T( SpawnPrefix )
+ return SpawnPrefix
+ end
+
+end
+
+--- Will find the first alive @{Group} it has spawned, and return the alive @{Group} object and the first Index where the first alive @{Group} object has been found.
+-- @param #SPAWN self
+-- @return Wrapper.Group#GROUP, #number The @{Group} object found, the new Index where the group was found.
+-- @return #nil, #nil When no group is found, #nil is returned.
+-- @usage
+-- -- Find the first alive @{Group} object of the SpawnPlanes SPAWN object @{Group} collection that it has spawned during the mission.
+-- local GroupPlane, Index = SpawnPlanes:GetFirstAliveGroup()
+-- while GroupPlane ~= nil do
+-- -- Do actions with the GroupPlane object.
+-- GroupPlane, Index = SpawnPlanes:GetNextAliveGroup( Index )
+-- end
+function SPAWN:GetFirstAliveGroup()
+ self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } )
+
+ for SpawnIndex = 1, self.SpawnCount do
+ local SpawnGroup = self:GetGroupFromIndex( SpawnIndex )
+ if SpawnGroup and SpawnGroup:IsAlive() then
+ return SpawnGroup, SpawnIndex
+ end
+ end
+
+ return nil, nil
+end
+
+
+--- Will find the next alive @{Group} object from a given Index, and return a reference to the alive @{Group} object and the next Index where the alive @{Group} has been found.
+-- @param #SPAWN self
+-- @param #number SpawnIndexStart A Index holding the start position to search from. This method can also be used to find the first alive @{Group} object from the given Index.
+-- @return Wrapper.Group#GROUP, #number The next alive @{Group} object found, the next Index where the next alive @{Group} object was found.
+-- @return #nil, #nil When no alive @{Group} object is found from the start Index position, #nil is returned.
+-- @usage
+-- -- Find the first alive @{Group} object of the SpawnPlanes SPAWN object @{Group} collection that it has spawned during the mission.
+-- local GroupPlane, Index = SpawnPlanes:GetFirstAliveGroup()
+-- while GroupPlane ~= nil do
+-- -- Do actions with the GroupPlane object.
+-- GroupPlane, Index = SpawnPlanes:GetNextAliveGroup( Index )
+-- end
+function SPAWN:GetNextAliveGroup( SpawnIndexStart )
+ self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndexStart } )
+
+ SpawnIndexStart = SpawnIndexStart + 1
+ for SpawnIndex = SpawnIndexStart, self.SpawnCount do
+ local SpawnGroup = self:GetGroupFromIndex( SpawnIndex )
+ if SpawnGroup and SpawnGroup:IsAlive() then
+ return SpawnGroup, SpawnIndex
+ end
+ end
+
+ return nil, nil
+end
+
+--- Will find the last alive @{Group} object, and will return a reference to the last live @{Group} object and the last Index where the last alive @{Group} object has been found.
+-- @param #SPAWN self
+-- @return Wrapper.Group#GROUP, #number The last alive @{Group} object found, the last Index where the last alive @{Group} object was found.
+-- @return #nil, #nil When no alive @{Group} object is found, #nil is returned.
+-- @usage
+-- -- Find the last alive @{Group} object of the SpawnPlanes SPAWN object @{Group} collection that it has spawned during the mission.
+-- local GroupPlane, Index = SpawnPlanes:GetLastAliveGroup()
+-- if GroupPlane then -- GroupPlane can be nil!!!
+-- -- Do actions with the GroupPlane object.
+-- end
+function SPAWN:GetLastAliveGroup()
+ self:F( { self.SpawnTemplatePrefixself.SpawnAliasPrefix } )
+
+ self.SpawnIndex = self:_GetLastIndex()
+ for SpawnIndex = self.SpawnIndex, 1, -1 do
+ local SpawnGroup = self:GetGroupFromIndex( SpawnIndex )
+ if SpawnGroup and SpawnGroup:IsAlive() then
+ self.SpawnIndex = SpawnIndex
+ return SpawnGroup
+ end
+ end
+
+ self.SpawnIndex = nil
+ return nil
+end
+
+
+
+--- Get the group from an index.
+-- Returns the group from the SpawnGroups list.
+-- If no index is given, it will return the first group in the list.
+-- @param #SPAWN self
+-- @param #number SpawnIndex The index of the group to return.
+-- @return Wrapper.Group#GROUP self
+function SPAWN:GetGroupFromIndex( SpawnIndex )
+ self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndex } )
+
+ if not SpawnIndex then
+ SpawnIndex = 1
+ end
+
+ if self.SpawnGroups and self.SpawnGroups[SpawnIndex] then
+ local SpawnGroup = self.SpawnGroups[SpawnIndex].Group
+ return SpawnGroup
+ else
+ return nil
+ end
+end
+
+
+--- Return the prefix of a SpawnUnit.
+-- The method will search for a #-mark, and will return the text before the #-mark.
+-- It will return nil of no prefix was found.
+-- @param #SPAWN self
+-- @param Dcs.DCSWrapper.Unit#UNIT DCSUnit The @{DCSUnit} to be searched.
+-- @return #string The prefix
+-- @return #nil Nothing found
+function SPAWN:_GetPrefixFromGroup( SpawnGroup )
+ self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnGroup } )
+
+ local GroupName = SpawnGroup:GetName()
+ if GroupName then
+ local SpawnPrefix = string.match( GroupName, ".*#" )
+ if SpawnPrefix then
+ SpawnPrefix = SpawnPrefix:sub( 1, -2 )
+ end
+ return SpawnPrefix
+ end
+
+ return nil
+end
+
+
+--- Get the index from a given group.
+-- The function will search the name of the group for a #, and will return the number behind the #-mark.
+function SPAWN:GetSpawnIndexFromGroup( SpawnGroup )
+ self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnGroup } )
+
+ local IndexString = string.match( SpawnGroup:GetName(), "#(%d*)$" ):sub( 2 )
+ local Index = tonumber( IndexString )
+
+ self:T3( IndexString, Index )
+ return Index
+
+end
+
+--- Return the last maximum index that can be used.
+function SPAWN:_GetLastIndex()
+ self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } )
+
+ return self.SpawnMaxGroups
+end
+
+--- Initalize the SpawnGroups collection.
+-- @param #SPAWN self
+function SPAWN:_InitializeSpawnGroups( SpawnIndex )
+ self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndex } )
+
+ if not self.SpawnGroups[SpawnIndex] then
+ self.SpawnGroups[SpawnIndex] = {}
+ self.SpawnGroups[SpawnIndex].Visible = false
+ self.SpawnGroups[SpawnIndex].Spawned = false
+ self.SpawnGroups[SpawnIndex].UnControlled = false
+ self.SpawnGroups[SpawnIndex].SpawnTime = 0
+
+ self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix = self.SpawnTemplatePrefix
+ self.SpawnGroups[SpawnIndex].SpawnTemplate = self:_Prepare( self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix, SpawnIndex )
+ end
+
+ self:_RandomizeTemplate( SpawnIndex )
+ self:_RandomizeRoute( SpawnIndex )
+ --self:_TranslateRotate( SpawnIndex )
+
+ return self.SpawnGroups[SpawnIndex]
+end
+
+
+
+--- Gets the CategoryID of the Group with the given SpawnPrefix
+function SPAWN:_GetGroupCategoryID( SpawnPrefix )
+ local TemplateGroup = Group.getByName( SpawnPrefix )
+
+ if TemplateGroup then
+ return TemplateGroup:getCategory()
+ else
+ return nil
+ end
+end
+
+--- Gets the CoalitionID of the Group with the given SpawnPrefix
+function SPAWN:_GetGroupCoalitionID( SpawnPrefix )
+ local TemplateGroup = Group.getByName( SpawnPrefix )
+
+ if TemplateGroup then
+ return TemplateGroup:getCoalition()
+ else
+ return nil
+ end
+end
+
+--- Gets the CountryID of the Group with the given SpawnPrefix
+function SPAWN:_GetGroupCountryID( SpawnPrefix )
+ self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnPrefix } )
+
+ local TemplateGroup = Group.getByName( SpawnPrefix )
+
+ if TemplateGroup then
+ local TemplateUnits = TemplateGroup:getUnits()
+ return TemplateUnits[1]:getCountry()
+ else
+ return nil
+ end
+end
+
+--- Gets the Group Template from the ME environment definition.
+-- This method used the @{DATABASE} object, which contains ALL initial and new spawned object in MOOSE.
+-- @param #SPAWN self
+-- @param #string SpawnTemplatePrefix
+-- @return @SPAWN self
+function SPAWN:_GetTemplate( SpawnTemplatePrefix )
+ self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnTemplatePrefix } )
+
+ local SpawnTemplate = nil
+
+ SpawnTemplate = routines.utils.deepCopy( _DATABASE.Templates.Groups[SpawnTemplatePrefix].Template )
+
+ if SpawnTemplate == nil then
+ error( 'No Template returned for SpawnTemplatePrefix = ' .. SpawnTemplatePrefix )
+ end
+
+ --SpawnTemplate.SpawnCoalitionID = self:_GetGroupCoalitionID( SpawnTemplatePrefix )
+ --SpawnTemplate.SpawnCategoryID = self:_GetGroupCategoryID( SpawnTemplatePrefix )
+ --SpawnTemplate.SpawnCountryID = self:_GetGroupCountryID( SpawnTemplatePrefix )
+
+ self:T3( { SpawnTemplate } )
+ return SpawnTemplate
+end
+
+--- Prepares the new Group Template.
+-- @param #SPAWN self
+-- @param #string SpawnTemplatePrefix
+-- @param #number SpawnIndex
+-- @return #SPAWN self
+function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) --R2.2
+ self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } )
+
+ local SpawnTemplate = self:_GetTemplate( SpawnTemplatePrefix )
+ SpawnTemplate.name = self:SpawnGroupName( SpawnIndex )
+
+ SpawnTemplate.groupId = nil
+ --SpawnTemplate.lateActivation = false
+ SpawnTemplate.lateActivation = false
+
+ if SpawnTemplate.CategoryID == Group.Category.GROUND then
+ self:T3( "For ground units, visible needs to be false..." )
+ SpawnTemplate.visible = false
+ end
+
+ if self.SpawnGrouping then
+ local UnitAmount = #SpawnTemplate.units
+ self:F( { UnitAmount = UnitAmount, SpawnGrouping = self.SpawnGrouping } )
+ if UnitAmount > self.SpawnGrouping then
+ for UnitID = self.SpawnGrouping + 1, UnitAmount do
+ SpawnTemplate.units[UnitID] = nil
+ end
+ else
+ if UnitAmount < self.SpawnGrouping then
+ for UnitID = UnitAmount + 1, self.SpawnGrouping do
+ SpawnTemplate.units[UnitID] = UTILS.DeepCopy( SpawnTemplate.units[1] )
+ SpawnTemplate.units[UnitID].unitId = nil
+ end
+ end
+ end
+ end
+
+ if self.SpawnInitKeepUnitNames == false then
+ for UnitID = 1, #SpawnTemplate.units do
+ SpawnTemplate.units[UnitID].name = string.format( SpawnTemplate.name .. '-%02d', UnitID )
+ SpawnTemplate.units[UnitID].unitId = nil
+ end
+ else
+ for UnitID = 1, #SpawnTemplate.units do
+ local UnitPrefix, Rest = string.match( SpawnTemplate.units[UnitID].name, "^([^#]+)#?" ):gsub( "^%s*(.-)%s*$", "%1" )
+ self:T( { UnitPrefix, Rest } )
+
+ SpawnTemplate.units[UnitID].name = string.format( '%s#%03d-%02d', UnitPrefix, SpawnIndex, UnitID )
+ SpawnTemplate.units[UnitID].unitId = nil
+ end
+ end
+
+ self:T3( { "Template:", SpawnTemplate } )
+ return SpawnTemplate
+
+end
+
+--- Private method randomizing the routes.
+-- @param #SPAWN self
+-- @param #number SpawnIndex The index of the group to be spawned.
+-- @return #SPAWN
+function SPAWN:_RandomizeRoute( SpawnIndex )
+ self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeRoute, self.SpawnRandomizeRouteStartPoint, self.SpawnRandomizeRouteEndPoint, self.SpawnRandomizeRouteRadius } )
+
+ if self.SpawnRandomizeRoute then
+ local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate
+ local RouteCount = #SpawnTemplate.route.points
+
+ for t = self.SpawnRandomizeRouteStartPoint + 1, ( RouteCount - self.SpawnRandomizeRouteEndPoint ) do
+
+ SpawnTemplate.route.points[t].x = SpawnTemplate.route.points[t].x + math.random( self.SpawnRandomizeRouteRadius * -1, self.SpawnRandomizeRouteRadius )
+ SpawnTemplate.route.points[t].y = SpawnTemplate.route.points[t].y + math.random( self.SpawnRandomizeRouteRadius * -1, self.SpawnRandomizeRouteRadius )
+
+ -- Manage randomization of altitude for airborne units ...
+ if SpawnTemplate.CategoryID == Group.Category.AIRPLANE or SpawnTemplate.CategoryID == Group.Category.HELICOPTER then
+ if SpawnTemplate.route.points[t].alt and self.SpawnRandomizeRouteHeight then
+ SpawnTemplate.route.points[t].alt = SpawnTemplate.route.points[t].alt + math.random( 1, self.SpawnRandomizeRouteHeight )
+ end
+ else
+ SpawnTemplate.route.points[t].alt = nil
+ end
+
+ self:T( 'SpawnTemplate.route.points[' .. t .. '].x = ' .. SpawnTemplate.route.points[t].x .. ', SpawnTemplate.route.points[' .. t .. '].y = ' .. SpawnTemplate.route.points[t].y )
+ end
+ end
+
+ self:_RandomizeZones( SpawnIndex )
+
+ return self
+end
+
+--- Private method that randomizes the template of the group.
+-- @param #SPAWN self
+-- @param #number SpawnIndex
+-- @return #SPAWN self
+function SPAWN:_RandomizeTemplate( SpawnIndex )
+ self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeTemplate } )
+
+ if self.SpawnRandomizeTemplate then
+ self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix = self.SpawnTemplatePrefixTable[ math.random( 1, #self.SpawnTemplatePrefixTable ) ]
+ self.SpawnGroups[SpawnIndex].SpawnTemplate = self:_Prepare( self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix, SpawnIndex )
+ self.SpawnGroups[SpawnIndex].SpawnTemplate.route = routines.utils.deepCopy( self.SpawnTemplate.route )
+ self.SpawnGroups[SpawnIndex].SpawnTemplate.x = self.SpawnTemplate.x
+ self.SpawnGroups[SpawnIndex].SpawnTemplate.y = self.SpawnTemplate.y
+ self.SpawnGroups[SpawnIndex].SpawnTemplate.start_time = self.SpawnTemplate.start_time
+ local OldX = self.SpawnGroups[SpawnIndex].SpawnTemplate.units[1].x
+ local OldY = self.SpawnGroups[SpawnIndex].SpawnTemplate.units[1].y
+ for UnitID = 1, #self.SpawnGroups[SpawnIndex].SpawnTemplate.units do
+ self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].heading = self.SpawnTemplate.units[1].heading
+ self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].x = self.SpawnTemplate.units[1].x + ( self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].x - OldX )
+ self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y = self.SpawnTemplate.units[1].y + ( self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y - OldY )
+ self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].alt = self.SpawnTemplate.units[1].alt
+ end
+ end
+
+ self:_RandomizeRoute( SpawnIndex )
+
+ return self
+end
+
+--- Private method that randomizes the @{Zone}s where the Group will be spawned.
+-- @param #SPAWN self
+-- @param #number SpawnIndex
+-- @return #SPAWN self
+function SPAWN:_RandomizeZones( SpawnIndex )
+ self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeZones } )
+
+ if self.SpawnRandomizeZones then
+ local SpawnZone = nil -- Core.Zone#ZONE_BASE
+ while not SpawnZone do
+ self:T( { SpawnZoneTableCount = #self.SpawnZoneTable, self.SpawnZoneTable } )
+ local ZoneID = math.random( #self.SpawnZoneTable )
+ self:T( ZoneID )
+ SpawnZone = self.SpawnZoneTable[ ZoneID ]:GetZoneMaybe()
+ end
+
+ self:T( "Preparing Spawn in Zone", SpawnZone:GetName() )
+
+ local SpawnVec2 = SpawnZone:GetRandomVec2()
+
+ self:T( { SpawnVec2 = SpawnVec2 } )
+
+ local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate
+
+ self:T( { Route = SpawnTemplate.route } )
+
+ for UnitID = 1, #SpawnTemplate.units do
+ local UnitTemplate = SpawnTemplate.units[UnitID]
+ self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y )
+ local SX = UnitTemplate.x
+ local SY = UnitTemplate.y
+ local BX = SpawnTemplate.route.points[1].x
+ local BY = SpawnTemplate.route.points[1].y
+ local TX = SpawnVec2.x + ( SX - BX )
+ local TY = SpawnVec2.y + ( SY - BY )
+ UnitTemplate.x = TX
+ UnitTemplate.y = TY
+ -- TODO: Manage altitude based on landheight...
+ --SpawnTemplate.units[UnitID].alt = SpawnVec2:
+ self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y )
+ end
+ SpawnTemplate.x = SpawnVec2.x
+ SpawnTemplate.y = SpawnVec2.y
+ SpawnTemplate.route.points[1].x = SpawnVec2.x
+ SpawnTemplate.route.points[1].y = SpawnVec2.y
+ end
+
+ return self
+
+end
+
+function SPAWN:_TranslateRotate( SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle )
+ self:F( { self.SpawnTemplatePrefix, SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle } )
+
+ -- Translate
+ local TranslatedX = SpawnX
+ local TranslatedY = SpawnY
+
+ -- Rotate
+ -- From Wikipedia: https://en.wikipedia.org/wiki/Rotation_matrix#Common_rotations
+ -- x' = x \cos \theta - y \sin \theta\
+ -- y' = x \sin \theta + y \cos \theta\
+ local RotatedX = - TranslatedX * math.cos( math.rad( SpawnAngle ) )
+ + TranslatedY * math.sin( math.rad( SpawnAngle ) )
+ local RotatedY = TranslatedX * math.sin( math.rad( SpawnAngle ) )
+ + TranslatedY * math.cos( math.rad( SpawnAngle ) )
+
+ -- Assign
+ self.SpawnGroups[SpawnIndex].SpawnTemplate.x = SpawnRootX - RotatedX
+ self.SpawnGroups[SpawnIndex].SpawnTemplate.y = SpawnRootY + RotatedY
+
+
+ local SpawnUnitCount = table.getn( self.SpawnGroups[SpawnIndex].SpawnTemplate.units )
+ for u = 1, SpawnUnitCount do
+
+ -- Translate
+ local TranslatedX = SpawnX
+ local TranslatedY = SpawnY - 10 * ( u - 1 )
+
+ -- Rotate
+ local RotatedX = - TranslatedX * math.cos( math.rad( SpawnAngle ) )
+ + TranslatedY * math.sin( math.rad( SpawnAngle ) )
+ local RotatedY = TranslatedX * math.sin( math.rad( SpawnAngle ) )
+ + TranslatedY * math.cos( math.rad( SpawnAngle ) )
+
+ -- Assign
+ self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].x = SpawnRootX - RotatedX
+ self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].y = SpawnRootY + RotatedY
+ self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].heading = self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].heading + math.rad( SpawnAngle )
+ end
+
+ return self
+end
+
+--- Get the next index of the groups to be spawned. This method is complicated, as it is used at several spaces.
+function SPAWN:_GetSpawnIndex( SpawnIndex )
+ self:F2( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive, self.AliveUnits, #self.SpawnTemplate.units } )
+
+ if ( self.SpawnMaxGroups == 0 ) or ( SpawnIndex <= self.SpawnMaxGroups ) then
+ if ( self.SpawnMaxUnitsAlive == 0 ) or ( self.AliveUnits + #self.SpawnTemplate.units <= self.SpawnMaxUnitsAlive ) or self.UnControlled == true then
+ if SpawnIndex and SpawnIndex >= self.SpawnCount + 1 then
+ self.SpawnCount = self.SpawnCount + 1
+ SpawnIndex = self.SpawnCount
+ end
+ self.SpawnIndex = SpawnIndex
+ if not self.SpawnGroups[self.SpawnIndex] then
+ self:_InitializeSpawnGroups( self.SpawnIndex )
+ end
+ else
+ return nil
+ end
+ else
+ return nil
+ end
+
+ return self.SpawnIndex
+end
+
+
+-- TODO Need to delete this... _DATABASE does this now ...
+
+--- @param #SPAWN self
+-- @param Core.Event#EVENTDATA EventData
+function SPAWN:_OnBirth( EventData )
+ self:F( self.SpawnTemplatePrefix )
+
+ local SpawnGroup = EventData.IniGroup
+
+ if SpawnGroup then
+ local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup )
+ if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group!
+ self:T( { "Birth Event:", EventPrefix, self.SpawnTemplatePrefix } )
+ if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then
+ self.AliveUnits = self.AliveUnits + 1
+ self:T( "Alive Units: " .. self.AliveUnits )
+ end
+ end
+ end
+
+end
+
+--- Obscolete
+-- @todo Need to delete this... _DATABASE does this now ...
+
+--- @param #SPAWN self
+-- @param Core.Event#EVENTDATA EventData
+function SPAWN:_OnDeadOrCrash( EventData )
+ self:F( self.SpawnTemplatePrefix )
+
+ local SpawnGroup = EventData.IniGroup
+
+ if SpawnGroup then
+ local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup )
+ if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group!
+ self:T( { "Dead event: " .. EventPrefix } )
+ if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then
+ self.AliveUnits = self.AliveUnits - 1
+ self:T( "Alive Units: " .. self.AliveUnits )
+ end
+ end
+ end
+end
+
+--- Will detect AIR Units taking off... When the event takes place, the spawned Group is registered as airborne...
+-- This is needed to ensure that Re-SPAWNing only is done for landed AIR Groups.
+-- @param #SPAWN self
+-- @param Core.Event#EVENTDATA EventData
+function SPAWN:_OnTakeOff( EventData )
+ self:F( self.SpawnTemplatePrefix )
+
+ local SpawnGroup = EventData.IniGroup
+ if SpawnGroup then
+ local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup )
+ if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group!
+ self:T( { "TakeOff event: " .. EventPrefix } )
+ if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then
+ self:T( "self.Landed = false" )
+ SpawnGroup:SetState( SpawnGroup, "Spawn_Landed", false )
+ end
+ end
+ end
+end
+
+--- Will detect AIR Units landing... When the event takes place, the spawned Group is registered as landed.
+-- This is needed to ensure that Re-SPAWNing is only done for landed AIR Groups.
+-- @param #SPAWN self
+-- @param Core.Event#EVENTDATA EventData
+function SPAWN:_OnLand( EventData )
+ self:F( self.SpawnTemplatePrefix )
+
+ local SpawnGroup = EventData.IniGroup
+ if SpawnGroup then
+ local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup )
+ if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group!
+ self:T( { "Land event: " .. EventPrefix } )
+ if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then
+ -- TODO: Check if this is the last unit of the group that lands.
+ SpawnGroup:SetState( SpawnGroup, "Spawn_Landed", true )
+ if self.RepeatOnLanding then
+ local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup )
+ self:T( { "Landed:", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } )
+ self:ReSpawn( SpawnGroupIndex )
+ end
+ end
+ end
+ end
+end
+
+--- Will detect AIR Units shutting down their engines ...
+-- When the event takes place, and the method @{RepeatOnEngineShutDown} was called, the spawned Group will Re-SPAWN.
+-- But only when the Unit was registered to have landed.
+-- @param #SPAWN self
+-- @param Core.Event#EVENTDATA EventData
+function SPAWN:_OnEngineShutDown( EventData )
+ self:F( self.SpawnTemplatePrefix )
+
+ local SpawnGroup = EventData.IniGroup
+ if SpawnGroup then
+ local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup )
+ if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group!
+ self:T( { "EngineShutdown event: " .. EventPrefix } )
+ if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then
+ -- todo: test if on the runway
+ local Landed = SpawnGroup:GetState( SpawnGroup, "Spawn_Landed" )
+ if Landed and self.RepeatOnEngineShutDown then
+ local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup )
+ self:T( { "EngineShutDown: ", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } )
+ self:ReSpawn( SpawnGroupIndex )
+ end
+ end
+ end
+ end
+end
+
+--- This function is called automatically by the Spawning scheduler.
+-- It is the internal worker method SPAWNing new Groups on the defined time intervals.
+function SPAWN:_Scheduler()
+ self:F2( { "_Scheduler", self.SpawnTemplatePrefix, self.SpawnAliasPrefix, self.SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive } )
+
+ -- Validate if there are still groups left in the batch...
+ self:Spawn()
+
+ return true
+end
+
+--- Schedules the CleanUp of Groups
+-- @param #SPAWN self
+-- @return #boolean True = Continue Scheduler
+function SPAWN:_SpawnCleanUpScheduler()
+ self:F( { "CleanUp Scheduler:", self.SpawnTemplatePrefix } )
+
+ local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup()
+ self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } )
+
+ while SpawnGroup do
+
+ local SpawnUnits = SpawnGroup:GetUnits()
+
+ for UnitID, UnitData in pairs( SpawnUnits ) do
+
+ local SpawnUnit = UnitData -- Wrapper.Unit#UNIT
+ local SpawnUnitName = SpawnUnit:GetName()
+
+
+ self.SpawnCleanUpTimeStamps[SpawnUnitName] = self.SpawnCleanUpTimeStamps[SpawnUnitName] or {}
+ local Stamp = self.SpawnCleanUpTimeStamps[SpawnUnitName]
+ self:T( { SpawnUnitName, Stamp } )
+
+ if Stamp.Vec2 then
+ if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1 then
+ local NewVec2 = SpawnUnit:GetVec2()
+ if Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y then
+ -- If the plane is not moving, and is on the ground, assign it with a timestamp...
+ if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then
+ self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } )
+ self:ReSpawn( SpawnCursor )
+ Stamp.Vec2 = nil
+ Stamp.Time = nil
+ end
+ else
+ Stamp.Time = timer.getTime()
+ Stamp.Vec2 = SpawnUnit:GetVec2()
+ end
+ else
+ Stamp.Vec2 = nil
+ Stamp.Time = nil
+ end
+ else
+ if SpawnUnit:InAir() == false then
+ Stamp.Vec2 = SpawnUnit:GetVec2()
+ if SpawnUnit:GetVelocityKMH() < 1 then
+ Stamp.Time = timer.getTime()
+ end
+ else
+ Stamp.Time = nil
+ Stamp.Vec2 = nil
+ end
+ end
+ end
+
+ SpawnGroup, SpawnCursor = self:GetNextAliveGroup( SpawnCursor )
+
+ self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } )
+
+ end
+
+ return true -- Repeat
+
+end
\ No newline at end of file
diff --git a/Moose Development/Moose/Functional/Spawn.lua b/Moose Development/Moose/Functional/Spawn.lua
index 3243ae7fa..14059f200 100644
--- a/Moose Development/Moose/Functional/Spawn.lua
+++ b/Moose Development/Moose/Functional/Spawn.lua
@@ -1,9076 +1,1901 @@
+--- **Functional** -- Spawn dynamically new GROUPs in your missions.
+--
+-- 
+--
+-- ====
+--
+-- The documentation of the SPAWN class can be found further in this document.
+--
+-- ====
+--
+-- # Demo Missions
+--
+-- ### [SPAWN Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/SPA%20-%20Spawning)
+--
+-- ### [SPAWN Demo Missions, only for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SPA%20-%20Spawning)
+--
+-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
+--
+-- ====
+--
+-- # YouTube Channel
+--
+-- ### [SPAWN YouTube Channel](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl1jirWIo4t4YxqN-HxjqRkL)
+--
+-- ===
+--
+-- # **AUTHORS and CONTRIBUTIONS**
+--
+-- ### Contributions:
+--
+-- * **Aaron**: Posed the idea for Group position randomization at SpawnInZone and make the Unit randomization separate from the Group randomization.
+-- * [**Entropy**](https://forums.eagle.ru/member.php?u=111471), **Afinegan**: Came up with the requirement for AIOnOff().
+--
+-- ### Authors:
+--
+-- * **FlightControl**: Design & Programming
+--
+-- @module Spawn
+
+----BASE:TraceClass("SPAWN")
+--- SPAWN Class
+-- @type SPAWN
+-- @field ClassName
+-- @field #string SpawnTemplatePrefix
+-- @field #string SpawnAliasPrefix
+-- @field #number AliveUnits
+-- @field #number MaxAliveUnits
+-- @field #number SpawnIndex
+-- @field #number MaxAliveGroups
+-- @field #SPAWN.SpawnZoneTable SpawnZoneTable
+-- @extends Core.Base#BASE
+--- # SPAWN class, extends @{Base#BASE}
+--
+-- The SPAWN class allows to spawn dynamically new groups.
+-- Each SPAWN object needs to be have a related **template group** setup in the Mission Editor (ME),
+-- which is a normal group with the **Late Activation** flag set.
+-- This template group will never be activated in your mission.
+-- SPAWN uses that **template group** to reference to all the characteristics
+-- (air, ground, livery, unit composition, formation, skill level etc) of each new group to be spawned.
+--
+-- Therefore, when creating a SPAWN object, the @{#SPAWN.New} and @{#SPAWN.NewWithAlias} require
+-- **the name of the template group** to be given as a string to those constructor methods.
+--
+-- Initialization settings can be applied on the SPAWN object,
+-- which modify the behaviour or the way groups are spawned.
+-- These initialization methods have the prefix **Init**.
+-- There are also spawn methods with the prefix **Spawn** and will spawn new groups in various ways.
+--
+-- ### IMPORTANT! The methods with prefix **Init** must be used before any methods with prefix **Spawn** method are used, or unexpected results may appear!!!
+--
+-- Because SPAWN can spawn multiple groups of a template group,
+-- SPAWN has an **internal index** that keeps track
+-- which was the latest group that was spawned.
+--
+-- **Limits** can be set on how many groups can be spawn in each SPAWN object,
+-- using the method @{#SPAWN.InitLimit}. SPAWN has 2 kind of limits:
+--
+-- * The maximum amount of @{Unit}s that can be **alive** at the same time...
+-- * The maximum amount of @{Group}s that can be **spawned**... This is more of a **resource**-type of limit.
+--
+-- When new groups get spawned using the **Spawn** methods,
+-- it will be evaluated whether any limits have been reached.
+-- When no spawn limit is reached, a new group will be created by the spawning methods,
+-- and the internal index will be increased with 1.
+--
+-- These limits ensure that your mission does not accidentally get flooded with spawned groups.
+-- Additionally, it also guarantees that independent of the group composition,
+-- at any time, the most optimal amount of groups are alive in your mission.
+-- For example, if your template group has a group composition of 10 units, and you specify a limit of 100 units alive at the same time,
+-- with unlimited resources = :InitLimit( 100, 0 ) and 10 groups are alive, but two groups have only one unit alive in the group,
+-- then a sequent Spawn(Scheduled) will allow a new group to be spawned!!!
+--
+-- ### IMPORTANT!! If a limit has been reached, it is possible that a **Spawn** method returns **nil**, meaning, no @{Group} had been spawned!!!
+--
+-- Spawned groups get **the same name** as the name of the template group.
+-- Spawned units in those groups keep _by default_ **the same name** as the name of the template group.
+-- However, because multiple groups and units are created from the template group,
+-- a suffix is added to each spawned group and unit.
+--
+-- Newly spawned groups will get the following naming structure at run-time:
+--
+-- 1. Spawned groups will have the name _GroupName_#_nnn_, where _GroupName_ is the name of the **template group**,
+-- and _nnn_ is a **counter from 0 to 999**.
+-- 2. Spawned units will have the name _GroupName_#_nnn_-_uu_,
+-- where _uu_ is a **counter from 0 to 99** for each new spawned unit belonging to the group.
+--
+-- That being said, there is a way to keep the same unit names!
+-- The method @{#SPAWN.InitKeepUnitNames}() will keep the same unit names as defined within the template group, thus:
+--
+-- 3. Spawned units will have the name _UnitName_#_nnn_-_uu_,
+-- where _UnitName_ is the **unit name as defined in the template group*,
+-- and _uu_ is a **counter from 0 to 99** for each new spawned unit belonging to the group.
+--
+-- Some **additional notes that need to be considered!!**:
+--
+-- * templates are actually groups defined within the mission editor, with the flag "Late Activation" set.
+-- As such, these groups are never used within the mission, but are used by the @{#SPAWN} module.
+-- * It is important to defined BEFORE you spawn new groups,
+-- a proper initialization of the SPAWN instance is done with the options you want to use.
+-- * When designing a mission, NEVER name groups using a "#" within the name of the group Spawn template(s),
+-- or the SPAWN module logic won't work anymore.
+--
+-- ## SPAWN construction methods
+--
+-- Create a new SPAWN object with the @{#SPAWN.New}() or the @{#SPAWN.NewWithAlias}() methods:
+--
+-- * @{#SPAWN.New}(): Creates a new SPAWN object taking the name of the group that represents the GROUP template (definition).
+-- * @{#SPAWN.NewWithAlias}(): Creates a new SPAWN object taking the name of the group that represents the GROUP template (definition), and gives each spawned @{Group} an different name.
+--
+-- It is important to understand how the SPAWN class works internally. The SPAWN object created will contain internally a list of groups that will be spawned and that are already spawned.
+-- The initialization methods will modify this list of groups so that when a group gets spawned, ALL information is already prepared when spawning. This is done for performance reasons.
+-- So in principle, the group list will contain all parameters and configurations after initialization, and when groups get actually spawned, this spawning can be done quickly and efficient.
+--
+-- ## SPAWN **Init**ialization methods
+--
+-- A spawn object will behave differently based on the usage of **initialization** methods, which all start with the **Init** prefix:
+--
+-- ### Unit Names
+--
+-- * @{#SPAWN.InitKeepUnitNames}(): Keeps the unit names as defined within the mission editor, but note that anything after a # mark is ignored, and any spaces before and after the resulting name are removed. IMPORTANT! This method MUST be the first used after :New !!!
+--
+-- ### Route randomization
+--
+-- * @{#SPAWN.InitRandomizeRoute}(): Randomize the routes of spawned groups, and for air groups also optionally the height.
+--
+-- ### Group composition randomization
+--
+-- * @{#SPAWN.InitRandomizeTemplate}(): Randomize the group templates so that when a new group is spawned, a random group template is selected from one of the templates defined.
+--
+-- ### Uncontrolled
+--
+-- * @{#SPAWN.InitUnControlled}(): Spawn plane groups uncontrolled.
+--
+-- ### Array formation
+--
+-- * @{#SPAWN.InitArray}(): Make groups visible before they are actually activated, and order these groups like a batallion in an array.
+--
+-- ### Position randomization
+--
+-- * @{#SPAWN.InitRandomizePosition}(): Randomizes the position of @{Group}s that are spawned within a **radius band**, given an Outer and Inner radius, from the point that the spawn happens.
+-- * @{#SPAWN.InitRandomizeUnits}(): Randomizes the @{Unit}s in the @{Group} that is spawned within a **radius band**, given an Outer and Inner radius.
+-- * @{#SPAWN.InitRandomizeZones}(): Randomizes the spawning between a predefined list of @{Zone}s that are declared using this function. Each zone can be given a probability factor.
+--
+-- ### Enable / Disable AI when spawning a new @{Group}
+--
+-- * @{#SPAWN.InitAIOn}(): Turns the AI On when spawning the new @{Group} object.
+-- * @{#SPAWN.InitAIOff}(): Turns the AI Off when spawning the new @{Group} object.
+-- * @{#SPAWN.InitAIOnOff}(): Turns the AI On or Off when spawning the new @{Group} object.
+--
+-- ### Limit scheduled spawning
+--
+-- * @{#SPAWN.InitLimit}(): Limits the amount of groups that can be alive at the same time and that can be dynamically spawned.
+--
+-- ### Delay initial scheduled spawn
+--
+-- * @{#SPAWN.InitDelayOnOff}(): Turns the inital delay On/Off when scheduled spawning the first @{Group} object.
+-- * @{#SPAWN.InitDelayOn}(): Turns the inital delay On when scheduled spawning the first @{Group} object.
+-- * @{#SPAWN.InitDelayOff}(): Turns the inital delay Off when scheduled spawning the first @{Group} object.
+--
+-- ### Repeat spawned @{Group}s upon landing
+--
+-- * @{#SPAWN.InitRepeat}() or @{#SPAWN.InitRepeatOnLanding}(): This method is used to re-spawn automatically the same group after it has landed.
+-- * @{#SPAWN.InitRepeatOnEngineShutDown}(): This method is used to re-spawn automatically the same group after it has landed and it shuts down the engines at the ramp.
+--
+--
+-- ## SPAWN **Spawn** methods
+--
+-- Groups can be spawned at different times and methods:
+--
+-- ### **Single** spawning methods
+--
+-- * @{#SPAWN.Spawn}(): Spawn one new group based on the last spawned index.
+-- * @{#SPAWN.ReSpawn}(): Re-spawn a group based on a given index.
+-- * @{#SPAWN.SpawnFromVec3}(): Spawn a new group from a Vec3 coordinate. (The group will can be spawned at a point in the air).
+-- * @{#SPAWN.SpawnFromVec2}(): Spawn a new group from a Vec2 coordinate. (The group will be spawned at land height ).
+-- * @{#SPAWN.SpawnFromStatic}(): Spawn a new group from a structure, taking the position of a @{Static}.
+-- * @{#SPAWN.SpawnFromUnit}(): Spawn a new group taking the position of a @{Unit}.
+-- * @{#SPAWN.SpawnInZone}(): Spawn a new group in a @{Zone}.
+--
+-- Note that @{#SPAWN.Spawn} and @{#SPAWN.ReSpawn} return a @{GROUP#GROUP.New} object, that contains a reference to the DCSGroup object.
+-- You can use the @{GROUP} object to do further actions with the DCSGroup.
+--
+-- ### **Scheduled** spawning methods
+--
+-- * @{#SPAWN.SpawnScheduled}(): Spawn groups at scheduled but randomized intervals.
+-- * @{#SPAWN.SpawnScheduledStart}(): Start or continue to spawn groups at scheduled time intervals.
+-- * @{#SPAWN.SpawnScheduledStop}(): Stop the spawning of groups at scheduled time intervals.
+--
+--
+--
+-- ## Retrieve alive GROUPs spawned by the SPAWN object
+--
+-- The SPAWN class administers which GROUPS it has reserved (in stock) or has created during mission execution.
+-- Every time a SPAWN object spawns a new GROUP object, a reference to the GROUP object is added to an internal table of GROUPS.
+-- SPAWN provides methods to iterate through that internal GROUP object reference table:
+--
+-- * @{#SPAWN.GetFirstAliveGroup}(): Will find the first alive GROUP it has spawned, and return the alive GROUP object and the first Index where the first alive GROUP object has been found.
+-- * @{#SPAWN.GetNextAliveGroup}(): Will find the next alive GROUP object from a given Index, and return a reference to the alive GROUP object and the next Index where the alive GROUP has been found.
+-- * @{#SPAWN.GetLastAliveGroup}(): Will find the last alive GROUP object, and will return a reference to the last live GROUP object and the last Index where the last alive GROUP object has been found.
+--
+-- You can use the methods @{#SPAWN.GetFirstAliveGroup}() and sequently @{#SPAWN.GetNextAliveGroup}() to iterate through the alive GROUPS within the SPAWN object, and to actions... See the respective methods for an example.
+-- The method @{#SPAWN.GetGroupFromIndex}() will return the GROUP object reference from the given Index, dead or alive...
+--
+-- ## Spawned cleaning of inactive groups
+--
+-- Sometimes, it will occur during a mission run-time, that ground or especially air objects get damaged, and will while being damged stop their activities, while remaining alive.
+-- In such cases, the SPAWN object will just sit there and wait until that group gets destroyed, but most of the time it won't,
+-- and it may occur that no new groups are or can be spawned as limits are reached.
+-- To prevent this, a @{#SPAWN.InitCleanUp}() initialization method has been defined that will silently monitor the status of each spawned group.
+-- Once a group has a velocity = 0, and has been waiting for a defined interval, that group will be cleaned or removed from run-time.
+-- There is a catch however :-) If a damaged group has returned to an airbase within the coalition, that group will not be considered as "lost"...
+-- In such a case, when the inactive group is cleaned, a new group will Re-spawned automatically.
+-- This models AI that has succesfully returned to their airbase, to restart their combat activities.
+-- Check the @{#SPAWN.InitCleanUp}() for further info.
+--
+-- ## Catch the @{Group} Spawn Event in a callback function!
+--
+-- When using the @{#SPAWN.SpawnScheduled)() method, new @{Group}s are created following the spawn time interval parameters.
+-- When a new @{Group} is spawned, you maybe want to execute actions with that group spawned at the spawn event.
+-- The SPAWN class supports this functionality through the method @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ),
+-- which takes a function as a parameter that you can define locally.
+-- Whenever a new @{Group} is spawned, the given function is called, and the @{Group} that was just spawned, is given as a parameter.
+-- As a result, your spawn event handling function requires one parameter to be declared, which will contain the spawned @{Group} object.
+-- A coding example is provided at the description of the @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ) method.
+--
+-- ## Delay the initial spawning
+--
+-- When using the @{#SPAWN.SpawnScheduled)() method, the default behaviour of this method will be that it will spawn the initial (first) @{Group}
+-- immediately when :SpawnScheduled() is initiated. The methods @{#SPAWN.InitDelayOnOff}() and @{#SPAWN.InitDelayOn}() can be used to
+-- activate a delay before the first @{Group} is spawned. For completeness, a method @{#SPAWN.InitDelayOff}() is also available, that
+-- can be used to switch off the initial delay. Because there is no delay by default, this method would only be used when a
+-- @{#SPAWN.SpawnScheduledStop}() ; @{#SPAWN.SpawnScheduledStart}() sequence would have been used.
+--
+--
+-- @field #SPAWN SPAWN
+--
+SPAWN = {
+ ClassName = "SPAWN",
+ SpawnTemplatePrefix = nil,
+ SpawnAliasPrefix = nil,
+}
-
-
-
-
-
-
-
-
-
-
-
+--- Enumerator for spawns at airbases
+-- @type SPAWN.Takeoff
+-- @extends Wrapper.Group#GROUP.Takeoff
+
+--- @field #SPAWN.Takeoff Takeoff
+SPAWN.Takeoff = GROUP.Takeoff
+--- @type SPAWN.SpawnZoneTable
+-- @list SpawnZone
-
-
-
-
-
+
+--- Creates the main object to spawn a @{Group} defined in the DCS ME.
+-- @param #SPAWN self
+-- @param #string SpawnTemplatePrefix is the name of the Group in the ME that defines the Template. Each new group will have the name starting with SpawnTemplatePrefix.
+-- @return #SPAWN
+-- @usage
+-- -- NATO helicopters engaging in the battle field.
+-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' )
+-- @usage local Plane = SPAWN:New( "Plane" ) -- Creates a new local variable that can initiate new planes with the name "Plane#ddd" using the template "Plane" as defined within the ME.
+function SPAWN:New( SpawnTemplatePrefix )
+ local self = BASE:Inherit( self, BASE:New() ) -- #SPAWN
+ self:F( { SpawnTemplatePrefix } )
+ local TemplateGroup = Group.getByName( SpawnTemplatePrefix )
+ if TemplateGroup then
+ self.SpawnTemplatePrefix = SpawnTemplatePrefix
+ self.SpawnIndex = 0
+ self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart.
+ self.AliveUnits = 0 -- Contains the counter how many units are currently alive
+ self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not.
+ self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!!
+ self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning.
+ self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts.
+ self.SpawnInitLimit = false -- By default, no InitLimit
+ self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time.
+ self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned.
+ self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false.
+ self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned.
+ self.AIOnOff = true -- The AI is on by default when spawning a group.
+ self.SpawnUnControlled = false
+ self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name.
+ self.DelayOnOff = false -- No intial delay when spawning the first group.
+ self.Grouping = nil -- No grouping
-
-
- MOOSE/Spawn.lua at master · FlightControl-Master/MOOSE
-
-
-
+ self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned.
+ else
+ error( "SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" )
+ end
+ self:SetEventPriority( 5 )
+
+ return self
+end
+
+--- Creates a new SPAWN instance to create new groups based on the defined template and using a new alias for each new group.
+-- @param #SPAWN self
+-- @param #string SpawnTemplatePrefix is the name of the Group in the ME that defines the Template.
+-- @param #string SpawnAliasPrefix is the name that will be given to the Group at runtime.
+-- @return #SPAWN
+-- @usage
+-- -- NATO helicopters engaging in the battle field.
+-- Spawn_BE_KA50 = SPAWN:NewWithAlias( 'BE KA-50@RAMP-Ground Defense', 'Helicopter Attacking a City' )
+-- @usage local PlaneWithAlias = SPAWN:NewWithAlias( "Plane", "Bomber" ) -- Creates a new local variable that can instantiate new planes with the name "Bomber#ddd" using the template "Plane" as defined within the ME.
+function SPAWN:NewWithAlias( SpawnTemplatePrefix, SpawnAliasPrefix )
+ local self = BASE:Inherit( self, BASE:New() )
+ self:F( { SpawnTemplatePrefix, SpawnAliasPrefix } )
+
+ local TemplateGroup = Group.getByName( SpawnTemplatePrefix )
+ if TemplateGroup then
+ self.SpawnTemplatePrefix = SpawnTemplatePrefix
+ self.SpawnAliasPrefix = SpawnAliasPrefix
+ self.SpawnIndex = 0
+ self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart.
+ self.AliveUnits = 0 -- Contains the counter how many units are currently alive
+ self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not.
+ self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!!
+ self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning.
+ self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts.
+ self.SpawnInitLimit = false -- By default, no InitLimit
+ self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time.
+ self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned.
+ self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false.
+ self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned.
+ self.AIOnOff = true -- The AI is on by default when spawning a group.
+ self.SpawnUnControlled = false
+ self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name.
+ self.DelayOnOff = false -- No intial delay when spawning the first group.
+ self.Grouping = nil
+
+ self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned.
+ else
+ error( "SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" )
+ end
+
+ self:SetEventPriority( 5 )
+
+ return self
+end
+
+
+--- Limits the Maximum amount of Units that can be alive at the same time, and the maximum amount of groups that can be spawned.
+-- Note that this method is exceptionally important to balance the performance of the mission. Depending on the machine etc, a mission can only process a maximum amount of units.
+-- If the time interval must be short, but there should not be more Units or Groups alive than a maximum amount of units, then this method should be used...
+-- When a @{#SPAWN.New} is executed and the limit of the amount of units alive is reached, then no new spawn will happen of the group, until some of these units of the spawn object will be destroyed.
+-- @param #SPAWN self
+-- @param #number SpawnMaxUnitsAlive The maximum amount of units that can be alive at runtime.
+-- @param #number SpawnMaxGroups The maximum amount of groups that can be spawned. When the limit is reached, then no more actual spawns will happen of the group.
+-- This parameter is useful to define a maximum amount of airplanes, ground troops, helicopters, ships etc within a supply area.
+-- This parameter accepts the value 0, which defines that there are no maximum group limits, but there are limits on the maximum of units that can be alive at the same time.
+-- @return #SPAWN self
+-- @usage
+-- -- NATO helicopters engaging in the battle field.
+-- -- This helicopter group consists of one Unit. So, this group will SPAWN maximum 2 groups simultaneously within the DCSRTE.
+-- -- There will be maximum 24 groups spawned during the whole mission lifetime.
+-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitLimit( 2, 24 )
+function SPAWN:InitLimit( SpawnMaxUnitsAlive, SpawnMaxGroups )
+ self:F( { self.SpawnTemplatePrefix, SpawnMaxUnitsAlive, SpawnMaxGroups } )
+
+ self.SpawnInitLimit = true
+ self.SpawnMaxUnitsAlive = SpawnMaxUnitsAlive -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time.
+ self.SpawnMaxGroups = SpawnMaxGroups -- The maximum amount of groups that can be spawned.
+
+ for SpawnGroupID = 1, self.SpawnMaxGroups do
+ self:_InitializeSpawnGroups( SpawnGroupID )
+ end
+
+ return self
+end
+
+--- Keeps the unit names as defined within the mission editor,
+-- but note that anything after a # mark is ignored,
+-- and any spaces before and after the resulting name are removed.
+-- IMPORTANT! This method MUST be the first used after :New !!!
+-- @param #SPAWN self
+-- @return #SPAWN self
+function SPAWN:InitKeepUnitNames()
+ self:F( )
+
+ self.SpawnInitKeepUnitNames = true
+
+ return self
+end
+
+
+--- Randomizes the defined route of the SpawnTemplatePrefix group in the ME. This is very useful to define extra variation of the behaviour of groups.
+-- @param #SPAWN self
+-- @param #number SpawnStartPoint is the waypoint where the randomization begins.
+-- Note that the StartPoint = 0 equaling the point where the group is spawned.
+-- @param #number SpawnEndPoint is the waypoint where the randomization ends counting backwards.
+-- This parameter is useful to avoid randomization to end at a waypoint earlier than the last waypoint on the route.
+-- @param #number SpawnRadius is the radius in meters in which the randomization of the new waypoints, with the original waypoint of the original template located in the middle ...
+-- @param #number SpawnHeight (optional) Specifies the **additional** height in meters that can be added to the base height specified at each waypoint in the ME.
+-- @return #SPAWN
+-- @usage
+-- -- NATO helicopters engaging in the battle field.
+-- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP).
+-- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter.
+-- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters.
+-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 )
+function SPAWN:InitRandomizeRoute( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight )
+ self:F( { self.SpawnTemplatePrefix, SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight } )
+
+ self.SpawnRandomizeRoute = true
+ self.SpawnRandomizeRouteStartPoint = SpawnStartPoint
+ self.SpawnRandomizeRouteEndPoint = SpawnEndPoint
+ self.SpawnRandomizeRouteRadius = SpawnRadius
+ self.SpawnRandomizeRouteHeight = SpawnHeight
+
+ for GroupID = 1, self.SpawnMaxGroups do
+ self:_RandomizeRoute( GroupID )
+ end
+
+ return self
+end
+
+--- Randomizes the position of @{Group}s that are spawned within a **radius band**, given an Outer and Inner radius, from the point that the spawn happens.
+-- @param #SPAWN self
+-- @param #boolean RandomizePosition If true, SPAWN will perform the randomization of the @{Group}s position between a given outer and inner radius.
+-- @param Dcs.DCSTypes#Distance OuterRadius (optional) The outer radius in meters where the new group will be spawned.
+-- @param Dcs.DCSTypes#Distance InnerRadius (optional) The inner radius in meters where the new group will NOT be spawned.
+-- @return #SPAWN
+function SPAWN:InitRandomizePosition( RandomizePosition, OuterRadius, InnerRadius )
+ self:F( { self.SpawnTemplatePrefix, RandomizePosition, OuterRadius, InnerRadius } )
+
+ self.SpawnRandomizePosition = RandomizePosition or false
+ self.SpawnRandomizePositionOuterRadius = OuterRadius or 0
+ self.SpawnRandomizePositionInnerRadius = InnerRadius or 0
+
+ for GroupID = 1, self.SpawnMaxGroups do
+ self:_RandomizeRoute( GroupID )
+ end
+
+ return self
+end
+
+
+--- Randomizes the UNITs that are spawned within a radius band given an Outer and Inner radius.
+-- @param #SPAWN self
+-- @param #boolean RandomizeUnits If true, SPAWN will perform the randomization of the @{UNIT}s position within the group between a given outer and inner radius.
+-- @param Dcs.DCSTypes#Distance OuterRadius (optional) The outer radius in meters where the new group will be spawned.
+-- @param Dcs.DCSTypes#Distance InnerRadius (optional) The inner radius in meters where the new group will NOT be spawned.
+-- @return #SPAWN
+-- @usage
+-- -- NATO helicopters engaging in the battle field.
+-- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP).
+-- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter.
+-- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters.
+-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 )
+function SPAWN:InitRandomizeUnits( RandomizeUnits, OuterRadius, InnerRadius )
+ self:F( { self.SpawnTemplatePrefix, RandomizeUnits, OuterRadius, InnerRadius } )
+
+ self.SpawnRandomizeUnits = RandomizeUnits or false
+ self.SpawnOuterRadius = OuterRadius or 0
+ self.SpawnInnerRadius = InnerRadius or 0
+
+ for GroupID = 1, self.SpawnMaxGroups do
+ self:_RandomizeRoute( GroupID )
+ end
+
+ return self
+end
+
+--- This method is rather complicated to understand. But I'll try to explain.
+-- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor,
+-- but they will all follow the same Template route and have the same prefix name.
+-- In other words, this method randomizes between a defined set of groups the template to be used for each new spawn of a group.
+-- @param #SPAWN self
+-- @param #string SpawnTemplatePrefixTable A table with the names of the groups defined within the mission editor, from which one will be choosen when a new group will be spawned.
+-- @return #SPAWN
+-- @usage
+-- -- NATO Tank Platoons invading Gori.
+-- -- Choose between 13 different 'US Tank Platoon' configurations for each new SPAWN the Group to be spawned for the
+-- -- 'US Tank Platoon Left', 'US Tank Platoon Middle' and 'US Tank Platoon Right' SpawnTemplatePrefixes.
+-- -- Each new SPAWN will randomize the route, with a defined time interval of 200 seconds with 40% time variation (randomization) and
+-- -- with a limit set of maximum 12 Units alive simulteneously and 150 Groups to be spawned during the whole mission.
+-- Spawn_US_Platoon = { 'US Tank Platoon 1', 'US Tank Platoon 2', 'US Tank Platoon 3', 'US Tank Platoon 4', 'US Tank Platoon 5',
+-- 'US Tank Platoon 6', 'US Tank Platoon 7', 'US Tank Platoon 8', 'US Tank Platoon 9', 'US Tank Platoon 10',
+-- 'US Tank Platoon 11', 'US Tank Platoon 12', 'US Tank Platoon 13' }
+-- Spawn_US_Platoon_Left = SPAWN:New( 'US Tank Platoon Left' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 )
+-- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 )
+-- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 )
+function SPAWN:InitRandomizeTemplate( SpawnTemplatePrefixTable )
+ self:F( { self.SpawnTemplatePrefix, SpawnTemplatePrefixTable } )
+
+ self.SpawnTemplatePrefixTable = SpawnTemplatePrefixTable
+ self.SpawnRandomizeTemplate = true
+
+ for SpawnGroupID = 1, self.SpawnMaxGroups do
+ self:_RandomizeTemplate( SpawnGroupID )
+ end
+
+ return self
+end
+
+--- When spawning a new group, make the grouping of the units according the InitGrouping setting.
+-- @param #SPAWN self
+-- @param #number Grouping Indicates the maximum amount of units in the group.
+-- @return #SPAWN
+function SPAWN:InitGrouping( Grouping ) -- R2.2
+ self:F( { self.SpawnTemplatePrefix, Grouping } )
+
+ self.SpawnGrouping = Grouping
+
+ return self
+end
+
+
+
+--TODO: Add example.
+--- This method provides the functionality to randomize the spawning of the Groups at a given list of zones of different types.
+-- @param #SPAWN self
+-- @param #table SpawnZoneTable A table with @{Zone} objects. If this table is given, then each spawn will be executed within the given list of @{Zone}s objects.
+-- @return #SPAWN
+-- @usage
+-- -- NATO Tank Platoons invading Gori.
+-- -- Choose between 3 different zones for each new SPAWN the Group to be executed, regardless of the zone type.
+function SPAWN:InitRandomizeZones( SpawnZoneTable )
+ self:F( { self.SpawnTemplatePrefix, SpawnZoneTable } )
+
+ self.SpawnZoneTable = SpawnZoneTable
+ self.SpawnRandomizeZones = true
+
+ for SpawnGroupID = 1, self.SpawnMaxGroups do
+ self:_RandomizeZones( SpawnGroupID )
+ end
+
+ return self
+end
+
+
+
+
+
+--- For planes and helicopters, when these groups go home and land on their home airbases and farps, they normally would taxi to the parking spot, shut-down their engines and wait forever until the Group is removed by the runtime environment.
+-- This method is used to re-spawn automatically (so no extra call is needed anymore) the same group after it has landed.
+-- This will enable a spawned group to be re-spawned after it lands, until it is destroyed...
+-- Note: When the group is respawned, it will re-spawn from the original airbase where it took off.
+-- So ensure that the routes for groups that respawn, always return to the original airbase, or players may get confused ...
+-- @param #SPAWN self
+-- @return #SPAWN self
+-- @usage
+-- -- RU Su-34 - AI Ship Attack
+-- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically.
+-- SpawnRU_SU34 = SPAWN:New( 'TF1 RU Su-34 Krymsk@AI - Attack Ships' ):Schedule( 2, 3, 1800, 0.4 ):SpawnUncontrolled():InitRandomizeRoute( 1, 1, 3000 ):RepeatOnEngineShutDown()
+function SPAWN:InitRepeat()
+ self:F( { self.SpawnTemplatePrefix, self.SpawnIndex } )
+
+ self.Repeat = true
+ self.RepeatOnEngineShutDown = false
+ self.RepeatOnLanding = true
+
+ return self
+end
+
+--- Respawn group after landing.
+-- @param #SPAWN self
+-- @return #SPAWN self
+function SPAWN:InitRepeatOnLanding()
+ self:F( { self.SpawnTemplatePrefix } )
+
+ self:InitRepeat()
+ self.RepeatOnEngineShutDown = false
+ self.RepeatOnLanding = true
+
+ return self
+end
+
+
+--- Respawn after landing when its engines have shut down.
+-- @param #SPAWN self
+-- @return #SPAWN self
+function SPAWN:InitRepeatOnEngineShutDown()
+ self:F( { self.SpawnTemplatePrefix } )
+
+ self:InitRepeat()
+ self.RepeatOnEngineShutDown = true
+ self.RepeatOnLanding = false
+
+ return self
+end
+
+
+--- CleanUp groups when they are still alive, but inactive.
+-- When groups are still alive and have become inactive due to damage and are unable to contribute anything, then this group will be removed at defined intervals in seconds.
+-- @param #SPAWN self
+-- @param #string SpawnCleanUpInterval The interval to check for inactive groups within seconds.
+-- @return #SPAWN self
+-- @usage Spawn_Helicopter:CleanUp( 20 ) -- CleanUp the spawning of the helicopters every 20 seconds when they become inactive.
+function SPAWN:InitCleanUp( SpawnCleanUpInterval )
+ self:F( { self.SpawnTemplatePrefix, SpawnCleanUpInterval } )
+
+ self.SpawnCleanUpInterval = SpawnCleanUpInterval
+ self.SpawnCleanUpTimeStamps = {}
+
+ local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup()
+ self:T( { "CleanUp Scheduler:", SpawnGroup } )
+
+ --self.CleanUpFunction = routines.scheduleFunction( self._SpawnCleanUpScheduler, { self }, timer.getTime() + 1, SpawnCleanUpInterval )
+ self.CleanUpScheduler = SCHEDULER:New( self, self._SpawnCleanUpScheduler, {}, 1, SpawnCleanUpInterval, 0.2 )
+ return self
+end
+
+
+
+--- Makes the groups visible before start (like a batallion).
+-- The method will take the position of the group as the first position in the array.
+-- @param #SPAWN self
+-- @param #number SpawnAngle The angle in degrees how the groups and each unit of the group will be positioned.
+-- @param #number SpawnWidth The amount of Groups that will be positioned on the X axis.
+-- @param #number SpawnDeltaX The space between each Group on the X-axis.
+-- @param #number SpawnDeltaY The space between each Group on the Y-axis.
+-- @return #SPAWN self
+-- @usage
+-- -- Define an array of Groups.
+-- Spawn_BE_Ground = SPAWN:New( 'BE Ground' ):InitLimit( 2, 24 ):InitArray( 90, "Diamond", 10, 100, 50 )
+function SPAWN:InitArray( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY )
+ self:F( { self.SpawnTemplatePrefix, SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY } )
+
+ self.SpawnVisible = true -- When the first Spawn executes, all the Groups need to be made visible before start.
+
+ local SpawnX = 0
+ local SpawnY = 0
+ local SpawnXIndex = 0
+ local SpawnYIndex = 0
+
+ for SpawnGroupID = 1, self.SpawnMaxGroups do
+ self:T( { SpawnX, SpawnY, SpawnXIndex, SpawnYIndex } )
+
+ self.SpawnGroups[SpawnGroupID].Visible = true
+ self.SpawnGroups[SpawnGroupID].Spawned = false
-
+ SpawnXIndex = SpawnXIndex + 1
+ if SpawnWidth and SpawnWidth ~= 0 then
+ if SpawnXIndex >= SpawnWidth then
+ SpawnXIndex = 0
+ SpawnYIndex = SpawnYIndex + 1
+ end
+ end
-
-
-
-
-
+ local SpawnRootX = self.SpawnGroups[SpawnGroupID].SpawnTemplate.x
+ local SpawnRootY = self.SpawnGroups[SpawnGroupID].SpawnTemplate.y
+
+ self:_TranslateRotate( SpawnGroupID, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle )
+
+ self.SpawnGroups[SpawnGroupID].SpawnTemplate.lateActivation = true
+ self.SpawnGroups[SpawnGroupID].SpawnTemplate.visible = true
+
+ self.SpawnGroups[SpawnGroupID].Visible = true
+
+ self:HandleEvent( EVENTS.Birth, self._OnBirth )
+ self:HandleEvent( EVENTS.Dead, self._OnDeadOrCrash )
+ self:HandleEvent( EVENTS.Crash, self._OnDeadOrCrash )
+ if self.Repeat then
+ self:HandleEvent( EVENTS.Takeoff, self._OnTakeOff )
+ self:HandleEvent( EVENTS.Land, self._OnLand )
+ end
+ if self.RepeatOnEngineShutDown then
+ self:HandleEvent( EVENTS.EngineShutdown, self._OnEngineShutDown )
+ end
+
+ self.SpawnGroups[SpawnGroupID].Group = _DATABASE:Spawn( self.SpawnGroups[SpawnGroupID].SpawnTemplate )
+
+ SpawnX = SpawnXIndex * SpawnDeltaX
+ SpawnY = SpawnYIndex * SpawnDeltaY
+ end
+ return self
+end
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+do -- AI methods
+ --- Turns the AI On or Off for the @{Group} when spawning.
+ -- @param #SPAWN self
+ -- @param #boolean AIOnOff A value of true sets the AI On, a value of false sets the AI Off.
+ -- @return #SPAWN The SPAWN object
+ function SPAWN:InitAIOnOff( AIOnOff )
-
-
-
-
-
-
-
-
-
-
-
-
+ self.AIOnOff = AIOnOff
+ return self
+ end
+ --- Turns the AI On for the @{Group} when spawning.
+ -- @param #SPAWN self
+ -- @return #SPAWN The SPAWN object
+ function SPAWN:InitAIOn()
+
+ return self:InitAIOnOff( true )
+ end
+
+ --- Turns the AI Off for the @{Group} when spawning.
+ -- @param #SPAWN self
+ -- @return #SPAWN The SPAWN object
+ function SPAWN:InitAIOff()
+
+ return self:InitAIOnOff( false )
+ end
-
+end -- AI methods
-
-
+do -- Delay methods
+ --- Turns the Delay On or Off for the first @{Group} scheduled spawning.
+ -- The default value is that for scheduled spawning, there is an initial delay when spawning the first @{Group}.
+ -- @param #SPAWN self
+ -- @param #boolean DelayOnOff A value of true sets the Delay On, a value of false sets the Delay Off.
+ -- @return #SPAWN The SPAWN object
+ function SPAWN:InitDelayOnOff( DelayOnOff )
+
+ self.DelayOnOff = DelayOnOff
+ return self
+ end
+
+ --- Turns the Delay On for the @{Group} when spawning.
+ -- @param #SPAWN self
+ -- @return #SPAWN The SPAWN object
+ function SPAWN:InitDelayOn()
+
+ return self:InitDelayOnOff( true )
+ end
+
+ --- Turns the Delay Off for the @{Group} when spawning.
+ -- @param #SPAWN self
+ -- @return #SPAWN The SPAWN object
+ function SPAWN:InitDelayOff()
+
+ return self:InitDelayOnOff( false )
+ end
-
+end -- Delay methods
+--- Will spawn a group based on the internal index.
+-- Note: Uses @{DATABASE} module defined in MOOSE.
+-- @param #SPAWN self
+-- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions.
+function SPAWN:Spawn()
+ self:F( { self.SpawnTemplatePrefix, self.SpawnIndex, self.AliveUnits } )
-
+ return self:SpawnWithIndex( self.SpawnIndex + 1 )
+end
+--- Will re-spawn a group based on a given index.
+-- Note: Uses @{DATABASE} module defined in MOOSE.
+-- @param #SPAWN self
+-- @param #string SpawnIndex The index of the group to be spawned.
+-- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions.
+function SPAWN:ReSpawn( SpawnIndex )
+ self:F( { self.SpawnTemplatePrefix, SpawnIndex } )
+
+ if not SpawnIndex then
+ SpawnIndex = 1
+ end
-
+-- TODO: This logic makes DCS crash and i don't know why (yet).
+ local SpawnGroup = self:GetGroupFromIndex( SpawnIndex )
+ local WayPoints = SpawnGroup and SpawnGroup.WayPoints or nil
+ if SpawnGroup then
+ local SpawnDCSGroup = SpawnGroup:GetDCSObject()
+ if SpawnDCSGroup then
+ SpawnGroup:Destroy()
+ end
+ end
-
+ local SpawnGroup = self:SpawnWithIndex( SpawnIndex )
+ if SpawnGroup and WayPoints then
+ -- If there were WayPoints set, then Re-Execute those WayPoints!
+ SpawnGroup:WayPointInitialize( WayPoints )
+ SpawnGroup:WayPointExecute( 1, 5 )
+ end
+
+ if SpawnGroup.ReSpawnFunction then
+ SpawnGroup:ReSpawnFunction()
+ end
+
+ SpawnGroup:ResetEvents()
+
+ return SpawnGroup
+end
-
-
-
-
-
-
-
-
-
-
-
+--- Will spawn a group with a specified index number.
+-- Uses @{DATABASE} global object defined in MOOSE.
+-- @param #SPAWN self
+-- @param #string SpawnIndex The index of the group to be spawned.
+-- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions.
+function SPAWN:SpawnWithIndex( SpawnIndex )
+ self:F2( { SpawnTemplatePrefix = self.SpawnTemplatePrefix, SpawnIndex = SpawnIndex, AliveUnits = self.AliveUnits, SpawnMaxGroups = self.SpawnMaxGroups } )
+
+ if self:_GetSpawnIndex( SpawnIndex ) then
+ if self.SpawnGroups[self.SpawnIndex].Visible then
+ self.SpawnGroups[self.SpawnIndex].Group:Activate()
+ else
-
+ -- If RandomizeUnits, then Randomize the formation at the start point.
+ if self.SpawnRandomizeUnits then
+ for UnitID = 1, #SpawnTemplate.units do
+ local RandomVec2 = PointVec3:GetRandomVec2InRadius( self.SpawnOuterRadius, self.SpawnInnerRadius )
+ SpawnTemplate.units[UnitID].x = RandomVec2.x
+ SpawnTemplate.units[UnitID].y = RandomVec2.y
+ self:T( 'SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
+ end
+ end
-
-
-
+ if SpawnTemplate.CategoryID == Group.Category.HELICOPTER or SpawnTemplate.CategoryID == Group.Category.AIRPLANE then
+ if SpawnTemplate.route.points[1].type == "TakeOffParking" then
+ SpawnTemplate.uncontrolled = self.SpawnUnControlled
+ end
+ end
+ end
+ self:HandleEvent( EVENTS.Birth, self._OnBirth )
+ self:HandleEvent( EVENTS.Dead, self._OnDeadOrCrash )
+ self:HandleEvent( EVENTS.Crash, self._OnDeadOrCrash )
+ if self.Repeat then
+ self:HandleEvent( EVENTS.Takeoff, self._OnTakeOff )
+ self:HandleEvent( EVENTS.Land, self._OnLand )
+ end
+ if self.RepeatOnEngineShutDown then
+ self:HandleEvent( EVENTS.EngineShutdown, self._OnEngineShutDown )
+ end
-
-
-- * **Aaron**: Posed the idea for Group position randomization at SpawnInZone and make the Unit randomization separate from the Group randomization.
-
-
-
-
-- * [**Entropy**](https://forums.eagle.ru/member.php?u=111471), **Afinegan**: Came up with the requirement for AIOnOff().
-
-
-
-
--
-
-
-
-
-- ### Authors:
-
-
-
-
--
-
-
-
-
-- * **FlightControl**: Design & Programming
-
-
-
-
--
-
-
-
-
-- @module Spawn
-
-
-
-
-
-
-
-
-
----BASE:TraceClass("SPAWN")
-
-
-
-
-
-
-
-
-
-
-
-
-
-
--- SPAWN Class
-
-
-
-
-- @type SPAWN
-
-
-
-
-- @field ClassName
-
-
-
-
-- @field #string SpawnTemplatePrefix
-
-
-
-
-- @field #string SpawnAliasPrefix
-
-
-
-
-- @field #number AliveUnits
-
-
-
-
-- @field #number MaxAliveUnits
-
-
-
-
-- @field #number SpawnIndex
-
-
-
-
-- @field #number MaxAliveGroups
-
-
-
-
-- @field #SPAWN.SpawnZoneTable SpawnZoneTable
-
-
-
-
-- @extends Core.Base#BASE
-
-
-
-
-
-
-
-
-
-
-
-
-
-
--- # SPAWN class, extends @{Base#BASE}
-
-
-
-
--
-
-
-
-
-- The SPAWN class allows to spawn dynamically new groups.
-
-
-
-
-- Each SPAWN object needs to be have a related **template group** setup in the Mission Editor (ME),
-
-
-
-
-- which is a normal group with the **Late Activation** flag set.
-
-
-
-
-- This template group will never be activated in your mission.
-
-
-
-
-- SPAWN uses that **template group** to reference to all the characteristics
-
-
-
-
-- (air, ground, livery, unit composition, formation, skill level etc) of each new group to be spawned.
-
-
-
-
--
-
-
-
-
-- Therefore, when creating a SPAWN object, the @{#SPAWN.New} and @{#SPAWN.NewWithAlias} require
-
-
-
-
-- **the name of the template group** to be given as a string to those constructor methods.
-
-
-
-
--
-
-
-
-
-- Initialization settings can be applied on the SPAWN object,
-
-
-
-
-- which modify the behaviour or the way groups are spawned.
-
-
-
-
-- These initialization methods have the prefix **Init**.
-
-
-
-
-- There are also spawn methods with the prefix **Spawn** and will spawn new groups in various ways.
-
-
-
-
--
-
-
-
-
-- ### IMPORTANT! The methods with prefix **Init** must be used before any methods with prefix **Spawn** method are used, or unexpected results may appear!!!
-
-
-
-
--
-
-
-
-
-- Because SPAWN can spawn multiple groups of a template group,
-
-
-
-
-- SPAWN has an **internal index** that keeps track
-
-
-
-
-- which was the latest group that was spawned.
-
-
-
-
--
-
-
-
-
-- **Limits** can be set on how many groups can be spawn in each SPAWN object,
-
-
-
-
-- using the method @{#SPAWN.InitLimit}. SPAWN has 2 kind of limits:
-
-
-
-
--
-
-
-
-
-- * The maximum amount of @{Unit}s that can be **alive** at the same time...
-
-
-
-
-- * The maximum amount of @{Group}s that can be **spawned**... This is more of a **resource**-type of limit.
-
-
-
-
--
-
-
-
-
-- When new groups get spawned using the **Spawn** methods,
-
-
-
-
-- it will be evaluated whether any limits have been reached.
-
-
-
-
-- When no spawn limit is reached, a new group will be created by the spawning methods,
-
-
-
-
-- and the internal index will be increased with 1.
-
-
-
-
--
-
-
-
-
-- These limits ensure that your mission does not accidentally get flooded with spawned groups.
-
-
-
-
-- Additionally, it also guarantees that independent of the group composition,
-
-
-
-
-- at any time, the most optimal amount of groups are alive in your mission.
-
-
-
-
-- For example, if your template group has a group composition of 10 units, and you specify a limit of 100 units alive at the same time,
-
-
-
-
-- with unlimited resources = :InitLimit( 100, 0 ) and 10 groups are alive, but two groups have only one unit alive in the group,
-
-
-
-
-- then a sequent Spawn(Scheduled) will allow a new group to be spawned!!!
-
-
-
-
--
-
-
-
-
-- ### IMPORTANT!! If a limit has been reached, it is possible that a **Spawn** method returns **nil**, meaning, no @{Group} had been spawned!!!
-
-
-
-
--
-
-
-
-
-- Spawned groups get **the same name** as the name of the template group.
-
-
-
-
-- Spawned units in those groups keep _by default_ **the same name** as the name of the template group.
-
-
-
-
-- However, because multiple groups and units are created from the template group,
-
-
-
-
-- a suffix is added to each spawned group and unit.
-
-
-
-
--
-
-
-
-
-- Newly spawned groups will get the following naming structure at run-time:
-
-
-
-
--
-
-
-
-
-- 1. Spawned groups will have the name _GroupName_#_nnn_, where _GroupName_ is the name of the **template group**,
-
-
-
-
-- and _nnn_ is a **counter from 0 to 999**.
-
-
-
-
-- 2. Spawned units will have the name _GroupName_#_nnn_-_uu_,
-
-
-
-
-- where _uu_ is a **counter from 0 to 99** for each new spawned unit belonging to the group.
-
-
-
-
--
-
-
-
-
-- That being said, there is a way to keep the same unit names!
-
-
-
-
-- The method @{#SPAWN.InitKeepUnitNames}() will keep the same unit names as defined within the template group, thus:
-
-
-
-
--
-
-
-
-
-- 3. Spawned units will have the name _UnitName_#_nnn_-_uu_,
-
-
-
-
-- where _UnitName_ is the **unit name as defined in the template group*,
-
-
-
-
-- and _uu_ is a **counter from 0 to 99** for each new spawned unit belonging to the group.
-
-
-
-
--
-
-
-
-
-- Some **additional notes that need to be considered!!**:
-
-
-
-
--
-
-
-
-
-- * templates are actually groups defined within the mission editor, with the flag "Late Activation" set.
-
-
-
-
-- As such, these groups are never used within the mission, but are used by the @{#SPAWN} module.
-
-
-
-
-- * It is important to defined BEFORE you spawn new groups,
-
-
-
-
-- a proper initialization of the SPAWN instance is done with the options you want to use.
-
-
-
-
-- * When designing a mission, NEVER name groups using a "#" within the name of the group Spawn template(s),
-
-
-
-
-- or the SPAWN module logic won't work anymore.
-
-
-
-
--
-
-
-
-
-- ## SPAWN construction methods
-
-
-
-
--
-
-
-
-
-- Create a new SPAWN object with the @{#SPAWN.New}() or the @{#SPAWN.NewWithAlias}() methods:
-
-
-
-
--
-
-
-
-
-- * @{#SPAWN.New}(): Creates a new SPAWN object taking the name of the group that represents the GROUP template (definition).
-
-
-
-
-- * @{#SPAWN.NewWithAlias}(): Creates a new SPAWN object taking the name of the group that represents the GROUP template (definition), and gives each spawned @{Group} an different name.
-
-
-
-
--
-
-
-
-
-- It is important to understand how the SPAWN class works internally. The SPAWN object created will contain internally a list of groups that will be spawned and that are already spawned.
-
-
-
-
-- The initialization methods will modify this list of groups so that when a group gets spawned, ALL information is already prepared when spawning. This is done for performance reasons.
-
-
-
-
-- So in principle, the group list will contain all parameters and configurations after initialization, and when groups get actually spawned, this spawning can be done quickly and efficient.
-
-
-
-
--
-
-
-
-
-- ## SPAWN **Init**ialization methods
-
-
-
-
--
-
-
-
-
-- A spawn object will behave differently based on the usage of **initialization** methods, which all start with the **Init** prefix:
-
-
-
-
--
-
-
-
-
-- ### Unit Names
-
-
-
-
--
-
-
-
-
-- * @{#SPAWN.InitKeepUnitNames}(): Keeps the unit names as defined within the mission editor, but note that anything after a # mark is ignored, and any spaces before and after the resulting name are removed. IMPORTANT! This method MUST be the first used after :New !!!
-
-
-
-
--
-
-
-
-
-- ### Route randomization
-
-
-
-
--
-
-
-
-
-- * @{#SPAWN.InitRandomizeRoute}(): Randomize the routes of spawned groups, and for air groups also optionally the height.
-
-
-
-
--
-
-
-
-
-- ### Group composition randomization
-
-
-
-
--
-
-
-
-
-- * @{#SPAWN.InitRandomizeTemplate}(): Randomize the group templates so that when a new group is spawned, a random group template is selected from one of the templates defined.
-
-
-
-
--
-
-
-
-
-- ### Uncontrolled
-
-
-
-
--
-
-
-
-
-- * @{#SPAWN.InitUnControlled}(): Spawn plane groups uncontrolled.
-
-
-
-
--
-
-
-
-
-- ### Array formation
-
-
-
-
--
-
-
-
-
-- * @{#SPAWN.InitArray}(): Make groups visible before they are actually activated, and order these groups like a batallion in an array.
-
-
-
-
--
-
-
-
-
-- ### Position randomization
-
-
-
-
--
-
-
-
-
-- * @{#SPAWN.InitRandomizePosition}(): Randomizes the position of @{Group}s that are spawned within a **radius band**, given an Outer and Inner radius, from the point that the spawn happens.
-
-
-
-
-- * @{#SPAWN.InitRandomizeUnits}(): Randomizes the @{Unit}s in the @{Group} that is spawned within a **radius band**, given an Outer and Inner radius.
-
-
-
-
-- * @{#SPAWN.InitRandomizeZones}(): Randomizes the spawning between a predefined list of @{Zone}s that are declared using this function. Each zone can be given a probability factor.
-
-
-
-
--
-
-
-
-
-- ### Enable / Disable AI when spawning a new @{Group}
-
-
-
-
--
-
-
-
-
-- * @{#SPAWN.InitAIOn}(): Turns the AI On when spawning the new @{Group} object.
-
-
-
-
-- * @{#SPAWN.InitAIOff}(): Turns the AI Off when spawning the new @{Group} object.
-
-
-
-
-- * @{#SPAWN.InitAIOnOff}(): Turns the AI On or Off when spawning the new @{Group} object.
-
-
-
-
--
-
-
-
-
-- ### Limit scheduled spawning
-
-
-
-
--
-
-
-
-
-- * @{#SPAWN.InitLimit}(): Limits the amount of groups that can be alive at the same time and that can be dynamically spawned.
-
-
-
-
--
-
-
-
-
-- ### Delay initial scheduled spawn
-
-
-
-
--
-
-
-
-
-- * @{#SPAWN.InitDelayOnOff}(): Turns the inital delay On/Off when scheduled spawning the first @{Group} object.
-
-
-
-
-- * @{#SPAWN.InitDelayOn}(): Turns the inital delay On when scheduled spawning the first @{Group} object.
-
-
-
-
-- * @{#SPAWN.InitDelayOff}(): Turns the inital delay Off when scheduled spawning the first @{Group} object.
-
-
-
-
--
-
-
-
-
-- ### Repeat spawned @{Group}s upon landing
-
-
-
-
--
-
-
-
-
-- * @{#SPAWN.InitRepeat}() or @{#SPAWN.InitRepeatOnLanding}(): This method is used to re-spawn automatically the same group after it has landed.
-
-
-
-
-- * @{#SPAWN.InitRepeatOnEngineShutDown}(): This method is used to re-spawn automatically the same group after it has landed and it shuts down the engines at the ramp.
-
-
-
-
--
-
-
-
-
--
-
-
-
-
-- ## SPAWN **Spawn** methods
-
-
-
-
--
-
-
-
-
-- Groups can be spawned at different times and methods:
-
-
-
-
--
-
-
-
-
-- ### **Single** spawning methods
-
-
-
-
--
-
-
-
-
-- * @{#SPAWN.Spawn}(): Spawn one new group based on the last spawned index.
-
-
-
-
-- * @{#SPAWN.ReSpawn}(): Re-spawn a group based on a given index.
-
-
-
-
-- * @{#SPAWN.SpawnFromVec3}(): Spawn a new group from a Vec3 coordinate. (The group will can be spawned at a point in the air).
-
-
-
-
-- * @{#SPAWN.SpawnFromVec2}(): Spawn a new group from a Vec2 coordinate. (The group will be spawned at land height ).
-
-
-
-
-- * @{#SPAWN.SpawnFromStatic}(): Spawn a new group from a structure, taking the position of a @{Static}.
-
-
-
-
-- * @{#SPAWN.SpawnFromUnit}(): Spawn a new group taking the position of a @{Unit}.
-
-
-
-
-- * @{#SPAWN.SpawnInZone}(): Spawn a new group in a @{Zone}.
-
-
-
-
--
-
-
-
-
-- Note that @{#SPAWN.Spawn} and @{#SPAWN.ReSpawn} return a @{GROUP#GROUP.New} object, that contains a reference to the DCSGroup object.
-
-
-
-
-- You can use the @{GROUP} object to do further actions with the DCSGroup.
-
-
-
-
--
-
-
-
-
-- ### **Scheduled** spawning methods
-
-
-
-
--
-
-
-
-
-- * @{#SPAWN.SpawnScheduled}(): Spawn groups at scheduled but randomized intervals.
-
-
-
-
-- * @{#SPAWN.SpawnScheduledStart}(): Start or continue to spawn groups at scheduled time intervals.
-
-
-
-
-- * @{#SPAWN.SpawnScheduledStop}(): Stop the spawning of groups at scheduled time intervals.
-
-
-
-
--
-
-
-
-
--
-
-
-
-
--
-
-
-
-
-- ## Retrieve alive GROUPs spawned by the SPAWN object
-
-
-
-
--
-
-
-
-
-- The SPAWN class administers which GROUPS it has reserved (in stock) or has created during mission execution.
-
-
-
-
-- Every time a SPAWN object spawns a new GROUP object, a reference to the GROUP object is added to an internal table of GROUPS.
-
-
-
-
-- SPAWN provides methods to iterate through that internal GROUP object reference table:
-
-
-
-
--
-
-
-
-
-- * @{#SPAWN.GetFirstAliveGroup}(): Will find the first alive GROUP it has spawned, and return the alive GROUP object and the first Index where the first alive GROUP object has been found.
-
-
-
-
-- * @{#SPAWN.GetNextAliveGroup}(): Will find the next alive GROUP object from a given Index, and return a reference to the alive GROUP object and the next Index where the alive GROUP has been found.
-
-
-
-
-- * @{#SPAWN.GetLastAliveGroup}(): Will find the last alive GROUP object, and will return a reference to the last live GROUP object and the last Index where the last alive GROUP object has been found.
-
-
-
-
--
-
-
-
-
-- You can use the methods @{#SPAWN.GetFirstAliveGroup}() and sequently @{#SPAWN.GetNextAliveGroup}() to iterate through the alive GROUPS within the SPAWN object, and to actions... See the respective methods for an example.
-
-
-
-
-- The method @{#SPAWN.GetGroupFromIndex}() will return the GROUP object reference from the given Index, dead or alive...
-
-
-
-
--
-
-
-
-
-- ## Spawned cleaning of inactive groups
-
-
-
-
--
-
-
-
-
-- Sometimes, it will occur during a mission run-time, that ground or especially air objects get damaged, and will while being damged stop their activities, while remaining alive.
-
-
-
-
-- In such cases, the SPAWN object will just sit there and wait until that group gets destroyed, but most of the time it won't,
-
-
-
-
-- and it may occur that no new groups are or can be spawned as limits are reached.
-
-
-
-
-- To prevent this, a @{#SPAWN.InitCleanUp}() initialization method has been defined that will silently monitor the status of each spawned group.
-
-
-
-
-- Once a group has a velocity = 0, and has been waiting for a defined interval, that group will be cleaned or removed from run-time.
-
-
-
-
-- There is a catch however :-) If a damaged group has returned to an airbase within the coalition, that group will not be considered as "lost"...
-
-
-
-
-- In such a case, when the inactive group is cleaned, a new group will Re-spawned automatically.
-
-
-
-
-- This models AI that has succesfully returned to their airbase, to restart their combat activities.
-
-
-
-
-- Check the @{#SPAWN.InitCleanUp}() for further info.
-
-
-
-
--
-
-
-
-
-- ## Catch the @{Group} Spawn Event in a callback function!
-
-
-
-
--
-
-
-
-
-- When using the @{#SPAWN.SpawnScheduled)() method, new @{Group}s are created following the spawn time interval parameters.
-
-
-
-
-- When a new @{Group} is spawned, you maybe want to execute actions with that group spawned at the spawn event.
-
-
-
-
-- The SPAWN class supports this functionality through the method @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ),
-
-
-
-
-- which takes a function as a parameter that you can define locally.
-
-
-
-
-- Whenever a new @{Group} is spawned, the given function is called, and the @{Group} that was just spawned, is given as a parameter.
-
-
-
-
-- As a result, your spawn event handling function requires one parameter to be declared, which will contain the spawned @{Group} object.
-
-
-
-
-- A coding example is provided at the description of the @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ) method.
-
-
-
-
--
-
-
-
-
-- ## Delay the initial spawning
-
-
-
-
--
-
-
-
-
-- When using the @{#SPAWN.SpawnScheduled)() method, the default behaviour of this method will be that it will spawn the initial (first) @{Group}
-
-
-
-
-- immediately when :SpawnScheduled() is initiated. The methods @{#SPAWN.InitDelayOnOff}() and @{#SPAWN.InitDelayOn}() can be used to
-
-
-
-
-- activate a delay before the first @{Group} is spawned. For completeness, a method @{#SPAWN.InitDelayOff}() is also available, that
-
-
-
-
-- can be used to switch off the initial delay. Because there is no delay by default, this method would only be used when a
-
-
-
-
-- @{#SPAWN.SpawnScheduledStop}() ; @{#SPAWN.SpawnScheduledStart}() sequence would have been used.
-
-
-
-
--
-
-
-
-
--
-
-
-
-
-- @field #SPAWN SPAWN
-
-
-
-
--
-
-
-
-
SPAWN = {
-
-
-
-
ClassName ="SPAWN",
-
-
-
-
SpawnTemplatePrefix =nil,
-
-
-
-
SpawnAliasPrefix =nil,
-
-
-
-
}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
--- Enumerator for spawns at airbases
-
-
-
-
-- @type SPAWN.Takeoff
-
-
-
-
-- @extends Wrapper.Group#GROUP.Takeoff
-
-
-
-
-
-
-
-
-
--- @field #SPAWN.Takeoff Takeoff
-
-
-
-
SPAWN.Takeoff= GROUP.Takeoff
-
-
-
-
-
-
-
-
-
-
-
-
-
-
--- @type SPAWN.SpawnZoneTable
-
-
-
-
-- @list <Core.Zone#ZONE_BASE> SpawnZone
-
-
-
-
-
-
-
-
-
-
-
-
-
-
--- Creates the main object to spawn a @{Group} defined in the DCS ME.
-
-
-
-
-- @param #SPAWN self
-
-
-
-
-- @param #string SpawnTemplatePrefix is the name of the Group in the ME that defines the Template. Each new group will have the name starting with SpawnTemplatePrefix.
-
-
-
-
-- @return #SPAWN
-
-
-
-
-- @usage
-
-
-
-
-- -- NATO helicopters engaging in the battle field.
-- @usage local Plane = SPAWN:New( "Plane" ) -- Creates a new local variable that can initiate new planes with the name "Plane#ddd" using the template "Plane" as defined within the ME.
local TemplateGroup = Group.getByName( SpawnTemplatePrefix )
-
-
-
-
if TemplateGroup then
-
-
-
-
self.SpawnTemplatePrefix= SpawnTemplatePrefix
-
-
-
-
self.SpawnIndex=0
-
-
-
-
self.SpawnCount=0-- The internal counter of the amount of spawning the has happened since SpawnStart.
-
-
-
-
self.AliveUnits=0-- Contains the counter how many units are currently alive
-
-
-
-
self.SpawnIsScheduled=false-- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not.
-
-
-
-
self.SpawnTemplate=self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!!
-
-
-
-
self.Repeat=false-- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning.
-
-
-
-
self.UnControlled=false-- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts.
-
-
-
-
self.SpawnInitLimit=false-- By default, no InitLimit
-
-
-
-
self.SpawnMaxUnitsAlive=0-- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time.
-
-
-
-
self.SpawnMaxGroups=0-- The maximum amount of groups that can be spawned.
-
-
-
-
self.SpawnRandomize=false-- Sets the randomization flag of new Spawned units to false.
-
-
-
-
self.SpawnVisible=false-- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned.
-
-
-
-
self.AIOnOff=true-- The AI is on by default when spawning a group.
-
-
-
-
self.SpawnUnControlled=false
-
-
-
-
self.SpawnInitKeepUnitNames=false-- Overwrite unit names by default with group name.
-
-
-
-
self.DelayOnOff=false-- No intial delay when spawning the first group.
-
-
-
-
self.Grouping=nil-- No grouping
-
-
-
-
-
-
-
-
-
self.SpawnGroups= {} -- Array containing the descriptions of each Group to be Spawned.
-
-
-
-
else
-
-
-
-
error( "SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '".. SpawnTemplatePrefix .."'" )
-
-
-
-
end
-
-
-
-
-
-
-
-
-
self:SetEventPriority( 5 )
-
-
-
-
-
-
-
-
-
returnself
-
-
-
-
end
-
-
-
-
-
-
-
-
-
--- Creates a new SPAWN instance to create new groups based on the defined template and using a new alias for each new group.
-
-
-
-
-- @param #SPAWN self
-
-
-
-
-- @param #string SpawnTemplatePrefix is the name of the Group in the ME that defines the Template.
-
-
-
-
-- @param #string SpawnAliasPrefix is the name that will be given to the Group at runtime.
-
-
-
-
-- @return #SPAWN
-
-
-
-
-- @usage
-
-
-
-
-- -- NATO helicopters engaging in the battle field.
-- @usage local PlaneWithAlias = SPAWN:NewWithAlias( "Plane", "Bomber" ) -- Creates a new local variable that can instantiate new planes with the name "Bomber#ddd" using the template "Plane" as defined within the ME.
-
-
-
-
function SPAWN:NewWithAlias( SpawnTemplatePrefix, SpawnAliasPrefix )
local TemplateGroup = Group.getByName( SpawnTemplatePrefix )
-
-
-
-
if TemplateGroup then
-
-
-
-
self.SpawnTemplatePrefix= SpawnTemplatePrefix
-
-
-
-
self.SpawnAliasPrefix= SpawnAliasPrefix
-
-
-
-
self.SpawnIndex=0
-
-
-
-
self.SpawnCount=0-- The internal counter of the amount of spawning the has happened since SpawnStart.
-
-
-
-
self.AliveUnits=0-- Contains the counter how many units are currently alive
-
-
-
-
self.SpawnIsScheduled=false-- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not.
-
-
-
-
self.SpawnTemplate=self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!!
-
-
-
-
self.Repeat=false-- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning.
-
-
-
-
self.UnControlled=false-- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts.
-
-
-
-
self.SpawnInitLimit=false-- By default, no InitLimit
-
-
-
-
self.SpawnMaxUnitsAlive=0-- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time.
-
-
-
-
self.SpawnMaxGroups=0-- The maximum amount of groups that can be spawned.
-
-
-
-
self.SpawnRandomize=false-- Sets the randomization flag of new Spawned units to false.
-
-
-
-
self.SpawnVisible=false-- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned.
-
-
-
-
self.AIOnOff=true-- The AI is on by default when spawning a group.
-
-
-
-
self.SpawnUnControlled=false
-
-
-
-
self.SpawnInitKeepUnitNames=false-- Overwrite unit names by default with group name.
-
-
-
-
self.DelayOnOff=false-- No intial delay when spawning the first group.
-
-
-
-
self.Grouping=nil
-
-
-
-
-
-
-
-
-
self.SpawnGroups= {} -- Array containing the descriptions of each Group to be Spawned.
-
-
-
-
else
-
-
-
-
error( "SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '".. SpawnTemplatePrefix .."'" )
-
-
-
-
end
-
-
-
-
-
-
-
-
self:SetEventPriority( 5 )
-
-
-
-
-
-
-
-
returnself
-
-
-
-
end
-
-
-
-
-
-
-
-
-
-
-
-
-
-
--- Limits the Maximum amount of Units that can be alive at the same time, and the maximum amount of groups that can be spawned.
-
-
-
-
-- Note that this method is exceptionally important to balance the performance of the mission. Depending on the machine etc, a mission can only process a maximum amount of units.
-
-
-
-
-- If the time interval must be short, but there should not be more Units or Groups alive than a maximum amount of units, then this method should be used...
-
-
-
-
-- When a @{#SPAWN.New} is executed and the limit of the amount of units alive is reached, then no new spawn will happen of the group, until some of these units of the spawn object will be destroyed.
-
-
-
-
-- @param #SPAWN self
-
-
-
-
-- @param #number SpawnMaxUnitsAlive The maximum amount of units that can be alive at runtime.
-
-
-
-
-- @param #number SpawnMaxGroups The maximum amount of groups that can be spawned. When the limit is reached, then no more actual spawns will happen of the group.
-
-
-
-
-- This parameter is useful to define a maximum amount of airplanes, ground troops, helicopters, ships etc within a supply area.
-
-
-
-
-- This parameter accepts the value 0, which defines that there are no maximum group limits, but there are limits on the maximum of units that can be alive at the same time.
-
-
-
-
-- @return #SPAWN self
-
-
-
-
-- @usage
-
-
-
-
-- -- NATO helicopters engaging in the battle field.
-
-
-
-
-- -- This helicopter group consists of one Unit. So, this group will SPAWN maximum 2 groups simultaneously within the DCSRTE.
-
-
-
-
-- -- There will be maximum 24 groups spawned during the whole mission lifetime.
self.SpawnMaxUnitsAlive= SpawnMaxUnitsAlive -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time.
-
-
-
-
self.SpawnMaxGroups= SpawnMaxGroups -- The maximum amount of groups that can be spawned.
-
-
-
-
-
-
-
-
for SpawnGroupID =1, self.SpawnMaxGroupsdo
-
-
-
-
self:_InitializeSpawnGroups( SpawnGroupID )
-
-
-
-
end
-
-
-
-
-
-
-
-
-
returnself
-
-
-
-
end
-
-
-
-
-
-
-
-
-
--- Keeps the unit names as defined within the mission editor,
-
-
-
-
-- but note that anything after a # mark is ignored,
-
-
-
-
-- and any spaces before and after the resulting name are removed.
-
-
-
-
-- IMPORTANT! This method MUST be the first used after :New !!!
-
-
-
-
-- @param #SPAWN self
-
-
-
-
-- @return #SPAWN self
-
-
-
-
function SPAWN:InitKeepUnitNames()
-
-
-
-
self:F( )
-
-
-
-
-
-
-
-
-
self.SpawnInitKeepUnitNames=true
-
-
-
-
-
-
-
-
returnself
-
-
-
-
end
-
-
-
-
-
-
-
-
-
-
-
-
-
-
--- Randomizes the defined route of the SpawnTemplatePrefix group in the ME. This is very useful to define extra variation of the behaviour of groups.
-
-
-
-
-- @param #SPAWN self
-
-
-
-
-- @param #number SpawnStartPoint is the waypoint where the randomization begins.
-
-
-
-
-- Note that the StartPoint = 0 equaling the point where the group is spawned.
-
-
-
-
-- @param #number SpawnEndPoint is the waypoint where the randomization ends counting backwards.
-
-
-
-
-- This parameter is useful to avoid randomization to end at a waypoint earlier than the last waypoint on the route.
-
-
-
-
-- @param #number SpawnRadius is the radius in meters in which the randomization of the new waypoints, with the original waypoint of the original template located in the middle ...
-
-
-
-
-- @param #number SpawnHeight (optional) Specifies the **additional** height in meters that can be added to the base height specified at each waypoint in the ME.
-
-
-
-
-- @return #SPAWN
-
-
-
-
-- @usage
-
-
-
-
-- -- NATO helicopters engaging in the battle field.
-
-
-
-
-- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP).
-
-
-
-
-- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter.
-
-
-
-
-- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters.
--- Randomizes the position of @{Group}s that are spawned within a **radius band**, given an Outer and Inner radius, from the point that the spawn happens.
-
-
-
-
-- @param #SPAWN self
-
-
-
-
-- @param #boolean RandomizePosition If true, SPAWN will perform the randomization of the @{Group}s position between a given outer and inner radius.
-
-
-
-
-- @param Dcs.DCSTypes#Distance OuterRadius (optional) The outer radius in meters where the new group will be spawned.
-
-
-
-
-- @param Dcs.DCSTypes#Distance InnerRadius (optional) The inner radius in meters where the new group will NOT be spawned.
-
-
-
-
-- @return #SPAWN
-
-
-
-
function SPAWN:InitRandomizePosition( RandomizePosition, OuterRadius, InnerRadius )
--- Randomizes the UNITs that are spawned within a radius band given an Outer and Inner radius.
-
-
-
-
-- @param #SPAWN self
-
-
-
-
-- @param #boolean RandomizeUnits If true, SPAWN will perform the randomization of the @{UNIT}s position within the group between a given outer and inner radius.
-
-
-
-
-- @param Dcs.DCSTypes#Distance OuterRadius (optional) The outer radius in meters where the new group will be spawned.
-
-
-
-
-- @param Dcs.DCSTypes#Distance InnerRadius (optional) The inner radius in meters where the new group will NOT be spawned.
-
-
-
-
-- @return #SPAWN
-
-
-
-
-- @usage
-
-
-
-
-- -- NATO helicopters engaging in the battle field.
-
-
-
-
-- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP).
-
-
-
-
-- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter.
-
-
-
-
-- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters.
--- This method is rather complicated to understand. But I'll try to explain.
-
-
-
-
-- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor,
-
-
-
-
-- but they will all follow the same Template route and have the same prefix name.
-
-
-
-
-- In other words, this method randomizes between a defined set of groups the template to be used for each new spawn of a group.
-
-
-
-
-- @param #SPAWN self
-
-
-
-
-- @param #string SpawnTemplatePrefixTable A table with the names of the groups defined within the mission editor, from which one will be choosen when a new group will be spawned.
-
-
-
-
-- @return #SPAWN
-
-
-
-
-- @usage
-
-
-
-
-- -- NATO Tank Platoons invading Gori.
-
-
-
-
-- -- Choose between 13 different 'US Tank Platoon' configurations for each new SPAWN the Group to be spawned for the
-
-
-
-
-- -- 'US Tank Platoon Left', 'US Tank Platoon Middle' and 'US Tank Platoon Right' SpawnTemplatePrefixes.
-
-
-
-
-- -- Each new SPAWN will randomize the route, with a defined time interval of 200 seconds with 40% time variation (randomization) and
-
-
-
-
-- -- with a limit set of maximum 12 Units alive simulteneously and 150 Groups to be spawned during the whole mission.
-
-
-
-
-- Spawn_US_Platoon = { 'US Tank Platoon 1', 'US Tank Platoon 2', 'US Tank Platoon 3', 'US Tank Platoon 4', 'US Tank Platoon 5',
-
-
-
-
-- 'US Tank Platoon 6', 'US Tank Platoon 7', 'US Tank Platoon 8', 'US Tank Platoon 9', 'US Tank Platoon 10',
-
-
-
-
-- 'US Tank Platoon 11', 'US Tank Platoon 12', 'US Tank Platoon 13' }
--- When spawning a new group, make the grouping of the units according the InitGrouping setting.
-
-
-
-
-- @param #SPAWN self
-
-
-
-
-- @param #number Grouping Indicates the maximum amount of units in the group.
-
-
-
-
-- @return #SPAWN
-
-
-
-
function SPAWN:InitGrouping( Grouping ) -- R2.2
-
-
-
-
self:F( { self.SpawnTemplatePrefix, Grouping } )
-
-
-
-
-
-
-
-
-
self.SpawnGrouping= Grouping
-
-
-
-
-
-
-
-
-
returnself
-
-
-
-
end
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
--TODO: Add example.
-
-
-
-
--- This method provides the functionality to randomize the spawning of the Groups at a given list of zones of different types.
-
-
-
-
-- @param #SPAWN self
-
-
-
-
-- @param #table SpawnZoneTable A table with @{Zone} objects. If this table is given, then each spawn will be executed within the given list of @{Zone}s objects.
-
-
-
-
-- @return #SPAWN
-
-
-
-
-- @usage
-
-
-
-
-- -- NATO Tank Platoons invading Gori.
-
-
-
-
-- -- Choose between 3 different zones for each new SPAWN the Group to be executed, regardless of the zone type.
-
-
-
-
function SPAWN:InitRandomizeZones( SpawnZoneTable )
--- For planes and helicopters, when these groups go home and land on their home airbases and farps, they normally would taxi to the parking spot, shut-down their engines and wait forever until the Group is removed by the runtime environment.
-
-
-
-
-- This method is used to re-spawn automatically (so no extra call is needed anymore) the same group after it has landed.
-
-
-
-
-- This will enable a spawned group to be re-spawned after it lands, until it is destroyed...
-
-
-
-
-- Note: When the group is respawned, it will re-spawn from the original airbase where it took off.
-
-
-
-
-- So ensure that the routes for groups that respawn, always return to the original airbase, or players may get confused ...
-
-
-
-
-- @param #SPAWN self
-
-
-
-
-- @return #SPAWN self
-
-
-
-
-- @usage
-
-
-
-
-- -- RU Su-34 - AI Ship Attack
-
-
-
-
-- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically.
--- Respawn after landing when its engines have shut down.
-
-
-
-
-- @param #SPAWN self
-
-
-
-
-- @return #SPAWN self
-
-
-
-
function SPAWN:InitRepeatOnEngineShutDown()
-
-
-
-
self:F( { self.SpawnTemplatePrefix } )
-
-
-
-
-
-
-
-
-
self:InitRepeat()
-
-
-
-
self.RepeatOnEngineShutDown=true
-
-
-
-
self.RepeatOnLanding=false
-
-
-
-
-
-
-
-
returnself
-
-
-
-
end
-
-
-
-
-
-
-
-
-
-
-
-
-
-
--- CleanUp groups when they are still alive, but inactive.
-
-
-
-
-- When groups are still alive and have become inactive due to damage and are unable to contribute anything, then this group will be removed at defined intervals in seconds.
-
-
-
-
-- @param #SPAWN self
-
-
-
-
-- @param #string SpawnCleanUpInterval The interval to check for inactive groups within seconds.
-
-
-
-
-- @return #SPAWN self
-
-
-
-
-- @usage Spawn_Helicopter:CleanUp( 20 ) -- CleanUp the spawning of the helicopters every 20 seconds when they become inactive.
-
-
-
-
function SPAWN:InitCleanUp( SpawnCleanUpInterval )
--- Will spawn a group from a hosting unit. This method is mostly advisable to be used if you want to simulate spawning from air units, like helicopters, which are dropping infantry into a defined Landing Zone.
-
-
-
-
-- Note that each point in the route assigned to the spawning group is reset to the point of the spawn.
-
-
-
-
-- You can use the returned group to further define the route to be followed.
-
-
-
-
-- @param #SPAWN self
-
-
-
-
-- @param Wrapper.Unit#UNIT HostUnit The air or ground unit dropping or unloading the group.
-
-
-
-
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
-
-
-
-
-- @return Wrapper.Group#GROUP that was spawned.
-
-
-
-
-- @return #nil Nothing was spawned.
-
-
-
-
function SPAWN:SpawnFromUnit( HostUnit, SpawnIndex )
--- Will spawn a group from a hosting static. This method is mostly advisable to be used if you want to simulate spawning from buldings and structures (static buildings).
-
-
-
-
-- You can use the returned group to further define the route to be followed.
-
-
-
-
-- @param #SPAWN self
-
-
-
-
-- @param Wrapper.Static#STATIC HostStatic The static dropping or unloading the group.
-
-
-
-
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
-
-
-
-
-- @return Wrapper.Group#GROUP that was spawned.
-
-
-
-
-- @return #nil Nothing was spawned.
-
-
-
-
function SPAWN:SpawnFromStatic( HostStatic, SpawnIndex )
local SpawnName =string.format( '%s#%03d', SpawnPrefix, SpawnIndex )
-
-
-
-
self:T( SpawnName )
-
-
-
-
return SpawnName
-
-
-
-
else
-
-
-
-
self:T( SpawnPrefix )
-
-
-
-
return SpawnPrefix
-
-
-
-
end
-
-
-
-
-
-
-
-
end
-
-
-
-
-
-
-
-
-
--- Will find the first alive @{Group} it has spawned, and return the alive @{Group} object and the first Index where the first alive @{Group} object has been found.
-
-
-
-
-- @param #SPAWN self
-
-
-
-
-- @return Wrapper.Group#GROUP, #number The @{Group} object found, the new Index where the group was found.
-
-
-
-
-- @return #nil, #nil When no group is found, #nil is returned.
-
-
-
-
-- @usage
-
-
-
-
-- -- Find the first alive @{Group} object of the SpawnPlanes SPAWN object @{Group} collection that it has spawned during the mission.
-
-
-
-
-- local GroupPlane, Index = SpawnPlanes:GetFirstAliveGroup()
-
-
-
-
-- while GroupPlane ~= nil do
-
-
-
-
-- -- Do actions with the GroupPlane object.
-
-
-
-
-- GroupPlane, Index = SpawnPlanes:GetNextAliveGroup( Index )
local SpawnGroup =self:GetGroupFromIndex( SpawnIndex )
-
-
-
-
if SpawnGroup and SpawnGroup:IsAlive() then
-
-
-
-
return SpawnGroup, SpawnIndex
-
-
-
-
end
-
-
-
-
end
-
-
-
-
-
-
-
-
returnnil, nil
-
-
-
-
end
-
-
-
-
-
-
-
-
-
-
-
-
-
-
--- Will find the next alive @{Group} object from a given Index, and return a reference to the alive @{Group} object and the next Index where the alive @{Group} has been found.
-
-
-
-
-- @param #SPAWN self
-
-
-
-
-- @param #number SpawnIndexStart A Index holding the start position to search from. This method can also be used to find the first alive @{Group} object from the given Index.
-
-
-
-
-- @return Wrapper.Group#GROUP, #number The next alive @{Group} object found, the next Index where the next alive @{Group} object was found.
-
-
-
-
-- @return #nil, #nil When no alive @{Group} object is found from the start Index position, #nil is returned.
-
-
-
-
-- @usage
-
-
-
-
-- -- Find the first alive @{Group} object of the SpawnPlanes SPAWN object @{Group} collection that it has spawned during the mission.
-
-
-
-
-- local GroupPlane, Index = SpawnPlanes:GetFirstAliveGroup()
-
-
-
-
-- while GroupPlane ~= nil do
-
-
-
-
-- -- Do actions with the GroupPlane object.
-
-
-
-
-- GroupPlane, Index = SpawnPlanes:GetNextAliveGroup( Index )
-
-
-
-
-- end
-
-
-
-
function SPAWN:GetNextAliveGroup( SpawnIndexStart )
for SpawnIndex = SpawnIndexStart, self.SpawnCountdo
-
-
-
-
local SpawnGroup =self:GetGroupFromIndex( SpawnIndex )
-
-
-
-
if SpawnGroup and SpawnGroup:IsAlive() then
-
-
-
-
return SpawnGroup, SpawnIndex
-
-
-
-
end
-
-
-
-
end
-
-
-
-
-
-
-
-
returnnil, nil
-
-
-
-
end
-
-
-
-
-
-
-
-
-
--- Will find the last alive @{Group} object, and will return a reference to the last live @{Group} object and the last Index where the last alive @{Group} object has been found.
-
-
-
-
-- @param #SPAWN self
-
-
-
-
-- @return Wrapper.Group#GROUP, #number The last alive @{Group} object found, the last Index where the last alive @{Group} object was found.
-
-
-
-
-- @return #nil, #nil When no alive @{Group} object is found, #nil is returned.
-
-
-
-
-- @usage
-
-
-
-
-- -- Find the last alive @{Group} object of the SpawnPlanes SPAWN object @{Group} collection that it has spawned during the mission.
-
-
-
-
-- local GroupPlane, Index = SpawnPlanes:GetLastAliveGroup()
-
+ --TODO: Need to check if this function doesn't need to be scheduled, as the group may not be immediately there!
+ if SpawnGroup then
-
-
-
-
-
-
-
- You can't perform that action at this time.
-
-
+ SpawnGroup:SetAIOnOff( self.AIOnOff )
+ end
+ self:T3( SpawnTemplate.name )
+
+ -- If there is a SpawnFunction hook defined, call it.
+ if self.SpawnFunctionHook then
+ -- delay calling this for .1 seconds so that it hopefully comes after the BIRTH event of the group.
+ self.SpawnHookScheduler = SCHEDULER:New()
+ self.SpawnHookScheduler:Schedule( nil, self.SpawnFunctionHook, { self.SpawnGroups[self.SpawnIndex].Group, unpack( self.SpawnFunctionArguments)}, 0.1 )
+ -- self.SpawnFunctionHook( self.SpawnGroups[self.SpawnIndex].Group, unpack( self.SpawnFunctionArguments ) )
+ end
+ -- TODO: Need to fix this by putting an "R" in the name of the group when the group repeats.
+ --if self.Repeat then
+ -- _DATABASE:SetStatusGroup( SpawnTemplate.name, "ReSpawn" )
+ --end
+ end
-
-
-
-
-
-
-
-
-
- You signed in with another tab or window. Reload to refresh your session.
- You signed out in another tab or window. Reload to refresh your session.
-
-
-
-
-
-
-
-
+ self.SpawnGroups[self.SpawnIndex].Spawned = true
+ return self.SpawnGroups[self.SpawnIndex].Group
+ else
+ --self:E( { self.SpawnTemplatePrefix, "No more Groups to Spawn:", SpawnIndex, self.SpawnMaxGroups } )
+ end
+
+ return nil
+end
+
+--- Spawns new groups at varying time intervals.
+-- This is useful if you want to have continuity within your missions of certain (AI) groups to be present (alive) within your missions.
+-- @param #SPAWN self
+-- @param #number SpawnTime The time interval defined in seconds between each new spawn of new groups.
+-- @param #number SpawnTimeVariation The variation to be applied on the defined time interval between each new spawn.
+-- The variation is a number between 0 and 1, representing the %-tage of variation to be applied on the time interval.
+-- @return #SPAWN self
+-- @usage
+-- -- NATO helicopters engaging in the battle field.
+-- -- The time interval is set to SPAWN new helicopters between each 600 seconds, with a time variation of 50%.
+-- -- The time variation in this case will be between 450 seconds and 750 seconds.
+-- -- This is calculated as follows:
+-- -- Low limit: 600 * ( 1 - 0.5 / 2 ) = 450
+-- -- High limit: 600 * ( 1 + 0.5 / 2 ) = 750
+-- -- Between these two values, a random amount of seconds will be choosen for each new spawn of the helicopters.
+-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):Schedule( 600, 0.5 )
+function SPAWN:SpawnScheduled( SpawnTime, SpawnTimeVariation )
+ self:F( { SpawnTime, SpawnTimeVariation } )
+
+ if SpawnTime ~= nil and SpawnTimeVariation ~= nil then
+ local InitialDelay = 0
+ if self.DelayOnOff == true then
+ InitialDelay = math.random( SpawnTime - SpawnTime * SpawnTimeVariation, SpawnTime + SpawnTime * SpawnTimeVariation )
+ end
+ self.SpawnScheduler = SCHEDULER:New( self, self._Scheduler, {}, InitialDelay, SpawnTime, SpawnTimeVariation )
+ end
+
+ return self
+end
+
+--- Will re-start the spawning scheduler.
+-- Note: This method is only required to be called when the schedule was stopped.
+-- @param #SPAWN self
+-- @return #SPAWN
+function SPAWN:SpawnScheduleStart()
+ self:F( { self.SpawnTemplatePrefix } )
+
+ self.SpawnScheduler:Start()
+ return self
+end
+
+--- Will stop the scheduled spawning scheduler.
+-- @param #SPAWN self
+-- @return #SPAWN
+function SPAWN:SpawnScheduleStop()
+ self:F( { self.SpawnTemplatePrefix } )
+
+ self.SpawnScheduler:Stop()
+ return self
+end
-
-
+--- Allows to place a CallFunction hook when a new group spawns.
+-- The provided method will be called when a new group is spawned, including its given parameters.
+-- The first parameter of the SpawnFunction is the @{Group#GROUP} that was spawned.
+-- @param #SPAWN self
+-- @param #function SpawnCallBackFunction The function to be called when a group spawns.
+-- @param SpawnFunctionArguments A random amount of arguments to be provided to the function when the group spawns.
+-- @return #SPAWN
+-- @usage
+-- -- Declare SpawnObject and call a function when a new Group is spawned.
+-- local SpawnObject = SPAWN
+-- :New( "SpawnObject" )
+-- :InitLimit( 2, 10 )
+-- :OnSpawnGroup(
+-- function( SpawnGroup )
+-- SpawnGroup:E( "I am spawned" )
+-- end
+-- )
+-- :SpawnScheduled( 300, 0.3 )
+function SPAWN:OnSpawnGroup( SpawnCallBackFunction, ... )
+ self:F( "OnSpawnGroup" )
+ self.SpawnFunctionHook = SpawnCallBackFunction
+ self.SpawnFunctionArguments = {}
+ if arg then
+ self.SpawnFunctionArguments = arg
+ end
+
+ return self
+end
+
+--- Will spawn a group at an airbase.
+-- This method is mostly advisable to be used if you want to simulate spawning units at an airbase.
+-- Note that each point in the route assigned to the spawning group is reset to the point of the spawn.
+-- You can use the returned group to further define the route to be followed.
+-- @param #SPAWN self
+-- @param Wrapper.Airbase#AIRBASE Airbase The @{Airbase} where to spawn the group.
+-- @param #SPAWN.Takeoff Takeoff (optional) The location and takeoff method. Default is Hot.
+-- @param #number TakeoffAltitude (optional) The altitude above the ground.
+-- @return Wrapper.Group#GROUP that was spawned.
+-- @return #nil Nothing was spawned.
+function SPAWN:SpawnAtAirbase( Airbase, Takeoff, TakeoffAltitude ) -- R2.2
+ self:E( { self.SpawnTemplatePrefix, Airbase, Takeoff, TakeoffAltitude } )
+
+ local PointVec3 = Airbase:GetPointVec3()
+ self:T2(PointVec3)
+
+ Takeoff = Takeoff or SPAWN.Takeoff.Hot
+
+ if self:_GetSpawnIndex( self.SpawnIndex + 1 ) then
+
+ local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate
+
+ if SpawnTemplate then
+
+ self:T( { "Current point of ", self.SpawnTemplatePrefix, Airbase } )
+
+ -- Translate the position of the Group Template to the Vec3.
+ for UnitID = 1, #SpawnTemplate.units do
+ self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
+ local UnitTemplate = SpawnTemplate.units[UnitID]
+ local SX = UnitTemplate.x
+ local SY = UnitTemplate.y
+ local BX = SpawnTemplate.route.points[1].x
+ local BY = SpawnTemplate.route.points[1].y
+ local TX = PointVec3.x + ( SX - BX )
+ local TY = PointVec3.z + ( SY - BY )
+ SpawnTemplate.units[UnitID].x = TX
+ SpawnTemplate.units[UnitID].y = TY
+ if Takeoff == GROUP.Takeoff.Air then
+ SpawnTemplate.units[UnitID].alt = PointVec3.y + ( TakeoffAltitude or 200 )
+ else
+ SpawnTemplate.units[UnitID].alt = PointVec3.y + 10
+ end
+ self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
+ end
+
+ SpawnTemplate.route.points[1].x = PointVec3.x
+ SpawnTemplate.route.points[1].y = PointVec3.z
+ if Takeoff == GROUP.Takeoff.Air then
+ SpawnTemplate.route.points[1].alt = PointVec3.y + ( TakeoffAltitude or 200 )
+ else
+ SpawnTemplate.route.points[1].alt = PointVec3.y + 10
+ SpawnTemplate.route.points[1].airdromeId = Airbase:GetID()
+ end
+ SpawnTemplate.route.points[1].type = GROUPTEMPLATE.Takeoff[Takeoff]
+
+ SpawnTemplate.x = PointVec3.x
+ SpawnTemplate.y = PointVec3.z
+
+ return self:SpawnWithIndex( self.SpawnIndex )
+ end
+ end
+
+ return nil
+end
+
+
+
+--- Will spawn a group from a Vec3 in 3D space.
+-- This method is mostly advisable to be used if you want to simulate spawning units in the air, like helicopters or airplanes.
+-- Note that each point in the route assigned to the spawning group is reset to the point of the spawn.
+-- You can use the returned group to further define the route to be followed.
+-- @param #SPAWN self
+-- @param Dcs.DCSTypes#Vec3 Vec3 The Vec3 coordinates where to spawn the group.
+-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
+-- @return Wrapper.Group#GROUP that was spawned.
+-- @return #nil Nothing was spawned.
+function SPAWN:SpawnFromVec3( Vec3, SpawnIndex )
+ self:F( { self.SpawnTemplatePrefix, Vec3, SpawnIndex } )
+
+ local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 )
+ self:T2(PointVec3)
+
+ if SpawnIndex then
+ else
+ SpawnIndex = self.SpawnIndex + 1
+ end
+
+ if self:_GetSpawnIndex( SpawnIndex ) then
+
+ local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate
+
+ if SpawnTemplate then
+
+ self:T( { "Current point of ", self.SpawnTemplatePrefix, Vec3 } )
+
+ -- Translate the position of the Group Template to the Vec3.
+ for UnitID = 1, #SpawnTemplate.units do
+ self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
+ local UnitTemplate = SpawnTemplate.units[UnitID]
+ local SX = UnitTemplate.x
+ local SY = UnitTemplate.y
+ local BX = SpawnTemplate.route.points[1].x
+ local BY = SpawnTemplate.route.points[1].y
+ local TX = Vec3.x + ( SX - BX )
+ local TY = Vec3.z + ( SY - BY )
+ SpawnTemplate.units[UnitID].x = TX
+ SpawnTemplate.units[UnitID].y = TY
+ SpawnTemplate.units[UnitID].alt = Vec3.y
+ self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
+ end
+
+ SpawnTemplate.route.points[1].x = Vec3.x
+ SpawnTemplate.route.points[1].y = Vec3.z
+ SpawnTemplate.route.points[1].alt = Vec3.y
+
+ SpawnTemplate.x = Vec3.x
+ SpawnTemplate.y = Vec3.z
+
+ return self:SpawnWithIndex( self.SpawnIndex )
+ end
+ end
+
+ return nil
+end
+
+--- Will spawn a group from a Vec2 in 3D space.
+-- This method is mostly advisable to be used if you want to simulate spawning groups on the ground from air units, like vehicles.
+-- Note that each point in the route assigned to the spawning group is reset to the point of the spawn.
+-- You can use the returned group to further define the route to be followed.
+-- @param #SPAWN self
+-- @param Dcs.DCSTypes#Vec2 Vec2 The Vec2 coordinates where to spawn the group.
+-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
+-- @return Wrapper.Group#GROUP that was spawned.
+-- @return #nil Nothing was spawned.
+function SPAWN:SpawnFromVec2( Vec2, SpawnIndex )
+ self:F( { self.SpawnTemplatePrefix, Vec2, SpawnIndex } )
+
+ local PointVec2 = POINT_VEC2:NewFromVec2( Vec2 )
+ return self:SpawnFromVec3( PointVec2:GetVec3(), SpawnIndex )
+end
+
+
+--- Will spawn a group from a hosting unit. This method is mostly advisable to be used if you want to simulate spawning from air units, like helicopters, which are dropping infantry into a defined Landing Zone.
+-- Note that each point in the route assigned to the spawning group is reset to the point of the spawn.
+-- You can use the returned group to further define the route to be followed.
+-- @param #SPAWN self
+-- @param Wrapper.Unit#UNIT HostUnit The air or ground unit dropping or unloading the group.
+-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
+-- @return Wrapper.Group#GROUP that was spawned.
+-- @return #nil Nothing was spawned.
+function SPAWN:SpawnFromUnit( HostUnit, SpawnIndex )
+ self:F( { self.SpawnTemplatePrefix, HostUnit, SpawnIndex } )
+
+ if HostUnit and HostUnit:IsAlive() ~= nil then -- and HostUnit:getUnit(1):inAir() == false then
+ return self:SpawnFromVec3( HostUnit:GetVec3(), SpawnIndex )
+ end
+
+ return nil
+end
+
+--- Will spawn a group from a hosting static. This method is mostly advisable to be used if you want to simulate spawning from buldings and structures (static buildings).
+-- You can use the returned group to further define the route to be followed.
+-- @param #SPAWN self
+-- @param Wrapper.Static#STATIC HostStatic The static dropping or unloading the group.
+-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
+-- @return Wrapper.Group#GROUP that was spawned.
+-- @return #nil Nothing was spawned.
+function SPAWN:SpawnFromStatic( HostStatic, SpawnIndex )
+ self:F( { self.SpawnTemplatePrefix, HostStatic, SpawnIndex } )
+
+ if HostStatic and HostStatic:IsAlive() then
+ return self:SpawnFromVec3( HostStatic:GetVec3(), SpawnIndex )
+ end
+
+ return nil
+end
+
+--- Will spawn a Group within a given @{Zone}.
+-- The @{Zone} can be of any type derived from @{Zone#ZONE_BASE}.
+-- Once the @{Group} is spawned within the zone, the @{Group} will continue on its route.
+-- The **first waypoint** (where the group is spawned) is replaced with the zone location coordinates.
+-- @param #SPAWN self
+-- @param Core.Zone#ZONE Zone The zone where the group is to be spawned.
+-- @param #boolean RandomizeGroup (optional) Randomization of the @{Group} position in the zone.
+-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
+-- @return Wrapper.Group#GROUP that was spawned.
+-- @return #nil when nothing was spawned.
+function SPAWN:SpawnInZone( Zone, RandomizeGroup, SpawnIndex )
+ self:F( { self.SpawnTemplatePrefix, Zone, RandomizeGroup, SpawnIndex } )
+
+ if Zone then
+ if RandomizeGroup then
+ return self:SpawnFromVec2( Zone:GetRandomVec2(), SpawnIndex )
+ else
+ return self:SpawnFromVec2( Zone:GetVec2(), SpawnIndex )
+ end
+ end
+
+ return nil
+end
+
+--- (**AIR**) Will spawn a plane group in UnControlled or Controlled mode...
+-- This will be similar to the uncontrolled flag setting in the ME.
+-- You can use UnControlled mode to simulate planes startup and ready for take-off but aren't moving (yet).
+-- ReSpawn the plane in Controlled mode, and the plane will move...
+-- @param #SPAWN self
+-- @param #boolean UnControlled true if UnControlled, false if Controlled.
+-- @return #SPAWN self
+function SPAWN:InitUnControlled( UnControlled )
+ self:F2( { self.SpawnTemplatePrefix, UnControlled } )
+
+ self.SpawnUnControlled = UnControlled
+
+ for SpawnGroupID = 1, self.SpawnMaxGroups do
+ self.SpawnGroups[SpawnGroupID].UnControlled = UnControlled
+ end
+
+ return self
+end
+
+
+--- Get the Coordinate of the Group that is Late Activated as the template for the SPAWN object.
+-- @param #SPAWN self
+-- @return Core.Point#COORDINATE The Coordinate
+function SPAWN:GetCoordinate()
+
+ local LateGroup = GROUP:FindByName( self.SpawnTemplatePrefix )
+ if LateGroup then
+ return LateGroup:GetCoordinate()
+ end
+
+ return nil
+end
+
+
+--- Will return the SpawnGroupName either with with a specific count number or without any count.
+-- @param #SPAWN self
+-- @param #number SpawnIndex Is the number of the Group that is to be spawned.
+-- @return #string SpawnGroupName
+function SPAWN:SpawnGroupName( SpawnIndex )
+ self:F( { self.SpawnTemplatePrefix, SpawnIndex } )
+
+ local SpawnPrefix = self.SpawnTemplatePrefix
+ if self.SpawnAliasPrefix then
+ SpawnPrefix = self.SpawnAliasPrefix
+ end
+
+ if SpawnIndex then
+ local SpawnName = string.format( '%s#%03d', SpawnPrefix, SpawnIndex )
+ self:T( SpawnName )
+ return SpawnName
+ else
+ self:T( SpawnPrefix )
+ return SpawnPrefix
+ end
+
+end
+
+--- Will find the first alive @{Group} it has spawned, and return the alive @{Group} object and the first Index where the first alive @{Group} object has been found.
+-- @param #SPAWN self
+-- @return Wrapper.Group#GROUP, #number The @{Group} object found, the new Index where the group was found.
+-- @return #nil, #nil When no group is found, #nil is returned.
+-- @usage
+-- -- Find the first alive @{Group} object of the SpawnPlanes SPAWN object @{Group} collection that it has spawned during the mission.
+-- local GroupPlane, Index = SpawnPlanes:GetFirstAliveGroup()
+-- while GroupPlane ~= nil do
+-- -- Do actions with the GroupPlane object.
+-- GroupPlane, Index = SpawnPlanes:GetNextAliveGroup( Index )
+-- end
+function SPAWN:GetFirstAliveGroup()
+ self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } )
+
+ for SpawnIndex = 1, self.SpawnCount do
+ local SpawnGroup = self:GetGroupFromIndex( SpawnIndex )
+ if SpawnGroup and SpawnGroup:IsAlive() then
+ return SpawnGroup, SpawnIndex
+ end
+ end
+
+ return nil, nil
+end
+
+
+--- Will find the next alive @{Group} object from a given Index, and return a reference to the alive @{Group} object and the next Index where the alive @{Group} has been found.
+-- @param #SPAWN self
+-- @param #number SpawnIndexStart A Index holding the start position to search from. This method can also be used to find the first alive @{Group} object from the given Index.
+-- @return Wrapper.Group#GROUP, #number The next alive @{Group} object found, the next Index where the next alive @{Group} object was found.
+-- @return #nil, #nil When no alive @{Group} object is found from the start Index position, #nil is returned.
+-- @usage
+-- -- Find the first alive @{Group} object of the SpawnPlanes SPAWN object @{Group} collection that it has spawned during the mission.
+-- local GroupPlane, Index = SpawnPlanes:GetFirstAliveGroup()
+-- while GroupPlane ~= nil do
+-- -- Do actions with the GroupPlane object.
+-- GroupPlane, Index = SpawnPlanes:GetNextAliveGroup( Index )
+-- end
+function SPAWN:GetNextAliveGroup( SpawnIndexStart )
+ self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndexStart } )
+
+ SpawnIndexStart = SpawnIndexStart + 1
+ for SpawnIndex = SpawnIndexStart, self.SpawnCount do
+ local SpawnGroup = self:GetGroupFromIndex( SpawnIndex )
+ if SpawnGroup and SpawnGroup:IsAlive() then
+ return SpawnGroup, SpawnIndex
+ end
+ end
+
+ return nil, nil
+end
+
+--- Will find the last alive @{Group} object, and will return a reference to the last live @{Group} object and the last Index where the last alive @{Group} object has been found.
+-- @param #SPAWN self
+-- @return Wrapper.Group#GROUP, #number The last alive @{Group} object found, the last Index where the last alive @{Group} object was found.
+-- @return #nil, #nil When no alive @{Group} object is found, #nil is returned.
+-- @usage
+-- -- Find the last alive @{Group} object of the SpawnPlanes SPAWN object @{Group} collection that it has spawned during the mission.
+-- local GroupPlane, Index = SpawnPlanes:GetLastAliveGroup()
+-- if GroupPlane then -- GroupPlane can be nil!!!
+-- -- Do actions with the GroupPlane object.
+-- end
+function SPAWN:GetLastAliveGroup()
+ self:F( { self.SpawnTemplatePrefixself.SpawnAliasPrefix } )
+
+ self.SpawnIndex = self:_GetLastIndex()
+ for SpawnIndex = self.SpawnIndex, 1, -1 do
+ local SpawnGroup = self:GetGroupFromIndex( SpawnIndex )
+ if SpawnGroup and SpawnGroup:IsAlive() then
+ self.SpawnIndex = SpawnIndex
+ return SpawnGroup
+ end
+ end
+
+ self.SpawnIndex = nil
+ return nil
+end
+
+
+
+--- Get the group from an index.
+-- Returns the group from the SpawnGroups list.
+-- If no index is given, it will return the first group in the list.
+-- @param #SPAWN self
+-- @param #number SpawnIndex The index of the group to return.
+-- @return Wrapper.Group#GROUP self
+function SPAWN:GetGroupFromIndex( SpawnIndex )
+ self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndex } )
+
+ if not SpawnIndex then
+ SpawnIndex = 1
+ end
+
+ if self.SpawnGroups and self.SpawnGroups[SpawnIndex] then
+ local SpawnGroup = self.SpawnGroups[SpawnIndex].Group
+ return SpawnGroup
+ else
+ return nil
+ end
+end
+
+
+--- Return the prefix of a SpawnUnit.
+-- The method will search for a #-mark, and will return the text before the #-mark.
+-- It will return nil of no prefix was found.
+-- @param #SPAWN self
+-- @param Dcs.DCSWrapper.Unit#UNIT DCSUnit The @{DCSUnit} to be searched.
+-- @return #string The prefix
+-- @return #nil Nothing found
+function SPAWN:_GetPrefixFromGroup( SpawnGroup )
+ self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnGroup } )
+
+ local GroupName = SpawnGroup:GetName()
+ if GroupName then
+ local SpawnPrefix = string.match( GroupName, ".*#" )
+ if SpawnPrefix then
+ SpawnPrefix = SpawnPrefix:sub( 1, -2 )
+ end
+ return SpawnPrefix
+ end
+
+ return nil
+end
+
+
+--- Get the index from a given group.
+-- The function will search the name of the group for a #, and will return the number behind the #-mark.
+function SPAWN:GetSpawnIndexFromGroup( SpawnGroup )
+ self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnGroup } )
+
+ local IndexString = string.match( SpawnGroup:GetName(), "#(%d*)$" ):sub( 2 )
+ local Index = tonumber( IndexString )
+
+ self:T3( IndexString, Index )
+ return Index
+
+end
+
+--- Return the last maximum index that can be used.
+function SPAWN:_GetLastIndex()
+ self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } )
+
+ return self.SpawnMaxGroups
+end
+
+--- Initalize the SpawnGroups collection.
+-- @param #SPAWN self
+function SPAWN:_InitializeSpawnGroups( SpawnIndex )
+ self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndex } )
+
+ if not self.SpawnGroups[SpawnIndex] then
+ self.SpawnGroups[SpawnIndex] = {}
+ self.SpawnGroups[SpawnIndex].Visible = false
+ self.SpawnGroups[SpawnIndex].Spawned = false
+ self.SpawnGroups[SpawnIndex].UnControlled = false
+ self.SpawnGroups[SpawnIndex].SpawnTime = 0
+
+ self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix = self.SpawnTemplatePrefix
+ self.SpawnGroups[SpawnIndex].SpawnTemplate = self:_Prepare( self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix, SpawnIndex )
+ end
+
+ self:_RandomizeTemplate( SpawnIndex )
+ self:_RandomizeRoute( SpawnIndex )
+ --self:_TranslateRotate( SpawnIndex )
+
+ return self.SpawnGroups[SpawnIndex]
+end
+
+
+
+--- Gets the CategoryID of the Group with the given SpawnPrefix
+function SPAWN:_GetGroupCategoryID( SpawnPrefix )
+ local TemplateGroup = Group.getByName( SpawnPrefix )
+
+ if TemplateGroup then
+ return TemplateGroup:getCategory()
+ else
+ return nil
+ end
+end
+
+--- Gets the CoalitionID of the Group with the given SpawnPrefix
+function SPAWN:_GetGroupCoalitionID( SpawnPrefix )
+ local TemplateGroup = Group.getByName( SpawnPrefix )
+
+ if TemplateGroup then
+ return TemplateGroup:getCoalition()
+ else
+ return nil
+ end
+end
+
+--- Gets the CountryID of the Group with the given SpawnPrefix
+function SPAWN:_GetGroupCountryID( SpawnPrefix )
+ self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnPrefix } )
+
+ local TemplateGroup = Group.getByName( SpawnPrefix )
+
+ if TemplateGroup then
+ local TemplateUnits = TemplateGroup:getUnits()
+ return TemplateUnits[1]:getCountry()
+ else
+ return nil
+ end
+end
+
+--- Gets the Group Template from the ME environment definition.
+-- This method used the @{DATABASE} object, which contains ALL initial and new spawned object in MOOSE.
+-- @param #SPAWN self
+-- @param #string SpawnTemplatePrefix
+-- @return @SPAWN self
+function SPAWN:_GetTemplate( SpawnTemplatePrefix )
+ self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnTemplatePrefix } )
+
+ local SpawnTemplate = nil
+
+ SpawnTemplate = routines.utils.deepCopy( _DATABASE.Templates.Groups[SpawnTemplatePrefix].Template )
+
+ if SpawnTemplate == nil then
+ error( 'No Template returned for SpawnTemplatePrefix = ' .. SpawnTemplatePrefix )
+ end
+
+ --SpawnTemplate.SpawnCoalitionID = self:_GetGroupCoalitionID( SpawnTemplatePrefix )
+ --SpawnTemplate.SpawnCategoryID = self:_GetGroupCategoryID( SpawnTemplatePrefix )
+ --SpawnTemplate.SpawnCountryID = self:_GetGroupCountryID( SpawnTemplatePrefix )
+
+ self:T3( { SpawnTemplate } )
+ return SpawnTemplate
+end
+
+--- Prepares the new Group Template.
+-- @param #SPAWN self
+-- @param #string SpawnTemplatePrefix
+-- @param #number SpawnIndex
+-- @return #SPAWN self
+function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) --R2.2
+ self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } )
+
+ local SpawnTemplate = self:_GetTemplate( SpawnTemplatePrefix )
+ SpawnTemplate.name = self:SpawnGroupName( SpawnIndex )
+
+ SpawnTemplate.groupId = nil
+ --SpawnTemplate.lateActivation = false
+ SpawnTemplate.lateActivation = false
+
+ if SpawnTemplate.CategoryID == Group.Category.GROUND then
+ self:T3( "For ground units, visible needs to be false..." )
+ SpawnTemplate.visible = false
+ end
+
+ if self.SpawnGrouping then
+ local UnitAmount = #SpawnTemplate.units
+ self:F( { UnitAmount = UnitAmount, SpawnGrouping = self.SpawnGrouping } )
+ if UnitAmount > self.SpawnGrouping then
+ for UnitID = self.SpawnGrouping + 1, UnitAmount do
+ SpawnTemplate.units[UnitID] = nil
+ end
+ else
+ if UnitAmount < self.SpawnGrouping then
+ for UnitID = UnitAmount + 1, self.SpawnGrouping do
+ SpawnTemplate.units[UnitID] = UTILS.DeepCopy( SpawnTemplate.units[1] )
+ SpawnTemplate.units[UnitID].unitId = nil
+ end
+ end
+ end
+ end
+
+ if self.SpawnInitKeepUnitNames == false then
+ for UnitID = 1, #SpawnTemplate.units do
+ SpawnTemplate.units[UnitID].name = string.format( SpawnTemplate.name .. '-%02d', UnitID )
+ SpawnTemplate.units[UnitID].unitId = nil
+ end
+ else
+ for UnitID = 1, #SpawnTemplate.units do
+ local UnitPrefix, Rest = string.match( SpawnTemplate.units[UnitID].name, "^([^#]+)#?" ):gsub( "^%s*(.-)%s*$", "%1" )
+ self:T( { UnitPrefix, Rest } )
+
+ SpawnTemplate.units[UnitID].name = string.format( '%s#%03d-%02d', UnitPrefix, SpawnIndex, UnitID )
+ SpawnTemplate.units[UnitID].unitId = nil
+ end
+ end
+
+ self:T3( { "Template:", SpawnTemplate } )
+ return SpawnTemplate
+
+end
+
+--- Private method randomizing the routes.
+-- @param #SPAWN self
+-- @param #number SpawnIndex The index of the group to be spawned.
+-- @return #SPAWN
+function SPAWN:_RandomizeRoute( SpawnIndex )
+ self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeRoute, self.SpawnRandomizeRouteStartPoint, self.SpawnRandomizeRouteEndPoint, self.SpawnRandomizeRouteRadius } )
+
+ if self.SpawnRandomizeRoute then
+ local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate
+ local RouteCount = #SpawnTemplate.route.points
+
+ for t = self.SpawnRandomizeRouteStartPoint + 1, ( RouteCount - self.SpawnRandomizeRouteEndPoint ) do
+
+ SpawnTemplate.route.points[t].x = SpawnTemplate.route.points[t].x + math.random( self.SpawnRandomizeRouteRadius * -1, self.SpawnRandomizeRouteRadius )
+ SpawnTemplate.route.points[t].y = SpawnTemplate.route.points[t].y + math.random( self.SpawnRandomizeRouteRadius * -1, self.SpawnRandomizeRouteRadius )
+
+ -- Manage randomization of altitude for airborne units ...
+ if SpawnTemplate.CategoryID == Group.Category.AIRPLANE or SpawnTemplate.CategoryID == Group.Category.HELICOPTER then
+ if SpawnTemplate.route.points[t].alt and self.SpawnRandomizeRouteHeight then
+ SpawnTemplate.route.points[t].alt = SpawnTemplate.route.points[t].alt + math.random( 1, self.SpawnRandomizeRouteHeight )
+ end
+ else
+ SpawnTemplate.route.points[t].alt = nil
+ end
+
+ self:T( 'SpawnTemplate.route.points[' .. t .. '].x = ' .. SpawnTemplate.route.points[t].x .. ', SpawnTemplate.route.points[' .. t .. '].y = ' .. SpawnTemplate.route.points[t].y )
+ end
+ end
+
+ self:_RandomizeZones( SpawnIndex )
+
+ return self
+end
+
+--- Private method that randomizes the template of the group.
+-- @param #SPAWN self
+-- @param #number SpawnIndex
+-- @return #SPAWN self
+function SPAWN:_RandomizeTemplate( SpawnIndex )
+ self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeTemplate } )
+
+ if self.SpawnRandomizeTemplate then
+ self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix = self.SpawnTemplatePrefixTable[ math.random( 1, #self.SpawnTemplatePrefixTable ) ]
+ self.SpawnGroups[SpawnIndex].SpawnTemplate = self:_Prepare( self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix, SpawnIndex )
+ self.SpawnGroups[SpawnIndex].SpawnTemplate.route = routines.utils.deepCopy( self.SpawnTemplate.route )
+ self.SpawnGroups[SpawnIndex].SpawnTemplate.x = self.SpawnTemplate.x
+ self.SpawnGroups[SpawnIndex].SpawnTemplate.y = self.SpawnTemplate.y
+ self.SpawnGroups[SpawnIndex].SpawnTemplate.start_time = self.SpawnTemplate.start_time
+ local OldX = self.SpawnGroups[SpawnIndex].SpawnTemplate.units[1].x
+ local OldY = self.SpawnGroups[SpawnIndex].SpawnTemplate.units[1].y
+ for UnitID = 1, #self.SpawnGroups[SpawnIndex].SpawnTemplate.units do
+ self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].heading = self.SpawnTemplate.units[1].heading
+ self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].x = self.SpawnTemplate.units[1].x + ( self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].x - OldX )
+ self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y = self.SpawnTemplate.units[1].y + ( self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y - OldY )
+ self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].alt = self.SpawnTemplate.units[1].alt
+ end
+ end
+
+ self:_RandomizeRoute( SpawnIndex )
+
+ return self
+end
+
+--- Private method that randomizes the @{Zone}s where the Group will be spawned.
+-- @param #SPAWN self
+-- @param #number SpawnIndex
+-- @return #SPAWN self
+function SPAWN:_RandomizeZones( SpawnIndex )
+ self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeZones } )
+
+ if self.SpawnRandomizeZones then
+ local SpawnZone = nil -- Core.Zone#ZONE_BASE
+ while not SpawnZone do
+ self:T( { SpawnZoneTableCount = #self.SpawnZoneTable, self.SpawnZoneTable } )
+ local ZoneID = math.random( #self.SpawnZoneTable )
+ self:T( ZoneID )
+ SpawnZone = self.SpawnZoneTable[ ZoneID ]:GetZoneMaybe()
+ end
+
+ self:T( "Preparing Spawn in Zone", SpawnZone:GetName() )
+
+ local SpawnVec2 = SpawnZone:GetRandomVec2()
+
+ self:T( { SpawnVec2 = SpawnVec2 } )
+
+ local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate
+
+ self:T( { Route = SpawnTemplate.route } )
+
+ for UnitID = 1, #SpawnTemplate.units do
+ local UnitTemplate = SpawnTemplate.units[UnitID]
+ self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y )
+ local SX = UnitTemplate.x
+ local SY = UnitTemplate.y
+ local BX = SpawnTemplate.route.points[1].x
+ local BY = SpawnTemplate.route.points[1].y
+ local TX = SpawnVec2.x + ( SX - BX )
+ local TY = SpawnVec2.y + ( SY - BY )
+ UnitTemplate.x = TX
+ UnitTemplate.y = TY
+ -- TODO: Manage altitude based on landheight...
+ --SpawnTemplate.units[UnitID].alt = SpawnVec2:
+ self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y )
+ end
+ SpawnTemplate.x = SpawnVec2.x
+ SpawnTemplate.y = SpawnVec2.y
+ SpawnTemplate.route.points[1].x = SpawnVec2.x
+ SpawnTemplate.route.points[1].y = SpawnVec2.y
+ end
+
+ return self
+
+end
+
+function SPAWN:_TranslateRotate( SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle )
+ self:F( { self.SpawnTemplatePrefix, SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle } )
+
+ -- Translate
+ local TranslatedX = SpawnX
+ local TranslatedY = SpawnY
+
+ -- Rotate
+ -- From Wikipedia: https://en.wikipedia.org/wiki/Rotation_matrix#Common_rotations
+ -- x' = x \cos \theta - y \sin \theta\
+ -- y' = x \sin \theta + y \cos \theta\
+ local RotatedX = - TranslatedX * math.cos( math.rad( SpawnAngle ) )
+ + TranslatedY * math.sin( math.rad( SpawnAngle ) )
+ local RotatedY = TranslatedX * math.sin( math.rad( SpawnAngle ) )
+ + TranslatedY * math.cos( math.rad( SpawnAngle ) )
+
+ -- Assign
+ self.SpawnGroups[SpawnIndex].SpawnTemplate.x = SpawnRootX - RotatedX
+ self.SpawnGroups[SpawnIndex].SpawnTemplate.y = SpawnRootY + RotatedY
+
+
+ local SpawnUnitCount = table.getn( self.SpawnGroups[SpawnIndex].SpawnTemplate.units )
+ for u = 1, SpawnUnitCount do
+
+ -- Translate
+ local TranslatedX = SpawnX
+ local TranslatedY = SpawnY - 10 * ( u - 1 )
+
+ -- Rotate
+ local RotatedX = - TranslatedX * math.cos( math.rad( SpawnAngle ) )
+ + TranslatedY * math.sin( math.rad( SpawnAngle ) )
+ local RotatedY = TranslatedX * math.sin( math.rad( SpawnAngle ) )
+ + TranslatedY * math.cos( math.rad( SpawnAngle ) )
+
+ -- Assign
+ self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].x = SpawnRootX - RotatedX
+ self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].y = SpawnRootY + RotatedY
+ self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].heading = self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].heading + math.rad( SpawnAngle )
+ end
+
+ return self
+end
+
+--- Get the next index of the groups to be spawned. This method is complicated, as it is used at several spaces.
+function SPAWN:_GetSpawnIndex( SpawnIndex )
+ self:F2( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive, self.AliveUnits, #self.SpawnTemplate.units } )
+
+ if ( self.SpawnMaxGroups == 0 ) or ( SpawnIndex <= self.SpawnMaxGroups ) then
+ if ( self.SpawnMaxUnitsAlive == 0 ) or ( self.AliveUnits + #self.SpawnTemplate.units <= self.SpawnMaxUnitsAlive ) or self.UnControlled == true then
+ if SpawnIndex and SpawnIndex >= self.SpawnCount + 1 then
+ self.SpawnCount = self.SpawnCount + 1
+ SpawnIndex = self.SpawnCount
+ end
+ self.SpawnIndex = SpawnIndex
+ if not self.SpawnGroups[self.SpawnIndex] then
+ self:_InitializeSpawnGroups( self.SpawnIndex )
+ end
+ else
+ return nil
+ end
+ else
+ return nil
+ end
+
+ return self.SpawnIndex
+end
+
+
+-- TODO Need to delete this... _DATABASE does this now ...
+
+--- @param #SPAWN self
+-- @param Core.Event#EVENTDATA EventData
+function SPAWN:_OnBirth( EventData )
+ self:F( self.SpawnTemplatePrefix )
+
+ local SpawnGroup = EventData.IniGroup
+
+ if SpawnGroup then
+ local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup )
+ if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group!
+ self:T( { "Birth Event:", EventPrefix, self.SpawnTemplatePrefix } )
+ if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then
+ self.AliveUnits = self.AliveUnits + 1
+ self:T( "Alive Units: " .. self.AliveUnits )
+ end
+ end
+ end
+
+end
+
+--- Obscolete
+-- @todo Need to delete this... _DATABASE does this now ...
+
+--- @param #SPAWN self
+-- @param Core.Event#EVENTDATA EventData
+function SPAWN:_OnDeadOrCrash( EventData )
+ self:F( self.SpawnTemplatePrefix )
+
+ local SpawnGroup = EventData.IniGroup
+
+ if SpawnGroup then
+ local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup )
+ if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group!
+ self:T( { "Dead event: " .. EventPrefix } )
+ if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then
+ self.AliveUnits = self.AliveUnits - 1
+ self:T( "Alive Units: " .. self.AliveUnits )
+ end
+ end
+ end
+end
+
+--- Will detect AIR Units taking off... When the event takes place, the spawned Group is registered as airborne...
+-- This is needed to ensure that Re-SPAWNing only is done for landed AIR Groups.
+-- @param #SPAWN self
+-- @param Core.Event#EVENTDATA EventData
+function SPAWN:_OnTakeOff( EventData )
+ self:F( self.SpawnTemplatePrefix )
+
+ local SpawnGroup = EventData.IniGroup
+ if SpawnGroup then
+ local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup )
+ if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group!
+ self:T( { "TakeOff event: " .. EventPrefix } )
+ if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then
+ self:T( "self.Landed = false" )
+ SpawnGroup:SetState( SpawnGroup, "Spawn_Landed", false )
+ end
+ end
+ end
+end
+
+--- Will detect AIR Units landing... When the event takes place, the spawned Group is registered as landed.
+-- This is needed to ensure that Re-SPAWNing is only done for landed AIR Groups.
+-- @param #SPAWN self
+-- @param Core.Event#EVENTDATA EventData
+function SPAWN:_OnLand( EventData )
+ self:F( self.SpawnTemplatePrefix )
+
+ local SpawnGroup = EventData.IniGroup
+ if SpawnGroup then
+ local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup )
+ if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group!
+ self:T( { "Land event: " .. EventPrefix } )
+ if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then
+ -- TODO: Check if this is the last unit of the group that lands.
+ SpawnGroup:SetState( SpawnGroup, "Spawn_Landed", true )
+ if self.RepeatOnLanding then
+ local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup )
+ self:T( { "Landed:", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } )
+ self:ReSpawn( SpawnGroupIndex )
+ end
+ end
+ end
+ end
+end
+
+--- Will detect AIR Units shutting down their engines ...
+-- When the event takes place, and the method @{RepeatOnEngineShutDown} was called, the spawned Group will Re-SPAWN.
+-- But only when the Unit was registered to have landed.
+-- @param #SPAWN self
+-- @param Core.Event#EVENTDATA EventData
+function SPAWN:_OnEngineShutDown( EventData )
+ self:F( self.SpawnTemplatePrefix )
+
+ local SpawnGroup = EventData.IniGroup
+ if SpawnGroup then
+ local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup )
+ if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group!
+ self:T( { "EngineShutdown event: " .. EventPrefix } )
+ if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then
+ -- todo: test if on the runway
+ local Landed = SpawnGroup:GetState( SpawnGroup, "Spawn_Landed" )
+ if Landed and self.RepeatOnEngineShutDown then
+ local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup )
+ self:T( { "EngineShutDown: ", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } )
+ self:ReSpawn( SpawnGroupIndex )
+ end
+ end
+ end
+ end
+end
+
+--- This function is called automatically by the Spawning scheduler.
+-- It is the internal worker method SPAWNing new Groups on the defined time intervals.
+function SPAWN:_Scheduler()
+ self:F2( { "_Scheduler", self.SpawnTemplatePrefix, self.SpawnAliasPrefix, self.SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive } )
+
+ -- Validate if there are still groups left in the batch...
+ self:Spawn()
+
+ return true
+end
+
+--- Schedules the CleanUp of Groups
+-- @param #SPAWN self
+-- @return #boolean True = Continue Scheduler
+function SPAWN:_SpawnCleanUpScheduler()
+ self:F( { "CleanUp Scheduler:", self.SpawnTemplatePrefix } )
+
+ local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup()
+ self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } )
+
+ while SpawnGroup do
+
+ local SpawnUnits = SpawnGroup:GetUnits()
+
+ for UnitID, UnitData in pairs( SpawnUnits ) do
+
+ local SpawnUnit = UnitData -- Wrapper.Unit#UNIT
+ local SpawnUnitName = SpawnUnit:GetName()
+
+
+ self.SpawnCleanUpTimeStamps[SpawnUnitName] = self.SpawnCleanUpTimeStamps[SpawnUnitName] or {}
+ local Stamp = self.SpawnCleanUpTimeStamps[SpawnUnitName]
+ self:T( { SpawnUnitName, Stamp } )
+
+ if Stamp.Vec2 then
+ if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1 then
+ local NewVec2 = SpawnUnit:GetVec2()
+ if Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y then
+ -- If the plane is not moving, and is on the ground, assign it with a timestamp...
+ if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then
+ self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } )
+ self:ReSpawn( SpawnCursor )
+ Stamp.Vec2 = nil
+ Stamp.Time = nil
+ end
+ else
+ Stamp.Time = timer.getTime()
+ Stamp.Vec2 = SpawnUnit:GetVec2()
+ end
+ else
+ Stamp.Vec2 = nil
+ Stamp.Time = nil
+ end
+ else
+ if SpawnUnit:InAir() == false then
+ Stamp.Vec2 = SpawnUnit:GetVec2()
+ if SpawnUnit:GetVelocityKMH() < 1 then
+ Stamp.Time = timer.getTime()
+ end
+ else
+ Stamp.Time = nil
+ Stamp.Vec2 = nil
+ end
+ end
+ end
+
+ SpawnGroup, SpawnCursor = self:GetNextAliveGroup( SpawnCursor )
+
+ self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } )
+
+ end
+
+ return true -- Repeat
+
+end
\ No newline at end of file
From 61884c07c750272ccc0ee360d93d8643f67226a8 Mon Sep 17 00:00:00 2001
From: funkyfranky
Date: Mon, 28 Aug 2017 15:35:09 +0200
Subject: [PATCH 03/28] Restored oritinal Spawn and AI_Balancer.lua files
---
Moose Development/Moose/AI/AI_Balancer.lua | 2022 ++----------------
Moose Development/Moose/Functional/Spawn.lua | 840 ++++----
2 files changed, 632 insertions(+), 2230 deletions(-)
diff --git a/Moose Development/Moose/AI/AI_Balancer.lua b/Moose Development/Moose/AI/AI_Balancer.lua
index 14059f200..643dfc5b7 100644
--- a/Moose Development/Moose/AI/AI_Balancer.lua
+++ b/Moose Development/Moose/AI/AI_Balancer.lua
@@ -1,18 +1,16 @@
---- **Functional** -- Spawn dynamically new GROUPs in your missions.
+--- **AI** -- **AI Balancing will replace in multi player missions
+-- non-occupied human slots with AI groups, in order to provide an engaging simulation environment,
+-- even when there are hardly any players in the mission.**
+--
+-- 
--
--- 
---
--- ====
---
--- The documentation of the SPAWN class can be found further in this document.
---
-- ====
--
-- # Demo Missions
--
--- ### [SPAWN Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/SPA%20-%20Spawning)
+-- ### [AI_BALANCER Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/AIB%20-%20AI%20Balancing)
--
--- ### [SPAWN Demo Missions, only for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SPA%20-%20Spawning)
+-- ### [AI_BALANCER Demo Missions, only for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AIB%20-%20AI%20Balancing)
--
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
--
@@ -20,1882 +18,286 @@
--
-- # YouTube Channel
--
--- ### [SPAWN YouTube Channel](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl1jirWIo4t4YxqN-HxjqRkL)
+-- ### [AI_BALANCER YouTube Channel](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl2CJVIrL1TdAumuVS8n64B7)
--
--- ===
---
--- # **AUTHORS and CONTRIBUTIONS**
+-- ====
--
+-- ### Author: **Sven Van de Velde (FlightControl)**
-- ### Contributions:
--
--- * **Aaron**: Posed the idea for Group position randomization at SpawnInZone and make the Unit randomization separate from the Group randomization.
--- * [**Entropy**](https://forums.eagle.ru/member.php?u=111471), **Afinegan**: Came up with the requirement for AIOnOff().
+-- * **[Dutch_Baron](https://forums.eagle.ru/member.php?u=112075)**: Working together with James has resulted in the creation of the AI_BALANCER class. James has shared his ideas on balancing AI with air units, and together we made a first design which you can use now :-)
--
--- ### Authors:
+-- ====
--
--- * **FlightControl**: Design & Programming
---
--- @module Spawn
+-- @module AI_Balancer
-----BASE:TraceClass("SPAWN")
+--- @type AI_BALANCER
+-- @field Core.Set#SET_CLIENT SetClient
+-- @field Functional.Spawn#SPAWN SpawnAI
+-- @field Wrapper.Group#GROUP Test
+-- @extends Core.Fsm#FSM_SET
---- SPAWN Class
--- @type SPAWN
--- @field ClassName
--- @field #string SpawnTemplatePrefix
--- @field #string SpawnAliasPrefix
--- @field #number AliveUnits
--- @field #number MaxAliveUnits
--- @field #number SpawnIndex
--- @field #number MaxAliveGroups
--- @field #SPAWN.SpawnZoneTable SpawnZoneTable
--- @extends Core.Base#BASE
-
-
---- # SPAWN class, extends @{Base#BASE}
+--- # AI_BALANCER class, extends @{Fsm#FSM_SET}
--
--- The SPAWN class allows to spawn dynamically new groups.
--- Each SPAWN object needs to be have a related **template group** setup in the Mission Editor (ME),
--- which is a normal group with the **Late Activation** flag set.
--- This template group will never be activated in your mission.
--- SPAWN uses that **template group** to reference to all the characteristics
--- (air, ground, livery, unit composition, formation, skill level etc) of each new group to be spawned.
+-- The AI_BALANCER class monitors and manages as many replacement AI groups as there are
+-- CLIENTS in a SET_CLIENT collection, which are not occupied by human players.
+-- In other words, use AI_BALANCER to simulate human behaviour by spawning in replacement AI in multi player missions.
--
--- Therefore, when creating a SPAWN object, the @{#SPAWN.New} and @{#SPAWN.NewWithAlias} require
--- **the name of the template group** to be given as a string to those constructor methods.
---
--- Initialization settings can be applied on the SPAWN object,
--- which modify the behaviour or the way groups are spawned.
--- These initialization methods have the prefix **Init**.
--- There are also spawn methods with the prefix **Spawn** and will spawn new groups in various ways.
+-- The parent class @{Fsm#FSM_SET} manages the functionality to control the Finite State Machine (FSM).
+-- The mission designer can tailor the behaviour of the AI_BALANCER, by defining event and state transition methods.
+-- An explanation about state and event transition methods can be found in the @{FSM} module documentation.
--
--- ### IMPORTANT! The methods with prefix **Init** must be used before any methods with prefix **Spawn** method are used, or unexpected results may appear!!!
+-- The mission designer can tailor the AI_BALANCER behaviour, by implementing a state or event handling method for the following:
--
--- Because SPAWN can spawn multiple groups of a template group,
--- SPAWN has an **internal index** that keeps track
--- which was the latest group that was spawned.
+-- * @{#AI_BALANCER.OnAfterSpawned}( AISet, From, Event, To, AIGroup ): Define to add extra logic when an AI is spawned.
--
--- **Limits** can be set on how many groups can be spawn in each SPAWN object,
--- using the method @{#SPAWN.InitLimit}. SPAWN has 2 kind of limits:
+-- ## 1. AI_BALANCER construction
--
--- * The maximum amount of @{Unit}s that can be **alive** at the same time...
--- * The maximum amount of @{Group}s that can be **spawned**... This is more of a **resource**-type of limit.
---
--- When new groups get spawned using the **Spawn** methods,
--- it will be evaluated whether any limits have been reached.
--- When no spawn limit is reached, a new group will be created by the spawning methods,
--- and the internal index will be increased with 1.
+-- Create a new AI_BALANCER object with the @{#AI_BALANCER.New}() method:
--
--- These limits ensure that your mission does not accidentally get flooded with spawned groups.
--- Additionally, it also guarantees that independent of the group composition,
--- at any time, the most optimal amount of groups are alive in your mission.
--- For example, if your template group has a group composition of 10 units, and you specify a limit of 100 units alive at the same time,
--- with unlimited resources = :InitLimit( 100, 0 ) and 10 groups are alive, but two groups have only one unit alive in the group,
--- then a sequent Spawn(Scheduled) will allow a new group to be spawned!!!
+-- ## 2. AI_BALANCER is a FSM
--
--- ### IMPORTANT!! If a limit has been reached, it is possible that a **Spawn** method returns **nil**, meaning, no @{Group} had been spawned!!!
+-- 
--
--- Spawned groups get **the same name** as the name of the template group.
--- Spawned units in those groups keep _by default_ **the same name** as the name of the template group.
--- However, because multiple groups and units are created from the template group,
--- a suffix is added to each spawned group and unit.
+-- ### 2.1. AI_BALANCER States
--
--- Newly spawned groups will get the following naming structure at run-time:
+-- * **Monitoring** ( Set ): Monitoring the Set if all AI is spawned for the Clients.
+-- * **Spawning** ( Set, ClientName ): There is a new AI group spawned with ClientName as the name of reference.
+-- * **Spawned** ( Set, AIGroup ): A new AI has been spawned. You can handle this event to customize the AI behaviour with other AI FSMs or own processes.
+-- * **Destroying** ( Set, AIGroup ): The AI is being destroyed.
+-- * **Returning** ( Set, AIGroup ): The AI is returning to the airbase specified by the ReturnToAirbase methods. Handle this state to customize the return behaviour of the AI, if any.
--
--- 1. Spawned groups will have the name _GroupName_#_nnn_, where _GroupName_ is the name of the **template group**,
--- and _nnn_ is a **counter from 0 to 999**.
--- 2. Spawned units will have the name _GroupName_#_nnn_-_uu_,
--- where _uu_ is a **counter from 0 to 99** for each new spawned unit belonging to the group.
+-- ### 2.2. AI_BALANCER Events
--
--- That being said, there is a way to keep the same unit names!
--- The method @{#SPAWN.InitKeepUnitNames}() will keep the same unit names as defined within the template group, thus:
+-- * **Monitor** ( Set ): Every 10 seconds, the Monitor event is triggered to monitor the Set.
+-- * **Spawn** ( Set, ClientName ): Triggers when there is a new AI group to be spawned with ClientName as the name of reference.
+-- * **Spawned** ( Set, AIGroup ): Triggers when a new AI has been spawned. You can handle this event to customize the AI behaviour with other AI FSMs or own processes.
+-- * **Destroy** ( Set, AIGroup ): The AI is being destroyed.
+-- * **Return** ( Set, AIGroup ): The AI is returning to the airbase specified by the ReturnToAirbase methods.
+--
+-- ## 3. AI_BALANCER spawn interval for replacement AI
--
--- 3. Spawned units will have the name _UnitName_#_nnn_-_uu_,
--- where _UnitName_ is the **unit name as defined in the template group*,
--- and _uu_ is a **counter from 0 to 99** for each new spawned unit belonging to the group.
+-- Use the method @{#AI_BALANCER.InitSpawnInterval}() to set the earliest and latest interval in seconds that is waited until a new replacement AI is spawned.
--
--- Some **additional notes that need to be considered!!**:
+-- ## 4. AI_BALANCER returns AI to Airbases
--
--- * templates are actually groups defined within the mission editor, with the flag "Late Activation" set.
--- As such, these groups are never used within the mission, but are used by the @{#SPAWN} module.
--- * It is important to defined BEFORE you spawn new groups,
--- a proper initialization of the SPAWN instance is done with the options you want to use.
--- * When designing a mission, NEVER name groups using a "#" within the name of the group Spawn template(s),
--- or the SPAWN module logic won't work anymore.
---
--- ## SPAWN construction methods
+-- By default, When a human player joins a slot that is AI_BALANCED, the AI group will be destroyed by default.
+-- However, there are 2 additional options that you can use to customize the destroy behaviour.
+-- When a human player joins a slot, you can configure to let the AI return to:
--
--- Create a new SPAWN object with the @{#SPAWN.New}() or the @{#SPAWN.NewWithAlias}() methods:
+-- * @{#AI_BALANCER.ReturnToHomeAirbase}: Returns the AI to the **home** @{Airbase#AIRBASE}.
+-- * @{#AI_BALANCER.ReturnToNearestAirbases}: Returns the AI to the **nearest friendly** @{Airbase#AIRBASE}.
--
--- * @{#SPAWN.New}(): Creates a new SPAWN object taking the name of the group that represents the GROUP template (definition).
--- * @{#SPAWN.NewWithAlias}(): Creates a new SPAWN object taking the name of the group that represents the GROUP template (definition), and gives each spawned @{Group} an different name.
---
--- It is important to understand how the SPAWN class works internally. The SPAWN object created will contain internally a list of groups that will be spawned and that are already spawned.
--- The initialization methods will modify this list of groups so that when a group gets spawned, ALL information is already prepared when spawning. This is done for performance reasons.
--- So in principle, the group list will contain all parameters and configurations after initialization, and when groups get actually spawned, this spawning can be done quickly and efficient.
---
--- ## SPAWN **Init**ialization methods
+-- Note that when AI returns to an airbase, the AI_BALANCER will trigger the **Return** event and the AI will return,
+-- otherwise the AI_BALANCER will trigger a **Destroy** event, and the AI will be destroyed.
--
--- A spawn object will behave differently based on the usage of **initialization** methods, which all start with the **Init** prefix:
---
--- ### Unit Names
---
--- * @{#SPAWN.InitKeepUnitNames}(): Keeps the unit names as defined within the mission editor, but note that anything after a # mark is ignored, and any spaces before and after the resulting name are removed. IMPORTANT! This method MUST be the first used after :New !!!
---
--- ### Route randomization
---
--- * @{#SPAWN.InitRandomizeRoute}(): Randomize the routes of spawned groups, and for air groups also optionally the height.
---
--- ### Group composition randomization
---
--- * @{#SPAWN.InitRandomizeTemplate}(): Randomize the group templates so that when a new group is spawned, a random group template is selected from one of the templates defined.
---
--- ### Uncontrolled
---
--- * @{#SPAWN.InitUnControlled}(): Spawn plane groups uncontrolled.
---
--- ### Array formation
---
--- * @{#SPAWN.InitArray}(): Make groups visible before they are actually activated, and order these groups like a batallion in an array.
---
--- ### Position randomization
---
--- * @{#SPAWN.InitRandomizePosition}(): Randomizes the position of @{Group}s that are spawned within a **radius band**, given an Outer and Inner radius, from the point that the spawn happens.
--- * @{#SPAWN.InitRandomizeUnits}(): Randomizes the @{Unit}s in the @{Group} that is spawned within a **radius band**, given an Outer and Inner radius.
--- * @{#SPAWN.InitRandomizeZones}(): Randomizes the spawning between a predefined list of @{Zone}s that are declared using this function. Each zone can be given a probability factor.
---
--- ### Enable / Disable AI when spawning a new @{Group}
---
--- * @{#SPAWN.InitAIOn}(): Turns the AI On when spawning the new @{Group} object.
--- * @{#SPAWN.InitAIOff}(): Turns the AI Off when spawning the new @{Group} object.
--- * @{#SPAWN.InitAIOnOff}(): Turns the AI On or Off when spawning the new @{Group} object.
---
--- ### Limit scheduled spawning
---
--- * @{#SPAWN.InitLimit}(): Limits the amount of groups that can be alive at the same time and that can be dynamically spawned.
---
--- ### Delay initial scheduled spawn
---
--- * @{#SPAWN.InitDelayOnOff}(): Turns the inital delay On/Off when scheduled spawning the first @{Group} object.
--- * @{#SPAWN.InitDelayOn}(): Turns the inital delay On when scheduled spawning the first @{Group} object.
--- * @{#SPAWN.InitDelayOff}(): Turns the inital delay Off when scheduled spawning the first @{Group} object.
---
--- ### Repeat spawned @{Group}s upon landing
---
--- * @{#SPAWN.InitRepeat}() or @{#SPAWN.InitRepeatOnLanding}(): This method is used to re-spawn automatically the same group after it has landed.
--- * @{#SPAWN.InitRepeatOnEngineShutDown}(): This method is used to re-spawn automatically the same group after it has landed and it shuts down the engines at the ramp.
---
---
--- ## SPAWN **Spawn** methods
---
--- Groups can be spawned at different times and methods:
---
--- ### **Single** spawning methods
---
--- * @{#SPAWN.Spawn}(): Spawn one new group based on the last spawned index.
--- * @{#SPAWN.ReSpawn}(): Re-spawn a group based on a given index.
--- * @{#SPAWN.SpawnFromVec3}(): Spawn a new group from a Vec3 coordinate. (The group will can be spawned at a point in the air).
--- * @{#SPAWN.SpawnFromVec2}(): Spawn a new group from a Vec2 coordinate. (The group will be spawned at land height ).
--- * @{#SPAWN.SpawnFromStatic}(): Spawn a new group from a structure, taking the position of a @{Static}.
--- * @{#SPAWN.SpawnFromUnit}(): Spawn a new group taking the position of a @{Unit}.
--- * @{#SPAWN.SpawnInZone}(): Spawn a new group in a @{Zone}.
---
--- Note that @{#SPAWN.Spawn} and @{#SPAWN.ReSpawn} return a @{GROUP#GROUP.New} object, that contains a reference to the DCSGroup object.
--- You can use the @{GROUP} object to do further actions with the DCSGroup.
---
--- ### **Scheduled** spawning methods
---
--- * @{#SPAWN.SpawnScheduled}(): Spawn groups at scheduled but randomized intervals.
--- * @{#SPAWN.SpawnScheduledStart}(): Start or continue to spawn groups at scheduled time intervals.
--- * @{#SPAWN.SpawnScheduledStop}(): Stop the spawning of groups at scheduled time intervals.
---
---
---
--- ## Retrieve alive GROUPs spawned by the SPAWN object
---
--- The SPAWN class administers which GROUPS it has reserved (in stock) or has created during mission execution.
--- Every time a SPAWN object spawns a new GROUP object, a reference to the GROUP object is added to an internal table of GROUPS.
--- SPAWN provides methods to iterate through that internal GROUP object reference table:
---
--- * @{#SPAWN.GetFirstAliveGroup}(): Will find the first alive GROUP it has spawned, and return the alive GROUP object and the first Index where the first alive GROUP object has been found.
--- * @{#SPAWN.GetNextAliveGroup}(): Will find the next alive GROUP object from a given Index, and return a reference to the alive GROUP object and the next Index where the alive GROUP has been found.
--- * @{#SPAWN.GetLastAliveGroup}(): Will find the last alive GROUP object, and will return a reference to the last live GROUP object and the last Index where the last alive GROUP object has been found.
---
--- You can use the methods @{#SPAWN.GetFirstAliveGroup}() and sequently @{#SPAWN.GetNextAliveGroup}() to iterate through the alive GROUPS within the SPAWN object, and to actions... See the respective methods for an example.
--- The method @{#SPAWN.GetGroupFromIndex}() will return the GROUP object reference from the given Index, dead or alive...
---
--- ## Spawned cleaning of inactive groups
---
--- Sometimes, it will occur during a mission run-time, that ground or especially air objects get damaged, and will while being damged stop their activities, while remaining alive.
--- In such cases, the SPAWN object will just sit there and wait until that group gets destroyed, but most of the time it won't,
--- and it may occur that no new groups are or can be spawned as limits are reached.
--- To prevent this, a @{#SPAWN.InitCleanUp}() initialization method has been defined that will silently monitor the status of each spawned group.
--- Once a group has a velocity = 0, and has been waiting for a defined interval, that group will be cleaned or removed from run-time.
--- There is a catch however :-) If a damaged group has returned to an airbase within the coalition, that group will not be considered as "lost"...
--- In such a case, when the inactive group is cleaned, a new group will Re-spawned automatically.
--- This models AI that has succesfully returned to their airbase, to restart their combat activities.
--- Check the @{#SPAWN.InitCleanUp}() for further info.
---
--- ## Catch the @{Group} Spawn Event in a callback function!
---
--- When using the @{#SPAWN.SpawnScheduled)() method, new @{Group}s are created following the spawn time interval parameters.
--- When a new @{Group} is spawned, you maybe want to execute actions with that group spawned at the spawn event.
--- The SPAWN class supports this functionality through the method @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ),
--- which takes a function as a parameter that you can define locally.
--- Whenever a new @{Group} is spawned, the given function is called, and the @{Group} that was just spawned, is given as a parameter.
--- As a result, your spawn event handling function requires one parameter to be declared, which will contain the spawned @{Group} object.
--- A coding example is provided at the description of the @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ) method.
---
--- ## Delay the initial spawning
---
--- When using the @{#SPAWN.SpawnScheduled)() method, the default behaviour of this method will be that it will spawn the initial (first) @{Group}
--- immediately when :SpawnScheduled() is initiated. The methods @{#SPAWN.InitDelayOnOff}() and @{#SPAWN.InitDelayOn}() can be used to
--- activate a delay before the first @{Group} is spawned. For completeness, a method @{#SPAWN.InitDelayOff}() is also available, that
--- can be used to switch off the initial delay. Because there is no delay by default, this method would only be used when a
--- @{#SPAWN.SpawnScheduledStop}() ; @{#SPAWN.SpawnScheduledStart}() sequence would have been used.
---
---
--- @field #SPAWN SPAWN
---
-SPAWN = {
- ClassName = "SPAWN",
- SpawnTemplatePrefix = nil,
- SpawnAliasPrefix = nil,
+-- @field #AI_BALANCER
+AI_BALANCER = {
+ ClassName = "AI_BALANCER",
+ PatrolZones = {},
+ AIGroups = {},
+ Earliest = 5, -- Earliest a new AI can be spawned is in 5 seconds.
+ Latest = 60, -- Latest a new AI can be spawned is in 60 seconds.
}
---- Enumerator for spawns at airbases
--- @type SPAWN.Takeoff
--- @extends Wrapper.Group#GROUP.Takeoff
---- @field #SPAWN.Takeoff Takeoff
-SPAWN.Takeoff = GROUP.Takeoff
-
-
---- @type SPAWN.SpawnZoneTable
--- @list SpawnZone
-
-
---- Creates the main object to spawn a @{Group} defined in the DCS ME.
--- @param #SPAWN self
--- @param #string SpawnTemplatePrefix is the name of the Group in the ME that defines the Template. Each new group will have the name starting with SpawnTemplatePrefix.
--- @return #SPAWN
--- @usage
--- -- NATO helicopters engaging in the battle field.
--- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' )
--- @usage local Plane = SPAWN:New( "Plane" ) -- Creates a new local variable that can initiate new planes with the name "Plane#ddd" using the template "Plane" as defined within the ME.
-function SPAWN:New( SpawnTemplatePrefix )
- local self = BASE:Inherit( self, BASE:New() ) -- #SPAWN
- self:F( { SpawnTemplatePrefix } )
+--- Creates a new AI_BALANCER object
+-- @param #AI_BALANCER self
+-- @param Core.Set#SET_CLIENT SetClient A SET\_CLIENT object that will contain the CLIENT objects to be monitored if they are alive or not (joined by a player).
+-- @param Functional.Spawn#SPAWN SpawnAI The default Spawn object to spawn new AI Groups when needed.
+-- @return #AI_BALANCER
+function AI_BALANCER:New( SetClient, SpawnAI )
- local TemplateGroup = Group.getByName( SpawnTemplatePrefix )
- if TemplateGroup then
- self.SpawnTemplatePrefix = SpawnTemplatePrefix
- self.SpawnIndex = 0
- self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart.
- self.AliveUnits = 0 -- Contains the counter how many units are currently alive
- self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not.
- self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!!
- self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning.
- self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts.
- self.SpawnInitLimit = false -- By default, no InitLimit
- self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time.
- self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned.
- self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false.
- self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned.
- self.AIOnOff = true -- The AI is on by default when spawning a group.
- self.SpawnUnControlled = false
- self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name.
- self.DelayOnOff = false -- No intial delay when spawning the first group.
- self.Grouping = nil -- No grouping
+ -- Inherits from BASE
+ local self = BASE:Inherit( self, FSM_SET:New( SET_GROUP:New() ) ) -- AI.AI_Balancer#AI_BALANCER
+
+ -- TODO: Define the OnAfterSpawned event
+ self:SetStartState( "None" )
+ self:AddTransition( "*", "Monitor", "Monitoring" )
+ self:AddTransition( "*", "Spawn", "Spawning" )
+ self:AddTransition( "Spawning", "Spawned", "Spawned" )
+ self:AddTransition( "*", "Destroy", "Destroying" )
+ self:AddTransition( "*", "Return", "Returning" )
+
+ self.SetClient = SetClient
+ self.SetClient:FilterOnce()
+ self.SpawnAI = SpawnAI
+
+ self.SpawnQueue = {}
- self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned.
- else
- error( "SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" )
- end
-
- self:SetEventPriority( 5 )
+ self.ToNearestAirbase = false
+ self.ToHomeAirbase = false
+
+ self:__Monitor( 1 )
return self
end
---- Creates a new SPAWN instance to create new groups based on the defined template and using a new alias for each new group.
--- @param #SPAWN self
--- @param #string SpawnTemplatePrefix is the name of the Group in the ME that defines the Template.
--- @param #string SpawnAliasPrefix is the name that will be given to the Group at runtime.
--- @return #SPAWN
--- @usage
--- -- NATO helicopters engaging in the battle field.
--- Spawn_BE_KA50 = SPAWN:NewWithAlias( 'BE KA-50@RAMP-Ground Defense', 'Helicopter Attacking a City' )
--- @usage local PlaneWithAlias = SPAWN:NewWithAlias( "Plane", "Bomber" ) -- Creates a new local variable that can instantiate new planes with the name "Bomber#ddd" using the template "Plane" as defined within the ME.
-function SPAWN:NewWithAlias( SpawnTemplatePrefix, SpawnAliasPrefix )
- local self = BASE:Inherit( self, BASE:New() )
- self:F( { SpawnTemplatePrefix, SpawnAliasPrefix } )
-
- local TemplateGroup = Group.getByName( SpawnTemplatePrefix )
- if TemplateGroup then
- self.SpawnTemplatePrefix = SpawnTemplatePrefix
- self.SpawnAliasPrefix = SpawnAliasPrefix
- self.SpawnIndex = 0
- self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart.
- self.AliveUnits = 0 -- Contains the counter how many units are currently alive
- self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not.
- self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!!
- self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning.
- self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts.
- self.SpawnInitLimit = false -- By default, no InitLimit
- self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time.
- self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned.
- self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false.
- self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned.
- self.AIOnOff = true -- The AI is on by default when spawning a group.
- self.SpawnUnControlled = false
- self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name.
- self.DelayOnOff = false -- No intial delay when spawning the first group.
- self.Grouping = nil
+--- Sets the earliest to the latest interval in seconds how long AI_BALANCER will wait to spawn a new AI.
+-- Provide 2 identical seconds if the interval should be a fixed amount of seconds.
+-- @param #AI_BALANCER self
+-- @param #number Earliest The earliest a new AI can be spawned in seconds.
+-- @param #number Latest The latest a new AI can be spawned in seconds.
+-- @return self
+function AI_BALANCER:InitSpawnInterval( Earliest, Latest )
- self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned.
- else
- error( "SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" )
- end
-
- self:SetEventPriority( 5 )
+ self.Earliest = Earliest
+ self.Latest = Latest
return self
end
+--- Returns the AI to the nearest friendly @{Airbase#AIRBASE}.
+-- @param #AI_BALANCER self
+-- @param Dcs.DCSTypes#Distance ReturnThresholdRange If there is an enemy @{Client#CLIENT} within the ReturnThresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}.
+-- @param Core.Set#SET_AIRBASE ReturnAirbaseSet The SET of @{Set#SET_AIRBASE}s to evaluate where to return to.
+function AI_BALANCER:ReturnToNearestAirbases( ReturnThresholdRange, ReturnAirbaseSet )
---- Limits the Maximum amount of Units that can be alive at the same time, and the maximum amount of groups that can be spawned.
--- Note that this method is exceptionally important to balance the performance of the mission. Depending on the machine etc, a mission can only process a maximum amount of units.
--- If the time interval must be short, but there should not be more Units or Groups alive than a maximum amount of units, then this method should be used...
--- When a @{#SPAWN.New} is executed and the limit of the amount of units alive is reached, then no new spawn will happen of the group, until some of these units of the spawn object will be destroyed.
--- @param #SPAWN self
--- @param #number SpawnMaxUnitsAlive The maximum amount of units that can be alive at runtime.
--- @param #number SpawnMaxGroups The maximum amount of groups that can be spawned. When the limit is reached, then no more actual spawns will happen of the group.
--- This parameter is useful to define a maximum amount of airplanes, ground troops, helicopters, ships etc within a supply area.
--- This parameter accepts the value 0, which defines that there are no maximum group limits, but there are limits on the maximum of units that can be alive at the same time.
--- @return #SPAWN self
--- @usage
--- -- NATO helicopters engaging in the battle field.
--- -- This helicopter group consists of one Unit. So, this group will SPAWN maximum 2 groups simultaneously within the DCSRTE.
--- -- There will be maximum 24 groups spawned during the whole mission lifetime.
--- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitLimit( 2, 24 )
-function SPAWN:InitLimit( SpawnMaxUnitsAlive, SpawnMaxGroups )
- self:F( { self.SpawnTemplatePrefix, SpawnMaxUnitsAlive, SpawnMaxGroups } )
-
- self.SpawnInitLimit = true
- self.SpawnMaxUnitsAlive = SpawnMaxUnitsAlive -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time.
- self.SpawnMaxGroups = SpawnMaxGroups -- The maximum amount of groups that can be spawned.
-
- for SpawnGroupID = 1, self.SpawnMaxGroups do
- self:_InitializeSpawnGroups( SpawnGroupID )
- end
-
- return self
+ self.ToNearestAirbase = true
+ self.ReturnThresholdRange = ReturnThresholdRange
+ self.ReturnAirbaseSet = ReturnAirbaseSet
end
---- Keeps the unit names as defined within the mission editor,
--- but note that anything after a # mark is ignored,
--- and any spaces before and after the resulting name are removed.
--- IMPORTANT! This method MUST be the first used after :New !!!
--- @param #SPAWN self
--- @return #SPAWN self
-function SPAWN:InitKeepUnitNames()
- self:F( )
+--- Returns the AI to the home @{Airbase#AIRBASE}.
+-- @param #AI_BALANCER self
+-- @param Dcs.DCSTypes#Distance ReturnThresholdRange If there is an enemy @{Client#CLIENT} within the ReturnThresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}.
+function AI_BALANCER:ReturnToHomeAirbase( ReturnThresholdRange )
- self.SpawnInitKeepUnitNames = true
-
- return self
+ self.ToHomeAirbase = true
+ self.ReturnThresholdRange = ReturnThresholdRange
end
+--- @param #AI_BALANCER self
+-- @param Core.Set#SET_GROUP SetGroup
+-- @param #string ClientName
+-- @param Wrapper.Group#GROUP AIGroup
+function AI_BALANCER:onenterSpawning( SetGroup, From, Event, To, ClientName )
---- Randomizes the defined route of the SpawnTemplatePrefix group in the ME. This is very useful to define extra variation of the behaviour of groups.
--- @param #SPAWN self
--- @param #number SpawnStartPoint is the waypoint where the randomization begins.
--- Note that the StartPoint = 0 equaling the point where the group is spawned.
--- @param #number SpawnEndPoint is the waypoint where the randomization ends counting backwards.
--- This parameter is useful to avoid randomization to end at a waypoint earlier than the last waypoint on the route.
--- @param #number SpawnRadius is the radius in meters in which the randomization of the new waypoints, with the original waypoint of the original template located in the middle ...
--- @param #number SpawnHeight (optional) Specifies the **additional** height in meters that can be added to the base height specified at each waypoint in the ME.
--- @return #SPAWN
--- @usage
--- -- NATO helicopters engaging in the battle field.
--- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP).
--- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter.
--- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters.
--- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 )
-function SPAWN:InitRandomizeRoute( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight )
- self:F( { self.SpawnTemplatePrefix, SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight } )
-
- self.SpawnRandomizeRoute = true
- self.SpawnRandomizeRouteStartPoint = SpawnStartPoint
- self.SpawnRandomizeRouteEndPoint = SpawnEndPoint
- self.SpawnRandomizeRouteRadius = SpawnRadius
- self.SpawnRandomizeRouteHeight = SpawnHeight
-
- for GroupID = 1, self.SpawnMaxGroups do
- self:_RandomizeRoute( GroupID )
- end
-
- return self
-end
-
---- Randomizes the position of @{Group}s that are spawned within a **radius band**, given an Outer and Inner radius, from the point that the spawn happens.
--- @param #SPAWN self
--- @param #boolean RandomizePosition If true, SPAWN will perform the randomization of the @{Group}s position between a given outer and inner radius.
--- @param Dcs.DCSTypes#Distance OuterRadius (optional) The outer radius in meters where the new group will be spawned.
--- @param Dcs.DCSTypes#Distance InnerRadius (optional) The inner radius in meters where the new group will NOT be spawned.
--- @return #SPAWN
-function SPAWN:InitRandomizePosition( RandomizePosition, OuterRadius, InnerRadius )
- self:F( { self.SpawnTemplatePrefix, RandomizePosition, OuterRadius, InnerRadius } )
-
- self.SpawnRandomizePosition = RandomizePosition or false
- self.SpawnRandomizePositionOuterRadius = OuterRadius or 0
- self.SpawnRandomizePositionInnerRadius = InnerRadius or 0
-
- for GroupID = 1, self.SpawnMaxGroups do
- self:_RandomizeRoute( GroupID )
- end
-
- return self
-end
-
-
---- Randomizes the UNITs that are spawned within a radius band given an Outer and Inner radius.
--- @param #SPAWN self
--- @param #boolean RandomizeUnits If true, SPAWN will perform the randomization of the @{UNIT}s position within the group between a given outer and inner radius.
--- @param Dcs.DCSTypes#Distance OuterRadius (optional) The outer radius in meters where the new group will be spawned.
--- @param Dcs.DCSTypes#Distance InnerRadius (optional) The inner radius in meters where the new group will NOT be spawned.
--- @return #SPAWN
--- @usage
--- -- NATO helicopters engaging in the battle field.
--- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP).
--- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter.
--- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters.
--- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 )
-function SPAWN:InitRandomizeUnits( RandomizeUnits, OuterRadius, InnerRadius )
- self:F( { self.SpawnTemplatePrefix, RandomizeUnits, OuterRadius, InnerRadius } )
-
- self.SpawnRandomizeUnits = RandomizeUnits or false
- self.SpawnOuterRadius = OuterRadius or 0
- self.SpawnInnerRadius = InnerRadius or 0
-
- for GroupID = 1, self.SpawnMaxGroups do
- self:_RandomizeRoute( GroupID )
- end
-
- return self
-end
-
---- This method is rather complicated to understand. But I'll try to explain.
--- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor,
--- but they will all follow the same Template route and have the same prefix name.
--- In other words, this method randomizes between a defined set of groups the template to be used for each new spawn of a group.
--- @param #SPAWN self
--- @param #string SpawnTemplatePrefixTable A table with the names of the groups defined within the mission editor, from which one will be choosen when a new group will be spawned.
--- @return #SPAWN
--- @usage
--- -- NATO Tank Platoons invading Gori.
--- -- Choose between 13 different 'US Tank Platoon' configurations for each new SPAWN the Group to be spawned for the
--- -- 'US Tank Platoon Left', 'US Tank Platoon Middle' and 'US Tank Platoon Right' SpawnTemplatePrefixes.
--- -- Each new SPAWN will randomize the route, with a defined time interval of 200 seconds with 40% time variation (randomization) and
--- -- with a limit set of maximum 12 Units alive simulteneously and 150 Groups to be spawned during the whole mission.
--- Spawn_US_Platoon = { 'US Tank Platoon 1', 'US Tank Platoon 2', 'US Tank Platoon 3', 'US Tank Platoon 4', 'US Tank Platoon 5',
--- 'US Tank Platoon 6', 'US Tank Platoon 7', 'US Tank Platoon 8', 'US Tank Platoon 9', 'US Tank Platoon 10',
--- 'US Tank Platoon 11', 'US Tank Platoon 12', 'US Tank Platoon 13' }
--- Spawn_US_Platoon_Left = SPAWN:New( 'US Tank Platoon Left' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 )
--- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 )
--- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 )
-function SPAWN:InitRandomizeTemplate( SpawnTemplatePrefixTable )
- self:F( { self.SpawnTemplatePrefix, SpawnTemplatePrefixTable } )
-
- self.SpawnTemplatePrefixTable = SpawnTemplatePrefixTable
- self.SpawnRandomizeTemplate = true
-
- for SpawnGroupID = 1, self.SpawnMaxGroups do
- self:_RandomizeTemplate( SpawnGroupID )
- end
-
- return self
-end
-
---- When spawning a new group, make the grouping of the units according the InitGrouping setting.
--- @param #SPAWN self
--- @param #number Grouping Indicates the maximum amount of units in the group.
--- @return #SPAWN
-function SPAWN:InitGrouping( Grouping ) -- R2.2
- self:F( { self.SpawnTemplatePrefix, Grouping } )
-
- self.SpawnGrouping = Grouping
-
- return self
-end
-
-
-
---TODO: Add example.
---- This method provides the functionality to randomize the spawning of the Groups at a given list of zones of different types.
--- @param #SPAWN self
--- @param #table SpawnZoneTable A table with @{Zone} objects. If this table is given, then each spawn will be executed within the given list of @{Zone}s objects.
--- @return #SPAWN
--- @usage
--- -- NATO Tank Platoons invading Gori.
--- -- Choose between 3 different zones for each new SPAWN the Group to be executed, regardless of the zone type.
-function SPAWN:InitRandomizeZones( SpawnZoneTable )
- self:F( { self.SpawnTemplatePrefix, SpawnZoneTable } )
-
- self.SpawnZoneTable = SpawnZoneTable
- self.SpawnRandomizeZones = true
-
- for SpawnGroupID = 1, self.SpawnMaxGroups do
- self:_RandomizeZones( SpawnGroupID )
- end
-
- return self
-end
-
-
-
-
-
---- For planes and helicopters, when these groups go home and land on their home airbases and farps, they normally would taxi to the parking spot, shut-down their engines and wait forever until the Group is removed by the runtime environment.
--- This method is used to re-spawn automatically (so no extra call is needed anymore) the same group after it has landed.
--- This will enable a spawned group to be re-spawned after it lands, until it is destroyed...
--- Note: When the group is respawned, it will re-spawn from the original airbase where it took off.
--- So ensure that the routes for groups that respawn, always return to the original airbase, or players may get confused ...
--- @param #SPAWN self
--- @return #SPAWN self
--- @usage
--- -- RU Su-34 - AI Ship Attack
--- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically.
--- SpawnRU_SU34 = SPAWN:New( 'TF1 RU Su-34 Krymsk@AI - Attack Ships' ):Schedule( 2, 3, 1800, 0.4 ):SpawnUncontrolled():InitRandomizeRoute( 1, 1, 3000 ):RepeatOnEngineShutDown()
-function SPAWN:InitRepeat()
- self:F( { self.SpawnTemplatePrefix, self.SpawnIndex } )
-
- self.Repeat = true
- self.RepeatOnEngineShutDown = false
- self.RepeatOnLanding = true
-
- return self
-end
-
---- Respawn group after landing.
--- @param #SPAWN self
--- @return #SPAWN self
-function SPAWN:InitRepeatOnLanding()
- self:F( { self.SpawnTemplatePrefix } )
-
- self:InitRepeat()
- self.RepeatOnEngineShutDown = false
- self.RepeatOnLanding = true
-
- return self
-end
-
-
---- Respawn after landing when its engines have shut down.
--- @param #SPAWN self
--- @return #SPAWN self
-function SPAWN:InitRepeatOnEngineShutDown()
- self:F( { self.SpawnTemplatePrefix } )
-
- self:InitRepeat()
- self.RepeatOnEngineShutDown = true
- self.RepeatOnLanding = false
-
- return self
-end
-
-
---- CleanUp groups when they are still alive, but inactive.
--- When groups are still alive and have become inactive due to damage and are unable to contribute anything, then this group will be removed at defined intervals in seconds.
--- @param #SPAWN self
--- @param #string SpawnCleanUpInterval The interval to check for inactive groups within seconds.
--- @return #SPAWN self
--- @usage Spawn_Helicopter:CleanUp( 20 ) -- CleanUp the spawning of the helicopters every 20 seconds when they become inactive.
-function SPAWN:InitCleanUp( SpawnCleanUpInterval )
- self:F( { self.SpawnTemplatePrefix, SpawnCleanUpInterval } )
-
- self.SpawnCleanUpInterval = SpawnCleanUpInterval
- self.SpawnCleanUpTimeStamps = {}
-
- local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup()
- self:T( { "CleanUp Scheduler:", SpawnGroup } )
-
- --self.CleanUpFunction = routines.scheduleFunction( self._SpawnCleanUpScheduler, { self }, timer.getTime() + 1, SpawnCleanUpInterval )
- self.CleanUpScheduler = SCHEDULER:New( self, self._SpawnCleanUpScheduler, {}, 1, SpawnCleanUpInterval, 0.2 )
- return self
-end
-
-
-
---- Makes the groups visible before start (like a batallion).
--- The method will take the position of the group as the first position in the array.
--- @param #SPAWN self
--- @param #number SpawnAngle The angle in degrees how the groups and each unit of the group will be positioned.
--- @param #number SpawnWidth The amount of Groups that will be positioned on the X axis.
--- @param #number SpawnDeltaX The space between each Group on the X-axis.
--- @param #number SpawnDeltaY The space between each Group on the Y-axis.
--- @return #SPAWN self
--- @usage
--- -- Define an array of Groups.
--- Spawn_BE_Ground = SPAWN:New( 'BE Ground' ):InitLimit( 2, 24 ):InitArray( 90, "Diamond", 10, 100, 50 )
-function SPAWN:InitArray( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY )
- self:F( { self.SpawnTemplatePrefix, SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY } )
-
- self.SpawnVisible = true -- When the first Spawn executes, all the Groups need to be made visible before start.
-
- local SpawnX = 0
- local SpawnY = 0
- local SpawnXIndex = 0
- local SpawnYIndex = 0
-
- for SpawnGroupID = 1, self.SpawnMaxGroups do
- self:T( { SpawnX, SpawnY, SpawnXIndex, SpawnYIndex } )
-
- self.SpawnGroups[SpawnGroupID].Visible = true
- self.SpawnGroups[SpawnGroupID].Spawned = false
+ -- OK, Spawn a new group from the default SpawnAI object provided.
+ local AIGroup = self.SpawnAI:Spawn() -- Wrapper.Group#GROUP
+ if AIGroup then
+ AIGroup:E( "Spawning new AIGroup" )
+ --TODO: need to rework UnitName thing ...
- SpawnXIndex = SpawnXIndex + 1
- if SpawnWidth and SpawnWidth ~= 0 then
- if SpawnXIndex >= SpawnWidth then
- SpawnXIndex = 0
- SpawnYIndex = SpawnYIndex + 1
- end
- end
-
- local SpawnRootX = self.SpawnGroups[SpawnGroupID].SpawnTemplate.x
- local SpawnRootY = self.SpawnGroups[SpawnGroupID].SpawnTemplate.y
+ SetGroup:Add( ClientName, AIGroup )
+ self.SpawnQueue[ClientName] = nil
- self:_TranslateRotate( SpawnGroupID, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle )
-
- self.SpawnGroups[SpawnGroupID].SpawnTemplate.lateActivation = true
- self.SpawnGroups[SpawnGroupID].SpawnTemplate.visible = true
-
- self.SpawnGroups[SpawnGroupID].Visible = true
-
- self:HandleEvent( EVENTS.Birth, self._OnBirth )
- self:HandleEvent( EVENTS.Dead, self._OnDeadOrCrash )
- self:HandleEvent( EVENTS.Crash, self._OnDeadOrCrash )
- if self.Repeat then
- self:HandleEvent( EVENTS.Takeoff, self._OnTakeOff )
- self:HandleEvent( EVENTS.Land, self._OnLand )
- end
- if self.RepeatOnEngineShutDown then
- self:HandleEvent( EVENTS.EngineShutdown, self._OnEngineShutDown )
- end
-
- self.SpawnGroups[SpawnGroupID].Group = _DATABASE:Spawn( self.SpawnGroups[SpawnGroupID].SpawnTemplate )
-
- SpawnX = SpawnXIndex * SpawnDeltaX
- SpawnY = SpawnYIndex * SpawnDeltaY
+ -- Fire the Spawned event. The first parameter is the AIGroup just Spawned.
+ -- Mission designers can catch this event to bind further actions to the AIGroup.
+ self:Spawned( AIGroup )
end
-
- return self
end
-do -- AI methods
- --- Turns the AI On or Off for the @{Group} when spawning.
- -- @param #SPAWN self
- -- @param #boolean AIOnOff A value of true sets the AI On, a value of false sets the AI Off.
- -- @return #SPAWN The SPAWN object
- function SPAWN:InitAIOnOff( AIOnOff )
-
- self.AIOnOff = AIOnOff
- return self
- end
-
- --- Turns the AI On for the @{Group} when spawning.
- -- @param #SPAWN self
- -- @return #SPAWN The SPAWN object
- function SPAWN:InitAIOn()
-
- return self:InitAIOnOff( true )
- end
-
- --- Turns the AI Off for the @{Group} when spawning.
- -- @param #SPAWN self
- -- @return #SPAWN The SPAWN object
- function SPAWN:InitAIOff()
-
- return self:InitAIOnOff( false )
- end
+--- @param #AI_BALANCER self
+-- @param Core.Set#SET_GROUP SetGroup
+-- @param Wrapper.Group#GROUP AIGroup
+function AI_BALANCER:onenterDestroying( SetGroup, From, Event, To, ClientName, AIGroup )
-end -- AI methods
-
-do -- Delay methods
- --- Turns the Delay On or Off for the first @{Group} scheduled spawning.
- -- The default value is that for scheduled spawning, there is an initial delay when spawning the first @{Group}.
- -- @param #SPAWN self
- -- @param #boolean DelayOnOff A value of true sets the Delay On, a value of false sets the Delay Off.
- -- @return #SPAWN The SPAWN object
- function SPAWN:InitDelayOnOff( DelayOnOff )
-
- self.DelayOnOff = DelayOnOff
- return self
- end
-
- --- Turns the Delay On for the @{Group} when spawning.
- -- @param #SPAWN self
- -- @return #SPAWN The SPAWN object
- function SPAWN:InitDelayOn()
-
- return self:InitDelayOnOff( true )
- end
-
- --- Turns the Delay Off for the @{Group} when spawning.
- -- @param #SPAWN self
- -- @return #SPAWN The SPAWN object
- function SPAWN:InitDelayOff()
-
- return self:InitDelayOnOff( false )
- end
-
-end -- Delay methods
-
---- Will spawn a group based on the internal index.
--- Note: Uses @{DATABASE} module defined in MOOSE.
--- @param #SPAWN self
--- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions.
-function SPAWN:Spawn()
- self:F( { self.SpawnTemplatePrefix, self.SpawnIndex, self.AliveUnits } )
-
- return self:SpawnWithIndex( self.SpawnIndex + 1 )
+ AIGroup:Destroy()
+ SetGroup:Flush()
+ SetGroup:Remove( ClientName )
+ SetGroup:Flush()
end
---- Will re-spawn a group based on a given index.
--- Note: Uses @{DATABASE} module defined in MOOSE.
--- @param #SPAWN self
--- @param #string SpawnIndex The index of the group to be spawned.
--- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions.
-function SPAWN:ReSpawn( SpawnIndex )
- self:F( { self.SpawnTemplatePrefix, SpawnIndex } )
-
- if not SpawnIndex then
- SpawnIndex = 1
- end
+--- @param #AI_BALANCER self
+-- @param Core.Set#SET_GROUP SetGroup
+-- @param Wrapper.Group#GROUP AIGroup
+function AI_BALANCER:onenterReturning( SetGroup, From, Event, To, AIGroup )
--- TODO: This logic makes DCS crash and i don't know why (yet).
- local SpawnGroup = self:GetGroupFromIndex( SpawnIndex )
- local WayPoints = SpawnGroup and SpawnGroup.WayPoints or nil
- if SpawnGroup then
- local SpawnDCSGroup = SpawnGroup:GetDCSObject()
- if SpawnDCSGroup then
- SpawnGroup:Destroy()
- end
- end
-
- local SpawnGroup = self:SpawnWithIndex( SpawnIndex )
- if SpawnGroup and WayPoints then
- -- If there were WayPoints set, then Re-Execute those WayPoints!
- SpawnGroup:WayPointInitialize( WayPoints )
- SpawnGroup:WayPointExecute( 1, 5 )
- end
-
- if SpawnGroup.ReSpawnFunction then
- SpawnGroup:ReSpawnFunction()
- end
-
- SpawnGroup:ResetEvents()
-
- return SpawnGroup
-end
-
---- Will spawn a group with a specified index number.
--- Uses @{DATABASE} global object defined in MOOSE.
--- @param #SPAWN self
--- @param #string SpawnIndex The index of the group to be spawned.
--- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions.
-function SPAWN:SpawnWithIndex( SpawnIndex )
- self:F2( { SpawnTemplatePrefix = self.SpawnTemplatePrefix, SpawnIndex = SpawnIndex, AliveUnits = self.AliveUnits, SpawnMaxGroups = self.SpawnMaxGroups } )
-
- if self:_GetSpawnIndex( SpawnIndex ) then
-
- if self.SpawnGroups[self.SpawnIndex].Visible then
- self.SpawnGroups[self.SpawnIndex].Group:Activate()
+ local AIGroupTemplate = AIGroup:GetTemplate()
+ if self.ToHomeAirbase == true then
+ local WayPointCount = #AIGroupTemplate.route.points
+ local SwitchWayPointCommand = AIGroup:CommandSwitchWayPoint( 1, WayPointCount, 1 )
+ AIGroup:SetCommand( SwitchWayPointCommand )
+ AIGroup:MessageToRed( "Returning to home base ...", 30 )
else
-
- local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate
- self:T( SpawnTemplate.name )
-
- if SpawnTemplate then
-
- local PointVec3 = POINT_VEC3:New( SpawnTemplate.route.points[1].x, SpawnTemplate.route.points[1].alt, SpawnTemplate.route.points[1].y )
- self:T( { "Current point of ", self.SpawnTemplatePrefix, PointVec3 } )
-
- -- If RandomizePosition, then Randomize the formation in the zone band, keeping the template.
- if self.SpawnRandomizePosition then
- local RandomVec2 = PointVec3:GetRandomVec2InRadius( self.SpawnRandomizePositionOuterRadius, self.SpawnRandomizePositionInnerRadius )
- local CurrentX = SpawnTemplate.units[1].x
- local CurrentY = SpawnTemplate.units[1].y
- SpawnTemplate.x = RandomVec2.x
- SpawnTemplate.y = RandomVec2.y
- for UnitID = 1, #SpawnTemplate.units do
- SpawnTemplate.units[UnitID].x = SpawnTemplate.units[UnitID].x + ( RandomVec2.x - CurrentX )
- SpawnTemplate.units[UnitID].y = SpawnTemplate.units[UnitID].y + ( RandomVec2.y - CurrentY )
- self:T( 'SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
- end
- end
-
- -- If RandomizeUnits, then Randomize the formation at the start point.
- if self.SpawnRandomizeUnits then
- for UnitID = 1, #SpawnTemplate.units do
- local RandomVec2 = PointVec3:GetRandomVec2InRadius( self.SpawnOuterRadius, self.SpawnInnerRadius )
- SpawnTemplate.units[UnitID].x = RandomVec2.x
- SpawnTemplate.units[UnitID].y = RandomVec2.y
- self:T( 'SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
- end
- end
-
- if SpawnTemplate.CategoryID == Group.Category.HELICOPTER or SpawnTemplate.CategoryID == Group.Category.AIRPLANE then
- if SpawnTemplate.route.points[1].type == "TakeOffParking" then
- SpawnTemplate.uncontrolled = self.SpawnUnControlled
- end
- end
- end
-
- self:HandleEvent( EVENTS.Birth, self._OnBirth )
- self:HandleEvent( EVENTS.Dead, self._OnDeadOrCrash )
- self:HandleEvent( EVENTS.Crash, self._OnDeadOrCrash )
- if self.Repeat then
- self:HandleEvent( EVENTS.Takeoff, self._OnTakeOff )
- self:HandleEvent( EVENTS.Land, self._OnLand )
- end
- if self.RepeatOnEngineShutDown then
- self:HandleEvent( EVENTS.EngineShutdown, self._OnEngineShutDown )
- end
-
- self.SpawnGroups[self.SpawnIndex].Group = _DATABASE:Spawn( SpawnTemplate )
-
- local SpawnGroup = self.SpawnGroups[self.SpawnIndex].Group -- Wrapper.Group#GROUP
-
- --TODO: Need to check if this function doesn't need to be scheduled, as the group may not be immediately there!
- if SpawnGroup then
-
- SpawnGroup:SetAIOnOff( self.AIOnOff )
- end
-
- self:T3( SpawnTemplate.name )
-
- -- If there is a SpawnFunction hook defined, call it.
- if self.SpawnFunctionHook then
- -- delay calling this for .1 seconds so that it hopefully comes after the BIRTH event of the group.
- self.SpawnHookScheduler = SCHEDULER:New()
- self.SpawnHookScheduler:Schedule( nil, self.SpawnFunctionHook, { self.SpawnGroups[self.SpawnIndex].Group, unpack( self.SpawnFunctionArguments)}, 0.1 )
- -- self.SpawnFunctionHook( self.SpawnGroups[self.SpawnIndex].Group, unpack( self.SpawnFunctionArguments ) )
- end
- -- TODO: Need to fix this by putting an "R" in the name of the group when the group repeats.
- --if self.Repeat then
- -- _DATABASE:SetStatusGroup( SpawnTemplate.name, "ReSpawn" )
- --end
+ -- Okay, we need to send this Group back to the nearest base of the Coalition of the AI.
+ --TODO: i need to rework the POINT_VEC2 thing.
+ local PointVec2 = POINT_VEC2:New( AIGroup:GetVec2().x, AIGroup:GetVec2().y )
+ local ClosestAirbase = self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2( PointVec2 )
+ self:T( ClosestAirbase.AirbaseName )
+ AIGroup:MessageToRed( "Returning to " .. ClosestAirbase:GetName().. " ...", 30 )
+ local RTBRoute = AIGroup:RouteReturnToAirbase( ClosestAirbase )
+ AIGroupTemplate.route = RTBRoute
+ AIGroup:Respawn( AIGroupTemplate )
end
-
- self.SpawnGroups[self.SpawnIndex].Spawned = true
- return self.SpawnGroups[self.SpawnIndex].Group
- else
- --self:E( { self.SpawnTemplatePrefix, "No more Groups to Spawn:", SpawnIndex, self.SpawnMaxGroups } )
- end
- return nil
-end
-
---- Spawns new groups at varying time intervals.
--- This is useful if you want to have continuity within your missions of certain (AI) groups to be present (alive) within your missions.
--- @param #SPAWN self
--- @param #number SpawnTime The time interval defined in seconds between each new spawn of new groups.
--- @param #number SpawnTimeVariation The variation to be applied on the defined time interval between each new spawn.
--- The variation is a number between 0 and 1, representing the %-tage of variation to be applied on the time interval.
--- @return #SPAWN self
--- @usage
--- -- NATO helicopters engaging in the battle field.
--- -- The time interval is set to SPAWN new helicopters between each 600 seconds, with a time variation of 50%.
--- -- The time variation in this case will be between 450 seconds and 750 seconds.
--- -- This is calculated as follows:
--- -- Low limit: 600 * ( 1 - 0.5 / 2 ) = 450
--- -- High limit: 600 * ( 1 + 0.5 / 2 ) = 750
--- -- Between these two values, a random amount of seconds will be choosen for each new spawn of the helicopters.
--- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):Schedule( 600, 0.5 )
-function SPAWN:SpawnScheduled( SpawnTime, SpawnTimeVariation )
- self:F( { SpawnTime, SpawnTimeVariation } )
-
- if SpawnTime ~= nil and SpawnTimeVariation ~= nil then
- local InitialDelay = 0
- if self.DelayOnOff == true then
- InitialDelay = math.random( SpawnTime - SpawnTime * SpawnTimeVariation, SpawnTime + SpawnTime * SpawnTimeVariation )
- end
- self.SpawnScheduler = SCHEDULER:New( self, self._Scheduler, {}, InitialDelay, SpawnTime, SpawnTimeVariation )
- end
-
- return self
-end
-
---- Will re-start the spawning scheduler.
--- Note: This method is only required to be called when the schedule was stopped.
--- @param #SPAWN self
--- @return #SPAWN
-function SPAWN:SpawnScheduleStart()
- self:F( { self.SpawnTemplatePrefix } )
-
- self.SpawnScheduler:Start()
- return self
-end
-
---- Will stop the scheduled spawning scheduler.
--- @param #SPAWN self
--- @return #SPAWN
-function SPAWN:SpawnScheduleStop()
- self:F( { self.SpawnTemplatePrefix } )
-
- self.SpawnScheduler:Stop()
- return self
end
---- Allows to place a CallFunction hook when a new group spawns.
--- The provided method will be called when a new group is spawned, including its given parameters.
--- The first parameter of the SpawnFunction is the @{Group#GROUP} that was spawned.
--- @param #SPAWN self
--- @param #function SpawnCallBackFunction The function to be called when a group spawns.
--- @param SpawnFunctionArguments A random amount of arguments to be provided to the function when the group spawns.
--- @return #SPAWN
--- @usage
--- -- Declare SpawnObject and call a function when a new Group is spawned.
--- local SpawnObject = SPAWN
--- :New( "SpawnObject" )
--- :InitLimit( 2, 10 )
--- :OnSpawnGroup(
--- function( SpawnGroup )
--- SpawnGroup:E( "I am spawned" )
--- end
--- )
--- :SpawnScheduled( 300, 0.3 )
-function SPAWN:OnSpawnGroup( SpawnCallBackFunction, ... )
- self:F( "OnSpawnGroup" )
+--- @param #AI_BALANCER self
+function AI_BALANCER:onenterMonitoring( SetGroup )
- self.SpawnFunctionHook = SpawnCallBackFunction
- self.SpawnFunctionArguments = {}
- if arg then
- self.SpawnFunctionArguments = arg
- end
+ self:T2( { self.SetClient:Count() } )
+ --self.SetClient:Flush()
- return self
-end
+ self.SetClient:ForEachClient(
+ --- @param Wrapper.Client#CLIENT Client
+ function( Client )
+ self:T3(Client.ClientName)
---- Will spawn a group at an airbase.
--- This method is mostly advisable to be used if you want to simulate spawning units at an airbase.
--- Note that each point in the route assigned to the spawning group is reset to the point of the spawn.
--- You can use the returned group to further define the route to be followed.
--- @param #SPAWN self
--- @param Wrapper.Airbase#AIRBASE Airbase The @{Airbase} where to spawn the group.
--- @param #SPAWN.Takeoff Takeoff (optional) The location and takeoff method. Default is Hot.
--- @param #number TakeoffAltitude (optional) The altitude above the ground.
--- @return Wrapper.Group#GROUP that was spawned.
--- @return #nil Nothing was spawned.
-function SPAWN:SpawnAtAirbase( Airbase, Takeoff, TakeoffAltitude ) -- R2.2
- self:E( { self.SpawnTemplatePrefix, Airbase, Takeoff, TakeoffAltitude } )
+ local AIGroup = self.Set:Get( Client.UnitName ) -- Wrapper.Group#GROUP
+ if Client:IsAlive() then
- local PointVec3 = Airbase:GetPointVec3()
- self:T2(PointVec3)
+ if AIGroup and AIGroup:IsAlive() == true then
- Takeoff = Takeoff or SPAWN.Takeoff.Hot
-
- if self:_GetSpawnIndex( self.SpawnIndex + 1 ) then
-
- local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate
-
- if SpawnTemplate then
-
- self:T( { "Current point of ", self.SpawnTemplatePrefix, Airbase } )
-
- -- Translate the position of the Group Template to the Vec3.
- for UnitID = 1, #SpawnTemplate.units do
- self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
- local UnitTemplate = SpawnTemplate.units[UnitID]
- local SX = UnitTemplate.x
- local SY = UnitTemplate.y
- local BX = SpawnTemplate.route.points[1].x
- local BY = SpawnTemplate.route.points[1].y
- local TX = PointVec3.x + ( SX - BX )
- local TY = PointVec3.z + ( SY - BY )
- SpawnTemplate.units[UnitID].x = TX
- SpawnTemplate.units[UnitID].y = TY
- if Takeoff == GROUP.Takeoff.Air then
- SpawnTemplate.units[UnitID].alt = PointVec3.y + ( TakeoffAltitude or 200 )
- else
- SpawnTemplate.units[UnitID].alt = PointVec3.y + 10
- end
- self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
- end
-
- SpawnTemplate.route.points[1].x = PointVec3.x
- SpawnTemplate.route.points[1].y = PointVec3.z
- if Takeoff == GROUP.Takeoff.Air then
- SpawnTemplate.route.points[1].alt = PointVec3.y + ( TakeoffAltitude or 200 )
- else
- SpawnTemplate.route.points[1].alt = PointVec3.y + 10
- SpawnTemplate.route.points[1].airdromeId = Airbase:GetID()
- end
- SpawnTemplate.route.points[1].type = GROUPTEMPLATE.Takeoff[Takeoff]
-
- SpawnTemplate.x = PointVec3.x
- SpawnTemplate.y = PointVec3.z
-
- return self:SpawnWithIndex( self.SpawnIndex )
- end
- end
-
- return nil
-end
-
-
-
---- Will spawn a group from a Vec3 in 3D space.
--- This method is mostly advisable to be used if you want to simulate spawning units in the air, like helicopters or airplanes.
--- Note that each point in the route assigned to the spawning group is reset to the point of the spawn.
--- You can use the returned group to further define the route to be followed.
--- @param #SPAWN self
--- @param Dcs.DCSTypes#Vec3 Vec3 The Vec3 coordinates where to spawn the group.
--- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
--- @return Wrapper.Group#GROUP that was spawned.
--- @return #nil Nothing was spawned.
-function SPAWN:SpawnFromVec3( Vec3, SpawnIndex )
- self:F( { self.SpawnTemplatePrefix, Vec3, SpawnIndex } )
-
- local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 )
- self:T2(PointVec3)
-
- if SpawnIndex then
- else
- SpawnIndex = self.SpawnIndex + 1
- end
-
- if self:_GetSpawnIndex( SpawnIndex ) then
-
- local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate
-
- if SpawnTemplate then
-
- self:T( { "Current point of ", self.SpawnTemplatePrefix, Vec3 } )
-
- -- Translate the position of the Group Template to the Vec3.
- for UnitID = 1, #SpawnTemplate.units do
- self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
- local UnitTemplate = SpawnTemplate.units[UnitID]
- local SX = UnitTemplate.x
- local SY = UnitTemplate.y
- local BX = SpawnTemplate.route.points[1].x
- local BY = SpawnTemplate.route.points[1].y
- local TX = Vec3.x + ( SX - BX )
- local TY = Vec3.z + ( SY - BY )
- SpawnTemplate.units[UnitID].x = TX
- SpawnTemplate.units[UnitID].y = TY
- SpawnTemplate.units[UnitID].alt = Vec3.y
- self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
- end
-
- SpawnTemplate.route.points[1].x = Vec3.x
- SpawnTemplate.route.points[1].y = Vec3.z
- SpawnTemplate.route.points[1].alt = Vec3.y
-
- SpawnTemplate.x = Vec3.x
- SpawnTemplate.y = Vec3.z
-
- return self:SpawnWithIndex( self.SpawnIndex )
- end
- end
-
- return nil
-end
-
---- Will spawn a group from a Vec2 in 3D space.
--- This method is mostly advisable to be used if you want to simulate spawning groups on the ground from air units, like vehicles.
--- Note that each point in the route assigned to the spawning group is reset to the point of the spawn.
--- You can use the returned group to further define the route to be followed.
--- @param #SPAWN self
--- @param Dcs.DCSTypes#Vec2 Vec2 The Vec2 coordinates where to spawn the group.
--- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
--- @return Wrapper.Group#GROUP that was spawned.
--- @return #nil Nothing was spawned.
-function SPAWN:SpawnFromVec2( Vec2, SpawnIndex )
- self:F( { self.SpawnTemplatePrefix, Vec2, SpawnIndex } )
-
- local PointVec2 = POINT_VEC2:NewFromVec2( Vec2 )
- return self:SpawnFromVec3( PointVec2:GetVec3(), SpawnIndex )
-end
-
-
---- Will spawn a group from a hosting unit. This method is mostly advisable to be used if you want to simulate spawning from air units, like helicopters, which are dropping infantry into a defined Landing Zone.
--- Note that each point in the route assigned to the spawning group is reset to the point of the spawn.
--- You can use the returned group to further define the route to be followed.
--- @param #SPAWN self
--- @param Wrapper.Unit#UNIT HostUnit The air or ground unit dropping or unloading the group.
--- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
--- @return Wrapper.Group#GROUP that was spawned.
--- @return #nil Nothing was spawned.
-function SPAWN:SpawnFromUnit( HostUnit, SpawnIndex )
- self:F( { self.SpawnTemplatePrefix, HostUnit, SpawnIndex } )
-
- if HostUnit and HostUnit:IsAlive() ~= nil then -- and HostUnit:getUnit(1):inAir() == false then
- return self:SpawnFromVec3( HostUnit:GetVec3(), SpawnIndex )
- end
-
- return nil
-end
-
---- Will spawn a group from a hosting static. This method is mostly advisable to be used if you want to simulate spawning from buldings and structures (static buildings).
--- You can use the returned group to further define the route to be followed.
--- @param #SPAWN self
--- @param Wrapper.Static#STATIC HostStatic The static dropping or unloading the group.
--- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
--- @return Wrapper.Group#GROUP that was spawned.
--- @return #nil Nothing was spawned.
-function SPAWN:SpawnFromStatic( HostStatic, SpawnIndex )
- self:F( { self.SpawnTemplatePrefix, HostStatic, SpawnIndex } )
-
- if HostStatic and HostStatic:IsAlive() then
- return self:SpawnFromVec3( HostStatic:GetVec3(), SpawnIndex )
- end
-
- return nil
-end
-
---- Will spawn a Group within a given @{Zone}.
--- The @{Zone} can be of any type derived from @{Zone#ZONE_BASE}.
--- Once the @{Group} is spawned within the zone, the @{Group} will continue on its route.
--- The **first waypoint** (where the group is spawned) is replaced with the zone location coordinates.
--- @param #SPAWN self
--- @param Core.Zone#ZONE Zone The zone where the group is to be spawned.
--- @param #boolean RandomizeGroup (optional) Randomization of the @{Group} position in the zone.
--- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
--- @return Wrapper.Group#GROUP that was spawned.
--- @return #nil when nothing was spawned.
-function SPAWN:SpawnInZone( Zone, RandomizeGroup, SpawnIndex )
- self:F( { self.SpawnTemplatePrefix, Zone, RandomizeGroup, SpawnIndex } )
-
- if Zone then
- if RandomizeGroup then
- return self:SpawnFromVec2( Zone:GetRandomVec2(), SpawnIndex )
- else
- return self:SpawnFromVec2( Zone:GetVec2(), SpawnIndex )
- end
- end
-
- return nil
-end
-
---- (**AIR**) Will spawn a plane group in UnControlled or Controlled mode...
--- This will be similar to the uncontrolled flag setting in the ME.
--- You can use UnControlled mode to simulate planes startup and ready for take-off but aren't moving (yet).
--- ReSpawn the plane in Controlled mode, and the plane will move...
--- @param #SPAWN self
--- @param #boolean UnControlled true if UnControlled, false if Controlled.
--- @return #SPAWN self
-function SPAWN:InitUnControlled( UnControlled )
- self:F2( { self.SpawnTemplatePrefix, UnControlled } )
-
- self.SpawnUnControlled = UnControlled
-
- for SpawnGroupID = 1, self.SpawnMaxGroups do
- self.SpawnGroups[SpawnGroupID].UnControlled = UnControlled
- end
-
- return self
-end
-
-
---- Get the Coordinate of the Group that is Late Activated as the template for the SPAWN object.
--- @param #SPAWN self
--- @return Core.Point#COORDINATE The Coordinate
-function SPAWN:GetCoordinate()
-
- local LateGroup = GROUP:FindByName( self.SpawnTemplatePrefix )
- if LateGroup then
- return LateGroup:GetCoordinate()
- end
-
- return nil
-end
-
-
---- Will return the SpawnGroupName either with with a specific count number or without any count.
--- @param #SPAWN self
--- @param #number SpawnIndex Is the number of the Group that is to be spawned.
--- @return #string SpawnGroupName
-function SPAWN:SpawnGroupName( SpawnIndex )
- self:F( { self.SpawnTemplatePrefix, SpawnIndex } )
-
- local SpawnPrefix = self.SpawnTemplatePrefix
- if self.SpawnAliasPrefix then
- SpawnPrefix = self.SpawnAliasPrefix
- end
-
- if SpawnIndex then
- local SpawnName = string.format( '%s#%03d', SpawnPrefix, SpawnIndex )
- self:T( SpawnName )
- return SpawnName
- else
- self:T( SpawnPrefix )
- return SpawnPrefix
- end
-
-end
-
---- Will find the first alive @{Group} it has spawned, and return the alive @{Group} object and the first Index where the first alive @{Group} object has been found.
--- @param #SPAWN self
--- @return Wrapper.Group#GROUP, #number The @{Group} object found, the new Index where the group was found.
--- @return #nil, #nil When no group is found, #nil is returned.
--- @usage
--- -- Find the first alive @{Group} object of the SpawnPlanes SPAWN object @{Group} collection that it has spawned during the mission.
--- local GroupPlane, Index = SpawnPlanes:GetFirstAliveGroup()
--- while GroupPlane ~= nil do
--- -- Do actions with the GroupPlane object.
--- GroupPlane, Index = SpawnPlanes:GetNextAliveGroup( Index )
--- end
-function SPAWN:GetFirstAliveGroup()
- self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } )
-
- for SpawnIndex = 1, self.SpawnCount do
- local SpawnGroup = self:GetGroupFromIndex( SpawnIndex )
- if SpawnGroup and SpawnGroup:IsAlive() then
- return SpawnGroup, SpawnIndex
- end
- end
-
- return nil, nil
-end
-
-
---- Will find the next alive @{Group} object from a given Index, and return a reference to the alive @{Group} object and the next Index where the alive @{Group} has been found.
--- @param #SPAWN self
--- @param #number SpawnIndexStart A Index holding the start position to search from. This method can also be used to find the first alive @{Group} object from the given Index.
--- @return Wrapper.Group#GROUP, #number The next alive @{Group} object found, the next Index where the next alive @{Group} object was found.
--- @return #nil, #nil When no alive @{Group} object is found from the start Index position, #nil is returned.
--- @usage
--- -- Find the first alive @{Group} object of the SpawnPlanes SPAWN object @{Group} collection that it has spawned during the mission.
--- local GroupPlane, Index = SpawnPlanes:GetFirstAliveGroup()
--- while GroupPlane ~= nil do
--- -- Do actions with the GroupPlane object.
--- GroupPlane, Index = SpawnPlanes:GetNextAliveGroup( Index )
--- end
-function SPAWN:GetNextAliveGroup( SpawnIndexStart )
- self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndexStart } )
-
- SpawnIndexStart = SpawnIndexStart + 1
- for SpawnIndex = SpawnIndexStart, self.SpawnCount do
- local SpawnGroup = self:GetGroupFromIndex( SpawnIndex )
- if SpawnGroup and SpawnGroup:IsAlive() then
- return SpawnGroup, SpawnIndex
- end
- end
-
- return nil, nil
-end
-
---- Will find the last alive @{Group} object, and will return a reference to the last live @{Group} object and the last Index where the last alive @{Group} object has been found.
--- @param #SPAWN self
--- @return Wrapper.Group#GROUP, #number The last alive @{Group} object found, the last Index where the last alive @{Group} object was found.
--- @return #nil, #nil When no alive @{Group} object is found, #nil is returned.
--- @usage
--- -- Find the last alive @{Group} object of the SpawnPlanes SPAWN object @{Group} collection that it has spawned during the mission.
--- local GroupPlane, Index = SpawnPlanes:GetLastAliveGroup()
--- if GroupPlane then -- GroupPlane can be nil!!!
--- -- Do actions with the GroupPlane object.
--- end
-function SPAWN:GetLastAliveGroup()
- self:F( { self.SpawnTemplatePrefixself.SpawnAliasPrefix } )
-
- self.SpawnIndex = self:_GetLastIndex()
- for SpawnIndex = self.SpawnIndex, 1, -1 do
- local SpawnGroup = self:GetGroupFromIndex( SpawnIndex )
- if SpawnGroup and SpawnGroup:IsAlive() then
- self.SpawnIndex = SpawnIndex
- return SpawnGroup
- end
- end
-
- self.SpawnIndex = nil
- return nil
-end
-
-
-
---- Get the group from an index.
--- Returns the group from the SpawnGroups list.
--- If no index is given, it will return the first group in the list.
--- @param #SPAWN self
--- @param #number SpawnIndex The index of the group to return.
--- @return Wrapper.Group#GROUP self
-function SPAWN:GetGroupFromIndex( SpawnIndex )
- self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndex } )
-
- if not SpawnIndex then
- SpawnIndex = 1
- end
-
- if self.SpawnGroups and self.SpawnGroups[SpawnIndex] then
- local SpawnGroup = self.SpawnGroups[SpawnIndex].Group
- return SpawnGroup
- else
- return nil
- end
-end
-
-
---- Return the prefix of a SpawnUnit.
--- The method will search for a #-mark, and will return the text before the #-mark.
--- It will return nil of no prefix was found.
--- @param #SPAWN self
--- @param Dcs.DCSWrapper.Unit#UNIT DCSUnit The @{DCSUnit} to be searched.
--- @return #string The prefix
--- @return #nil Nothing found
-function SPAWN:_GetPrefixFromGroup( SpawnGroup )
- self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnGroup } )
-
- local GroupName = SpawnGroup:GetName()
- if GroupName then
- local SpawnPrefix = string.match( GroupName, ".*#" )
- if SpawnPrefix then
- SpawnPrefix = SpawnPrefix:sub( 1, -2 )
- end
- return SpawnPrefix
- end
-
- return nil
-end
-
-
---- Get the index from a given group.
--- The function will search the name of the group for a #, and will return the number behind the #-mark.
-function SPAWN:GetSpawnIndexFromGroup( SpawnGroup )
- self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnGroup } )
-
- local IndexString = string.match( SpawnGroup:GetName(), "#(%d*)$" ):sub( 2 )
- local Index = tonumber( IndexString )
-
- self:T3( IndexString, Index )
- return Index
-
-end
-
---- Return the last maximum index that can be used.
-function SPAWN:_GetLastIndex()
- self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } )
-
- return self.SpawnMaxGroups
-end
-
---- Initalize the SpawnGroups collection.
--- @param #SPAWN self
-function SPAWN:_InitializeSpawnGroups( SpawnIndex )
- self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndex } )
-
- if not self.SpawnGroups[SpawnIndex] then
- self.SpawnGroups[SpawnIndex] = {}
- self.SpawnGroups[SpawnIndex].Visible = false
- self.SpawnGroups[SpawnIndex].Spawned = false
- self.SpawnGroups[SpawnIndex].UnControlled = false
- self.SpawnGroups[SpawnIndex].SpawnTime = 0
-
- self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix = self.SpawnTemplatePrefix
- self.SpawnGroups[SpawnIndex].SpawnTemplate = self:_Prepare( self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix, SpawnIndex )
- end
-
- self:_RandomizeTemplate( SpawnIndex )
- self:_RandomizeRoute( SpawnIndex )
- --self:_TranslateRotate( SpawnIndex )
-
- return self.SpawnGroups[SpawnIndex]
-end
-
-
-
---- Gets the CategoryID of the Group with the given SpawnPrefix
-function SPAWN:_GetGroupCategoryID( SpawnPrefix )
- local TemplateGroup = Group.getByName( SpawnPrefix )
-
- if TemplateGroup then
- return TemplateGroup:getCategory()
- else
- return nil
- end
-end
-
---- Gets the CoalitionID of the Group with the given SpawnPrefix
-function SPAWN:_GetGroupCoalitionID( SpawnPrefix )
- local TemplateGroup = Group.getByName( SpawnPrefix )
-
- if TemplateGroup then
- return TemplateGroup:getCoalition()
- else
- return nil
- end
-end
-
---- Gets the CountryID of the Group with the given SpawnPrefix
-function SPAWN:_GetGroupCountryID( SpawnPrefix )
- self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnPrefix } )
-
- local TemplateGroup = Group.getByName( SpawnPrefix )
-
- if TemplateGroup then
- local TemplateUnits = TemplateGroup:getUnits()
- return TemplateUnits[1]:getCountry()
- else
- return nil
- end
-end
-
---- Gets the Group Template from the ME environment definition.
--- This method used the @{DATABASE} object, which contains ALL initial and new spawned object in MOOSE.
--- @param #SPAWN self
--- @param #string SpawnTemplatePrefix
--- @return @SPAWN self
-function SPAWN:_GetTemplate( SpawnTemplatePrefix )
- self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnTemplatePrefix } )
-
- local SpawnTemplate = nil
-
- SpawnTemplate = routines.utils.deepCopy( _DATABASE.Templates.Groups[SpawnTemplatePrefix].Template )
-
- if SpawnTemplate == nil then
- error( 'No Template returned for SpawnTemplatePrefix = ' .. SpawnTemplatePrefix )
- end
-
- --SpawnTemplate.SpawnCoalitionID = self:_GetGroupCoalitionID( SpawnTemplatePrefix )
- --SpawnTemplate.SpawnCategoryID = self:_GetGroupCategoryID( SpawnTemplatePrefix )
- --SpawnTemplate.SpawnCountryID = self:_GetGroupCountryID( SpawnTemplatePrefix )
-
- self:T3( { SpawnTemplate } )
- return SpawnTemplate
-end
-
---- Prepares the new Group Template.
--- @param #SPAWN self
--- @param #string SpawnTemplatePrefix
--- @param #number SpawnIndex
--- @return #SPAWN self
-function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) --R2.2
- self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } )
-
- local SpawnTemplate = self:_GetTemplate( SpawnTemplatePrefix )
- SpawnTemplate.name = self:SpawnGroupName( SpawnIndex )
-
- SpawnTemplate.groupId = nil
- --SpawnTemplate.lateActivation = false
- SpawnTemplate.lateActivation = false
-
- if SpawnTemplate.CategoryID == Group.Category.GROUND then
- self:T3( "For ground units, visible needs to be false..." )
- SpawnTemplate.visible = false
- end
-
- if self.SpawnGrouping then
- local UnitAmount = #SpawnTemplate.units
- self:F( { UnitAmount = UnitAmount, SpawnGrouping = self.SpawnGrouping } )
- if UnitAmount > self.SpawnGrouping then
- for UnitID = self.SpawnGrouping + 1, UnitAmount do
- SpawnTemplate.units[UnitID] = nil
- end
- else
- if UnitAmount < self.SpawnGrouping then
- for UnitID = UnitAmount + 1, self.SpawnGrouping do
- SpawnTemplate.units[UnitID] = UTILS.DeepCopy( SpawnTemplate.units[1] )
- SpawnTemplate.units[UnitID].unitId = nil
- end
- end
- end
- end
-
- if self.SpawnInitKeepUnitNames == false then
- for UnitID = 1, #SpawnTemplate.units do
- SpawnTemplate.units[UnitID].name = string.format( SpawnTemplate.name .. '-%02d', UnitID )
- SpawnTemplate.units[UnitID].unitId = nil
- end
- else
- for UnitID = 1, #SpawnTemplate.units do
- local UnitPrefix, Rest = string.match( SpawnTemplate.units[UnitID].name, "^([^#]+)#?" ):gsub( "^%s*(.-)%s*$", "%1" )
- self:T( { UnitPrefix, Rest } )
-
- SpawnTemplate.units[UnitID].name = string.format( '%s#%03d-%02d', UnitPrefix, SpawnIndex, UnitID )
- SpawnTemplate.units[UnitID].unitId = nil
- end
- end
-
- self:T3( { "Template:", SpawnTemplate } )
- return SpawnTemplate
-
-end
-
---- Private method randomizing the routes.
--- @param #SPAWN self
--- @param #number SpawnIndex The index of the group to be spawned.
--- @return #SPAWN
-function SPAWN:_RandomizeRoute( SpawnIndex )
- self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeRoute, self.SpawnRandomizeRouteStartPoint, self.SpawnRandomizeRouteEndPoint, self.SpawnRandomizeRouteRadius } )
-
- if self.SpawnRandomizeRoute then
- local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate
- local RouteCount = #SpawnTemplate.route.points
-
- for t = self.SpawnRandomizeRouteStartPoint + 1, ( RouteCount - self.SpawnRandomizeRouteEndPoint ) do
-
- SpawnTemplate.route.points[t].x = SpawnTemplate.route.points[t].x + math.random( self.SpawnRandomizeRouteRadius * -1, self.SpawnRandomizeRouteRadius )
- SpawnTemplate.route.points[t].y = SpawnTemplate.route.points[t].y + math.random( self.SpawnRandomizeRouteRadius * -1, self.SpawnRandomizeRouteRadius )
-
- -- Manage randomization of altitude for airborne units ...
- if SpawnTemplate.CategoryID == Group.Category.AIRPLANE or SpawnTemplate.CategoryID == Group.Category.HELICOPTER then
- if SpawnTemplate.route.points[t].alt and self.SpawnRandomizeRouteHeight then
- SpawnTemplate.route.points[t].alt = SpawnTemplate.route.points[t].alt + math.random( 1, self.SpawnRandomizeRouteHeight )
- end
- else
- SpawnTemplate.route.points[t].alt = nil
- end
-
- self:T( 'SpawnTemplate.route.points[' .. t .. '].x = ' .. SpawnTemplate.route.points[t].x .. ', SpawnTemplate.route.points[' .. t .. '].y = ' .. SpawnTemplate.route.points[t].y )
- end
- end
-
- self:_RandomizeZones( SpawnIndex )
-
- return self
-end
-
---- Private method that randomizes the template of the group.
--- @param #SPAWN self
--- @param #number SpawnIndex
--- @return #SPAWN self
-function SPAWN:_RandomizeTemplate( SpawnIndex )
- self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeTemplate } )
-
- if self.SpawnRandomizeTemplate then
- self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix = self.SpawnTemplatePrefixTable[ math.random( 1, #self.SpawnTemplatePrefixTable ) ]
- self.SpawnGroups[SpawnIndex].SpawnTemplate = self:_Prepare( self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix, SpawnIndex )
- self.SpawnGroups[SpawnIndex].SpawnTemplate.route = routines.utils.deepCopy( self.SpawnTemplate.route )
- self.SpawnGroups[SpawnIndex].SpawnTemplate.x = self.SpawnTemplate.x
- self.SpawnGroups[SpawnIndex].SpawnTemplate.y = self.SpawnTemplate.y
- self.SpawnGroups[SpawnIndex].SpawnTemplate.start_time = self.SpawnTemplate.start_time
- local OldX = self.SpawnGroups[SpawnIndex].SpawnTemplate.units[1].x
- local OldY = self.SpawnGroups[SpawnIndex].SpawnTemplate.units[1].y
- for UnitID = 1, #self.SpawnGroups[SpawnIndex].SpawnTemplate.units do
- self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].heading = self.SpawnTemplate.units[1].heading
- self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].x = self.SpawnTemplate.units[1].x + ( self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].x - OldX )
- self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y = self.SpawnTemplate.units[1].y + ( self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y - OldY )
- self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].alt = self.SpawnTemplate.units[1].alt
- end
- end
-
- self:_RandomizeRoute( SpawnIndex )
-
- return self
-end
-
---- Private method that randomizes the @{Zone}s where the Group will be spawned.
--- @param #SPAWN self
--- @param #number SpawnIndex
--- @return #SPAWN self
-function SPAWN:_RandomizeZones( SpawnIndex )
- self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeZones } )
-
- if self.SpawnRandomizeZones then
- local SpawnZone = nil -- Core.Zone#ZONE_BASE
- while not SpawnZone do
- self:T( { SpawnZoneTableCount = #self.SpawnZoneTable, self.SpawnZoneTable } )
- local ZoneID = math.random( #self.SpawnZoneTable )
- self:T( ZoneID )
- SpawnZone = self.SpawnZoneTable[ ZoneID ]:GetZoneMaybe()
- end
-
- self:T( "Preparing Spawn in Zone", SpawnZone:GetName() )
-
- local SpawnVec2 = SpawnZone:GetRandomVec2()
-
- self:T( { SpawnVec2 = SpawnVec2 } )
-
- local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate
-
- self:T( { Route = SpawnTemplate.route } )
-
- for UnitID = 1, #SpawnTemplate.units do
- local UnitTemplate = SpawnTemplate.units[UnitID]
- self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y )
- local SX = UnitTemplate.x
- local SY = UnitTemplate.y
- local BX = SpawnTemplate.route.points[1].x
- local BY = SpawnTemplate.route.points[1].y
- local TX = SpawnVec2.x + ( SX - BX )
- local TY = SpawnVec2.y + ( SY - BY )
- UnitTemplate.x = TX
- UnitTemplate.y = TY
- -- TODO: Manage altitude based on landheight...
- --SpawnTemplate.units[UnitID].alt = SpawnVec2:
- self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y )
- end
- SpawnTemplate.x = SpawnVec2.x
- SpawnTemplate.y = SpawnVec2.y
- SpawnTemplate.route.points[1].x = SpawnVec2.x
- SpawnTemplate.route.points[1].y = SpawnVec2.y
- end
-
- return self
-
-end
-
-function SPAWN:_TranslateRotate( SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle )
- self:F( { self.SpawnTemplatePrefix, SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle } )
-
- -- Translate
- local TranslatedX = SpawnX
- local TranslatedY = SpawnY
-
- -- Rotate
- -- From Wikipedia: https://en.wikipedia.org/wiki/Rotation_matrix#Common_rotations
- -- x' = x \cos \theta - y \sin \theta\
- -- y' = x \sin \theta + y \cos \theta\
- local RotatedX = - TranslatedX * math.cos( math.rad( SpawnAngle ) )
- + TranslatedY * math.sin( math.rad( SpawnAngle ) )
- local RotatedY = TranslatedX * math.sin( math.rad( SpawnAngle ) )
- + TranslatedY * math.cos( math.rad( SpawnAngle ) )
-
- -- Assign
- self.SpawnGroups[SpawnIndex].SpawnTemplate.x = SpawnRootX - RotatedX
- self.SpawnGroups[SpawnIndex].SpawnTemplate.y = SpawnRootY + RotatedY
-
-
- local SpawnUnitCount = table.getn( self.SpawnGroups[SpawnIndex].SpawnTemplate.units )
- for u = 1, SpawnUnitCount do
-
- -- Translate
- local TranslatedX = SpawnX
- local TranslatedY = SpawnY - 10 * ( u - 1 )
-
- -- Rotate
- local RotatedX = - TranslatedX * math.cos( math.rad( SpawnAngle ) )
- + TranslatedY * math.sin( math.rad( SpawnAngle ) )
- local RotatedY = TranslatedX * math.sin( math.rad( SpawnAngle ) )
- + TranslatedY * math.cos( math.rad( SpawnAngle ) )
-
- -- Assign
- self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].x = SpawnRootX - RotatedX
- self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].y = SpawnRootY + RotatedY
- self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].heading = self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].heading + math.rad( SpawnAngle )
- end
-
- return self
-end
-
---- Get the next index of the groups to be spawned. This method is complicated, as it is used at several spaces.
-function SPAWN:_GetSpawnIndex( SpawnIndex )
- self:F2( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive, self.AliveUnits, #self.SpawnTemplate.units } )
-
- if ( self.SpawnMaxGroups == 0 ) or ( SpawnIndex <= self.SpawnMaxGroups ) then
- if ( self.SpawnMaxUnitsAlive == 0 ) or ( self.AliveUnits + #self.SpawnTemplate.units <= self.SpawnMaxUnitsAlive ) or self.UnControlled == true then
- if SpawnIndex and SpawnIndex >= self.SpawnCount + 1 then
- self.SpawnCount = self.SpawnCount + 1
- SpawnIndex = self.SpawnCount
- end
- self.SpawnIndex = SpawnIndex
- if not self.SpawnGroups[self.SpawnIndex] then
- self:_InitializeSpawnGroups( self.SpawnIndex )
- end
- else
- return nil
- end
- else
- return nil
- end
-
- return self.SpawnIndex
-end
-
-
--- TODO Need to delete this... _DATABASE does this now ...
-
---- @param #SPAWN self
--- @param Core.Event#EVENTDATA EventData
-function SPAWN:_OnBirth( EventData )
- self:F( self.SpawnTemplatePrefix )
-
- local SpawnGroup = EventData.IniGroup
-
- if SpawnGroup then
- local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup )
- if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group!
- self:T( { "Birth Event:", EventPrefix, self.SpawnTemplatePrefix } )
- if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then
- self.AliveUnits = self.AliveUnits + 1
- self:T( "Alive Units: " .. self.AliveUnits )
- end
- end
- end
-
-end
-
---- Obscolete
--- @todo Need to delete this... _DATABASE does this now ...
-
---- @param #SPAWN self
--- @param Core.Event#EVENTDATA EventData
-function SPAWN:_OnDeadOrCrash( EventData )
- self:F( self.SpawnTemplatePrefix )
-
- local SpawnGroup = EventData.IniGroup
-
- if SpawnGroup then
- local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup )
- if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group!
- self:T( { "Dead event: " .. EventPrefix } )
- if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then
- self.AliveUnits = self.AliveUnits - 1
- self:T( "Alive Units: " .. self.AliveUnits )
- end
- end
- end
-end
-
---- Will detect AIR Units taking off... When the event takes place, the spawned Group is registered as airborne...
--- This is needed to ensure that Re-SPAWNing only is done for landed AIR Groups.
--- @param #SPAWN self
--- @param Core.Event#EVENTDATA EventData
-function SPAWN:_OnTakeOff( EventData )
- self:F( self.SpawnTemplatePrefix )
-
- local SpawnGroup = EventData.IniGroup
- if SpawnGroup then
- local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup )
- if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group!
- self:T( { "TakeOff event: " .. EventPrefix } )
- if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then
- self:T( "self.Landed = false" )
- SpawnGroup:SetState( SpawnGroup, "Spawn_Landed", false )
- end
- end
- end
-end
-
---- Will detect AIR Units landing... When the event takes place, the spawned Group is registered as landed.
--- This is needed to ensure that Re-SPAWNing is only done for landed AIR Groups.
--- @param #SPAWN self
--- @param Core.Event#EVENTDATA EventData
-function SPAWN:_OnLand( EventData )
- self:F( self.SpawnTemplatePrefix )
-
- local SpawnGroup = EventData.IniGroup
- if SpawnGroup then
- local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup )
- if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group!
- self:T( { "Land event: " .. EventPrefix } )
- if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then
- -- TODO: Check if this is the last unit of the group that lands.
- SpawnGroup:SetState( SpawnGroup, "Spawn_Landed", true )
- if self.RepeatOnLanding then
- local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup )
- self:T( { "Landed:", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } )
- self:ReSpawn( SpawnGroupIndex )
- end
- end
- end
- end
-end
-
---- Will detect AIR Units shutting down their engines ...
--- When the event takes place, and the method @{RepeatOnEngineShutDown} was called, the spawned Group will Re-SPAWN.
--- But only when the Unit was registered to have landed.
--- @param #SPAWN self
--- @param Core.Event#EVENTDATA EventData
-function SPAWN:_OnEngineShutDown( EventData )
- self:F( self.SpawnTemplatePrefix )
-
- local SpawnGroup = EventData.IniGroup
- if SpawnGroup then
- local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup )
- if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group!
- self:T( { "EngineShutdown event: " .. EventPrefix } )
- if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then
- -- todo: test if on the runway
- local Landed = SpawnGroup:GetState( SpawnGroup, "Spawn_Landed" )
- if Landed and self.RepeatOnEngineShutDown then
- local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup )
- self:T( { "EngineShutDown: ", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } )
- self:ReSpawn( SpawnGroupIndex )
- end
- end
- end
- end
-end
-
---- This function is called automatically by the Spawning scheduler.
--- It is the internal worker method SPAWNing new Groups on the defined time intervals.
-function SPAWN:_Scheduler()
- self:F2( { "_Scheduler", self.SpawnTemplatePrefix, self.SpawnAliasPrefix, self.SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive } )
-
- -- Validate if there are still groups left in the batch...
- self:Spawn()
-
- return true
-end
-
---- Schedules the CleanUp of Groups
--- @param #SPAWN self
--- @return #boolean True = Continue Scheduler
-function SPAWN:_SpawnCleanUpScheduler()
- self:F( { "CleanUp Scheduler:", self.SpawnTemplatePrefix } )
-
- local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup()
- self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } )
-
- while SpawnGroup do
-
- local SpawnUnits = SpawnGroup:GetUnits()
-
- for UnitID, UnitData in pairs( SpawnUnits ) do
-
- local SpawnUnit = UnitData -- Wrapper.Unit#UNIT
- local SpawnUnitName = SpawnUnit:GetName()
-
-
- self.SpawnCleanUpTimeStamps[SpawnUnitName] = self.SpawnCleanUpTimeStamps[SpawnUnitName] or {}
- local Stamp = self.SpawnCleanUpTimeStamps[SpawnUnitName]
- self:T( { SpawnUnitName, Stamp } )
-
- if Stamp.Vec2 then
- if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1 then
- local NewVec2 = SpawnUnit:GetVec2()
- if Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y then
- -- If the plane is not moving, and is on the ground, assign it with a timestamp...
- if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then
- self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } )
- self:ReSpawn( SpawnCursor )
- Stamp.Vec2 = nil
- Stamp.Time = nil
- end
+ if self.ToNearestAirbase == false and self.ToHomeAirbase == false then
+ self:Destroy( Client.UnitName, AIGroup )
else
- Stamp.Time = timer.getTime()
- Stamp.Vec2 = SpawnUnit:GetVec2()
+ -- We test if there is no other CLIENT within the self.ReturnThresholdRange of the first unit of the AI group.
+ -- If there is a CLIENT, the AI stays engaged and will not return.
+ -- If there is no CLIENT within the self.ReturnThresholdRange, then the unit will return to the Airbase return method selected.
+
+ local PlayerInRange = { Value = false }
+ local RangeZone = ZONE_RADIUS:New( 'RangeZone', AIGroup:GetVec2(), self.ReturnThresholdRange )
+
+ self:T2( RangeZone )
+
+ _DATABASE:ForEachPlayer(
+ --- @param Wrapper.Unit#UNIT RangeTestUnit
+ function( RangeTestUnit, RangeZone, AIGroup, PlayerInRange )
+ self:T2( { PlayerInRange, RangeTestUnit.UnitName, RangeZone.ZoneName } )
+ if RangeTestUnit:IsInZone( RangeZone ) == true then
+ self:T2( "in zone" )
+ if RangeTestUnit:GetCoalition() ~= AIGroup:GetCoalition() then
+ self:T2( "in range" )
+ PlayerInRange.Value = true
+ end
+ end
+ end,
+
+ --- @param Core.Zone#ZONE_RADIUS RangeZone
+ -- @param Wrapper.Group#GROUP AIGroup
+ function( RangeZone, AIGroup, PlayerInRange )
+ if PlayerInRange.Value == false then
+ self:Return( AIGroup )
+ end
+ end
+ , RangeZone, AIGroup, PlayerInRange
+ )
+
end
- else
- Stamp.Vec2 = nil
- Stamp.Time = nil
+ self.Set:Remove( Client.UnitName )
end
else
- if SpawnUnit:InAir() == false then
- Stamp.Vec2 = SpawnUnit:GetVec2()
- if SpawnUnit:GetVelocityKMH() < 1 then
- Stamp.Time = timer.getTime()
+ if not AIGroup or not AIGroup:IsAlive() == true then
+ self:T( "Client " .. Client.UnitName .. " not alive." )
+ if not self.SpawnQueue[Client.UnitName] then
+ -- Spawn a new AI taking into account the spawn interval Earliest, Latest
+ self:__Spawn( math.random( self.Earliest, self.Latest ), Client.UnitName )
+ self.SpawnQueue[Client.UnitName] = true
+ self:E( "New AI Spawned for Client " .. Client.UnitName )
end
- else
- Stamp.Time = nil
- Stamp.Vec2 = nil
end
end
+ return true
end
-
- SpawnGroup, SpawnCursor = self:GetNextAliveGroup( SpawnCursor )
-
- self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } )
-
- end
+ )
- return true -- Repeat
-
-end
\ No newline at end of file
+ self:__Monitor( 10 )
+end
+
+
+
diff --git a/Moose Development/Moose/Functional/Spawn.lua b/Moose Development/Moose/Functional/Spawn.lua
index 14059f200..17f79d6db 100644
--- a/Moose Development/Moose/Functional/Spawn.lua
+++ b/Moose Development/Moose/Functional/Spawn.lua
@@ -286,38 +286,38 @@ SPAWN.Takeoff = GROUP.Takeoff
-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' )
-- @usage local Plane = SPAWN:New( "Plane" ) -- Creates a new local variable that can initiate new planes with the name "Plane#ddd" using the template "Plane" as defined within the ME.
function SPAWN:New( SpawnTemplatePrefix )
- local self = BASE:Inherit( self, BASE:New() ) -- #SPAWN
- self:F( { SpawnTemplatePrefix } )
+ local self = BASE:Inherit( self, BASE:New() ) -- #SPAWN
+ self:F( { SpawnTemplatePrefix } )
- local TemplateGroup = Group.getByName( SpawnTemplatePrefix )
- if TemplateGroup then
- self.SpawnTemplatePrefix = SpawnTemplatePrefix
- self.SpawnIndex = 0
- self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart.
- self.AliveUnits = 0 -- Contains the counter how many units are currently alive
- self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not.
- self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!!
- self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning.
- self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts.
+ local TemplateGroup = Group.getByName( SpawnTemplatePrefix )
+ if TemplateGroup then
+ self.SpawnTemplatePrefix = SpawnTemplatePrefix
+ self.SpawnIndex = 0
+ self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart.
+ self.AliveUnits = 0 -- Contains the counter how many units are currently alive
+ self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not.
+ self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!!
+ self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning.
+ self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts.
self.SpawnInitLimit = false -- By default, no InitLimit
- self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time.
- self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned.
- self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false.
- self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned.
- self.AIOnOff = true -- The AI is on by default when spawning a group.
+ self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time.
+ self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned.
+ self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false.
+ self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned.
+ self.AIOnOff = true -- The AI is on by default when spawning a group.
self.SpawnUnControlled = false
self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name.
self.DelayOnOff = false -- No intial delay when spawning the first group.
self.Grouping = nil -- No grouping
- self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned.
- else
- error( "SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" )
- end
+ self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned.
+ else
+ error( "SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" )
+ end
self:SetEventPriority( 5 )
- return self
+ return self
end
--- Creates a new SPAWN instance to create new groups based on the defined template and using a new alias for each new group.
@@ -330,39 +330,39 @@ end
-- Spawn_BE_KA50 = SPAWN:NewWithAlias( 'BE KA-50@RAMP-Ground Defense', 'Helicopter Attacking a City' )
-- @usage local PlaneWithAlias = SPAWN:NewWithAlias( "Plane", "Bomber" ) -- Creates a new local variable that can instantiate new planes with the name "Bomber#ddd" using the template "Plane" as defined within the ME.
function SPAWN:NewWithAlias( SpawnTemplatePrefix, SpawnAliasPrefix )
- local self = BASE:Inherit( self, BASE:New() )
- self:F( { SpawnTemplatePrefix, SpawnAliasPrefix } )
+ local self = BASE:Inherit( self, BASE:New() )
+ self:F( { SpawnTemplatePrefix, SpawnAliasPrefix } )
- local TemplateGroup = Group.getByName( SpawnTemplatePrefix )
- if TemplateGroup then
- self.SpawnTemplatePrefix = SpawnTemplatePrefix
- self.SpawnAliasPrefix = SpawnAliasPrefix
- self.SpawnIndex = 0
- self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart.
- self.AliveUnits = 0 -- Contains the counter how many units are currently alive
- self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not.
- self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!!
- self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning.
- self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts.
- self.SpawnInitLimit = false -- By default, no InitLimit
- self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time.
- self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned.
- self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false.
- self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned.
+ local TemplateGroup = Group.getByName( SpawnTemplatePrefix )
+ if TemplateGroup then
+ self.SpawnTemplatePrefix = SpawnTemplatePrefix
+ self.SpawnAliasPrefix = SpawnAliasPrefix
+ self.SpawnIndex = 0
+ self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart.
+ self.AliveUnits = 0 -- Contains the counter how many units are currently alive
+ self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not.
+ self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!!
+ self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning.
+ self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts.
+ self.SpawnInitLimit = false -- By default, no InitLimit
+ self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time.
+ self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned.
+ self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false.
+ self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned.
self.AIOnOff = true -- The AI is on by default when spawning a group.
self.SpawnUnControlled = false
self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name.
self.DelayOnOff = false -- No intial delay when spawning the first group.
self.Grouping = nil
- self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned.
- else
- error( "SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" )
- end
-
+ self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned.
+ else
+ error( "SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" )
+ end
+
self:SetEventPriority( 5 )
-
- return self
+
+ return self
end
@@ -382,17 +382,17 @@ end
-- -- There will be maximum 24 groups spawned during the whole mission lifetime.
-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitLimit( 2, 24 )
function SPAWN:InitLimit( SpawnMaxUnitsAlive, SpawnMaxGroups )
- self:F( { self.SpawnTemplatePrefix, SpawnMaxUnitsAlive, SpawnMaxGroups } )
+ self:F( { self.SpawnTemplatePrefix, SpawnMaxUnitsAlive, SpawnMaxGroups } )
self.SpawnInitLimit = true
- self.SpawnMaxUnitsAlive = SpawnMaxUnitsAlive -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time.
- self.SpawnMaxGroups = SpawnMaxGroups -- The maximum amount of groups that can be spawned.
-
- for SpawnGroupID = 1, self.SpawnMaxGroups do
- self:_InitializeSpawnGroups( SpawnGroupID )
- end
+ self.SpawnMaxUnitsAlive = SpawnMaxUnitsAlive -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time.
+ self.SpawnMaxGroups = SpawnMaxGroups -- The maximum amount of groups that can be spawned.
+
+ for SpawnGroupID = 1, self.SpawnMaxGroups do
+ self:_InitializeSpawnGroups( SpawnGroupID )
+ end
- return self
+ return self
end
--- Keeps the unit names as defined within the mission editor,
@@ -426,19 +426,19 @@ end
-- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters.
-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 )
function SPAWN:InitRandomizeRoute( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight )
- self:F( { self.SpawnTemplatePrefix, SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight } )
+ self:F( { self.SpawnTemplatePrefix, SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight } )
- self.SpawnRandomizeRoute = true
- self.SpawnRandomizeRouteStartPoint = SpawnStartPoint
- self.SpawnRandomizeRouteEndPoint = SpawnEndPoint
- self.SpawnRandomizeRouteRadius = SpawnRadius
- self.SpawnRandomizeRouteHeight = SpawnHeight
+ self.SpawnRandomizeRoute = true
+ self.SpawnRandomizeRouteStartPoint = SpawnStartPoint
+ self.SpawnRandomizeRouteEndPoint = SpawnEndPoint
+ self.SpawnRandomizeRouteRadius = SpawnRadius
+ self.SpawnRandomizeRouteHeight = SpawnHeight
- for GroupID = 1, self.SpawnMaxGroups do
- self:_RandomizeRoute( GroupID )
- end
-
- return self
+ for GroupID = 1, self.SpawnMaxGroups do
+ self:_RandomizeRoute( GroupID )
+ end
+
+ return self
end
--- Randomizes the position of @{Group}s that are spawned within a **radius band**, given an Outer and Inner radius, from the point that the spawn happens.
@@ -508,16 +508,16 @@ end
-- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 )
-- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 )
function SPAWN:InitRandomizeTemplate( SpawnTemplatePrefixTable )
- self:F( { self.SpawnTemplatePrefix, SpawnTemplatePrefixTable } )
+ self:F( { self.SpawnTemplatePrefix, SpawnTemplatePrefixTable } )
- self.SpawnTemplatePrefixTable = SpawnTemplatePrefixTable
- self.SpawnRandomizeTemplate = true
+ self.SpawnTemplatePrefixTable = SpawnTemplatePrefixTable
+ self.SpawnRandomizeTemplate = true
- for SpawnGroupID = 1, self.SpawnMaxGroups do
- self:_RandomizeTemplate( SpawnGroupID )
- end
-
- return self
+ for SpawnGroupID = 1, self.SpawnMaxGroups do
+ self:_RandomizeTemplate( SpawnGroupID )
+ end
+
+ return self
end
--- When spawning a new group, make the grouping of the units according the InitGrouping setting.
@@ -571,26 +571,26 @@ end
-- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically.
-- SpawnRU_SU34 = SPAWN:New( 'TF1 RU Su-34 Krymsk@AI - Attack Ships' ):Schedule( 2, 3, 1800, 0.4 ):SpawnUncontrolled():InitRandomizeRoute( 1, 1, 3000 ):RepeatOnEngineShutDown()
function SPAWN:InitRepeat()
- self:F( { self.SpawnTemplatePrefix, self.SpawnIndex } )
+ self:F( { self.SpawnTemplatePrefix, self.SpawnIndex } )
- self.Repeat = true
- self.RepeatOnEngineShutDown = false
- self.RepeatOnLanding = true
+ self.Repeat = true
+ self.RepeatOnEngineShutDown = false
+ self.RepeatOnLanding = true
- return self
+ return self
end
--- Respawn group after landing.
-- @param #SPAWN self
-- @return #SPAWN self
function SPAWN:InitRepeatOnLanding()
- self:F( { self.SpawnTemplatePrefix } )
+ self:F( { self.SpawnTemplatePrefix } )
- self:InitRepeat()
- self.RepeatOnEngineShutDown = false
- self.RepeatOnLanding = true
-
- return self
+ self:InitRepeat()
+ self.RepeatOnEngineShutDown = false
+ self.RepeatOnLanding = true
+
+ return self
end
@@ -598,13 +598,13 @@ end
-- @param #SPAWN self
-- @return #SPAWN self
function SPAWN:InitRepeatOnEngineShutDown()
- self:F( { self.SpawnTemplatePrefix } )
+ self:F( { self.SpawnTemplatePrefix } )
- self:InitRepeat()
- self.RepeatOnEngineShutDown = true
- self.RepeatOnLanding = false
-
- return self
+ self:InitRepeat()
+ self.RepeatOnEngineShutDown = true
+ self.RepeatOnLanding = false
+
+ return self
end
@@ -615,17 +615,17 @@ end
-- @return #SPAWN self
-- @usage Spawn_Helicopter:CleanUp( 20 ) -- CleanUp the spawning of the helicopters every 20 seconds when they become inactive.
function SPAWN:InitCleanUp( SpawnCleanUpInterval )
- self:F( { self.SpawnTemplatePrefix, SpawnCleanUpInterval } )
+ self:F( { self.SpawnTemplatePrefix, SpawnCleanUpInterval } )
- self.SpawnCleanUpInterval = SpawnCleanUpInterval
- self.SpawnCleanUpTimeStamps = {}
+ self.SpawnCleanUpInterval = SpawnCleanUpInterval
+ self.SpawnCleanUpTimeStamps = {}
local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup()
self:T( { "CleanUp Scheduler:", SpawnGroup } )
-
- --self.CleanUpFunction = routines.scheduleFunction( self._SpawnCleanUpScheduler, { self }, timer.getTime() + 1, SpawnCleanUpInterval )
- self.CleanUpScheduler = SCHEDULER:New( self, self._SpawnCleanUpScheduler, {}, 1, SpawnCleanUpInterval, 0.2 )
- return self
+
+ --self.CleanUpFunction = routines.scheduleFunction( self._SpawnCleanUpScheduler, { self }, timer.getTime() + 1, SpawnCleanUpInterval )
+ self.CleanUpScheduler = SCHEDULER:New( self, self._SpawnCleanUpScheduler, {}, 1, SpawnCleanUpInterval, 0.2 )
+ return self
end
@@ -634,46 +634,46 @@ end
-- The method will take the position of the group as the first position in the array.
-- @param #SPAWN self
-- @param #number SpawnAngle The angle in degrees how the groups and each unit of the group will be positioned.
--- @param #number SpawnWidth The amount of Groups that will be positioned on the X axis.
+-- @param #number SpawnWidth The amount of Groups that will be positioned on the X axis.
-- @param #number SpawnDeltaX The space between each Group on the X-axis.
--- @param #number SpawnDeltaY The space between each Group on the Y-axis.
+-- @param #number SpawnDeltaY The space between each Group on the Y-axis.
-- @return #SPAWN self
-- @usage
-- -- Define an array of Groups.
-- Spawn_BE_Ground = SPAWN:New( 'BE Ground' ):InitLimit( 2, 24 ):InitArray( 90, "Diamond", 10, 100, 50 )
function SPAWN:InitArray( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY )
- self:F( { self.SpawnTemplatePrefix, SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY } )
+ self:F( { self.SpawnTemplatePrefix, SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY } )
- self.SpawnVisible = true -- When the first Spawn executes, all the Groups need to be made visible before start.
-
- local SpawnX = 0
- local SpawnY = 0
- local SpawnXIndex = 0
- local SpawnYIndex = 0
-
- for SpawnGroupID = 1, self.SpawnMaxGroups do
- self:T( { SpawnX, SpawnY, SpawnXIndex, SpawnYIndex } )
+ self.SpawnVisible = true -- When the first Spawn executes, all the Groups need to be made visible before start.
+
+ local SpawnX = 0
+ local SpawnY = 0
+ local SpawnXIndex = 0
+ local SpawnYIndex = 0
+
+ for SpawnGroupID = 1, self.SpawnMaxGroups do
+ self:T( { SpawnX, SpawnY, SpawnXIndex, SpawnYIndex } )
- self.SpawnGroups[SpawnGroupID].Visible = true
- self.SpawnGroups[SpawnGroupID].Spawned = false
-
- SpawnXIndex = SpawnXIndex + 1
- if SpawnWidth and SpawnWidth ~= 0 then
- if SpawnXIndex >= SpawnWidth then
- SpawnXIndex = 0
- SpawnYIndex = SpawnYIndex + 1
- end
- end
+ self.SpawnGroups[SpawnGroupID].Visible = true
+ self.SpawnGroups[SpawnGroupID].Spawned = false
+
+ SpawnXIndex = SpawnXIndex + 1
+ if SpawnWidth and SpawnWidth ~= 0 then
+ if SpawnXIndex >= SpawnWidth then
+ SpawnXIndex = 0
+ SpawnYIndex = SpawnYIndex + 1
+ end
+ end
- local SpawnRootX = self.SpawnGroups[SpawnGroupID].SpawnTemplate.x
- local SpawnRootY = self.SpawnGroups[SpawnGroupID].SpawnTemplate.y
-
- self:_TranslateRotate( SpawnGroupID, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle )
-
- self.SpawnGroups[SpawnGroupID].SpawnTemplate.lateActivation = true
- self.SpawnGroups[SpawnGroupID].SpawnTemplate.visible = true
-
- self.SpawnGroups[SpawnGroupID].Visible = true
+ local SpawnRootX = self.SpawnGroups[SpawnGroupID].SpawnTemplate.x
+ local SpawnRootY = self.SpawnGroups[SpawnGroupID].SpawnTemplate.y
+
+ self:_TranslateRotate( SpawnGroupID, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle )
+
+ self.SpawnGroups[SpawnGroupID].SpawnTemplate.lateActivation = true
+ self.SpawnGroups[SpawnGroupID].SpawnTemplate.visible = true
+
+ self.SpawnGroups[SpawnGroupID].Visible = true
self:HandleEvent( EVENTS.Birth, self._OnBirth )
self:HandleEvent( EVENTS.Dead, self._OnDeadOrCrash )
@@ -685,14 +685,14 @@ function SPAWN:InitArray( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY )
if self.RepeatOnEngineShutDown then
self:HandleEvent( EVENTS.EngineShutdown, self._OnEngineShutDown )
end
-
- self.SpawnGroups[SpawnGroupID].Group = _DATABASE:Spawn( self.SpawnGroups[SpawnGroupID].SpawnTemplate )
+
+ self.SpawnGroups[SpawnGroupID].Group = _DATABASE:Spawn( self.SpawnGroups[SpawnGroupID].SpawnTemplate )
- SpawnX = SpawnXIndex * SpawnDeltaX
- SpawnY = SpawnYIndex * SpawnDeltaY
- end
-
- return self
+ SpawnX = SpawnXIndex * SpawnDeltaX
+ SpawnY = SpawnYIndex * SpawnDeltaY
+ end
+
+ return self
end
do -- AI methods
@@ -759,9 +759,9 @@ end -- Delay methods
-- @param #SPAWN self
-- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions.
function SPAWN:Spawn()
- self:F( { self.SpawnTemplatePrefix, self.SpawnIndex, self.AliveUnits } )
+ self:F( { self.SpawnTemplatePrefix, self.SpawnIndex, self.AliveUnits } )
- return self:SpawnWithIndex( self.SpawnIndex + 1 )
+ return self:SpawnWithIndex( self.SpawnIndex + 1 )
end
--- Will re-spawn a group based on a given index.
@@ -770,36 +770,36 @@ end
-- @param #string SpawnIndex The index of the group to be spawned.
-- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions.
function SPAWN:ReSpawn( SpawnIndex )
- self:F( { self.SpawnTemplatePrefix, SpawnIndex } )
-
- if not SpawnIndex then
- SpawnIndex = 1
- end
+ self:F( { self.SpawnTemplatePrefix, SpawnIndex } )
+
+ if not SpawnIndex then
+ SpawnIndex = 1
+ end
-- TODO: This logic makes DCS crash and i don't know why (yet).
- local SpawnGroup = self:GetGroupFromIndex( SpawnIndex )
- local WayPoints = SpawnGroup and SpawnGroup.WayPoints or nil
- if SpawnGroup then
+ local SpawnGroup = self:GetGroupFromIndex( SpawnIndex )
+ local WayPoints = SpawnGroup and SpawnGroup.WayPoints or nil
+ if SpawnGroup then
local SpawnDCSGroup = SpawnGroup:GetDCSObject()
- if SpawnDCSGroup then
+ if SpawnDCSGroup then
SpawnGroup:Destroy()
- end
+ end
end
- local SpawnGroup = self:SpawnWithIndex( SpawnIndex )
- if SpawnGroup and WayPoints then
- -- If there were WayPoints set, then Re-Execute those WayPoints!
- SpawnGroup:WayPointInitialize( WayPoints )
- SpawnGroup:WayPointExecute( 1, 5 )
- end
-
- if SpawnGroup.ReSpawnFunction then
- SpawnGroup:ReSpawnFunction()
- end
-
- SpawnGroup:ResetEvents()
-
- return SpawnGroup
+ local SpawnGroup = self:SpawnWithIndex( SpawnIndex )
+ if SpawnGroup and WayPoints then
+ -- If there were WayPoints set, then Re-Execute those WayPoints!
+ SpawnGroup:WayPointInitialize( WayPoints )
+ SpawnGroup:WayPointExecute( 1, 5 )
+ end
+
+ if SpawnGroup.ReSpawnFunction then
+ SpawnGroup:ReSpawnFunction()
+ end
+
+ SpawnGroup:ResetEvents()
+
+ return SpawnGroup
end
--- Will spawn a group with a specified index number.
@@ -808,16 +808,16 @@ end
-- @param #string SpawnIndex The index of the group to be spawned.
-- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions.
function SPAWN:SpawnWithIndex( SpawnIndex )
- self:F2( { SpawnTemplatePrefix = self.SpawnTemplatePrefix, SpawnIndex = SpawnIndex, AliveUnits = self.AliveUnits, SpawnMaxGroups = self.SpawnMaxGroups } )
-
- if self:_GetSpawnIndex( SpawnIndex ) then
-
- if self.SpawnGroups[self.SpawnIndex].Visible then
- self.SpawnGroups[self.SpawnIndex].Group:Activate()
- else
+ self:F2( { SpawnTemplatePrefix = self.SpawnTemplatePrefix, SpawnIndex = SpawnIndex, AliveUnits = self.AliveUnits, SpawnMaxGroups = self.SpawnMaxGroups } )
+
+ if self:_GetSpawnIndex( SpawnIndex ) then
+
+ if self.SpawnGroups[self.SpawnIndex].Visible then
+ self.SpawnGroups[self.SpawnIndex].Group:Activate()
+ else
- local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate
- self:T( SpawnTemplate.name )
+ local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate
+ self:T( SpawnTemplate.name )
if SpawnTemplate then
@@ -854,7 +854,7 @@ function SPAWN:SpawnWithIndex( SpawnIndex )
end
end
end
-
+
self:HandleEvent( EVENTS.Birth, self._OnBirth )
self:HandleEvent( EVENTS.Dead, self._OnDeadOrCrash )
self:HandleEvent( EVENTS.Crash, self._OnDeadOrCrash )
@@ -866,38 +866,38 @@ function SPAWN:SpawnWithIndex( SpawnIndex )
self:HandleEvent( EVENTS.EngineShutdown, self._OnEngineShutDown )
end
- self.SpawnGroups[self.SpawnIndex].Group = _DATABASE:Spawn( SpawnTemplate )
-
- local SpawnGroup = self.SpawnGroups[self.SpawnIndex].Group -- Wrapper.Group#GROUP
-
- --TODO: Need to check if this function doesn't need to be scheduled, as the group may not be immediately there!
+ self.SpawnGroups[self.SpawnIndex].Group = _DATABASE:Spawn( SpawnTemplate )
+
+ local SpawnGroup = self.SpawnGroups[self.SpawnIndex].Group -- Wrapper.Group#GROUP
+
+ --TODO: Need to check if this function doesn't need to be scheduled, as the group may not be immediately there!
if SpawnGroup then
- SpawnGroup:SetAIOnOff( self.AIOnOff )
- end
+ SpawnGroup:SetAIOnOff( self.AIOnOff )
+ end
self:T3( SpawnTemplate.name )
-
- -- If there is a SpawnFunction hook defined, call it.
- if self.SpawnFunctionHook then
- -- delay calling this for .1 seconds so that it hopefully comes after the BIRTH event of the group.
- self.SpawnHookScheduler = SCHEDULER:New()
- self.SpawnHookScheduler:Schedule( nil, self.SpawnFunctionHook, { self.SpawnGroups[self.SpawnIndex].Group, unpack( self.SpawnFunctionArguments)}, 0.1 )
- -- self.SpawnFunctionHook( self.SpawnGroups[self.SpawnIndex].Group, unpack( self.SpawnFunctionArguments ) )
- end
- -- TODO: Need to fix this by putting an "R" in the name of the group when the group repeats.
- --if self.Repeat then
- -- _DATABASE:SetStatusGroup( SpawnTemplate.name, "ReSpawn" )
- --end
- end
-
- self.SpawnGroups[self.SpawnIndex].Spawned = true
- return self.SpawnGroups[self.SpawnIndex].Group
- else
- --self:E( { self.SpawnTemplatePrefix, "No more Groups to Spawn:", SpawnIndex, self.SpawnMaxGroups } )
- end
+
+ -- If there is a SpawnFunction hook defined, call it.
+ if self.SpawnFunctionHook then
+ -- delay calling this for .1 seconds so that it hopefully comes after the BIRTH event of the group.
+ self.SpawnHookScheduler = SCHEDULER:New()
+ self.SpawnHookScheduler:Schedule( nil, self.SpawnFunctionHook, { self.SpawnGroups[self.SpawnIndex].Group, unpack( self.SpawnFunctionArguments)}, 0.1 )
+ -- self.SpawnFunctionHook( self.SpawnGroups[self.SpawnIndex].Group, unpack( self.SpawnFunctionArguments ) )
+ end
+ -- TODO: Need to fix this by putting an "R" in the name of the group when the group repeats.
+ --if self.Repeat then
+ -- _DATABASE:SetStatusGroup( SpawnTemplate.name, "ReSpawn" )
+ --end
+ end
+
+ self.SpawnGroups[self.SpawnIndex].Spawned = true
+ return self.SpawnGroups[self.SpawnIndex].Group
+ else
+ --self:E( { self.SpawnTemplatePrefix, "No more Groups to Spawn:", SpawnIndex, self.SpawnMaxGroups } )
+ end
- return nil
+ return nil
end
--- Spawns new groups at varying time intervals.
@@ -917,17 +917,17 @@ end
-- -- Between these two values, a random amount of seconds will be choosen for each new spawn of the helicopters.
-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):Schedule( 600, 0.5 )
function SPAWN:SpawnScheduled( SpawnTime, SpawnTimeVariation )
- self:F( { SpawnTime, SpawnTimeVariation } )
+ self:F( { SpawnTime, SpawnTimeVariation } )
- if SpawnTime ~= nil and SpawnTimeVariation ~= nil then
- local InitialDelay = 0
- if self.DelayOnOff == true then
- InitialDelay = math.random( SpawnTime - SpawnTime * SpawnTimeVariation, SpawnTime + SpawnTime * SpawnTimeVariation )
- end
+ if SpawnTime ~= nil and SpawnTimeVariation ~= nil then
+ local InitialDelay = 0
+ if self.DelayOnOff == true then
+ InitialDelay = math.random( SpawnTime - SpawnTime * SpawnTimeVariation, SpawnTime + SpawnTime * SpawnTimeVariation )
+ end
self.SpawnScheduler = SCHEDULER:New( self, self._Scheduler, {}, InitialDelay, SpawnTime, SpawnTimeVariation )
- end
+ end
- return self
+ return self
end
--- Will re-start the spawning scheduler.
@@ -1134,7 +1134,7 @@ end
-- @return Wrapper.Group#GROUP that was spawned.
-- @return #nil Nothing was spawned.
function SPAWN:SpawnFromUnit( HostUnit, SpawnIndex )
- self:F( { self.SpawnTemplatePrefix, HostUnit, SpawnIndex } )
+ self:F( { self.SpawnTemplatePrefix, HostUnit, SpawnIndex } )
if HostUnit and HostUnit:IsAlive() ~= nil then -- and HostUnit:getUnit(1):inAir() == false then
return self:SpawnFromVec3( HostUnit:GetVec3(), SpawnIndex )
@@ -1171,7 +1171,7 @@ end
-- @return Wrapper.Group#GROUP that was spawned.
-- @return #nil when nothing was spawned.
function SPAWN:SpawnInZone( Zone, RandomizeGroup, SpawnIndex )
- self:F( { self.SpawnTemplatePrefix, Zone, RandomizeGroup, SpawnIndex } )
+ self:F( { self.SpawnTemplatePrefix, Zone, RandomizeGroup, SpawnIndex } )
if Zone then
if RandomizeGroup then
@@ -1192,15 +1192,15 @@ end
-- @param #boolean UnControlled true if UnControlled, false if Controlled.
-- @return #SPAWN self
function SPAWN:InitUnControlled( UnControlled )
- self:F2( { self.SpawnTemplatePrefix, UnControlled } )
-
- self.SpawnUnControlled = UnControlled
-
- for SpawnGroupID = 1, self.SpawnMaxGroups do
- self.SpawnGroups[SpawnGroupID].UnControlled = UnControlled
- end
-
- return self
+ self:F2( { self.SpawnTemplatePrefix, UnControlled } )
+
+ self.SpawnUnControlled = UnControlled
+
+ for SpawnGroupID = 1, self.SpawnMaxGroups do
+ self.SpawnGroups[SpawnGroupID].UnControlled = UnControlled
+ end
+
+ return self
end
@@ -1223,22 +1223,22 @@ end
-- @param #number SpawnIndex Is the number of the Group that is to be spawned.
-- @return #string SpawnGroupName
function SPAWN:SpawnGroupName( SpawnIndex )
- self:F( { self.SpawnTemplatePrefix, SpawnIndex } )
+ self:F( { self.SpawnTemplatePrefix, SpawnIndex } )
- local SpawnPrefix = self.SpawnTemplatePrefix
- if self.SpawnAliasPrefix then
- SpawnPrefix = self.SpawnAliasPrefix
- end
+ local SpawnPrefix = self.SpawnTemplatePrefix
+ if self.SpawnAliasPrefix then
+ SpawnPrefix = self.SpawnAliasPrefix
+ end
- if SpawnIndex then
- local SpawnName = string.format( '%s#%03d', SpawnPrefix, SpawnIndex )
- self:T( SpawnName )
- return SpawnName
- else
- self:T( SpawnPrefix )
- return SpawnPrefix
- end
-
+ if SpawnIndex then
+ local SpawnName = string.format( '%s#%03d', SpawnPrefix, SpawnIndex )
+ self:T( SpawnName )
+ return SpawnName
+ else
+ self:T( SpawnPrefix )
+ return SpawnPrefix
+ end
+
end
--- Will find the first alive @{Group} it has spawned, and return the alive @{Group} object and the first Index where the first alive @{Group} object has been found.
@@ -1253,7 +1253,7 @@ end
-- GroupPlane, Index = SpawnPlanes:GetNextAliveGroup( Index )
-- end
function SPAWN:GetFirstAliveGroup()
- self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } )
+ self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } )
for SpawnIndex = 1, self.SpawnCount do
local SpawnGroup = self:GetGroupFromIndex( SpawnIndex )
@@ -1279,7 +1279,7 @@ end
-- GroupPlane, Index = SpawnPlanes:GetNextAliveGroup( Index )
-- end
function SPAWN:GetNextAliveGroup( SpawnIndexStart )
- self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndexStart } )
+ self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndexStart } )
SpawnIndexStart = SpawnIndexStart + 1
for SpawnIndex = SpawnIndexStart, self.SpawnCount do
@@ -1303,7 +1303,7 @@ end
-- -- Do actions with the GroupPlane object.
-- end
function SPAWN:GetLastAliveGroup()
- self:F( { self.SpawnTemplatePrefixself.SpawnAliasPrefix } )
+ self:F( { self.SpawnTemplatePrefixself.SpawnAliasPrefix } )
self.SpawnIndex = self:_GetLastIndex()
for SpawnIndex = self.SpawnIndex, 1, -1 do
@@ -1327,18 +1327,18 @@ end
-- @param #number SpawnIndex The index of the group to return.
-- @return Wrapper.Group#GROUP self
function SPAWN:GetGroupFromIndex( SpawnIndex )
- self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndex } )
-
- if not SpawnIndex then
+ self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndex } )
+
+ if not SpawnIndex then
SpawnIndex = 1
- end
-
- if self.SpawnGroups and self.SpawnGroups[SpawnIndex] then
- local SpawnGroup = self.SpawnGroups[SpawnIndex].Group
- return SpawnGroup
- else
+ end
+
+ if self.SpawnGroups and self.SpawnGroups[SpawnIndex] then
+ local SpawnGroup = self.SpawnGroups[SpawnIndex].Group
+ return SpawnGroup
+ else
return nil
- end
+ end
end
@@ -1368,82 +1368,82 @@ end
--- Get the index from a given group.
-- The function will search the name of the group for a #, and will return the number behind the #-mark.
function SPAWN:GetSpawnIndexFromGroup( SpawnGroup )
- self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnGroup } )
-
- local IndexString = string.match( SpawnGroup:GetName(), "#(%d*)$" ):sub( 2 )
- local Index = tonumber( IndexString )
-
- self:T3( IndexString, Index )
- return Index
-
+ self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnGroup } )
+
+ local IndexString = string.match( SpawnGroup:GetName(), "#(%d*)$" ):sub( 2 )
+ local Index = tonumber( IndexString )
+
+ self:T3( IndexString, Index )
+ return Index
+
end
--- Return the last maximum index that can be used.
function SPAWN:_GetLastIndex()
- self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } )
+ self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } )
- return self.SpawnMaxGroups
+ return self.SpawnMaxGroups
end
--- Initalize the SpawnGroups collection.
-- @param #SPAWN self
function SPAWN:_InitializeSpawnGroups( SpawnIndex )
- self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndex } )
+ self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndex } )
- if not self.SpawnGroups[SpawnIndex] then
- self.SpawnGroups[SpawnIndex] = {}
- self.SpawnGroups[SpawnIndex].Visible = false
- self.SpawnGroups[SpawnIndex].Spawned = false
- self.SpawnGroups[SpawnIndex].UnControlled = false
- self.SpawnGroups[SpawnIndex].SpawnTime = 0
-
- self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix = self.SpawnTemplatePrefix
- self.SpawnGroups[SpawnIndex].SpawnTemplate = self:_Prepare( self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix, SpawnIndex )
- end
-
- self:_RandomizeTemplate( SpawnIndex )
- self:_RandomizeRoute( SpawnIndex )
- --self:_TranslateRotate( SpawnIndex )
-
- return self.SpawnGroups[SpawnIndex]
+ if not self.SpawnGroups[SpawnIndex] then
+ self.SpawnGroups[SpawnIndex] = {}
+ self.SpawnGroups[SpawnIndex].Visible = false
+ self.SpawnGroups[SpawnIndex].Spawned = false
+ self.SpawnGroups[SpawnIndex].UnControlled = false
+ self.SpawnGroups[SpawnIndex].SpawnTime = 0
+
+ self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix = self.SpawnTemplatePrefix
+ self.SpawnGroups[SpawnIndex].SpawnTemplate = self:_Prepare( self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix, SpawnIndex )
+ end
+
+ self:_RandomizeTemplate( SpawnIndex )
+ self:_RandomizeRoute( SpawnIndex )
+ --self:_TranslateRotate( SpawnIndex )
+
+ return self.SpawnGroups[SpawnIndex]
end
--- Gets the CategoryID of the Group with the given SpawnPrefix
function SPAWN:_GetGroupCategoryID( SpawnPrefix )
- local TemplateGroup = Group.getByName( SpawnPrefix )
-
- if TemplateGroup then
- return TemplateGroup:getCategory()
- else
- return nil
- end
+ local TemplateGroup = Group.getByName( SpawnPrefix )
+
+ if TemplateGroup then
+ return TemplateGroup:getCategory()
+ else
+ return nil
+ end
end
--- Gets the CoalitionID of the Group with the given SpawnPrefix
function SPAWN:_GetGroupCoalitionID( SpawnPrefix )
- local TemplateGroup = Group.getByName( SpawnPrefix )
-
- if TemplateGroup then
- return TemplateGroup:getCoalition()
- else
- return nil
- end
+ local TemplateGroup = Group.getByName( SpawnPrefix )
+
+ if TemplateGroup then
+ return TemplateGroup:getCoalition()
+ else
+ return nil
+ end
end
--- Gets the CountryID of the Group with the given SpawnPrefix
function SPAWN:_GetGroupCountryID( SpawnPrefix )
- self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnPrefix } )
-
- local TemplateGroup = Group.getByName( SpawnPrefix )
-
- if TemplateGroup then
- local TemplateUnits = TemplateGroup:getUnits()
- return TemplateUnits[1]:getCountry()
- else
- return nil
- end
+ self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnPrefix } )
+
+ local TemplateGroup = Group.getByName( SpawnPrefix )
+
+ if TemplateGroup then
+ local TemplateUnits = TemplateGroup:getUnits()
+ return TemplateUnits[1]:getCountry()
+ else
+ return nil
+ end
end
--- Gets the Group Template from the ME environment definition.
@@ -1452,22 +1452,22 @@ end
-- @param #string SpawnTemplatePrefix
-- @return @SPAWN self
function SPAWN:_GetTemplate( SpawnTemplatePrefix )
- self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnTemplatePrefix } )
+ self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnTemplatePrefix } )
- local SpawnTemplate = nil
+ local SpawnTemplate = nil
- SpawnTemplate = routines.utils.deepCopy( _DATABASE.Templates.Groups[SpawnTemplatePrefix].Template )
-
- if SpawnTemplate == nil then
- error( 'No Template returned for SpawnTemplatePrefix = ' .. SpawnTemplatePrefix )
- end
+ SpawnTemplate = routines.utils.deepCopy( _DATABASE.Templates.Groups[SpawnTemplatePrefix].Template )
+
+ if SpawnTemplate == nil then
+ error( 'No Template returned for SpawnTemplatePrefix = ' .. SpawnTemplatePrefix )
+ end
- --SpawnTemplate.SpawnCoalitionID = self:_GetGroupCoalitionID( SpawnTemplatePrefix )
- --SpawnTemplate.SpawnCategoryID = self:_GetGroupCategoryID( SpawnTemplatePrefix )
- --SpawnTemplate.SpawnCountryID = self:_GetGroupCountryID( SpawnTemplatePrefix )
-
- self:T3( { SpawnTemplate } )
- return SpawnTemplate
+ --SpawnTemplate.SpawnCoalitionID = self:_GetGroupCoalitionID( SpawnTemplatePrefix )
+ --SpawnTemplate.SpawnCategoryID = self:_GetGroupCategoryID( SpawnTemplatePrefix )
+ --SpawnTemplate.SpawnCountryID = self:_GetGroupCountryID( SpawnTemplatePrefix )
+
+ self:T3( { SpawnTemplate } )
+ return SpawnTemplate
end
--- Prepares the new Group Template.
@@ -1476,24 +1476,24 @@ end
-- @param #number SpawnIndex
-- @return #SPAWN self
function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) --R2.2
- self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } )
-
- local SpawnTemplate = self:_GetTemplate( SpawnTemplatePrefix )
- SpawnTemplate.name = self:SpawnGroupName( SpawnIndex )
-
- SpawnTemplate.groupId = nil
- --SpawnTemplate.lateActivation = false
+ self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } )
+
+ local SpawnTemplate = self:_GetTemplate( SpawnTemplatePrefix )
+ SpawnTemplate.name = self:SpawnGroupName( SpawnIndex )
+
+ SpawnTemplate.groupId = nil
+ --SpawnTemplate.lateActivation = false
SpawnTemplate.lateActivation = false
- if SpawnTemplate.CategoryID == Group.Category.GROUND then
- self:T3( "For ground units, visible needs to be false..." )
- SpawnTemplate.visible = false
- end
-
- if self.SpawnGrouping then
- local UnitAmount = #SpawnTemplate.units
- self:F( { UnitAmount = UnitAmount, SpawnGrouping = self.SpawnGrouping } )
- if UnitAmount > self.SpawnGrouping then
+ if SpawnTemplate.CategoryID == Group.Category.GROUND then
+ self:T3( "For ground units, visible needs to be false..." )
+ SpawnTemplate.visible = false
+ end
+
+ if self.SpawnGrouping then
+ local UnitAmount = #SpawnTemplate.units
+ self:F( { UnitAmount = UnitAmount, SpawnGrouping = self.SpawnGrouping } )
+ if UnitAmount > self.SpawnGrouping then
for UnitID = self.SpawnGrouping + 1, UnitAmount do
SpawnTemplate.units[UnitID] = nil
end
@@ -1506,12 +1506,12 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) --R2.2
end
end
end
-
+
if self.SpawnInitKeepUnitNames == false then
- for UnitID = 1, #SpawnTemplate.units do
- SpawnTemplate.units[UnitID].name = string.format( SpawnTemplate.name .. '-%02d', UnitID )
- SpawnTemplate.units[UnitID].unitId = nil
- end
+ for UnitID = 1, #SpawnTemplate.units do
+ SpawnTemplate.units[UnitID].name = string.format( SpawnTemplate.name .. '-%02d', UnitID )
+ SpawnTemplate.units[UnitID].unitId = nil
+ end
else
for UnitID = 1, #SpawnTemplate.units do
local UnitPrefix, Rest = string.match( SpawnTemplate.units[UnitID].name, "^([^#]+)#?" ):gsub( "^%s*(.-)%s*$", "%1" )
@@ -1521,10 +1521,10 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) --R2.2
SpawnTemplate.units[UnitID].unitId = nil
end
end
-
- self:T3( { "Template:", SpawnTemplate } )
- return SpawnTemplate
-
+
+ self:T3( { "Template:", SpawnTemplate } )
+ return SpawnTemplate
+
end
--- Private method randomizing the routes.
@@ -1532,7 +1532,7 @@ end
-- @param #number SpawnIndex The index of the group to be spawned.
-- @return #SPAWN
function SPAWN:_RandomizeRoute( SpawnIndex )
- self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeRoute, self.SpawnRandomizeRouteStartPoint, self.SpawnRandomizeRouteEndPoint, self.SpawnRandomizeRouteRadius } )
+ self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeRoute, self.SpawnRandomizeRouteStartPoint, self.SpawnRandomizeRouteEndPoint, self.SpawnRandomizeRouteRadius } )
if self.SpawnRandomizeRoute then
local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate
@@ -1566,7 +1566,7 @@ end
-- @param #number SpawnIndex
-- @return #SPAWN self
function SPAWN:_RandomizeTemplate( SpawnIndex )
- self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeTemplate } )
+ self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeTemplate } )
if self.SpawnRandomizeTemplate then
self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix = self.SpawnTemplatePrefixTable[ math.random( 1, #self.SpawnTemplatePrefixTable ) ]
@@ -1642,7 +1642,7 @@ function SPAWN:_RandomizeZones( SpawnIndex )
end
function SPAWN:_TranslateRotate( SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle )
- self:F( { self.SpawnTemplatePrefix, SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle } )
+ self:F( { self.SpawnTemplatePrefix, SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle } )
-- Translate
local TranslatedX = SpawnX
@@ -1686,7 +1686,7 @@ end
--- Get the next index of the groups to be spawned. This method is complicated, as it is used at several spaces.
function SPAWN:_GetSpawnIndex( SpawnIndex )
- self:F2( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive, self.AliveUnits, #self.SpawnTemplate.units } )
+ self:F2( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive, self.AliveUnits, #self.SpawnTemplate.units } )
if ( self.SpawnMaxGroups == 0 ) or ( SpawnIndex <= self.SpawnMaxGroups ) then
if ( self.SpawnMaxUnitsAlive == 0 ) or ( self.AliveUnits + #self.SpawnTemplate.units <= self.SpawnMaxUnitsAlive ) or self.UnControlled == true then
@@ -1721,13 +1721,13 @@ function SPAWN:_OnBirth( EventData )
if SpawnGroup then
local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup )
if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group!
- self:T( { "Birth Event:", EventPrefix, self.SpawnTemplatePrefix } )
- if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then
- self.AliveUnits = self.AliveUnits + 1
- self:T( "Alive Units: " .. self.AliveUnits )
- end
+ self:T( { "Birth Event:", EventPrefix, self.SpawnTemplatePrefix } )
+ if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then
+ self.AliveUnits = self.AliveUnits + 1
+ self:T( "Alive Units: " .. self.AliveUnits )
+ end
end
- end
+ end
end
@@ -1741,16 +1741,16 @@ function SPAWN:_OnDeadOrCrash( EventData )
local SpawnGroup = EventData.IniGroup
- if SpawnGroup then
- local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup )
- if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group!
+ if SpawnGroup then
+ local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup )
+ if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group!
self:T( { "Dead event: " .. EventPrefix } )
- if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then
- self.AliveUnits = self.AliveUnits - 1
- self:T( "Alive Units: " .. self.AliveUnits )
- end
+ if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then
+ self.AliveUnits = self.AliveUnits - 1
+ self:T( "Alive Units: " .. self.AliveUnits )
+ end
end
- end
+ end
end
--- Will detect AIR Units taking off... When the event takes place, the spawned Group is registered as airborne...
@@ -1766,11 +1766,11 @@ function SPAWN:_OnTakeOff( EventData )
if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group!
self:T( { "TakeOff event: " .. EventPrefix } )
if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then
- self:T( "self.Landed = false" )
- SpawnGroup:SetState( SpawnGroup, "Spawn_Landed", false )
+ self:T( "self.Landed = false" )
+ SpawnGroup:SetState( SpawnGroup, "Spawn_Landed", false )
end
end
- end
+ end
end
--- Will detect AIR Units landing... When the event takes place, the spawned Group is registered as landed.
@@ -1786,16 +1786,16 @@ function SPAWN:_OnLand( EventData )
if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group!
self:T( { "Land event: " .. EventPrefix } )
if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then
- -- TODO: Check if this is the last unit of the group that lands.
- SpawnGroup:SetState( SpawnGroup, "Spawn_Landed", true )
- if self.RepeatOnLanding then
- local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup )
- self:T( { "Landed:", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } )
- self:ReSpawn( SpawnGroupIndex )
- end
- end
+ -- TODO: Check if this is the last unit of the group that lands.
+ SpawnGroup:SetState( SpawnGroup, "Spawn_Landed", true )
+ if self.RepeatOnLanding then
+ local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup )
+ self:T( { "Landed:", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } )
+ self:ReSpawn( SpawnGroupIndex )
+ end
+ end
end
- end
+ end
end
--- Will detect AIR Units shutting down their engines ...
@@ -1812,72 +1812,72 @@ function SPAWN:_OnEngineShutDown( EventData )
if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group!
self:T( { "EngineShutdown event: " .. EventPrefix } )
if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then
- -- todo: test if on the runway
- local Landed = SpawnGroup:GetState( SpawnGroup, "Spawn_Landed" )
- if Landed and self.RepeatOnEngineShutDown then
- local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup )
- self:T( { "EngineShutDown: ", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } )
- self:ReSpawn( SpawnGroupIndex )
- end
- end
+ -- todo: test if on the runway
+ local Landed = SpawnGroup:GetState( SpawnGroup, "Spawn_Landed" )
+ if Landed and self.RepeatOnEngineShutDown then
+ local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup )
+ self:T( { "EngineShutDown: ", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } )
+ self:ReSpawn( SpawnGroupIndex )
+ end
+ end
end
- end
+ end
end
--- This function is called automatically by the Spawning scheduler.
-- It is the internal worker method SPAWNing new Groups on the defined time intervals.
function SPAWN:_Scheduler()
- self:F2( { "_Scheduler", self.SpawnTemplatePrefix, self.SpawnAliasPrefix, self.SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive } )
-
- -- Validate if there are still groups left in the batch...
- self:Spawn()
-
- return true
+ self:F2( { "_Scheduler", self.SpawnTemplatePrefix, self.SpawnAliasPrefix, self.SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive } )
+
+ -- Validate if there are still groups left in the batch...
+ self:Spawn()
+
+ return true
end
--- Schedules the CleanUp of Groups
-- @param #SPAWN self
-- @return #boolean True = Continue Scheduler
function SPAWN:_SpawnCleanUpScheduler()
- self:F( { "CleanUp Scheduler:", self.SpawnTemplatePrefix } )
+ self:F( { "CleanUp Scheduler:", self.SpawnTemplatePrefix } )
- local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup()
- self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } )
+ local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup()
+ self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } )
- while SpawnGroup do
+ while SpawnGroup do
local SpawnUnits = SpawnGroup:GetUnits()
-
- for UnitID, UnitData in pairs( SpawnUnits ) do
-
- local SpawnUnit = UnitData -- Wrapper.Unit#UNIT
- local SpawnUnitName = SpawnUnit:GetName()
-
-
- self.SpawnCleanUpTimeStamps[SpawnUnitName] = self.SpawnCleanUpTimeStamps[SpawnUnitName] or {}
- local Stamp = self.SpawnCleanUpTimeStamps[SpawnUnitName]
+
+ for UnitID, UnitData in pairs( SpawnUnits ) do
+
+ local SpawnUnit = UnitData -- Wrapper.Unit#UNIT
+ local SpawnUnitName = SpawnUnit:GetName()
+
+
+ self.SpawnCleanUpTimeStamps[SpawnUnitName] = self.SpawnCleanUpTimeStamps[SpawnUnitName] or {}
+ local Stamp = self.SpawnCleanUpTimeStamps[SpawnUnitName]
self:T( { SpawnUnitName, Stamp } )
-
- if Stamp.Vec2 then
- if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1 then
- local NewVec2 = SpawnUnit:GetVec2()
- if Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y then
- -- If the plane is not moving, and is on the ground, assign it with a timestamp...
- if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then
- self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } )
- self:ReSpawn( SpawnCursor )
+
+ if Stamp.Vec2 then
+ if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1 then
+ local NewVec2 = SpawnUnit:GetVec2()
+ if Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y then
+ -- If the plane is not moving, and is on the ground, assign it with a timestamp...
+ if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then
+ self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } )
+ self:ReSpawn( SpawnCursor )
Stamp.Vec2 = nil
Stamp.Time = nil
- end
- else
- Stamp.Time = timer.getTime()
+ end
+ else
+ Stamp.Time = timer.getTime()
Stamp.Vec2 = SpawnUnit:GetVec2()
- end
- else
- Stamp.Vec2 = nil
- Stamp.Time = nil
- end
- else
+ end
+ else
+ Stamp.Vec2 = nil
+ Stamp.Time = nil
+ end
+ else
if SpawnUnit:InAir() == false then
Stamp.Vec2 = SpawnUnit:GetVec2()
if SpawnUnit:GetVelocityKMH() < 1 then
@@ -1889,13 +1889,13 @@ function SPAWN:_SpawnCleanUpScheduler()
end
end
end
-
- SpawnGroup, SpawnCursor = self:GetNextAliveGroup( SpawnCursor )
-
- self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } )
-
- end
-
- return true -- Repeat
-
-end
\ No newline at end of file
+
+ SpawnGroup, SpawnCursor = self:GetNextAliveGroup( SpawnCursor )
+
+ self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } )
+
+ end
+
+ return true -- Repeat
+
+end
From 5a29b272dc8961d1e41432800bc2f12043ce6f49 Mon Sep 17 00:00:00 2001
From: funkyfranky
Date: Mon, 28 Aug 2017 23:51:54 +0200
Subject: [PATCH 04/28] First implementation of "air" start.
Added possibility to spawn in zones.
Other improvements and fixes.
---
Moose Development/Moose/AI/AI_RAT.lua | 272 ++++++++++++++++----------
1 file changed, 168 insertions(+), 104 deletions(-)
diff --git a/Moose Development/Moose/AI/AI_RAT.lua b/Moose Development/Moose/AI/AI_RAT.lua
index c2787f805..0cf3792e8 100644
--- a/Moose Development/Moose/AI/AI_RAT.lua
+++ b/Moose Development/Moose/AI/AI_RAT.lua
@@ -17,10 +17,10 @@ myid="RAT | "
-- @param #string ClassName Name of class.
RAT={
ClassName = "RAT", -- Name of class: RAT = Random Air Traffic.
- debug=false, -- Turn debug messages on or off.
+ debug=true, -- Turn debug messages on or off.
prefix=nil, -- Prefix of the template group defined in the mission editor.
spawndelay=5, -- Delay time in seconds before first spawning happens.
- spawninterval=5, -- Interval between spawning units/groups. note that we add a randomization of 10%
+ spawninterval=2, -- Interval between spawning units/groups. Note that we add a randomization of 10%.
coalition = nil, -- Coalition of spawn 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.
@@ -40,7 +40,7 @@ RAT={
departure_name="random", -- Name of the departure airport. Default is "random" for a randomly chosen one of the coalition airports.
destination_name="random",-- Name of the destination airport. Default is "random" for a randomly chosen one of the coalition airports.
zones_departure={}, -- Departure zones for air start.
- zones_radius=5000, -- Radius of departure zones in meters.
+ Rzone=5000, -- Radius of departure zones in meters.
ratcraft={}, -- Array with the spawned RAT aircraft.
}
@@ -99,13 +99,13 @@ function RAT:New(prefix, friendly)
-- Initialize aircraft parameters based on ME group template.
self:_InitAircraft(DCSgroup)
- -- Get all airports of this map.
+ -- Get all airports of current map (Caucasus, NTTR, Normandy, ...).
self:_GetAirportsOfMap()
-- Set the coalition table based on choice of self.coalition and self.friendly.
self:_SetCoalitionTable()
- -- Get all airports of this map beloning to "our" coalition.
+ -- Get all airports of this map beloning to friendly coalition(s).
self:_GetAirportsOfCoalition()
return self
@@ -113,7 +113,7 @@ end
--- Initialize basic parameters of the aircraft based on its (template) group in the mission editor.
-- @param #RAT self
--- @param #DCSgroup DCSgroup Group of the aircraft in the mission editor.
+-- @param Dcs.DCSWrapper.Group#Group DCSgroup Group of the aircraft in the mission editor.
function RAT:_InitAircraft(DCSgroup)
local DCSunit=DCSgroup:getUnit(1)
@@ -121,7 +121,7 @@ function RAT:_InitAircraft(DCSgroup)
local DCScategory=DCSgroup:getCategory()
local DCStype=DCSunit:getTypeName()
- -- print descriptors table of unit
+ -- Ddescriptors table of unit.
if self.debug then
self:E({"DCSdesc", DCSdesc})
end
@@ -143,6 +143,10 @@ function RAT:_InitAircraft(DCSgroup)
error(myid.."Group of RAT is neither airplane nor helicopter!")
end
+ -- Define a first departure zone around the point where the group template in the ME was placed.
+ local ZoneTemplate = ZONE_GROUP:New( "Template", GROUP:FindByName(self.prefix), self.Rzone)
+ table.insert(self.zones_departure, ZoneTemplate)
+
-- Get type of aircraft.
self.aircraft.type=DCStype
@@ -162,21 +166,21 @@ function RAT:_InitAircraft(DCSgroup)
self.aircraft.Vmin = self.aircraft.Vmax*0.75
-- actual travel speed (random between ASmin and ASmax)
- --TODO: this needs to be placed somewhere else!
+ --TODO: This needs to be placed somewhere else! Randomization should not happen here.
self.aircraft.Vcruise = math.random(self.aircraft.Vmin, self.aircraft.Vmax)
-- limit travel speed to ~900 km/h
- self.aircraft.Vcruise = math.min(self.aircraft.Vcruise,self.Vcruisemax)
+ self.aircraft.Vcruise = math.min(self.aircraft.Vcruise, self.Vcruisemax)
-- max climb speed in m/s
self.aircraft.Vymax=DCSdesc.VyMax
- -- reasonably civil climb speed Vy=1500 ft/min but max aircraft specific climb rate
+ -- Reasonably civil climb speed Vy=1500 ft/min but max aircraft specific climb rate.
self.aircraft.Vclimb=math.min(self.Vclimb*ft2meter/60, self.aircraft.Vymax)
- -- climb angle
+ -- Climb angle in rad.
self.aircraft.AlphaClimb=math.asin(self.aircraft.Vclimb/self.aircraft.Vmax)
- -- descent angle 3:1 rule of descent, i.e. 3 miles of travel and 1000 ft of descent.
+ -- Descent angle in rad.
self.aircraft.AlphaDescent=math.rad(self.AlphaDescent)
-- service ceiling in meters
@@ -184,33 +188,26 @@ function RAT:_InitAircraft(DCSgroup)
-- default flight levels (ASL)
if self.category=="plane" then
- --TODO: need to fine tune and text these default parameters (also for different maps)
- self.aircraft.FLmin=100*FL2m
- self.aircraft.FLmax=self.aircraft.ceiling*0.9
self.aircraft.FLcruise=self.FLcruise*FL2m
else
- --TODO: need to check these parameters for helos
- self.aircraft.FLmin=001*FL2m
- self.aircraft.FLmax=self.aircraft.ceiling*0.9
+ --TODO: need to check these parameters for helos: FL005 = 152 m
self.aircraft.FLcruise=005*FL2m
end
-- send debug message
local text=string.format("Aircraft parameters:\n")
- text=text..string.format("Category = %s\n", self.category)
- text=text..string.format("Max Speed = %6.1f m/s.\n", self.aircraft.Vmax)
- text=text..string.format("Cruise Speed = %6.1f m/s.\n", self.aircraft.Vcruise)
- text=text..string.format("Climb Speed Max = %6.1f m/s.\n", self.aircraft.Vymax)
- text=text..string.format("Climb Speed = %6.1f m/s.\n", self.aircraft.Vclimb)
+ text=text..string.format("Category = %s\n", self.category)
+ text=text..string.format("Max speed = %6.1f m/s.\n", self.aircraft.Vmax)
+ text=text..string.format("Max cruise speed = %6.1f m/s.\n", self.aircraft.Vcruise)
+ text=text..string.format("Max climb speed = %6.1f m/s.\n", self.aircraft.Vymax)
+ text=text..string.format("Climb speed = %6.1f m/s.\n", self.aircraft.Vclimb)
text=text..string.format("Angle of climb = %6.1f Deg.\n", math.deg(self.aircraft.AlphaClimb))
text=text..string.format("Angle of descent = %6.1f Deg.\n", math.deg(self.aircraft.AlphaDescent))
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("Ceiling = %3.0f = %6.1f km.\n", self.aircraft.ceiling/FL2m, self.aircraft.ceiling/1000)
- text=text..string.format("FL min = %3.0f = %6.1f km.\n", self.aircraft.FLmin/FL2m, self.aircraft.FLmin/1000)
- text=text..string.format("FL max = %3.0f = %6.1f km.\n", self.aircraft.FLmax/FL2m, self.aircraft.FLmax/1000)
- text=text..string.format("FL cruise = %3.0f = %6.1f km.", self.aircraft.FLcruise/FL2m, self.aircraft.FLcruise/1000)
+ 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("Ceiling = FL%3.0f = %6.1f km.\n", self.aircraft.ceiling/FL2m, self.aircraft.ceiling/1000)
+ text=text..string.format("FL cruise = FL%3.0f = %6.1f km.", self.aircraft.FLcruise/FL2m, self.aircraft.FLcruise/1000)
env.info(myid..text)
if self.debug then
MESSAGE:New(text, 60):ToAll()
@@ -241,15 +238,18 @@ function RAT:Spawn(naircraft, name)
MESSAGE:New(text, 60, "Info"):ToAll()
end
- -- schedule the spawning
- --TODO: make dt user input self.SpawnInterval
- --TODO: check the interval is > 180 seconds if spawn at runway is chosen.
- local tstart=5
- local dt=5
- local tstop=tstart+dt*(naircraft-1)
- SCHEDULER:New(nil, self._SpawnWithRoute, {self}, tstart, dt, 0.1, tstop)
+ -- Schedule spawning of aircraft.
+ --TODO: make self.SpawnInterval and sef.spawndelay user input
+ local Tstart=self.spawndelay
+ local dt=self.spawninterval
+ if self.takeoff:lower()=="takeoff-runway" or self.takeoff:lower()=="runway" then
+ -- Ensure that interval is >= 180 seconds if spawn at runway is chosen. Aircraft need time to takeoff or the runway gets jammed.
+ dt=math.max(dt, 180)
+ end
+ local Tstop=Tstart+dt*(naircraft-1)
+ SCHEDULER:New(nil, self._SpawnWithRoute, {self}, Tstart, dt, 0.1, Tstop)
- -- Status report scheduler
+ -- Status report scheduler.
SCHEDULER:New(nil, self.Status, {self}, 30, 10, 0.0)
end
@@ -263,7 +263,7 @@ function RAT:_SpawnWithRoute()
local departure, destination, waypoints = self:_SetRoute()
-- Modify the spawn template to follow the flight plan.
- self:_ModifySpawnTemplate(departure, waypoints)
+ self:_ModifySpawnTemplate(waypoints)
-- Actually spawn the group.
local group=self:SpawnWithIndex(self.SpawnIndex) -- Core.Group#GROUP
@@ -283,7 +283,7 @@ function RAT:_SpawnWithRoute()
-- Handle events.
-- TODO: add hit event?
- self:HandleEvent(EVENTS.Birth, self._OnBirthDay)
+ self:HandleEvent(EVENTS.Birth, self._OnBirthDay)
self:HandleEvent(EVENTS.EngineStartup, self._EngineStartup)
self:HandleEvent(EVENTS.Takeoff, self._OnTakeoff)
self:HandleEvent(EVENTS.Land, self._OnLand)
@@ -311,18 +311,22 @@ function RAT:Status()
end
end
---- Get life of unit.
+--- Get (relative) life of unit.
-- @param #RAT self
-- @return #number Life of unit in percent.
function RAT:_GetLife(group)
+ local life=0.0
if group then
local unit=group:GetUnit(1)
- local life=unit:GetLife()/unit:GetLife0()*100
- return life
+ if unit then
+ life=unit:GetLife()/unit:GetLife0()*100
+ else
+ error(myid.."Unit does not exists in RAT_Getlife(). Returning zero.")
+ end
else
- error("Group does not exists in RAT_Getlife(). Returning zero.")
- return 0.0
+ error(myid.."Group does not exists in RAT_Getlife(). Returning zero.")
end
+ return life
end
--- Set status of group.
@@ -424,7 +428,7 @@ function RAT:_OnDead(EventData)
self:_SetStatus(SpawnGroup, "dead (died)")
--MESSAGE:New(text, 180):ToAll()
else
- error("Group does not exist in RAT:_OnDedad().")
+ error("Group does not exist in RAT:_OnDead().")
end
end
@@ -455,6 +459,26 @@ function RAT:SetDeparture(name)
end
end
+--- Set names of departure zones for spawning the AI aircraft.
+-- @param #RAT self
+-- @param #table zonenames Table of zone names where spawning should happen.
+function RAT:SetDepartureZones(zonenames)
+ self.zones_departure={}
+ local z
+ for _,name in pairs(zonenames) do
+ if name:lower()=="zone template" then
+ z=ZONE_GROUP:New("Zone Template", GROUP:FindByName(self.prefix), self.Rzone)
+ else
+ z=ZONE:New(name)
+ end
+ if z then
+ table.insert(self.zones_departure, z)
+ else
+ error(myid.."A zone with name "..name.." does not exist!")
+ end
+ end
+end
+
--- Set name of destination airport for the AI aircraft. If no name is given an airport from the coalition is chosen randomly.
-- @param #RAT self
-- @param #string name Name of the destination airport or "random" for a randomly chosen one of the coalition.
@@ -469,23 +493,32 @@ end
--- Set the departure airport of the AI. If no airport name is given an airport from the coalition is chosen randomly.
-- @param #RAT self
--- @return Wrapper.Airbase#AIRBASE Departure airport
+-- @return Wrapper.Airbase#AIRBASE Departure airport if spawning at airport.
function RAT:_SetDeparture()
- local departure -- Wrapper.Airbase#AIRBASE
- if self.departure_name=="random" then
- -- Get a random departure airport from all friendly coalition airports.
- departure=self.airports[math.random(1, #self.airports)]
- elseif AIRBASE:FindByName(self.departure_name) then
- -- Take the explicit airport provided.
- departure=AIRBASE:FindByName(self.departure_name)
+ local departure
+ local text
+ if self.takeoff=="air" then
+ if self.departure_name=="random" then
+ departure=self.zones_departure[math.random(1, #self.zones_departure)]
+ else
+ departure=ZONE:FindByName(self.departure_name)
+ end
+ text="Chosen departure zone: "..departure:GetName()
else
- -- If nothing else works, we randomly choose from frindly coalition airports.
- departure=self.airports[math.random(1, #self.airports)]
+ if self.departure_name=="random" then
+ -- Get a random departure airport from all friendly coalition airports.
+ departure=self.airports[math.random(1, #self.airports)]
+ elseif AIRBASE:FindByName(self.departure_name) then
+ -- Take the explicit airport provided.
+ departure=AIRBASE:FindByName(self.departure_name)
+ else
+ -- If nothing else works, we randomly choose from frindly coalition airports.
+ departure=self.airports[math.random(1, #self.airports)]
+ end
+ text="Chosen departure airport: "..departure:GetName().." with ID "..departure:GetID()
end
- local text="Chosen departure airport: "..departure:GetName().." with ID "..departure:GetID()
- self:E(departure:GetDesc())
env.info(myid..text)
- MESSAGE:New(text, 60):ToAll()
+ MESSAGE:New(text, 60):ToAll()
return departure
end
@@ -614,7 +647,7 @@ end
-- @param #number Speed Speed in m/s.
-- @param #number Altitude Altitude in m.
-- @param Wrapper.Airbase#AIRBASE Airport Airport of object to spawn.
--- @return #list RoutePoint Waypoint for DCS task route.
+-- @return #table Waypoints for DCS task route or spawn template.
function RAT:Waypoint(Type, Coord, Speed, Altitude, Airport)
-- set altitude to input parameter or take y of 3D-coordinate
@@ -623,30 +656,42 @@ function RAT:Waypoint(Type, Coord, Speed, Altitude, Airport)
--TODO: _Type should be generalized to Grouptemplate.Type
--TODO: Only use _alttype="BARO" and add landheight for _alttype="RADIO". Don't know if "BARO" really works atm.
+ -- Land height at given coordinate.
+ local Hland=Coord:GetLandHeight()
+
-- convert type and action in DCS format
- local _Action=nil
- local _AID=nil
- local _hold=nil
local _Type=nil
+ local _Action=nil
local _alttype="RADIO"
+ local _AID=nil
+
if Type:lower()=="takeoff-cold" or Type:lower()=="cold" then
-- take-off with engine off
_Type="TakeOffParking"
+ _Action="From Parking Area"
_Altitude = 2
_alttype="RADIO"
_AID = Airport:GetID()
elseif Type:lower()=="takeoff-hot" or Type:lower()=="hot" then
-- take-off with engine on
_Type="TakeOffParkingHot"
- _AID = Airport:GetID()
+ _Action="From Parking Area"
_Altitude = 2
_alttype="RADIO"
+ _AID = Airport:GetID()
elseif Type:lower()=="takeoff-runway" or Type:lower()=="runway" then
-- take-off from runway
_Type="TakeOff"
- _AID = Airport:GetID()
+ _Action="From Parking Area" --TODO: Is this correct for a runway start?
_Altitude = 2
_alttype="RADIO"
+ _AID = Airport:GetID()
+ elseif Type:lower()=="air" then
+ -- air start
+ _Type="Turning Point"
+ _Action="Turning Point"
+ _Altitude = 5000
+ _alttype="BARO"
elseif Type:lower()=="climb" or Type:lower()=="cruise" then
_Type="Turning Point"
_Action="Turning Point"
@@ -659,13 +704,11 @@ function RAT:Waypoint(Type, Coord, Speed, Altitude, Airport)
_Type="Turning Point"
_Action="Fly Over Point"
_alttype="RADIO"
- local P1= {x=Coord.x, y=Coord.z}
- _hold=self:_TaskHolding(P1, Altitude, Speed)
elseif Type:lower()=="landing" or Type:lower()=="land" then
_Type="Land"
_Action="Landing"
- _alttype="RADIO"
_Altitude = 2
+ _alttype="RADIO"
_AID = Airport:GetID()
else
error("Unknown waypoint type in RAT:Waypoint function!")
@@ -683,16 +726,20 @@ function RAT:Waypoint(Type, Coord, Speed, Altitude, Airport)
at="AGL"
end
local text=string.format("\nType: %s.\n", Type)
- if Action then
- text=text..string.format("Action: %s.\n", tostring(Action))
+ if _Action then
+ text=text..string.format("Action: %s.\n", tostring(_Action))
end
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)
text=text..string.format("Speed = %6.1f m/s = %6.1f km/h = %6.1f knots.\n", Speed, Speed*3.6, Speed*1.94384)
- text=text..string.format("Altitude = %6.1f m "..at..".\n", Altitude)
+ text=text..string.format("Altitude = %6.1f m "..at..".\n", _Altitude)
if Airport then
- text=text..string.format("Destination airport = %s with ID %i.", Airport:GetName(), Airport:GetID())
+ if Type:lower() == "air" then
+ text=text..string.format("Zone = %s.", Airport:GetName())
+ else
+ text=text..string.format("Airport = %s with ID %i.", Airport:GetName(), Airport:GetID())
+ end
else
- text=text..string.format("No airport specified.")
+ text=text..string.format("No (valid) airport specified.")
end
local debugmessage=false
if debugmessage then
@@ -734,7 +781,7 @@ function RAT:Waypoint(Type, Coord, Speed, Altitude, Airport)
}
-- task
if Type:lower()=="holding" then
- RoutePoint.task=_hold
+ RoutePoint.task=self:_TaskHolding({x=Coord.x, y=Coord.z}, Altitude, Speed)
else
RoutePoint.task = {}
RoutePoint.task.id = "ComboTask"
@@ -751,7 +798,7 @@ end
-- @param #RAT self
-- @param #string type Type can be "takeoff-cold" or "cold", "takeoff-hot" or "hot", "takeoff-runway" or "runway", "air", "random".
function RAT:SetTakeoff(type)
- -- all possible types
+ -- All possible types for random selection.
local types={"takeoff-cold", "takeoff-hot", "takeoff-runway"}
local _Type
if type:lower()=="takeoff-cold" or type:lower()=="cold" then
@@ -862,22 +909,29 @@ function RAT:_SetRoute()
--TODO: Add case where we don't have a departure airport but rather a zone!
-- DEPARTURE AIRPORT
- -- set departure airport
- local departure=self:_SetDeparture()
+ -- Departure airport or zone.
+ local departure=self:_SetDeparture()
-- coordinates
- local Pdeparture=departure:GetCoordinate()
+ local Pdeparture
+ if self.takeoff=="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)
+ else
+ Pdeparture=departure:GetCoordinate()
+ end
-- height ASL if departure
local H_departure=Pdeparture.y
-- DESTINATION AIRPORT
- -- get all destination airports in reach and at least 10 km away from departure
- self:_GetDestinations(Pdeparture, self.mindist, self.range)
+ -- get all destination airports within reach and at least 10 km away from departure
+ self:_GetDestinations(Pdeparture, self.mindist, self.aircraft.Reff)
local destination=self:_SetDestination()
if destination:GetName()==departure:GetName() then
- local text="Destination and departure aiport are identical: "..destination:GetName().." with ID "..destination:GetID()
+ local text="Destination and departure airport are identical: "..destination:GetName().." with ID "..destination:GetID()
MESSAGE:New(text, 120):ToAll()
error(myid..text)
end
@@ -905,9 +959,7 @@ function RAT:_SetRoute()
else
h_holding=100
end
- env.info(myid.."h_holding before = "..h_holding)
h_holding=self:_Randomize(h_holding, 0.2)
- env.info(myid.."h_holding after = "..h_holding)
-- distance from holding point to destination
local d_holding=Pholding:Get2DDistance(Pdestination)
@@ -916,7 +968,7 @@ function RAT:_SetRoute()
-- heading from departure to holding point of destination
local heading=self:_Course(Pdeparture, Pholding) -- heading from departure to destination
- -- total distance between departure and holding point + last bit to destination
+ -- total distance between departure and holding point (+last bit to destination)
local d_total=Pdeparture:Get2DDistance(Pholding)
-- CLIMB and DESCENT angles
@@ -931,9 +983,12 @@ function RAT:_SetRoute()
if self.category=="plane" then
-- min cruise alt is above 100 meter above holding point
FLmin=math.max(H_departure, H_destination+h_holding)
- -- quick check if the distance between the two airports is large enough to
- -- actually reach the desired FL and then descent again at the given climb/descent rates.
+ -- Check if the distance between the two airports is large enough to reach the desired FL and descent again at the given climb/descent rates.
+ -- TODO: need to modify this for "air" start.
FLmax=self:_FLmax(AlphaClimb, AlphaDescent, d_total, H_departure)
+ if self.takeoff=="air" then
+ FLmax=FLmax*2
+ end
if FLmin>FLmax then
FLmin=FLmax*0.8
end
@@ -947,10 +1002,9 @@ function RAT:_SetRoute()
end
-- set randomized cruise altitude: default +-50% but limited
FLcruise=self:_Randomize(FLcruise, 0.5, FLmin, FLmax)
- -- check that we are not above 90% of serice ceiling
+ -- check that we are not above 90% of service ceiling
FLcruise=math.min(FLcruise, self.aircraft.ceiling*0.9)
-
--CLIMB
-- height of climb relative to ASL height of departure airport
local h_climb=FLcruise-H_departure
@@ -979,17 +1033,17 @@ function RAT:_SetRoute()
text=text..string.format("H_departure = %6.1f m ASL\n", H_departure)
text=text..string.format("H_destination = %6.1f m ASL\n", H_destination)
text=text..string.format("h_climb = %6.1f m AGL\n", h_climb)
+ text=text..string.format("h_descent = %6.1f m\n", h_descent)
+ text=text..string.format("h_holding = %6.1f m AGL\n", h_holding)
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("FLmin = %6.1f m ASL\n", FLmin)
text=text..string.format("FLmax = %6.1f m ASL\n", FLmax)
text=text..string.format("FLcruise = %6.1f m ASL\n", FLcruise)
- text=text..string.format("h_descent = %6.1f m\n", h_descent)
- text=text..string.format("h_holding = %6.1f m AGL\n", h_holding)
text=text..string.format("Heading = %6.1f Degrees", heading)
env.info(myid..text)
if self.debug then
- MESSAGE:New(text, 180):ToAll()
+ MESSAGE:New(text, 60):ToAll()
end
-- coordinates of route from depature (0) to cruise (1) to descent (2) to holing (3) to destination (4)
@@ -1046,18 +1100,27 @@ end
--- Modifies the template of the group to be spawned.
--- In particular a template can be spawned at an airbase or in the air at a certain point.
--- Also adds the waypoints to the template, which circumvents the DCS "landing bug".
+-- In particular, the waypoints of the group's flight plan are copied into the spawn template.
+-- This allows to spawn at airports and also land at other airports, i.e. circumventing the DCS "landing bug".
-- @param #RAT self
--- @param Wrapper.Airbase#AIRBASE Airbase The @{Airbase} where to spawn the group.
--- @param #number TakeoffAltitude (optional) The altitude above the ground.
-function RAT:_ModifySpawnTemplate(Airbase, waypoints)
+-- @param #table waypoints The waypoints of the AI flight plan.
+function RAT:_ModifySpawnTemplate(waypoints)
-- point of Airbase
- local PointVec3 = Airbase:GetPointVec3()
+ --local PointVec3 = Airbase:GetPointVec3()
+
+ local PointVec3 = {x=waypoints[1].x, y=waypoints[1].alt, z=waypoints[1].y}
+ local addheight
+ if self.takeoff=="air" then
+ addheight=5000
+ else
+ addheight=2
+ end
- if self:_GetSpawnIndex( self.SpawnIndex + 1 ) then
+ if self:_GetSpawnIndex(self.SpawnIndex+1) then
+
+ -- Get copy of spawn template.
local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate
if SpawnTemplate then
@@ -1071,11 +1134,11 @@ function RAT:_ModifySpawnTemplate(Airbase, waypoints)
local SY = UnitTemplate.y
local BX = SpawnTemplate.route.points[1].x
local BY = SpawnTemplate.route.points[1].y
- local TX = PointVec3.x + ( SX - BX )
- local TY = PointVec3.z + ( SY - BY )
+ local TX = PointVec3.x + (SX-BX)
+ local TY = PointVec3.z + (SY-BY)
SpawnTemplate.units[UnitID].x = TX
SpawnTemplate.units[UnitID].y = TY
- SpawnTemplate.units[UnitID].alt = PointVec3.y + 2
+ SpawnTemplate.units[UnitID].alt = PointVec3.y + addheight
self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
end
@@ -1086,9 +1149,10 @@ function RAT:_ModifySpawnTemplate(Airbase, waypoints)
end
--TODO: Is this really necessary. Should already be defined in _Waypoints() function
- SpawnTemplate.route.points[1].x = PointVec3.x
- SpawnTemplate.route.points[1].y = PointVec3.z
- SpawnTemplate.route.points[1].alt = PointVec3.y + 2
+ --SpawnTemplate.route.points[1].x = PointVec3.x
+ --SpawnTemplate.route.points[1].y = PointVec3.z
+ --SpawnTemplate.route.points[1].alt = PointVec3.y + addheight
+
--SpawnTemplate.route.points[1].airdromeId = Airbase:GetID()
--SpawnTemplate.route.points[1].type = GROUPTEMPLATE.Takeoff[Takeoff]
@@ -1127,7 +1191,7 @@ function RAT:_SetCoalitionTable()
self.ctable={self.coalition, coalition.side.NEUTRAL}
end
-- debug info
- self:E({"Coalition table: ", self.ctable})
+ self:T({"Coalition table: ", self.ctable})
end
From eac89f784d27de56edc3a2171f35c7b04fef57d8 Mon Sep 17 00:00:00 2001
From: funkyfranky
Date: Tue, 29 Aug 2017 16:46:56 +0200
Subject: [PATCH 05/28] Improvments for air spawn behaviour.
---
Moose Development/Moose/AI/AI_RAT.lua | 158 +++++++++++++-------------
1 file changed, 81 insertions(+), 77 deletions(-)
diff --git a/Moose Development/Moose/AI/AI_RAT.lua b/Moose Development/Moose/AI/AI_RAT.lua
index 0cf3792e8..ec2d75183 100644
--- a/Moose Development/Moose/AI/AI_RAT.lua
+++ b/Moose Development/Moose/AI/AI_RAT.lua
@@ -11,10 +11,6 @@ myid="RAT | "
-- @field #string prefix
-- @field #RAT
-- @extends #SPAWN
-
---- Table of RAT
--- @field #RAT RAT
--- @param #string ClassName Name of class.
RAT={
ClassName = "RAT", -- Name of class: RAT = Random Air Traffic.
debug=true, -- Turn debug messages on or off.
@@ -26,13 +22,12 @@ RAT={
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.
ctable = {}, -- Table with the valid coalitons from choice self.friendly.
aircraft = {}, -- Table which holds the basic aircraft properties (speed, range, ...).
- Vcruisemax=250, -- Max cruise speed in m/s (250 m/s = 900 km/h = 486 kt)
+ Vcruisemax=250, -- Max cruise speed in m/s (250 m/s = 900 km/h = 486 kt).
Vclimb=1500, -- Default climb rate in ft/min.
AlphaDescent=3.6, -- 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.
- FLcruise=200, -- Default cruise flight level in. FL200 = 20000 ft.
roe = "hold", -- ROE of spawned groups, default is weapon hold (this is a peaceful class for civil aircraft or ferry missions).
takeoff = "hot", -- Takeoff type: "hot", "cold", "runway", "air", "random".
- mindist = 10000, -- Min distance from departure to destination in meters.
+ mindist = 5000, -- Min distance from departure to destination in meters.
airports_map={}, -- All airports available on current map (Caucasus, Nevada, Normandy, ...).
airports={}, -- All airports of friedly coalitions.
airports_departure={}, -- Possible departure airports if unit/group is spawned at airport, spawnpoint=air or spawnpoint=airport.
@@ -47,11 +42,10 @@ RAT={
--TODO list:
--TODO: Add scheduled spawn and corresponding user functions.
--TODO: Add possibility to spawn in air.
---TODO: Add from zones and to zones for aircraft to fly from/to.
+--TODO: Add departure zones for air start.
--TODO: Make more functions to adjust/set parameters.
--TODO: Clean up debug messages.
--DONE: Improve flight plan. Especially check FL against route length.
---TODO: Integrate RAT:DB? No, not now.
--DONE: Add event handlers.
--DONE: Respawn units when they have landed.
--DONE: Change ROE state.
@@ -61,8 +55,6 @@ RAT={
--TODO: Add possibility to continue journey at destination. Need "place" in event data for that.
--DONE: Check that FARPS are not used as airbases for planes. Don't know if they appear in list of airports.
--DONE: Add cases for helicopters.
---TODO: Lots of other little things...
-
--- Creates a new RAT object.
-- @param #RAT self
@@ -166,9 +158,10 @@ function RAT:_InitAircraft(DCSgroup)
self.aircraft.Vmin = self.aircraft.Vmax*0.75
-- actual travel speed (random between ASmin and ASmax)
- --TODO: This needs to be placed somewhere else! Randomization should not happen here.
+ --TODO: This needs to be placed somewhere else! Randomization should not happen here. Otherwise it is not changed for multiple spawns.
self.aircraft.Vcruise = math.random(self.aircraft.Vmin, self.aircraft.Vmax)
- -- limit travel speed to ~900 km/h
+
+ -- Limit travel speed to ~900 km/h for jets.
self.aircraft.Vcruise = math.min(self.aircraft.Vcruise, self.Vcruisemax)
-- max climb speed in m/s
@@ -186,11 +179,12 @@ function RAT:_InitAircraft(DCSgroup)
-- service ceiling in meters
self.aircraft.ceiling=DCSdesc.Hmax
- -- default flight levels (ASL)
+ -- Default flight level (ASL).
if self.category=="plane" then
- self.aircraft.FLcruise=self.FLcruise*FL2m
+ -- For planes: FL200 = 20000 ft = 6096 m.
+ self.aircraft.FLcruise=200*FL2m
else
- --TODO: need to check these parameters for helos: FL005 = 152 m
+ -- For helos: FL005 = 500 ft = 152 m.
self.aircraft.FLcruise=005*FL2m
end
@@ -256,6 +250,10 @@ end
--- Spawn the AI aircraft with a route.
+-- Sets the departure and destination airports and waypoints.
+-- Modifies the spawn template.
+-- Sets ROE/ROT.
+-- Initializes the ratcraft array and event handlers.
-- @param #RAT self
function RAT:_SpawnWithRoute()
@@ -459,7 +457,7 @@ function RAT:SetDeparture(name)
end
end
---- Set names of departure zones for spawning the AI aircraft.
+--- Set departure zones for spawning the AI aircraft.
-- @param #RAT self
-- @param #table zonenames Table of zone names where spawning should happen.
function RAT:SetDepartureZones(zonenames)
@@ -467,8 +465,10 @@ function RAT:SetDepartureZones(zonenames)
local z
for _,name in pairs(zonenames) do
if name:lower()=="zone template" then
+ -- Zone with radius 5 km around the template group in the ME.
z=ZONE_GROUP:New("Zone Template", GROUP:FindByName(self.prefix), self.Rzone)
else
+ -- Zone defined my user in the ME.
z=ZONE:New(name)
end
if z then
@@ -553,7 +553,7 @@ end
-- @param #number minrange Minimum range to q in meters.
-- @param #number maxrange Maximum range to q in meters.
function RAT:_GetDestinations(q, minrange, maxrange)
- local absolutemin=10000 -- Absolute minimum is 10 km.
+ local absolutemin=5000 -- Absolute minimum is 5 km.
minrange=minrange or absolutemin -- Default min is absolute min.
maxrange=maxrange or 10000000 -- Default max 10,000 km.
-- Ensure that minrange is always > 10 km to ensure the destination != departure.
@@ -624,7 +624,8 @@ function RAT:_GetAirportsOfCoalition()
for _,airport in pairs(self.airports_map) do
if airport:GetCoalition()==coalition then
airport:GetTypeName()
- -- remember that planes cannot land on FARPs.
+ -- Remember that planes cannot land on FARPs.
+ -- TODO: Probably have to add ships as well!
if not (self.category=="plane" and airport:GetTypeName()=="FARP") then
table.insert(self.airports, airport)
end
@@ -650,11 +651,11 @@ end
-- @return #table Waypoints for DCS task route or spawn template.
function RAT:Waypoint(Type, Coord, Speed, Altitude, Airport)
- -- set altitude to input parameter or take y of 3D-coordinate
+ -- Altitude of input parameter or y-component of 3D-coordinate.
local _Altitude=Altitude or Coord.y
--TODO: _Type should be generalized to Grouptemplate.Type
- --TODO: Only use _alttype="BARO" and add landheight for _alttype="RADIO". Don't know if "BARO" really works atm.
+ --TODO: Only use _alttype="BARO" and add landheight for _alttype="RADIO". Don't know if "RADIO" really works well.
-- Land height at given coordinate.
local Hland=Coord:GetLandHeight()
@@ -690,7 +691,6 @@ function RAT:Waypoint(Type, Coord, Speed, Altitude, Airport)
-- air start
_Type="Turning Point"
_Action="Turning Point"
- _Altitude = 5000
_alttype="BARO"
elseif Type:lower()=="climb" or Type:lower()=="cruise" then
_Type="Turning Point"
@@ -823,7 +823,7 @@ end
-- @param Dcs.DCSTypes#Vec2 P1 The point to hold the position.
-- @param #number Altitude The altitude AGL to hold the position.
-- @param #number Speed The speed flying when holding the position in m/s.
--- @return Dcs.DCSTasking.Task#Task
+-- @return Dcs.DCSTasking.Task#Task DCSTask
function RAT:_TaskHolding(P1, Altitude, Speed)
local LandHeight = land.getHeight(P1)
@@ -905,54 +905,69 @@ function RAT:_SetRoute()
local kmh2ms=0.278
local FL2m=30.48
local nm2km=1.852
-
- --TODO: Add case where we don't have a departure airport but rather a zone!
-- DEPARTURE AIRPORT
-- Departure airport or zone.
local departure=self:_SetDeparture()
- -- coordinates
+ -- Coordinates of departure point.
local Pdeparture
if self.takeoff=="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:New(vec2.x, self.aircraft.FLcruise, vec2.y)
+ Pdeparture=COORDINATE:NewFromVec2(vec2)
else
Pdeparture=departure:GetCoordinate()
end
- -- height ASL if departure
- local H_departure=Pdeparture.y
+ -- Height ASL of departure point.
+ local H_departure
+ if self.takeoff=="air" then
+ -- Departure altitude is 70% of default cruise with 30% variation and limited to 1000 m AGL (50 m for helos).
+ local Hmin
+ if self.category=="pland" then
+ Hmin=1000
+ else
+ Hmin=50
+ end
+ H_departure=self:_Randomize(self.aircraft.FLcruise*0.7, 0.3, Pdeparture.y+Hmin, self.aircraft.FLcruise)
+ else
+ H_departure=Pdeparture.y
+ end
-- DESTINATION AIRPORT
- -- get all destination airports within reach and at least 10 km away from departure
+ -- Get all destination airports within reach and at least 10 km away from departure.
self:_GetDestinations(Pdeparture, self.mindist, self.aircraft.Reff)
+
+ -- Pick a destination airport.
local destination=self:_SetDestination()
+ -- Check that departure and destination are not the same. Should not happen due to mindist.
if destination:GetName()==departure:GetName() then
local text="Destination and departure airport are identical: "..destination:GetName().." with ID "..destination:GetID()
MESSAGE:New(text, 120):ToAll()
error(myid..text)
end
- -- coordinates of destination
+ -- Coordinates of destination airport.
local Pdestination=destination:GetCoordinate()
- -- height ASL of destination
+ -- Height ASL of destination airport.
local H_destination=Pdestination.y
-- DESCENT/HOLDING POINT
- -- get a random point between 10 and 20 km away from the destination
+ -- Get a random point between 10 and 20 km away from the destination.
local Vholding
if self.category=="plane" then
Vholding=destination:GetCoordinate():GetRandomVec2InRadius(20000, 10000)
else
- -- for helos we set 500 to 1000 m
+ -- For helos we set a distance between 500 to 1000 m.
Vholding=destination:GetCoordinate():GetRandomVec2InRadius(1000, 500)
end
+ -- Coordinates of the holding point. y is the land height at that point.
local Pholding=COORDINATE:NewFromVec2(Vholding)
- -- holding point altitude
+ -- Holding point altitude. For planes between 800 and 1200 m AGL. For helos 80 to 120 m AGL.
local h_holding
if self.category=="plane" then
h_holding=1000
@@ -961,7 +976,7 @@ function RAT:_SetRoute()
end
h_holding=self:_Randomize(h_holding, 0.2)
- -- distance from holding point to destination
+ -- Distance from holding point to destination.
local d_holding=Pholding:Get2DDistance(Pdestination)
-- GENERAL
@@ -972,54 +987,59 @@ function RAT:_SetRoute()
local d_total=Pdeparture:Get2DDistance(Pholding)
-- CLIMB and DESCENT angles
+ -- TODO: Randomize climb/descent angles. This did not work in rad. Need to convert to deg first.
local AlphaClimb=self.aircraft.AlphaClimb --=self:_Randomize(self.aircraft.AlphaClimb, 0.1)
local AlphaDescent=self.aircraft.AlphaDescent --self:_Randomize(self.aircraft.AlphaDescent, 0.1)
--CRUISE
- -- set min/max cruise altitudes
+ -- Set min/max cruise altitudes.
local FLmax
local FLmin
local FLcruise=self.aircraft.FLcruise
if self.category=="plane" then
- -- min cruise alt is above 100 meter above holding point
+ -- Min cruise alt is just above holding point at destination or departure height, whatever is larger.
FLmin=math.max(H_departure, H_destination+h_holding)
-- Check if the distance between the two airports is large enough to reach the desired FL and descent again at the given climb/descent rates.
- -- TODO: need to modify this for "air" start.
- FLmax=self:_FLmax(AlphaClimb, AlphaDescent, d_total, H_departure)
if self.takeoff=="air" then
- FLmax=FLmax*2
+ -- This is the case where we only descent to the ground at the given descent angle.
+ FLmax=d_total*math.tan(AlphaDescent)+H_destination
+ else
+ FLmax=self:_FLmax(AlphaClimb, AlphaDescent, d_total, H_departure)
end
+ -- If the route is very short we set FLmin a bit lower than FLmax.
if FLmin>FLmax then
FLmin=FLmax*0.8
end
+ -- Again, if the route is too short to climb and descent, we set the default cruise alt at bit lower than the max we can reach.
if FLcruise>FLmax then
FLcruise=FLmax*0.9
end
else
- -- for helicopters we take cruise alt between 50 to 1000 meters above ground
+ -- For helicopters we take cruise alt between 50 to 1000 meters above ground. Default cruise alt is ~150 m.
FLmin=math.max(H_departure, H_destination)+50
FLmax=math.max(H_departure, H_destination)+1000
end
- -- set randomized cruise altitude: default +-50% but limited
+ -- Set randomized cruise altitude: default +-50% but limited to FLmin and FLmax.
FLcruise=self:_Randomize(FLcruise, 0.5, FLmin, FLmax)
- -- check that we are not above 90% of service ceiling
+ -- Finally, check that we are not above 90% of service ceiling.
FLcruise=math.min(FLcruise, self.aircraft.ceiling*0.9)
- --CLIMB
- -- height of climb relative to ASL height of departure airport
+ -- 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(AlphaClimb)
+ local d_climb=math.abs(h_climb/math.tan(AlphaClimb))
-- time of climb in seconds
local t_climb=h_climb/self.aircraft.Vclimb
-- DESCENT
- -- height difference for descent form cruise alt to holding point
+ -- Height difference for descent form cruise alt to holding point.
local h_descent=FLcruise-H_destination-h_holding
-- x-distance of descent part
local d_descent=math.abs(h_descent/math.tan(AlphaDescent))
-- 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
-- debug message
@@ -1046,7 +1066,7 @@ function RAT:_SetRoute()
MESSAGE:New(text, 60):ToAll()
end
- -- coordinates of route from depature (0) to cruise (1) to descent (2) to holing (3) to destination (4)
+ -- 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, heading)
local c2=c1:Translate(d_cruise, heading)
@@ -1054,9 +1074,8 @@ function RAT:_SetRoute()
local c3=Pholding
local c4=Pdestination
- -- convert coordinates into route waypoints
- --TODO: modify this for air start
- local wp0=self:Waypoint(self.takeoff, c0, self.aircraft.Vmin, 2, departure)
+ --Convert coordinates into route waypoints.
+ local wp0=self:Waypoint(self.takeoff, c0, self.aircraft.Vmin, H_departure, departure)
local wp1=self:Waypoint("climb", c1, self.aircraft.Vmax, FLcruise)
local wp2=self:Waypoint("cruise", c2, self.aircraft.Vcruise, FLcruise)
--TODO: add the possibility for a holing point, i.e. we circle a bit before final approach.
@@ -1085,15 +1104,16 @@ end
-- @param #number h0 Height [m] of departure airport. Note we implicitly assume that the height difference between departure and destination is negligible.
-- @return #number Maximal flight level in meters.
function RAT:_FLmax(alpha, beta, d, h0)
--- solve ASA triangle for one side (d) and two adjacent angles (alpha, beta) given
+-- Solve ASA triangle for one side (d) and two adjacent angles (alpha, beta) given.
local gamma=math.rad(180)-alpha-beta
local a=d*math.sin(alpha)/math.sin(gamma)
local b=d*math.sin(beta)/math.sin(gamma)
local h1=b*math.sin(alpha)
local h2=a*math.sin(beta)
local FL2m=30.48
+ -- h1 and h2 should be equal.
local text=string.format("FLmax = FL%3.0f = %6.1f m.\n", h1/FL2m, h1)
- text=text..string.format("FLmax = FL%3.0f = %6.1f m.", h2/FL2m, h2)
+ text=text..string.format("FLmax = FL%3.0f = %6.1f m.", h2/FL2m, h2)
env.info(myid..text)
return b*math.sin(alpha)+h0
end
@@ -1106,18 +1126,9 @@ end
-- @param #table waypoints The waypoints of the AI flight plan.
function RAT:_ModifySpawnTemplate(waypoints)
- -- point of Airbase
- --local PointVec3 = Airbase:GetPointVec3()
-
+ -- 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}
- local addheight
- if self.takeoff=="air" then
- addheight=5000
- else
- addheight=2
- end
-
if self:_GetSpawnIndex(self.SpawnIndex+1) then
-- Get copy of spawn template.
@@ -1128,7 +1139,7 @@ function RAT:_ModifySpawnTemplate(waypoints)
-- Translate the position of the Group Template to the Vec3.
for UnitID = 1, #SpawnTemplate.units do
- self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
+ self:T('Before Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y)
local UnitTemplate = SpawnTemplate.units[UnitID]
local SX = UnitTemplate.x
local SY = UnitTemplate.y
@@ -1138,29 +1149,22 @@ function RAT:_ModifySpawnTemplate(waypoints)
local TY = PointVec3.z + (SY-BY)
SpawnTemplate.units[UnitID].x = TX
SpawnTemplate.units[UnitID].y = TY
- SpawnTemplate.units[UnitID].alt = PointVec3.y + addheight
- self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
+ SpawnTemplate.units[UnitID].alt = PointVec3.y
+ self:T('After Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y)
end
- -- Copy waypoints into spawntemplate.
- -- this way we avoid the "landing bug" DCS currently has :)
+ -- Copy waypoints into spawntemplate. By this we avoid the nasty DCS "landing bug" :)
for i,wp in ipairs(waypoints) do
SpawnTemplate.route.points[i]=wp
end
- --TODO: Is this really necessary. Should already be defined in _Waypoints() function
- --SpawnTemplate.route.points[1].x = PointVec3.x
- --SpawnTemplate.route.points[1].y = PointVec3.z
- --SpawnTemplate.route.points[1].alt = PointVec3.y + addheight
-
- --SpawnTemplate.route.points[1].airdromeId = Airbase:GetID()
- --SpawnTemplate.route.points[1].type = GROUPTEMPLATE.Takeoff[Takeoff]
-
+ -- Also modify x,y of the template. Not sure why.
SpawnTemplate.x = PointVec3.x
SpawnTemplate.y = PointVec3.z
-- Update modified template for spawn group.
self.SpawnGroups[self.SpawnIndex].SpawnTemplate=SpawnTemplate
+
self:E(SpawnTemplate)
end
end
From 9568f7f87f2827feb89d6b7282d6449de4fddbbf Mon Sep 17 00:00:00 2001
From: funkyfranky
Date: Wed, 30 Aug 2017 00:41:34 +0200
Subject: [PATCH 06/28] Improvements of air start.
Improved air start parameters.
Added markers.
Other fixes.
---
Moose Development/Moose/AI/AI_RAT.lua | 90 +++++++++++++++++++++------
1 file changed, 72 insertions(+), 18 deletions(-)
diff --git a/Moose Development/Moose/AI/AI_RAT.lua b/Moose Development/Moose/AI/AI_RAT.lua
index ec2d75183..279b86678 100644
--- a/Moose Development/Moose/AI/AI_RAT.lua
+++ b/Moose Development/Moose/AI/AI_RAT.lua
@@ -27,7 +27,8 @@ RAT={
AlphaDescent=3.6, -- 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.
roe = "hold", -- ROE of spawned groups, default is weapon hold (this is a peaceful class for civil aircraft or ferry missions).
takeoff = "hot", -- Takeoff type: "hot", "cold", "runway", "air", "random".
- mindist = 5000, -- Min distance from departure to destination in meters.
+ 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, ...).
airports={}, -- All airports of friedly coalitions.
airports_departure={}, -- Possible departure airports if unit/group is spawned at airport, spawnpoint=air or spawnpoint=airport.
@@ -37,6 +38,7 @@ RAT={
zones_departure={}, -- Departure zones for air start.
Rzone=5000, -- Radius of departure zones in meters.
ratcraft={}, -- Array with the spawned RAT aircraft.
+ markerid=0,
}
--TODO list:
@@ -244,7 +246,7 @@ function RAT:Spawn(naircraft, name)
SCHEDULER:New(nil, self._SpawnWithRoute, {self}, Tstart, dt, 0.1, Tstop)
-- Status report scheduler.
- SCHEDULER:New(nil, self.Status, {self}, 30, 10, 0.0)
+ SCHEDULER:New(nil, self.Status, {self}, 30, 30)
end
@@ -300,7 +302,7 @@ function RAT:Status()
local group=self.SpawnGroups[i].Group
local prefix=self:_GetPrefixFromGroup(group)
local life=self:_GetLife(group)
- local text=string.format("Group %s ID %i:", prefix, i)
+ local text=string.format("Group %s ID %i:\n", prefix, i)
text=text..string.format("Life = %3.0f\n", life)
text=text..string.format("Status = %s\n", self.ratcraft[i].status)
text=text..string.format("Flying from %s to %s.",self.ratcraft[i].departure:GetName(), self.ratcraft[i].destination:GetName())
@@ -309,12 +311,13 @@ function RAT:Status()
end
end
---- Get (relative) life of unit.
+--- Get (relative) life of first unit of a group.
-- @param #RAT self
+-- @param #Group group Group of unit.
-- @return #number Life of unit in percent.
function RAT:_GetLife(group)
local life=0.0
- if group then
+ if group and group:IsAlive() then
local unit=group:GetUnit(1)
if unit then
life=unit:GetLife()/unit:GetLife0()*100
@@ -322,7 +325,7 @@ function RAT:_GetLife(group)
error(myid.."Unit does not exists in RAT_Getlife(). Returning zero.")
end
else
- error(myid.."Group does not exists in RAT_Getlife(). Returning zero.")
+ env.info(myid.."Group does not exists in RAT_Getlife(). Returning zero.")
end
return life
end
@@ -360,7 +363,13 @@ function RAT:_EngineStartup(EventData)
local text="Event: Group "..SpawnGroup:GetName().." started engines. Life="..self:_GetLife(SpawnGroup)
env.info(myid..text)
--MESSAGE:New(text, 180):ToAll()
- self:_SetStatus(SpawnGroup, "taxi (engines started)")
+ local status
+ if SpawnGroup:IsAir() then
+ status="airborn"
+ else
+ status="taxi (engines started)"
+ end
+ self:_SetStatus(SpawnGroup, status)
else
error("Group does not exist in RAT:_EngineStartup().")
end
@@ -518,7 +527,7 @@ function RAT:_SetDeparture()
text="Chosen departure airport: "..departure:GetName().." with ID "..departure:GetID()
end
env.info(myid..text)
- MESSAGE:New(text, 60):ToAll()
+ MESSAGE:New(text, 60):ToAll()
return departure
end
@@ -926,7 +935,7 @@ function RAT:_SetRoute()
if self.takeoff=="air" then
-- Departure altitude is 70% of default cruise with 30% variation and limited to 1000 m AGL (50 m for helos).
local Hmin
- if self.category=="pland" then
+ if self.category=="plane" then
Hmin=1000
else
Hmin=50
@@ -1001,7 +1010,8 @@ function RAT:_SetRoute()
FLmin=math.max(H_departure, H_destination+h_holding)
-- Check if the distance between the two airports is large enough to reach the desired FL and descent again at the given climb/descent rates.
if self.takeoff=="air" then
- -- This is the case where we only descent to the ground at the given descent angle.
+ -- This is the case where we only descent to the ground at the given descent angle.
+ -- TODO: should this not better be h_holding Pholding.y?
FLmax=d_total*math.tan(AlphaDescent)+H_destination
else
FLmax=self:_FLmax(AlphaClimb, AlphaDescent, d_total, H_departure)
@@ -1023,6 +1033,10 @@ function RAT:_SetRoute()
FLcruise=self:_Randomize(FLcruise, 0.5, FLmin, FLmax)
-- Finally, check that we are not above 90% of service ceiling.
FLcruise=math.min(FLcruise, self.aircraft.ceiling*0.9)
+
+ if self.takeoff=="air" then
+ H_departure=math.min(H_departure,FLmax)
+ end
-- CLIMB
-- Height of climb relative to ASL height of departure airport.
@@ -1034,7 +1048,7 @@ function RAT:_SetRoute()
-- DESCENT
-- Height difference for descent form cruise alt to holding point.
- local h_descent=FLcruise-H_destination-h_holding
+ local h_descent=FLcruise-h_holding-Pholding.y
-- x-distance of descent part
local d_descent=math.abs(h_descent/math.tan(AlphaDescent))
@@ -1055,6 +1069,7 @@ function RAT:_SetRoute()
text=text..string.format("h_climb = %6.1f m AGL\n", h_climb)
text=text..string.format("h_descent = %6.1f m\n", h_descent)
text=text..string.format("h_holding = %6.1f m AGL\n", h_holding)
+ text=text..string.format("P_holding alt = %6.1f m ASL\n", Pholding.y)
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("FLmin = %6.1f m ASL\n", FLmin)
@@ -1079,13 +1094,19 @@ function RAT:_SetRoute()
local wp1=self:Waypoint("climb", c1, self.aircraft.Vmax, FLcruise)
local wp2=self:Waypoint("cruise", c2, self.aircraft.Vcruise, FLcruise)
--TODO: add the possibility for a holing point, i.e. we circle a bit before final approach.
- local wp3=self:Waypoint("descent", c3, self.aircraft.Vmin, h_holding)
- --local wp3=self:Waypoint("holding", c3, self.aircraft.Vmin, h_holding)
+ --local wp3=self:Waypoint("descent", c3, self.aircraft.Vmin, h_holding)
+ local wp3=self:Waypoint("holding", c3, self.aircraft.Vmin, h_holding)
local wp4=self:Waypoint("landing", c4, self.aircraft.Vmin, 2, destination)
-- set waypoints
local waypoints = {wp0, wp1, wp2, wp3, wp4}
+ self:_SetMarker("Takeoff and begin of climb.", c0)
+ self:_SetMarker("End of climb and begin of cruise", c1)
+ self:_SetMarker("End of Cruise and begin of descent", c2)
+ self:_SetMarker("Holding Point", c3)
+ self:_SetMarker("Final Destination", c4)
+
-- some info on the route as message
self:_Routeinfo(waypoints, "Waypoint info in set_route:")
@@ -1128,6 +1149,10 @@ function RAT:_ModifySpawnTemplate(waypoints)
-- 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
+ local heading = self:_Course(waypoints[1], waypoints[2])
+ env.info(myid.."Heading wp1->wp2: ", heading)
if self:_GetSpawnIndex(self.SpawnIndex+1) then
@@ -1150,6 +1175,7 @@ function RAT:_ModifySpawnTemplate(waypoints)
SpawnTemplate.units[UnitID].x = TX
SpawnTemplate.units[UnitID].y = TY
SpawnTemplate.units[UnitID].alt = PointVec3.y
+ SpawnTemplate.units[UnitID].heading = heading
self:T('After Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y)
end
@@ -1161,6 +1187,7 @@ function RAT:_ModifySpawnTemplate(waypoints)
-- Also modify x,y of the template. Not sure why.
SpawnTemplate.x = PointVec3.x
SpawnTemplate.y = PointVec3.z
+ SpawnTemplate.heading = heading
-- Update modified template for spawn group.
self.SpawnGroups[self.SpawnIndex].SpawnTemplate=SpawnTemplate
@@ -1248,12 +1275,39 @@ end
-- @usage _Randomize(100, 0.1) returns a value between 90 and 110, i.e. a plus/minus ten percent variation.
-- @usage _Randomize(100, 0.5, nil, 120) returns a value between 50 and 120, i.e. a plus/minus fivty percent variation with upper bound 120.
function RAT:_Randomize(value, fac, lower, upper)
- local r=math.random(value-value*fac,value+value*fac)
- if upper and r>upper then
- r=upper
+ local min
+ if lower then
+ min=math.max(value-value*fac, lower)
+ else
+ min=value-value*fac
end
- if lower and rupper then
+-- r=upper
+-- end
+-- if lower and r
Date: Wed, 30 Aug 2017 17:04:54 +0200
Subject: [PATCH 07/28] Improvements of departure selection (unfinished).
---
Moose Development/Moose/AI/AI_RAT.lua | 186 +++++++++++++++++++++++---
1 file changed, 169 insertions(+), 17 deletions(-)
diff --git a/Moose Development/Moose/AI/AI_RAT.lua b/Moose Development/Moose/AI/AI_RAT.lua
index 279b86678..d20bca4ee 100644
--- a/Moose Development/Moose/AI/AI_RAT.lua
+++ b/Moose Development/Moose/AI/AI_RAT.lua
@@ -35,26 +35,38 @@ RAT={
airports_destination={}, -- Possible destination airports if unit does not fly "overseas", destpoint=overseas or destpoint=airport.
departure_name="random", -- Name of the departure airport. Default is "random" for a randomly chosen one of the coalition airports.
destination_name="random",-- Name of the destination airport. Default is "random" for a randomly chosen one of the coalition airports.
+ 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 destination zones.
zones_departure={}, -- Departure zones for air start.
Rzone=5000, -- Radius of departure zones in meters.
ratcraft={}, -- Array with the spawned RAT aircraft.
markerid=0,
}
+--- RAT categories.
+-- @field #RAT cat
+RAT.cat={
+ plane="plane",
+ heli="heli"
+}
+
--TODO list:
---TODO: Add scheduled spawn and corresponding user functions.
---TODO: Add possibility to spawn in air.
---TODO: Add departure zones for air start.
---TODO: Make more functions to adjust/set parameters.
+--DONE: Add scheduled spawn.
+--DONE: Add possibility to spawn in air.
+--DONE: Add departure zones for air start.
+--TODO: Make more functions to adjust/set RAT parameters.
--TODO: Clean up debug messages.
--DONE: Improve flight plan. Especially check FL against route length.
--DONE: Add event handlers.
--DONE: Respawn units when they have landed.
--DONE: Change ROE state.
--TODO: Make ROE state user function
---TODO: Add status reports.
---TODO: Check compatibility with #SPAWN functions.
+--TODO: Improve status reports.
+--TODO: Check compatibility with other #SPAWN functions.
--TODO: Add possibility to continue journey at destination. Need "place" in event data for that.
+--TODO: Add enumerators and get rid off error prone string comparisons.
--DONE: Check that FARPS are not used as airbases for planes. Don't know if they appear in list of airports.
--DONE: Add cases for helicopters.
@@ -157,14 +169,14 @@ function RAT:_InitAircraft(DCSgroup)
self.aircraft.Vmax = DCSdesc.speedMax
-- min cruise airspeed = 75% of max
- self.aircraft.Vmin = self.aircraft.Vmax*0.75
+ self.aircraft.Vmin = self.aircraft.Vmax*0.60
-- actual travel speed (random between ASmin and ASmax)
--TODO: This needs to be placed somewhere else! Randomization should not happen here. Otherwise it is not changed for multiple spawns.
self.aircraft.Vcruise = math.random(self.aircraft.Vmin, self.aircraft.Vmax)
-- Limit travel speed to ~900 km/h for jets.
- self.aircraft.Vcruise = math.min(self.aircraft.Vcruise, self.Vcruisemax)
+ self.aircraft.Vcruise = math.min(self.aircraft.Vcruise, self.aircraft.Vmax)
-- max climb speed in m/s
self.aircraft.Vymax=DCSdesc.VyMax
@@ -289,7 +301,8 @@ function RAT:_SpawnWithRoute()
self:HandleEvent(EVENTS.Land, self._OnLand)
self:HandleEvent(EVENTS.EngineShutdown, self._OnEngineShutdown)
self:HandleEvent(EVENTS.Dead, self._OnDead)
- self:HandleEvent(EVENTS.Crash, self._OnCrash)
+ -- TODO: Crash needs to be handled better. Does it always occur when dead?
+ --self:HandleEvent(EVENTS.Crash, self._OnCrash)
end
@@ -488,6 +501,59 @@ function RAT:SetDepartureZones(zonenames)
end
end
+--- Test if an airport exists on the current map.
+-- @param #RAT self
+-- @param #string name
+-- @return #boolean True if airport exsits, false otherwise.
+function RAT:_AirportExists(name)
+ for _,airport in pairs(self.airports_map) do
+ if airport:GeName()==name then
+ return true
+ end
+ end
+ return false
+end
+
+
+--- Set possible departure ports. This can be an airport or a zone defined in the mission editor.
+-- @param #RAT self
+function RAT:SetDepartureAll(names)
+
+ -- 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.
+ table.insert(self.departure_zones,"name")
+ 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
+
+ else
+ -- error message
+ error("Input parameter must be a string or a table!")
+ end
+
+end
+
--- Set name of destination airport for the AI aircraft. If no name is given an airport from the coalition is chosen randomly.
-- @param #RAT self
-- @param #string name Name of the destination airport or "random" for a randomly chosen one of the coalition.
@@ -500,34 +566,91 @@ function RAT:SetDestination(name)
end
---- Set the departure airport of the AI. If no airport name is given an airport from the coalition is chosen randomly.
+--- Set the departure airport of the AI. If no airport name is given explicitly an airport from the coalition is chosen randomly.
+-- If takeoff style is set to "air", we use zones around the airports or the zones specified by user input.
-- @param #RAT self
-- @return Wrapper.Airbase#AIRBASE Departure airport if spawning at airport.
function RAT:_SetDeparture()
+
local departure
- local text
+
+ -- Array containing possible departure airports or zones.
+ local departures={}
+
if self.takeoff=="air" then
+
+ if self.random_departure then
+
+ -- Air start above a random airport.
+ for _,airport in pairs(self.airports)do
+ table.insert(departures, airport:GetZone())
+ end
+
+ else
+
+ -- Put all specified zones in table.
+ for _,name in pairs(self.departure_zones) do
+ table.insert(departures, ZONE:New(name))
+ end
+ -- Put all specified airport zones in table.
+ for _,name in pairs(self.departure_zones) do
+ table.insert(departures, AIRBASE:FindByName("name"):GetZone())
+ end
+
+ end
+--[[
if self.departure_name=="random" then
- departure=self.zones_departure[math.random(1, #self.zones_departure)]
+ departure=self.zones_departure[math.random(#self.zones_departure)]
else
departure=ZONE:FindByName(self.departure_name)
end
+
text="Chosen departure zone: "..departure:GetName()
+]]
else
+
+ if self.random_departure then
+
+ -- All friendly departure airports.
+ departures=self.airports
+
+ else
+
+ for _,name in pairs(self.departure_ports) do
+ table.insert(departures, AIRBASE:FindByName(name))
+ end
+
+ end
+ end
+
+--[[
if self.departure_name=="random" then
-- Get a random departure airport from all friendly coalition airports.
- departure=self.airports[math.random(1, #self.airports)]
+ departure=self.airports[math.random(#self.airports)]
elseif AIRBASE:FindByName(self.departure_name) then
-- Take the explicit airport provided.
departure=AIRBASE:FindByName(self.departure_name)
else
- -- If nothing else works, we randomly choose from frindly coalition airports.
- departure=self.airports[math.random(1, #self.airports)]
+ -- If nothing else works, we randomly choose from friendly coalition airports.
+ departure=self.airports[math.random(#self.airports)]
end
+
text="Chosen departure airport: "..departure:GetName().." with ID "..departure:GetID()
end
+]]
+
+ -- Select departure airport or zone.
+ local departure=departures[math.random(#departures)]
+
+ local text
+ if self.takeoff=="air" then
+ text="Chosen departure zone: "..departure:GetName()
+ else
+ text="Chosen departure airport: "..departure:GetName().." (ID "..departure:GetID()..")"
+ end
env.info(myid..text)
MESSAGE:New(text, 60):ToAll()
+
return departure
end
@@ -562,11 +685,14 @@ end
-- @param #number minrange Minimum range to q in meters.
-- @param #number maxrange Maximum range to q in meters.
function RAT:_GetDestinations(q, minrange, maxrange)
+
local absolutemin=5000 -- Absolute minimum is 5 km.
minrange=minrange or absolutemin -- Default min is absolute min.
maxrange=maxrange or 10000000 -- Default max 10,000 km.
+
-- Ensure that minrange is always > 10 km to ensure the destination != departure.
- minrange=math.max(absolutemin, minrange)
+ minrange=math.max(absolutemin, minrange)
+
-- loop over all friendly airports
for _,airport in pairs(self.airports) do
local p=airport:GetCoordinate()
@@ -577,6 +703,7 @@ function RAT:_GetDestinations(q, minrange, maxrange)
end
end
env.info(myid.."Number of possible destination airports = "..#self.airports_destination)
+
if #self.airports_destination > 1 then
--- Compare distance of destination airports.
-- @param Core.Point#COORDINATE a Coordinate of point a.
@@ -589,6 +716,7 @@ function RAT:_GetDestinations(q, minrange, maxrange)
end
table.sort(self.airports_destination, compare)
end
+
end
@@ -1310,4 +1438,28 @@ function RAT:_SetMarker(text, vec3)
self.markerid=self.markerid+1
env.info(myid.."Placing marker with ID "..self.markerid.." and text "..text)
trigger.action.markToAll(self.markerid, text, vec3)
-end
\ No newline at end of file
+end
+
+--[[
+--- @type RATPORT
+-- @extends Wrapper.Positionable#POSITIONABLE
+
+--- #RATPORT class, extends @{Positionable#POSITIONABLE}
+-- @field #RATPORT RATPORT
+RATPORT={
+ ClassName="RATPORT",
+}
+
+--- Creates a new RATPORT object.
+-- @param #RATPORT self
+-- @param #string name Name of airport or zone.
+-- @return #RATPORT self
+function RATPORT:New(name)
+ local self = BASE:Inherit(self, POSITIONABLE:New(name)) -- #RATPORT
+ return self
+end
+
+--function RATCRAFT:New(name)
+-- local self = BASE:Inherit(self, GROUP:New(name))
+--end
+]]
\ No newline at end of file
From 6ff2dfe44441dcd0034bef0a27f4251b126ef351 Mon Sep 17 00:00:00 2001
From: funkyfranky
Date: Thu, 31 Aug 2017 01:01:55 +0200
Subject: [PATCH 08/28] Improved takeoff types for zones and airports.
Cleand up function position in code.
---
Moose Development/Moose/AI/AI_RAT.lua | 1383 +++++++++++++------------
1 file changed, 722 insertions(+), 661 deletions(-)
diff --git a/Moose Development/Moose/AI/AI_RAT.lua b/Moose Development/Moose/AI/AI_RAT.lua
index d20bca4ee..cfe838309 100644
--- a/Moose Development/Moose/AI/AI_RAT.lua
+++ b/Moose Development/Moose/AI/AI_RAT.lua
@@ -1,22 +1,60 @@
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+
--- ** AI **
-- @module AI_RAT
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+
--- Some ID to identify where we are
-- #string myid
myid="RAT | "
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+
--- RAT class
-- @type RAT
--- @field ClassName
+-- @field #string ClassName
+-- @field #boolean debug
-- @field #string prefix
--- @field #RAT
+-- @field #number sapwndelay
+-- @field #number spawninterval
+-- @field #number coalition
+-- @field #string category
+-- @field #string friendly
+-- @field #table ctable
+-- @field #table aircraft
+-- @field #number Vcruisemax
+-- @field #number Vclimb
+-- @field #number AlphaDescent
+-- @field #string roe
+-- @field #string takeoff
+-- @field #number mindist
+-- @field #number maxdist
+-- @field #table airports_map
+-- @field #table airports
+-- @field #table airports_departure
+-- @field #table airports_destination
+-- @field #boolean random_departure
+-- @field #boolean random_destination
+-- @field #table departure_zones
+-- @field #table departure_ports
+-- @field #table destination_ports
+-- @field #number Rzone
+-- @field #table ratcraft
+-- @field #number markerid
+-- @field #number marker0
+-- @field #table RAT
-- @extends #SPAWN
+
+
+--- RAT class
+-- @field #RAT RAT
RAT={
ClassName = "RAT", -- Name of class: RAT = Random Air Traffic.
- debug=true, -- Turn debug messages on or off.
+ debug=false, -- Turn debug messages on or off.
prefix=nil, -- Prefix of the template group defined in the mission editor.
- spawndelay=5, -- Delay time in seconds before first spawning happens.
- spawninterval=2, -- Interval between spawning units/groups. Note that we add a randomization of 10%.
+ spawndelay=1, -- Delay time in seconds before first spawning happens.
+ spawninterval=1, -- Interval between spawning units/groups. Note that we add a randomization of 10%.
coalition = nil, -- Coalition of spawn 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.
@@ -33,24 +71,42 @@ RAT={
airports={}, -- All airports of friedly coalitions.
airports_departure={}, -- Possible departure airports if unit/group is spawned at airport, spawnpoint=air or spawnpoint=airport.
airports_destination={}, -- Possible destination airports if unit does not fly "overseas", destpoint=overseas or destpoint=airport.
- departure_name="random", -- Name of the departure airport. Default is "random" for a randomly chosen one of the coalition airports.
- destination_name="random",-- Name of the destination airport. Default is "random" for a randomly chosen one of the coalition airports.
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 destination zones.
- zones_departure={}, -- Departure zones for air start.
+ departure_ports={}, -- Array containing the names of the departure zones.
+ destination_ports={}, -- Array containing the names of the destination zones.
Rzone=5000, -- Radius of departure zones in meters.
ratcraft={}, -- Array with the spawned RAT aircraft.
- markerid=0,
+ markerid=0, -- Running number of the ID of markers on the F10 map.
+ marker0=nil, -- Specific marker ID offset.
}
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+
--- RAT categories.
-- @field #RAT cat
RAT.cat={
plane="plane",
heli="heli"
}
+--- RAT unit conversions.
+-- @field #RAT unit
+RAT.unit={
+ meter2feet=1,
+ nm2km=1,
+ FL2m=1,
+ --[[
+ -- unit conversions
+ local ft2meter=0.305
+ local kmh2ms=0.278
+ local FL2m=30.48
+ local nm2km=1.852
+ local nm2m=1852
+ ]]
+}
+
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--TODO list:
--DONE: Add scheduled spawn.
@@ -70,13 +126,15 @@ RAT.cat={
--DONE: Check that FARPS are not used as airbases for planes. Don't know if they appear in list of airports.
--DONE: Add cases for helicopters.
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+
--- Creates a new RAT object.
-- @param #RAT self
-- @param #string prefix Prefix of the (template) group name defined in the mission editor.
-- @param #string friendly Friendly coalitions from which airports can be used.
-- "all"=neutral+red+blue, "same"=spawn coalition+neutral, "sameonly"=spawn coalition, "blue"=blue+neutral, "blueonly"=blue, "red"=red+neutral, "redonly"=red, "neutral"=neutral.
-- Default is "same", so aircraft will use airports of the coalition their spawn template has plus all neutral airports.
--- @return #RAT self Object of RAT class.
+-- @return #RAT Object of RAT class.
-- @return #nil Nil if the group does not exists in the mission editor.
function RAT:New(prefix, friendly)
@@ -113,10 +171,155 @@ function RAT:New(prefix, friendly)
-- Get all airports of this map beloning to friendly coalition(s).
self:_GetAirportsOfCoalition()
+
+ self.marker0=math.random(1000000)
return self
end
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+
+--- Set takeoff type. Starting cold at airport, starting hot at airport, starting at runway, starting in the air or randomly select one of the previous.
+-- Default is "takeoff-hot" for a start at airport with engines already running.
+-- @param #RAT self
+-- @param #string type Type can be "takeoff-cold" or "cold", "takeoff-hot" or "hot", "takeoff-runway" or "runway", "air", "random".
+-- @usage RAT:Takeoff("hot") will spawn RAT objects at airports with engines started.
+-- @usage RAT:Takeoff("cold") will spawn RAT objects at airports with engines off.
+-- @usage RAT:Takeoff("air") will spawn RAT objects in air over random airports or within pre-defined zones.
+function RAT:SetTakeoff(type)
+
+ -- All possible types for random selection.
+ local types={"takeoff-cold", "takeoff-hot", "air"}
+
+ local _Type
+ if type:lower()=="takeoff-cold" or type:lower()=="cold" then
+ _Type="takeoff-cold"
+ elseif type:lower()=="takeoff-hot" or type:lower()=="hot" then
+ _Type="takeoff-hot"
+ elseif type:lower()=="takeoff-runway" or type:lower()=="runway" then
+ _Type="takeoff-runway"
+ elseif type:lower()=="air" then
+ _Type="air"
+ elseif type:lower()=="random" then
+ _Type=types[math.random(#types)]
+ else
+ _Type="takeoff-hot"
+ end
+
+ self.takeoff=_Type
+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.
+-- @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)
+ self:E({"SetDeparture Names", names})
+
+ -- 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.
+ table.insert(self.departure_zones, name)
+ 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
+
+ else
+ -- error message
+ error("Input parameter must be a string or a table!")
+ end
+
+end
+
+--- Set name of destination airport for the AI aircraft. If no name is given an airport from the coalition is chosen randomly.
+-- @param #RAT self
+-- @param #string names Name of the destination airport or table of destination airports.
+function RAT:SetDestination(names)
+
+ -- 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
+ table.insert(self.destination_ports, name)
+ end
+
+ elseif type(names)=="string" then
+
+ self.destination_ports={names}
+
+ else
+ -- Error message.
+ error("Input parameter must be a string or a table!")
+ end
+
+end
+
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+
+--- Spawn the AI aircraft.
+-- @param #RAT self
+-- @param #number naircraft (Optional) Number of aircraft to spawn. Default is one aircraft.
+-- @param #string name (Optional) Name of the spawn group (for debugging only).
+function RAT:Spawn(naircraft, name)
+
+ -- Number of aircraft to spawn. Default is one.
+ naircraft=naircraft or 1
+
+ -- some of group for debugging
+ --TODO: remove name from input parameter and make better unique RAT AI name
+ name=name or "RAT AI "..self.aircraft.type
+
+ -- debug message
+ local text="Spawning "..naircraft.." aircraft of group "..self.prefix.." with name "..name.." of type "..self.aircraft.type..".\n"
+ text=text.."Takeoff type: "..self.takeoff.."\n"
+ text=text.."Friendly airports: "..self.friendly
+ env.info(myid..text)
+ if self.debug then
+ MESSAGE:New(text, 60, "Info"):ToAll()
+ end
+
+ -- Schedule spawning of aircraft.
+ local Tstart=self.spawndelay
+ local dt=self.spawninterval
+ if self.takeoff:lower()=="takeoff-runway" or self.takeoff:lower()=="runway" then
+ -- Ensure that interval is >= 180 seconds if spawn at runway is chosen. Aircraft need time to takeoff or the runway gets jammed.
+ dt=math.max(dt, 180)
+ end
+ local Tstop=Tstart+dt*(naircraft-1)
+ SCHEDULER:New(nil, self._SpawnWithRoute, {self}, Tstart, dt, 0.1, Tstop)
+
+ -- Status report scheduler.
+ SCHEDULER:New(nil, self.Status, {self}, 30, 30)
+
+end
+
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+
--- Initialize basic parameters of the aircraft based on its (template) group in the mission editor.
-- @param #RAT self
-- @param Dcs.DCSWrapper.Group#Group DCSgroup Group of the aircraft in the mission editor.
@@ -149,10 +352,6 @@ function RAT:_InitAircraft(DCSgroup)
error(myid.."Group of RAT is neither airplane nor helicopter!")
end
- -- Define a first departure zone around the point where the group template in the ME was placed.
- local ZoneTemplate = ZONE_GROUP:New( "Template", GROUP:FindByName(self.prefix), self.Rzone)
- table.insert(self.zones_departure, ZoneTemplate)
-
-- Get type of aircraft.
self.aircraft.type=DCStype
@@ -204,7 +403,7 @@ function RAT:_InitAircraft(DCSgroup)
-- send debug message
local text=string.format("Aircraft parameters:\n")
- text=text..string.format("Category = %s\n", self.category)
+ text=text..string.format("Category = %s\n", self.category)
text=text..string.format("Max speed = %6.1f m/s.\n", self.aircraft.Vmax)
text=text..string.format("Max cruise speed = %6.1f m/s.\n", self.aircraft.Vcruise)
text=text..string.format("Max climb speed = %6.1f m/s.\n", self.aircraft.Vymax)
@@ -223,45 +422,7 @@ function RAT:_InitAircraft(DCSgroup)
end
-
---- Spawn the AI aircraft.
--- @param #RAT self
--- @param #number naircraft (Optional) Number of aircraft to spawn. Default is one aircraft.
--- @param #string name (Optional) Name of the spawn group (for debugging only).
-function RAT:Spawn(naircraft, name)
-
- -- Number of aircraft to spawn. Default is one.
- naircraft=naircraft or 1
-
- -- some of group for debugging
- --TODO: remove name from input parameter and make better unique RAT AI name
- name=name or "RAT AI "..self.aircraft.type
-
- -- debug message
- local text="Spawning "..naircraft.." aircraft of group "..self.prefix.." with name "..name.." of type "..self.aircraft.type..".\n"
- text=text.."Takeoff type: "..self.takeoff.."\n"
- text=text.."Friendly airports: "..self.friendly
- env.info(myid..text)
- if self.debug then
- MESSAGE:New(text, 60, "Info"):ToAll()
- end
-
- -- Schedule spawning of aircraft.
- --TODO: make self.SpawnInterval and sef.spawndelay user input
- local Tstart=self.spawndelay
- local dt=self.spawninterval
- if self.takeoff:lower()=="takeoff-runway" or self.takeoff:lower()=="runway" then
- -- Ensure that interval is >= 180 seconds if spawn at runway is chosen. Aircraft need time to takeoff or the runway gets jammed.
- dt=math.max(dt, 180)
- end
- local Tstop=Tstart+dt*(naircraft-1)
- SCHEDULER:New(nil, self._SpawnWithRoute, {self}, Tstart, dt, 0.1, Tstop)
-
- -- Status report scheduler.
- SCHEDULER:New(nil, self.Status, {self}, 30, 30)
-
-end
-
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Spawn the AI aircraft with a route.
-- Sets the departure and destination airports and waypoints.
@@ -305,6 +466,431 @@ function RAT:_SpawnWithRoute()
--self:HandleEvent(EVENTS.Crash, self._OnCrash)
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
+-- @return Wrapper.Airport#AIRBASE Departure airbase.
+-- @return Wrapper.Airport#AIRBASE Destination airbase.
+-- @return #table Table of flight plan waypoints.
+function RAT:_SetRoute()
+
+ -- unit conversions
+ local ft2meter=0.305
+ local kmh2ms=0.278
+ local FL2m=30.48
+ local nm2km=1.852
+
+ -- DEPARTURE AIRPORT
+ -- Departure airport or zone.
+ local departure=self:_SetDeparture()
+
+ -- Coordinates of departure point.
+ local Pdeparture
+ if self.takeoff=="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()
+ end
+
+ -- Height ASL of departure point.
+ local H_departure
+ if self.takeoff=="air" then
+ -- Departure altitude is 70% of default cruise with 30% variation and limited to 1000 m AGL (50 m for helos).
+ local Hmin
+ if self.category=="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)
+ else
+ H_departure=Pdeparture.y
+ end
+
+ -- DESTINATION AIRPORT
+ -- Get all destination airports within reach and at least 10 km away from departure.
+ self:_GetDestinations(Pdeparture, self.mindist, self.aircraft.Reff)
+
+ -- Pick a destination airport.
+ local destination=self:_SetDestination()
+
+ -- Check that departure and destination are not the same. Should not happen due to mindist.
+ if destination:GetName()==departure:GetName() then
+ local text="Destination and departure airport are identical: "..destination:GetName().." with ID "..destination:GetID()
+ MESSAGE:New(text, 120):ToAll()
+ error(myid..text)
+ end
+
+ -- Coordinates of destination airport.
+ local Pdestination=destination:GetCoordinate()
+ -- Height ASL of destination airport.
+ local H_destination=Pdestination.y
+
+ -- DESCENT/HOLDING POINT
+ -- Get a random point between 10 and 20 km away from the destination.
+ local Vholding
+ if self.category=="plane" then
+ Vholding=destination:GetCoordinate():GetRandomVec2InRadius(20000, 10000)
+ else
+ -- For helos we set a distance between 500 to 1000 m.
+ Vholding=destination:GetCoordinate():GetRandomVec2InRadius(1000, 500)
+ end
+ -- Coordinates of the holding point. y is the land height at that point.
+ local Pholding=COORDINATE:NewFromVec2(Vholding)
+
+ -- Holding point altitude. For planes between 800 and 1200 m AGL. For helos 80 to 120 m AGL.
+ local h_holding
+ if self.category=="plane" then
+ h_holding=1000
+ else
+ h_holding=100
+ end
+ h_holding=self:_Randomize(h_holding, 0.2)
+
+ -- Distance from holding point to destination.
+ local d_holding=Pholding:Get2DDistance(Pdestination)
+
+ -- GENERAL
+ -- heading from departure to holding point of destination
+ local heading=self:_Course(Pdeparture, Pholding) -- heading from departure to destination
+
+ -- total distance between departure and holding point (+last bit to destination)
+ local d_total=Pdeparture:Get2DDistance(Pholding)
+
+ -- CLIMB and DESCENT angles
+ -- TODO: Randomize climb/descent angles. This did not work in rad. Need to convert to deg first.
+ local AlphaClimb=self.aircraft.AlphaClimb --=self:_Randomize(self.aircraft.AlphaClimb, 0.1)
+ local AlphaDescent=self.aircraft.AlphaDescent --self:_Randomize(self.aircraft.AlphaDescent, 0.1)
+
+ --CRUISE
+ -- Set min/max cruise altitudes.
+ local FLmax
+ local FLmin
+ local FLcruise=self.aircraft.FLcruise
+ if self.category=="plane" then
+ -- Min cruise alt is just above holding point at destination or departure height, whatever is larger.
+ FLmin=math.max(H_departure, H_destination+h_holding)
+ -- Check if the distance between the two airports is large enough to reach the desired FL and descent again at the given climb/descent rates.
+ if self.takeoff=="air" then
+ -- This is the case where we only descent to the ground at the given descent angle.
+ -- TODO: should this not better be h_holding Pholding.y?
+ FLmax=d_total*math.tan(AlphaDescent)+H_destination
+ else
+ FLmax=self:_FLmax(AlphaClimb, AlphaDescent, d_total, H_departure)
+ end
+ -- If the route is very short we set FLmin a bit lower than FLmax.
+ if FLmin>FLmax then
+ FLmin=FLmax*0.8
+ end
+ -- Again, if the route is too short to climb and descent, we set the default cruise alt at bit lower than the max we can reach.
+ if FLcruise>FLmax then
+ FLcruise=FLmax*0.9
+ end
+ else
+ -- For helicopters we take cruise alt between 50 to 1000 meters above ground. Default cruise alt is ~150 m.
+ FLmin=math.max(H_departure, H_destination)+50
+ FLmax=math.max(H_departure, H_destination)+1000
+ end
+
+ -- Ensure that FLmax not above 90% of service ceiling.
+ FLmax=math.min(FLmax, self.aircraft.ceiling*0.9)
+
+ -- Set randomized cruise altitude: default +-50% but limited to FLmin and FLmax.
+ FLcruise=self:_Randomize(FLcruise, 0.5, FLmin, FLmax)
+
+
+ if self.takeoff=="air" then
+ H_departure=math.min(H_departure,FLmax)
+ 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=math.abs(h_climb/math.tan(AlphaClimb))
+ -- time of climb in seconds
+ local t_climb=h_climb/self.aircraft.Vclimb
+
+ -- DESCENT
+ -- Height difference for descent form cruise alt to holding point.
+ local h_descent=FLcruise-h_holding-Pholding.y
+ -- x-distance of descent part
+ local d_descent=math.abs(h_descent/math.tan(AlphaDescent))
+
+ -- 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
+
+ -- debug message
+ local text=string.format("Route distances:\n")
+ text=text..string.format("d_climb = %6.1f km\n", d_climb/1000)
+ text=text..string.format("d_cruise = %6.1f km\n", d_cruise/1000)
+ text=text..string.format("d_descent = %6.1f km\n", d_descent/1000)
+ text=text..string.format("d_holding = %6.1f km\n", d_holding/1000)
+ text=text..string.format("d_total = %6.1f km\n", d_total/1000)
+ text=text..string.format("Route heights:\n")
+ text=text..string.format("H_departure = %6.1f m ASL\n", H_departure)
+ text=text..string.format("H_destination = %6.1f m ASL\n", H_destination)
+ text=text..string.format("h_climb = %6.1f m AGL\n", h_climb)
+ text=text..string.format("h_descent = %6.1f m\n", h_descent)
+ text=text..string.format("h_holding = %6.1f m AGL\n", h_holding)
+ text=text..string.format("P_holding alt = %6.1f m ASL\n", Pholding.y)
+ 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("FLmin = %6.1f m ASL\n", FLmin)
+ text=text..string.format("FLmax = %6.1f m ASL\n", FLmax)
+ text=text..string.format("FLcruise = %6.1f m ASL\n", FLcruise)
+ text=text..string.format("Heading = %6.1f Degrees", heading)
+ env.info(myid..text)
+ if self.debug then
+ MESSAGE:New(text, 60):ToAll()
+ 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, heading)
+ local c2=c1:Translate(d_cruise, heading)
+ local c3=c2:Translate(d_descent, heading)
+ local c3=Pholding
+ local c4=Pdestination
+
+ --Convert coordinates into route waypoints.
+ local wp0=self:_Waypoint(self.takeoff, c0, self.aircraft.Vmin, H_departure, departure)
+ local wp1=self:_Waypoint("climb", c1, self.aircraft.Vmax, FLcruise)
+ local wp2=self:_Waypoint("cruise", c2, self.aircraft.Vcruise, FLcruise)
+ --TODO: add the possibility for a holing point, i.e. we circle a bit before final approach.
+ --local wp3=self:Waypoint("descent", c3, self.aircraft.Vmin, h_holding)
+ local wp3=self:_Waypoint("holding", c3, self.aircraft.Vmin, h_holding)
+ local wp4=self:_Waypoint("landing", c4, self.aircraft.Vmin, 2, destination)
+
+ -- set waypoints
+ local waypoints = {wp0, wp1, wp2, wp3, wp4}
+
+ self:_SetMarker("Takeoff and begin of climb.", c0)
+ self:_SetMarker("End of climb and begin of cruise", c1)
+ self:_SetMarker("End of Cruise and begin of descent", c2)
+ self:_SetMarker("Holding Point", c3)
+ self:_SetMarker("Final Destination", c4)
+
+ -- some info on the route as message
+ self:_Routeinfo(waypoints, "Waypoint info in set_route:")
+
+ -- return departure, destination and waypoints
+ return departure, destination, waypoints
+
+end
+
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+
+--- Set the departure airport of the AI. If no airport name is given explicitly an airport from the coalition is chosen randomly.
+-- If takeoff style is set to "air", we use zones around the airports or the zones specified by user input.
+-- @param #RAT self
+-- @return Wrapper.Airbase#AIRBASE Departure airport if spawning at airport.
+function RAT:_SetDeparture()
+
+ -- Array of possible departure airports or zones.
+ local departures={}
+
+ if self.takeoff=="air" then
+
+ if self.random_departure then
+
+ -- Air start above a random airport.
+ for _,airport in pairs(self.airports)do
+ table.insert(departures, airport:GetZone())
+ end
+
+ else
+
+ -- Put all specified zones in table.
+ for _,name in pairs(self.departure_zones) do
+ self:E(self.departure_zones)
+ env.info(myid.."Zone name: "..name)
+ table.insert(departures, ZONE:New(name))
+ end
+ -- Put all specified airport zones in table.
+ for _,name in pairs(self.departure_ports) do
+ table.insert(departures, AIRBASE:FindByName(name):GetZone())
+ end
+
+ end
+
+ else
+
+ if self.random_departure then
+
+ -- All friendly departure airports.
+ for _,airport in pairs(self.airports) do
+ table.insert(departures, airport)
+ end
+
+ else
+
+ -- All airports specified by user
+ for _,name in pairs(self.departure_ports) do
+ table.insert(departures, AIRBASE:FindByName(name))
+ end
+
+ end
+ end
+
+ -- Select departure airport or zone.
+ local departure=departures[math.random(#departures)]
+
+ local text
+ if self.takeoff=="air" then
+ text="Chosen departure zone: "..departure:GetName()
+ else
+ text="Chosen departure airport: "..departure:GetName().." (ID "..departure:GetID()..")"
+ end
+ env.info(myid..text)
+ MESSAGE:New(text, 60):ToAll()
+
+ return departure
+end
+
+
+--- Set the destination airport of the AI. If no airport name is given an airport from the coalition is chosen randomly.
+-- @param #RAT self
+-- @return Wrapper.Airbase#AIRBASE Destination airport.
+function RAT:_SetDestination()
+
+ -- Array of possible destination airports.
+ local destinations={}
+
+ if self.random_destination then
+
+ -- All airports of friendly coalitons.
+ for _,airport in pairs(self.airports_destination) do
+ table.insert(destinations, airport)
+ end
+
+ else
+
+ -- All airports specified by user.
+ for _,name in pairs(self.destination_ports) do
+ table.insert(destinations, AIRBASE:FindByName(name))
+ end
+
+ end
+
+ -- Randomly select one possible destination.
+ local destination=destinations[math.random(#destinations)] -- Wrapper.Airbase#AIRBASE
+
+ local text="Chosen destination airport: "..destination:GetName().." (ID "..destination:GetID()..")"
+ self:E(destination:GetDesc())
+ env.info(myid..text)
+ MESSAGE:New(text, 60):ToAll()
+
+ return destination
+end
+
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+
+--- Get all possible destination airports depending on departure position.
+-- The list is sorted w.r.t. distance to departure position.
+-- @param #RAT self
+-- @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.
+function RAT:_GetDestinations(q, minrange, maxrange)
+
+ local absolutemin=5000 -- Absolute minimum is 5 km.
+ minrange=minrange or absolutemin -- Default min is absolute min.
+ maxrange=maxrange or 10000000 -- Default max 10,000 km.
+
+ -- Ensure that minrange is always > 10 km to ensure the destination != departure.
+ minrange=math.max(absolutemin, minrange)
+
+ -- loop over all friendly airports
+ for _,airport in pairs(self.airports) do
+ local p=airport:GetCoordinate()
+ local distance=q:Get2DDistance(p)
+ -- check if distance form departure to destination is within min/max range
+ if distance>=minrange and distance<=maxrange then
+ table.insert(self.airports_destination, airport)
+ end
+ end
+ env.info(myid.."Number of possible destination airports = "..#self.airports_destination)
+
+ if #self.airports_destination > 1 then
+ --- Compare distance of destination airports.
+ -- @param Core.Point#COORDINATE a Coordinate of point a.
+ -- @param Core.Point#COORDINATE b Coordinate of point b.
+ -- @return #list Table sorted by distance.
+ local function compare(a,b)
+ local qa=q:Get2DDistance(a:GetCoordinate())
+ local qb=q:Get2DDistance(b:GetCoordinate())
+ return qa < qb
+ end
+ table.sort(self.airports_destination, compare)
+ end
+
+end
+
+
+--- Get all airports of the current map.
+-- @param #RAT self
+function RAT:_GetAirportsOfMap()
+ local _coalition
+
+ for i=0,2 do -- cycle coalition.side 0=NEUTRAL, 1=RED, 2=BLUE
+
+ -- set coalition
+ if i==0 then
+ _coalition=coalition.side.NEUTRAL
+ elseif i==1 then
+ _coalition=coalition.side.RED
+ elseif i==2 then
+ _coalition=coalition.side.BLUE
+ end
+
+ -- get airbases of coalition
+ local ab=coalition.getAirbases(i)
+
+ -- loop over airbases and put them in a table
+ for _,airbase in pairs(ab) do -- loop over airbases
+ local _id=airbase:getID()
+ local _p=airbase:getPosition().p
+ local _name=airbase:getName()
+ local _myab=AIRBASE:FindByName(_name)
+ local text="Airport ID = ".._myab:GetID().." and Name = ".._myab:GetName()..", Category = ".._myab:GetCategory()..", TypeName = ".._myab:GetTypeName()
+ env.info(myid..text)
+ table.insert(self.airports_map, _myab)
+ end
+
+ end
+end
+
+
+--- Get all "friendly" airports of the current map.
+-- @param #RAT self
+function RAT:_GetAirportsOfCoalition()
+ for _,coalition in pairs(self.ctable) do
+ for _,airport in pairs(self.airports_map) do
+ if airport:GetCoalition()==coalition then
+ airport:GetTypeName()
+ -- Remember that planes cannot land on FARPs.
+ -- TODO: Probably have to add ships as well!
+ if not (self.category=="plane" and airport:GetTypeName()=="FARP") then
+ table.insert(self.airports, airport)
+ end
+ end
+ end
+ end
+
+ if #self.airports==0 then
+ local text="No possible departure/destination airports found!"
+ MESSAGE:New(text, 180):ToAll()
+ error(myid..text)
+ end
+end
+
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Report status of RAT groups.
-- @param #RAT self
@@ -351,6 +937,8 @@ function RAT:_SetStatus(group, status)
self.ratcraft[index].status=status
end
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+
--- Function is executed when a unit is spawned.
-- @param #RAT self
function RAT:_OnBirthDay(EventData)
@@ -467,316 +1055,7 @@ function RAT:_OnCrash(EventData)
end
end
-
---- Set name of departure airport for the AI aircraft. If no name is given an airport from the coalition is chosen randomly.
--- @param #RAT self
--- @param #string name Name of the departure airport or "random" for a randomly chosen one of the coalition.
-function RAT:SetDeparture(name)
- if name and AIRBASE:FindByName(name) then
- self.departure_name=name
- else
- self.departure_name="random"
- end
-end
-
---- Set departure zones for spawning the AI aircraft.
--- @param #RAT self
--- @param #table zonenames Table of zone names where spawning should happen.
-function RAT:SetDepartureZones(zonenames)
- self.zones_departure={}
- local z
- for _,name in pairs(zonenames) do
- if name:lower()=="zone template" then
- -- Zone with radius 5 km around the template group in the ME.
- z=ZONE_GROUP:New("Zone Template", GROUP:FindByName(self.prefix), self.Rzone)
- else
- -- Zone defined my user in the ME.
- z=ZONE:New(name)
- end
- if z then
- table.insert(self.zones_departure, z)
- else
- error(myid.."A zone with name "..name.." does not exist!")
- end
- end
-end
-
---- Test if an airport exists on the current map.
--- @param #RAT self
--- @param #string name
--- @return #boolean True if airport exsits, false otherwise.
-function RAT:_AirportExists(name)
- for _,airport in pairs(self.airports_map) do
- if airport:GeName()==name then
- return true
- end
- end
- return false
-end
-
-
---- Set possible departure ports. This can be an airport or a zone defined in the mission editor.
--- @param #RAT self
-function RAT:SetDepartureAll(names)
-
- -- 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.
- table.insert(self.departure_zones,"name")
- 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
-
- else
- -- error message
- error("Input parameter must be a string or a table!")
- end
-
-end
-
---- Set name of destination airport for the AI aircraft. If no name is given an airport from the coalition is chosen randomly.
--- @param #RAT self
--- @param #string name Name of the destination airport or "random" for a randomly chosen one of the coalition.
-function RAT:SetDestination(name)
- if name and AIRBASE:FindByName(name) then
- self.destination_name=name
- else
- self.destination_name="random"
- end
-end
-
-
---- Set the departure airport of the AI. If no airport name is given explicitly an airport from the coalition is chosen randomly.
--- If takeoff style is set to "air", we use zones around the airports or the zones specified by user input.
--- @param #RAT self
--- @return Wrapper.Airbase#AIRBASE Departure airport if spawning at airport.
-function RAT:_SetDeparture()
-
- local departure
-
- -- Array containing possible departure airports or zones.
- local departures={}
-
- if self.takeoff=="air" then
-
- if self.random_departure then
-
- -- Air start above a random airport.
- for _,airport in pairs(self.airports)do
- table.insert(departures, airport:GetZone())
- end
-
- else
-
- -- Put all specified zones in table.
- for _,name in pairs(self.departure_zones) do
- table.insert(departures, ZONE:New(name))
- end
- -- Put all specified airport zones in table.
- for _,name in pairs(self.departure_zones) do
- table.insert(departures, AIRBASE:FindByName("name"):GetZone())
- end
-
- end
---[[
- if self.departure_name=="random" then
- departure=self.zones_departure[math.random(#self.zones_departure)]
- else
- departure=ZONE:FindByName(self.departure_name)
- end
-
- text="Chosen departure zone: "..departure:GetName()
-]]
- else
-
- if self.random_departure then
-
- -- All friendly departure airports.
- departures=self.airports
-
- else
-
- for _,name in pairs(self.departure_ports) do
- table.insert(departures, AIRBASE:FindByName(name))
- end
-
- end
- end
-
---[[
- if self.departure_name=="random" then
- -- Get a random departure airport from all friendly coalition airports.
- departure=self.airports[math.random(#self.airports)]
- elseif AIRBASE:FindByName(self.departure_name) then
- -- Take the explicit airport provided.
- departure=AIRBASE:FindByName(self.departure_name)
- else
- -- If nothing else works, we randomly choose from friendly coalition airports.
- departure=self.airports[math.random(#self.airports)]
- end
-
- text="Chosen departure airport: "..departure:GetName().." with ID "..departure:GetID()
- end
-]]
-
- -- Select departure airport or zone.
- local departure=departures[math.random(#departures)]
-
- local text
- if self.takeoff=="air" then
- text="Chosen departure zone: "..departure:GetName()
- else
- text="Chosen departure airport: "..departure:GetName().." (ID "..departure:GetID()..")"
- end
- env.info(myid..text)
- MESSAGE:New(text, 60):ToAll()
-
- return departure
-end
-
-
---- Set the destination airport of the AI. If no airport name is given an airport from the coalition is chosen randomly.
--- @param #RAT self
--- @return Wrapper.Airbase#AIRBASE Destination airport.
-function RAT:_SetDestination()
- local destination -- Wrapper.Airbase#AIRBASE
- if self.destination_name=="random" then
- -- Get random destination from all friendly airports within range.
- destination=self.airports_destination[math.random(1, #self.airports_destination)]
- elseif self.destination_name and AIRBASE:FindByName(self.destination_name) then
- -- Take the explicit airport provided.
- destination=AIRBASE:FindByName(self.destination_name)
- else
- -- If nothing else works, we randomly choose from frindly coalition airports.
- destination=self.airports_destination[math.random(1, #self.airports_destination)]
- end
- local text="Chosen destination airport: "..destination:GetName().." with ID "..destination:GetID()
- self:E(destination:GetDesc())
- env.info(myid..text)
- MESSAGE:New(text, 60):ToAll()
- return destination
-end
-
-
---- Get all possible destination airports depending on departure position.
--- The list is sorted w.r.t. distance to departure position.
--- @param #RAT self
--- @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.
-function RAT:_GetDestinations(q, minrange, maxrange)
-
- local absolutemin=5000 -- Absolute minimum is 5 km.
- minrange=minrange or absolutemin -- Default min is absolute min.
- maxrange=maxrange or 10000000 -- Default max 10,000 km.
-
- -- Ensure that minrange is always > 10 km to ensure the destination != departure.
- minrange=math.max(absolutemin, minrange)
-
- -- loop over all friendly airports
- for _,airport in pairs(self.airports) do
- local p=airport:GetCoordinate()
- local distance=q:Get2DDistance(p)
- -- check if distance form departure to destination is within min/max range
- if distance>=minrange and distance<=maxrange then
- table.insert(self.airports_destination, airport)
- end
- end
- env.info(myid.."Number of possible destination airports = "..#self.airports_destination)
-
- if #self.airports_destination > 1 then
- --- Compare distance of destination airports.
- -- @param Core.Point#COORDINATE a Coordinate of point a.
- -- @param Core.Point#COORDINATE b Coordinate of point b.
- -- @return #list Table sorted by distance.
- local function compare(a,b)
- local qa=q:Get2DDistance(a:GetCoordinate())
- local qb=q:Get2DDistance(b:GetCoordinate())
- return qa < qb
- end
- table.sort(self.airports_destination, compare)
- end
-
-end
-
-
---- Get all airports of the current map.
--- @param #RAT self
-function RAT:_GetAirportsOfMap()
- local _coalition
-
- for i=0,2 do -- cycle coalition.side 0=NEUTRAL, 1=RED, 2=BLUE
-
- -- set coalition
- if i==0 then
- _coalition=coalition.side.NEUTRAL
- elseif i==1 then
- _coalition=coalition.side.RED
- elseif i==2 then
- _coalition=coalition.side.BLUE
- end
-
- -- get airbases of coalition
- local ab=coalition.getAirbases(i)
-
- -- loop over airbases and put them in a table
- for _,airbase in pairs(ab) do -- loop over airbases
- local _id=airbase:getID()
- local _p=airbase:getPosition().p
- local _name=airbase:getName()
- local _myab=AIRBASE:FindByName(_name)
- local text="Airport ID = ".._myab:GetID().." and Name = ".._myab:GetName()..", Category = ".._myab:GetCategory()..", TypeName = ".._myab:GetTypeName()
- env.info(myid..text)
- table.insert(self.airports_map, _myab)
- end
-
- end
-end
-
-
---- Get all "friendly" airports of the current map.
--- @param #RAT self
-function RAT:_GetAirportsOfCoalition()
- for _,coalition in pairs(self.ctable) do
- for _,airport in pairs(self.airports_map) do
- if airport:GetCoalition()==coalition then
- airport:GetTypeName()
- -- Remember that planes cannot land on FARPs.
- -- TODO: Probably have to add ships as well!
- if not (self.category=="plane" and airport:GetTypeName()=="FARP") then
- table.insert(self.airports, airport)
- end
- end
- end
- end
-
- if #self.airports==0 then
- local text="No possible departure/destination airports found!"
- MESSAGE:New(text, 180):ToAll()
- error(myid..text)
- end
-end
-
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a waypoint that can be used with the Route command.
-- @param #RAT self
@@ -786,7 +1065,7 @@ end
-- @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(Type, Coord, Speed, Altitude, Airport)
-- Altitude of input parameter or y-component of 3D-coordinate.
local _Altitude=Altitude or Coord.y
@@ -929,62 +1208,7 @@ function RAT:Waypoint(Type, Coord, Speed, Altitude, Airport)
return RoutePoint
end
-
---- Set takeoff type. Starting cold at airport, starting hot at airport, starting at runway, starting in the air or randomly select one of the previous.
--- Default is "takeoff-hot" for a start at airport with engines already running.
--- @param #RAT self
--- @param #string type Type can be "takeoff-cold" or "cold", "takeoff-hot" or "hot", "takeoff-runway" or "runway", "air", "random".
-function RAT:SetTakeoff(type)
- -- All possible types for random selection.
- local types={"takeoff-cold", "takeoff-hot", "takeoff-runway"}
- local _Type
- if type:lower()=="takeoff-cold" or type:lower()=="cold" then
- _Type="takeoff-cold"
- elseif type:lower()=="takeoff-hot" or type:lower()=="hot" then
- _Type="takeoff-hot"
- elseif type:lower()=="takeoff-runway" or type:lower()=="runway" then
- _Type="takeoff-runway"
- elseif type:lower()=="air" then
- --TODO: not implemented yet
- _Type="air"
- elseif type:lower()=="random" then
- _Type=types[math.random(1, #types)]
- else
- _Type="takeoff-hot"
- end
- self.takeoff=_Type
-end
-
---- Orbit at a specified position at a specified alititude with a specified speed.
--- @param #RAT self
--- @param Dcs.DCSTypes#Vec2 P1 The point to hold the position.
--- @param #number Altitude The altitude AGL to hold the position.
--- @param #number Speed The speed flying when holding the position in m/s.
--- @return Dcs.DCSTasking.Task#Task DCSTask
-function RAT:_TaskHolding(P1, Altitude, Speed)
- local LandHeight = land.getHeight(P1)
-
- --TODO: Add duration of holding. Otherwise it will hold until fuel is emtpy.
-
- -- second point is 10 km north of P1
- --TODO: randomize P1
- local P2={}
- P2.x=P1.x
- P2.y=P1.y+10000
- local DCSTask = {
- id = 'Orbit',
- params = {
- pattern = AI.Task.OrbitPattern.RACE_TRACK,
- point = P1,
- point2 = P2,
- speed = Speed,
- altitude = Altitude + LandHeight
- }
- }
-
- return DCSTask
-end
-
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Provide information about the assigned flightplan.
-- @param #RAT self
@@ -1029,244 +1253,7 @@ function RAT:_Routeinfo(waypoints, comment)
return total
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
--- @return Wrapper.Airport#AIRBASE Departure airbase.
--- @return Wrapper.Airport#AIRBASE Destination airbase.
--- @return #table Table of flight plan waypoints.
-function RAT:_SetRoute()
-
- -- unit conversions
- local ft2meter=0.305
- local kmh2ms=0.278
- local FL2m=30.48
- local nm2km=1.852
-
- -- DEPARTURE AIRPORT
- -- Departure airport or zone.
- local departure=self:_SetDeparture()
-
- -- Coordinates of departure point.
- local Pdeparture
- if self.takeoff=="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()
- end
-
- -- Height ASL of departure point.
- local H_departure
- if self.takeoff=="air" then
- -- Departure altitude is 70% of default cruise with 30% variation and limited to 1000 m AGL (50 m for helos).
- local Hmin
- if self.category=="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)
- else
- H_departure=Pdeparture.y
- end
-
- -- DESTINATION AIRPORT
- -- Get all destination airports within reach and at least 10 km away from departure.
- self:_GetDestinations(Pdeparture, self.mindist, self.aircraft.Reff)
-
- -- Pick a destination airport.
- local destination=self:_SetDestination()
-
- -- Check that departure and destination are not the same. Should not happen due to mindist.
- if destination:GetName()==departure:GetName() then
- local text="Destination and departure airport are identical: "..destination:GetName().." with ID "..destination:GetID()
- MESSAGE:New(text, 120):ToAll()
- error(myid..text)
- end
-
- -- Coordinates of destination airport.
- local Pdestination=destination:GetCoordinate()
- -- Height ASL of destination airport.
- local H_destination=Pdestination.y
-
- -- DESCENT/HOLDING POINT
- -- Get a random point between 10 and 20 km away from the destination.
- local Vholding
- if self.category=="plane" then
- Vholding=destination:GetCoordinate():GetRandomVec2InRadius(20000, 10000)
- else
- -- For helos we set a distance between 500 to 1000 m.
- Vholding=destination:GetCoordinate():GetRandomVec2InRadius(1000, 500)
- end
- -- Coordinates of the holding point. y is the land height at that point.
- local Pholding=COORDINATE:NewFromVec2(Vholding)
-
- -- Holding point altitude. For planes between 800 and 1200 m AGL. For helos 80 to 120 m AGL.
- local h_holding
- if self.category=="plane" then
- h_holding=1000
- else
- h_holding=100
- end
- h_holding=self:_Randomize(h_holding, 0.2)
-
- -- Distance from holding point to destination.
- local d_holding=Pholding:Get2DDistance(Pdestination)
-
- -- GENERAL
- -- heading from departure to holding point of destination
- local heading=self:_Course(Pdeparture, Pholding) -- heading from departure to destination
-
- -- total distance between departure and holding point (+last bit to destination)
- local d_total=Pdeparture:Get2DDistance(Pholding)
-
- -- CLIMB and DESCENT angles
- -- TODO: Randomize climb/descent angles. This did not work in rad. Need to convert to deg first.
- local AlphaClimb=self.aircraft.AlphaClimb --=self:_Randomize(self.aircraft.AlphaClimb, 0.1)
- local AlphaDescent=self.aircraft.AlphaDescent --self:_Randomize(self.aircraft.AlphaDescent, 0.1)
-
- --CRUISE
- -- Set min/max cruise altitudes.
- local FLmax
- local FLmin
- local FLcruise=self.aircraft.FLcruise
- if self.category=="plane" then
- -- Min cruise alt is just above holding point at destination or departure height, whatever is larger.
- FLmin=math.max(H_departure, H_destination+h_holding)
- -- Check if the distance between the two airports is large enough to reach the desired FL and descent again at the given climb/descent rates.
- if self.takeoff=="air" then
- -- This is the case where we only descent to the ground at the given descent angle.
- -- TODO: should this not better be h_holding Pholding.y?
- FLmax=d_total*math.tan(AlphaDescent)+H_destination
- else
- FLmax=self:_FLmax(AlphaClimb, AlphaDescent, d_total, H_departure)
- end
- -- If the route is very short we set FLmin a bit lower than FLmax.
- if FLmin>FLmax then
- FLmin=FLmax*0.8
- end
- -- Again, if the route is too short to climb and descent, we set the default cruise alt at bit lower than the max we can reach.
- if FLcruise>FLmax then
- FLcruise=FLmax*0.9
- end
- else
- -- For helicopters we take cruise alt between 50 to 1000 meters above ground. Default cruise alt is ~150 m.
- FLmin=math.max(H_departure, H_destination)+50
- FLmax=math.max(H_departure, H_destination)+1000
- end
- -- Set randomized cruise altitude: default +-50% but limited to FLmin and FLmax.
- FLcruise=self:_Randomize(FLcruise, 0.5, FLmin, FLmax)
- -- Finally, check that we are not above 90% of service ceiling.
- FLcruise=math.min(FLcruise, self.aircraft.ceiling*0.9)
-
- if self.takeoff=="air" then
- H_departure=math.min(H_departure,FLmax)
- 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=math.abs(h_climb/math.tan(AlphaClimb))
- -- time of climb in seconds
- local t_climb=h_climb/self.aircraft.Vclimb
-
- -- DESCENT
- -- Height difference for descent form cruise alt to holding point.
- local h_descent=FLcruise-h_holding-Pholding.y
- -- x-distance of descent part
- local d_descent=math.abs(h_descent/math.tan(AlphaDescent))
-
- -- 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
-
- -- debug message
- local text=string.format("Route distances:\n")
- text=text..string.format("d_climb = %6.1f km\n", d_climb/1000)
- text=text..string.format("d_cruise = %6.1f km\n", d_cruise/1000)
- text=text..string.format("d_descent = %6.1f km\n", d_descent/1000)
- text=text..string.format("d_holding = %6.1f km\n", d_holding/1000)
- text=text..string.format("d_total = %6.1f km\n", d_total/1000)
- text=text..string.format("Route heights:\n")
- text=text..string.format("H_departure = %6.1f m ASL\n", H_departure)
- text=text..string.format("H_destination = %6.1f m ASL\n", H_destination)
- text=text..string.format("h_climb = %6.1f m AGL\n", h_climb)
- text=text..string.format("h_descent = %6.1f m\n", h_descent)
- text=text..string.format("h_holding = %6.1f m AGL\n", h_holding)
- text=text..string.format("P_holding alt = %6.1f m ASL\n", Pholding.y)
- 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("FLmin = %6.1f m ASL\n", FLmin)
- text=text..string.format("FLmax = %6.1f m ASL\n", FLmax)
- text=text..string.format("FLcruise = %6.1f m ASL\n", FLcruise)
- text=text..string.format("Heading = %6.1f Degrees", heading)
- env.info(myid..text)
- if self.debug then
- MESSAGE:New(text, 60):ToAll()
- 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, heading)
- local c2=c1:Translate(d_cruise, heading)
- local c3=c2:Translate(d_descent, heading)
- local c3=Pholding
- local c4=Pdestination
-
- --Convert coordinates into route waypoints.
- local wp0=self:Waypoint(self.takeoff, c0, self.aircraft.Vmin, H_departure, departure)
- local wp1=self:Waypoint("climb", c1, self.aircraft.Vmax, FLcruise)
- local wp2=self:Waypoint("cruise", c2, self.aircraft.Vcruise, FLcruise)
- --TODO: add the possibility for a holing point, i.e. we circle a bit before final approach.
- --local wp3=self:Waypoint("descent", c3, self.aircraft.Vmin, h_holding)
- local wp3=self:Waypoint("holding", c3, self.aircraft.Vmin, h_holding)
- local wp4=self:Waypoint("landing", c4, self.aircraft.Vmin, 2, destination)
-
- -- set waypoints
- local waypoints = {wp0, wp1, wp2, wp3, wp4}
-
- self:_SetMarker("Takeoff and begin of climb.", c0)
- self:_SetMarker("End of climb and begin of cruise", c1)
- self:_SetMarker("End of Cruise and begin of descent", c2)
- self:_SetMarker("Holding Point", c3)
- self:_SetMarker("Final Destination", c4)
-
- -- some info on the route as message
- self:_Routeinfo(waypoints, "Waypoint info in set_route:")
-
- -- return departure, destination and waypoints
- return departure, destination, waypoints
-
-end
-
---- Calculate the max flight level for a given distance and fixed climb and descent rates.
--- In other words we have a distance between two airports and want to know how high we
--- can climb before we must descent again to arrive at the destination without any level/cruising part.
--- @param #RAT self
--- @param #number alpha Angle of climb [rad].
--- @param #number beta Angle of descent [rad].
--- @param #number d Distance between the two airports [m].
--- @param #number h0 Height [m] of departure airport. Note we implicitly assume that the height difference between departure and destination is negligible.
--- @return #number Maximal flight level in meters.
-function RAT:_FLmax(alpha, beta, d, h0)
--- Solve ASA triangle for one side (d) and two adjacent angles (alpha, beta) given.
- local gamma=math.rad(180)-alpha-beta
- local a=d*math.sin(alpha)/math.sin(gamma)
- local b=d*math.sin(beta)/math.sin(gamma)
- local h1=b*math.sin(alpha)
- local h2=a*math.sin(beta)
- local FL2m=30.48
- -- h1 and h2 should be equal.
- local text=string.format("FLmax = FL%3.0f = %6.1f m.\n", h1/FL2m, h1)
- text=text..string.format("FLmax = FL%3.0f = %6.1f m.", h2/FL2m, h2)
- env.info(myid..text)
- return b*math.sin(alpha)+h0
-end
-
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Modifies the template of the group to be spawned.
-- In particular, the waypoints of the group's flight plan are copied into the spawn template.
@@ -1325,6 +1312,77 @@ function RAT:_ModifySpawnTemplate(waypoints)
end
end
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+
+--- Orbit at a specified position at a specified alititude with a specified speed.
+-- @param #RAT self
+-- @param Dcs.DCSTypes#Vec2 P1 The point to hold the position.
+-- @param #number Altitude The altitude AGL to hold the position.
+-- @param #number Speed The speed flying when holding the position in m/s.
+-- @return Dcs.DCSTasking.Task#Task DCSTask
+function RAT:_TaskHolding(P1, Altitude, Speed)
+ local LandHeight = land.getHeight(P1)
+
+ --TODO: Add duration of holding. Otherwise it will hold until fuel is emtpy.
+
+ -- second point is 10 km north of P1
+ --TODO: randomize P1
+ local P2={}
+ P2.x=P1.x
+ P2.y=P1.y+10000
+ local DCSTask = {
+ id = 'Orbit',
+ params = {
+ pattern = AI.Task.OrbitPattern.RACE_TRACK,
+ point = P1,
+ point2 = P2,
+ speed = Speed,
+ altitude = Altitude + LandHeight
+ }
+ }
+
+ return DCSTask
+end
+
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+
+--- Calculate the max flight level for a given distance and fixed climb and descent rates.
+-- In other words we have a distance between two airports and want to know how high we
+-- can climb before we must descent again to arrive at the destination without any level/cruising part.
+-- @param #RAT self
+-- @param #number alpha Angle of climb [rad].
+-- @param #number beta Angle of descent [rad].
+-- @param #number d Distance between the two airports [m].
+-- @param #number h0 Height [m] of departure airport. Note we implicitly assume that the height difference between departure and destination is negligible.
+-- @return #number Maximal flight level in meters.
+function RAT:_FLmax(alpha, beta, d, h0)
+-- Solve ASA triangle for one side (d) and two adjacent angles (alpha, beta) given.
+ local gamma=math.rad(180)-alpha-beta
+ local a=d*math.sin(alpha)/math.sin(gamma)
+ local b=d*math.sin(beta)/math.sin(gamma)
+ local h1=b*math.sin(alpha)
+ local h2=a*math.sin(beta)
+ local FL2m=30.48
+ -- h1 and h2 should be equal.
+ local text=string.format("FLmax = FL%3.0f = %6.1f m.\n", h1/FL2m, h1)
+ text=text..string.format("FLmax = FL%3.0f = %6.1f m.", h2/FL2m, h2)
+ env.info(myid..text)
+ return b*math.sin(alpha)+h0
+end
+
+
+--- Test if an airport exists on the current map.
+-- @param #RAT self
+-- @param #string name
+-- @return #boolean True if airport exsits, false otherwise.
+function RAT:_AirportExists(name)
+ for _,airport in pairs(self.airports_map) do
+ if airport:GetName()==name then
+ return true
+ end
+ end
+ return false
+end
--- Create a table with the valid coalitions for departure and destination airports.
-- @param #RAT self
@@ -1429,6 +1487,7 @@ function RAT:_Randomize(value, fac, lower, upper)
return r
end
+
--- Set a marker for all on the F10 map.
-- @param #RAT self
-- @param #number id Index of marker.
@@ -1462,4 +1521,6 @@ end
--function RATCRAFT:New(name)
-- local self = BASE:Inherit(self, GROUP:New(name))
--end
-]]
\ No newline at end of file
+]]
+
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
\ No newline at end of file
From 6e27b93e455cb8f8c5fb60e663941877ffd5b0e9 Mon Sep 17 00:00:00 2001
From: funkyfranky
Date: Thu, 31 Aug 2017 15:57:25 +0200
Subject: [PATCH 09/28] Added user functions.
---
Moose Development/Moose/AI/AI_RAT.lua | 392 +++++++++++++++++++-------
1 file changed, 295 insertions(+), 97 deletions(-)
diff --git a/Moose Development/Moose/AI/AI_RAT.lua b/Moose Development/Moose/AI/AI_RAT.lua
index cfe838309..f44b0d933 100644
--- a/Moose Development/Moose/AI/AI_RAT.lua
+++ b/Moose Development/Moose/AI/AI_RAT.lua
@@ -13,10 +13,10 @@ myid="RAT | "
--- RAT class
-- @type RAT
--- @field #string ClassName
+-- @field #string ClassName Name of the class.
-- @field #boolean debug
-- @field #string prefix
--- @field #number sapwndelay
+-- @field #number spawndelay
-- @field #number spawninterval
-- @field #number coalition
-- @field #string category
@@ -27,24 +27,28 @@ myid="RAT | "
-- @field #number Vclimb
-- @field #number AlphaDescent
-- @field #string roe
+-- @field #string rot
-- @field #string takeoff
-- @field #number mindist
-- @field #number maxdist
-- @field #table airports_map
-- @field #table airports
--- @field #table airports_departure
-- @field #table airports_destination
-- @field #boolean random_departure
-- @field #boolean random_destination
-- @field #table departure_zones
-- @field #table departure_ports
-- @field #table destination_ports
--- @field #number Rzone
-- @field #table ratcraft
+-- @field #boolean reportstatus
+-- @field #number statusinterval
+-- @field #boolean placemarkers
-- @field #number markerid
-- @field #number marker0
+-- @field #number FLuser
+-- @field #number Vuser
-- @field #table RAT
--- @extends #SPAWN
+-- @extends Functional.Spawn#SPAWN
--- RAT class
@@ -63,23 +67,27 @@ RAT={
Vcruisemax=250, -- Max cruise speed in m/s (250 m/s = 900 km/h = 486 kt).
Vclimb=1500, -- Default climb rate in ft/min.
AlphaDescent=3.6, -- 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.
- roe = "hold", -- ROE of spawned groups, default is weapon hold (this is a peaceful class for civil aircraft or ferry missions).
+ 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 = "hot", -- Takeoff type: "hot", "cold", "runway", "air", "random".
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, ...).
airports={}, -- All airports of friedly coalitions.
- airports_departure={}, -- Possible departure airports if unit/group is spawned at airport, spawnpoint=air or spawnpoint=airport.
- airports_destination={}, -- Possible destination airports if unit does not fly "overseas", destpoint=overseas or destpoint=airport.
+ airports_destination={}, -- Possible destination airports which are in range of the chosen departure airport/zone.
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 zones.
- destination_ports={}, -- Array containing the names of the destination zones.
- Rzone=5000, -- Radius of departure zones in meters.
+ 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.
ratcraft={}, -- Array with the spawned RAT aircraft.
- markerid=0, -- Running number of the ID of markers on the F10 map.
- marker0=nil, -- Specific marker ID offset.
+ reportstatus=false, -- Aircraft report status.
+ statusinterval=30, -- Intervall between status reports.
+ placemarkers=false, -- Place markers of waypoints on F10 map.
+ markerid=0, -- Running number of the ID of markers.
+ marker0=nil, -- Specific randomized marker ID offset.
+ FLuser=nil, -- Flight level set by users explicitly.
+ Vuser=nil, -- Cruising speed set by user explicitly.
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -92,18 +100,13 @@ RAT.cat={
}
--- RAT unit conversions.
-- @field #RAT unit
+-- @field #number ft2meter
RAT.unit={
- meter2feet=1,
- nm2km=1,
- FL2m=1,
- --[[
- -- unit conversions
- local ft2meter=0.305
- local kmh2ms=0.278
- local FL2m=30.48
- local nm2km=1.852
- local nm2m=1852
- ]]
+ ft2meter=0.305,
+ kmh2ms=0.278,
+ FL2m=30.48,
+ nm2km=1.852,
+ nm2m=1852,
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -112,23 +115,24 @@ RAT.unit={
--DONE: Add scheduled spawn.
--DONE: Add possibility to spawn in air.
--DONE: Add departure zones for air start.
---TODO: Make more functions to adjust/set RAT parameters.
+--DONE: Make more functions to adjust/set RAT parameters.
--TODO: Clean up debug messages.
--DONE: Improve flight plan. Especially check FL against route length.
--DONE: Add event handlers.
--DONE: Respawn units when they have landed.
--DONE: Change ROE state.
---TODO: Make ROE state user function
+--DONE: Make ROE state user function
--TODO: Improve status reports.
--TODO: Check compatibility with other #SPAWN functions.
--TODO: Add possibility to continue journey at destination. Need "place" in event data for that.
--TODO: Add enumerators and get rid off error prone string comparisons.
---DONE: Check that FARPS are not used as airbases for planes. Don't know if they appear in list of airports.
+--DONE: Check that FARPS are not used as airbases for planes.
+--TODO: Add special cases for ships (similar to FARPs).
--DONE: Add cases for helicopters.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
---- Creates a new RAT object.
+--- Create a new RAT object.
-- @param #RAT self
-- @param #string prefix Prefix of the (template) group name defined in the mission editor.
-- @param #string friendly Friendly coalitions from which airports can be used.
@@ -209,7 +213,6 @@ function RAT:SetTakeoff(type)
self.takeoff=_Type
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.
@@ -279,6 +282,128 @@ function RAT:SetDestination(names)
end
+--- Set the delay before first group is spawned. Minimum delay is 0.5 seconds.
+-- @param #RAT self
+-- @param #number delay Delay in seconds.
+function RAT:SetSpawnDelay(delay)
+ self.spawndelay=math.max(0.5, delay)
+end
+
+--- Set the interval between spawnings of the template group. Minimum interval is 0.5 seconds.
+-- @param #RAT self
+-- @param #number interval Interval in seconds.
+function RAT:SetSpawnInterval(interval)
+ self.spawninterval=math.max(0.5, interval)
+end
+
+--- Set the maximum cruise speed of the aircraft.
+-- @param #RAT self
+-- @param #number speed Speed in km/h.
+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.
+-- @param #RAT self
+-- @param #number rate Climb rate in ft/min.
+function RAT:SetClimbRate(rate)
+
+ -- Convert from ft/min to m/s.
+ self.Vclimb=rate*RAT.unit.ft2m/60
+
+ -- Climb rate in m/s. Max is aircraft specific.
+ self.aircraft.Vclimb=math.min(self.Vclimb, self.aircraft.Vymax)
+
+ -- Climb angle in rad.
+ self.aircraft.AlphaClimb=math.asin(self.aircraft.Vclimb/self.aircraft.Vmax)
+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.
+function RAT:SetDescentAngle(angle)
+ -- Convert to rad.
+ self.aircraft.AlphaDescent=math.rad(angle)
+end
+
+--- Set rules of engagement (ROE). Default is weapon hold. This is a peaceful class.
+-- @param #RAT self
+-- @param #string roe "hold" = weapon hold, "return" = return fire, "free" = weapons free.
+function RAT:SetROE(roe)
+ if roe=="hold" or roe=="return" or roe=="free" then
+ self.roe=roe
+ else
+ self.roe="hold"
+ end
+end
+
+--- Set reaction to threat (ROT). Default is no reaction, i.e. aircraft will simply ignore all enemies.
+-- @param #RAT self
+-- @param #string rot "noreaction = no reactino, "passive" = passive defence, "evade" = weapons free.
+function RAT:SetROT(rot)
+ if rot=="noreaction" or rot=="passive" or rot=="evade" then
+ self.rot=rot
+ else
+ self.rot="noreaction"
+ end
+end
+
+--- Set minimum distance between departure and destination. Default is 5 km.
+-- Minimum distance should not be smaller than ~500(?) meters to ensure that departure and destination are different.
+-- @param #RAT self
+-- @param #number dist Distance in km.
+function RAT:SetMinDistance(dist)
+ -- Distance in meters. Absolute minimum is 500 m.
+ self.mindist=math.max(500, dist*1000)
+end
+
+--- Set maximum distance between departure and destination. Default is 5000 km but aircarft range is also taken into account automatically.
+-- @param #RAT self
+-- @param #number dist Distance in km.
+function RAT:SetMaxDistance(dist)
+ -- Distance in meters.
+ self.maxdist=dist*1000
+end
+
+--- Turn debug messages on or off. Default is off.
+-- @param #RAT self
+-- @param #boolean switch true turn messages on, false=off.
+function RAT:_Debug(switch)
+ switch = switch or true
+ self.debug=switch
+end
+
+--- Aircraft report status messages. Default is off.
+-- @param #RAT self
+-- @param #boolean switch true=on, false=off.
+function RAT:StatusReports(switch)
+ switch = switch or true
+ 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
+ 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
+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
+end
+
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Spawn the AI aircraft.
@@ -314,7 +439,9 @@ function RAT:Spawn(naircraft, name)
SCHEDULER:New(nil, self._SpawnWithRoute, {self}, Tstart, dt, 0.1, Tstop)
-- Status report scheduler.
- SCHEDULER:New(nil, self.Status, {self}, 30, 30)
+ if self.reportstatus then
+ SCHEDULER:New(nil, self.Status, {self}, self.statusinterval, 30)
+ end
end
@@ -367,18 +494,20 @@ function RAT:_InitAircraft(DCSgroup)
-- max airspeed from group
self.aircraft.Vmax = DCSdesc.speedMax
- -- min cruise airspeed = 75% of max
+ -- min cruise airspeed = 60% of max speed
self.aircraft.Vmin = self.aircraft.Vmax*0.60
+
+ -- max climb speed in m/s
+ self.aircraft.Vymax=DCSdesc.VyMax
- -- actual travel speed (random between ASmin and ASmax)
+ -- TODO: This should all NOT be done here!
+
+ -- actual travel speed (random between ASmin and ASmax)
--TODO: This needs to be placed somewhere else! Randomization should not happen here. Otherwise it is not changed for multiple spawns.
self.aircraft.Vcruise = math.random(self.aircraft.Vmin, self.aircraft.Vmax)
-- Limit travel speed to ~900 km/h for jets.
self.aircraft.Vcruise = math.min(self.aircraft.Vcruise, self.aircraft.Vmax)
-
- -- max climb speed in m/s
- self.aircraft.Vymax=DCSdesc.VyMax
-- Reasonably civil climb speed Vy=1500 ft/min but max aircraft specific climb rate.
self.aircraft.Vclimb=math.min(self.Vclimb*ft2meter/60, self.aircraft.Vymax)
@@ -439,15 +568,13 @@ function RAT:_SpawnWithRoute()
self:_ModifySpawnTemplate(waypoints)
-- Actually spawn the group.
- local group=self:SpawnWithIndex(self.SpawnIndex) -- Core.Group#GROUP
+ local group=self:SpawnWithIndex(self.SpawnIndex) -- Wrapper.Group#GROUP
-- set ROE to "weapon hold" and ROT to "no reaction"
- -- TODO: make user function to set this
- group:OptionROEReturnFire()
- --group:OptionROEHoldFire()
- group:OptionROTNoReaction()
- --group:OptionROTPassiveDefense()
-
+ self:_SetROE(group)
+ self:_SetROT(group)
+
+ -- Init ratcraft array.
self.ratcraft[self.SpawnIndex]={}
self.ratcraft[self.SpawnIndex]["group"]=group
self.ratcraft[self.SpawnIndex]["destination"]=destination
@@ -512,8 +639,8 @@ function RAT:_SetRoute()
end
-- DESTINATION AIRPORT
- -- Get all destination airports within reach and at least 10 km away from departure.
- self:_GetDestinations(Pdeparture, self.mindist, self.aircraft.Reff)
+ -- Get all destination airports within reach and at least a bit away from departure.
+ self:_GetDestinations(Pdeparture, self.mindist, math.min(self.aircraft.Reff, self.maxdist))
-- Pick a destination airport.
local destination=self:_SetDestination()
@@ -563,8 +690,8 @@ function RAT:_SetRoute()
-- CLIMB and DESCENT angles
-- TODO: Randomize climb/descent angles. This did not work in rad. Need to convert to deg first.
- local AlphaClimb=self.aircraft.AlphaClimb --=self:_Randomize(self.aircraft.AlphaClimb, 0.1)
- local AlphaDescent=self.aircraft.AlphaDescent --self:_Randomize(self.aircraft.AlphaDescent, 0.1)
+ local AlphaClimb=self.aircraft.AlphaClimb
+ local AlphaDescent=self.aircraft.AlphaDescent
--CRUISE
-- Set min/max cruise altitudes.
@@ -572,28 +699,37 @@ function RAT:_SetRoute()
local FLmin
local FLcruise=self.aircraft.FLcruise
if self.category=="plane" then
+
-- Min cruise alt is just above holding point at destination or departure height, whatever is larger.
FLmin=math.max(H_departure, H_destination+h_holding)
+
-- Check if the distance between the two airports is large enough to reach the desired FL and descent again at the given climb/descent rates.
if self.takeoff=="air" then
+
-- This is the case where we only descent to the ground at the given descent angle.
-- TODO: should this not better be h_holding Pholding.y?
FLmax=d_total*math.tan(AlphaDescent)+H_destination
+
else
+
FLmax=self:_FLmax(AlphaClimb, AlphaDescent, d_total, H_departure)
+
end
-- If the route is very short we set FLmin a bit lower than FLmax.
if FLmin>FLmax then
FLmin=FLmax*0.8
end
+
-- Again, if the route is too short to climb and descent, we set the default cruise alt at bit lower than the max we can reach.
if FLcruise>FLmax then
FLcruise=FLmax*0.9
end
else
+
-- For helicopters we take cruise alt between 50 to 1000 meters above ground. Default cruise alt is ~150 m.
FLmin=math.max(H_departure, H_destination)+50
FLmax=math.max(H_departure, H_destination)+1000
+
end
-- Ensure that FLmax not above 90% of service ceiling.
@@ -601,8 +737,13 @@ function RAT:_SetRoute()
-- Set randomized cruise altitude: default +-50% but limited to FLmin and FLmax.
FLcruise=self:_Randomize(FLcruise, 0.5, FLmin, FLmax)
-
+ -- Overrule setting if user specifies a flight level very explicitly.
+ if self.FLuser then
+ FLcruise=self.FLuser
+ end
+
+ -- Ensure that departure height is not above FLmax
if self.takeoff=="air" then
H_departure=math.min(H_departure,FLmax)
end
@@ -799,12 +940,8 @@ end
-- @param #number maxrange Maximum range to q in meters.
function RAT:_GetDestinations(q, minrange, maxrange)
- local absolutemin=5000 -- Absolute minimum is 5 km.
- minrange=minrange or absolutemin -- Default min is absolute min.
- maxrange=maxrange or 10000000 -- Default max 10,000 km.
-
- -- Ensure that minrange is always > 10 km to ensure the destination != departure.
- minrange=math.max(absolutemin, minrange)
+ minrange=minrange or self.mindist
+ maxrange=maxrange or self.maxdist
-- loop over all friendly airports
for _,airport in pairs(self.airports) do
@@ -853,7 +990,7 @@ function RAT:_GetAirportsOfMap()
local ab=coalition.getAirbases(i)
-- loop over airbases and put them in a table
- for _,airbase in pairs(ab) do -- loop over airbases
+ for _,airbase in pairs(ab) do
local _id=airbase:getID()
local _p=airbase:getPosition().p
local _name=airbase:getName()
@@ -895,24 +1032,29 @@ end
--- Report status of RAT groups.
-- @param #RAT self
function RAT:Status()
+
local ngroups=#self.SpawnGroups
MESSAGE:New("Number of groups spawned = "..ngroups, 60):ToAll()
+
for i=1, ngroups do
+
local group=self.SpawnGroups[i].Group
local prefix=self:_GetPrefixFromGroup(group)
local life=self:_GetLife(group)
+
local text=string.format("Group %s ID %i:\n", prefix, i)
text=text..string.format("Life = %3.0f\n", life)
text=text..string.format("Status = %s\n", self.ratcraft[i].status)
text=text..string.format("Flying from %s to %s.",self.ratcraft[i].departure:GetName(), self.ratcraft[i].destination:GetName())
MESSAGE:New(text, 60):ToAll()
env.info(myid..text)
+
end
end
--- Get (relative) life of first unit of a group.
-- @param #RAT self
--- @param #Group group Group of unit.
+-- @param Wrapper.Group#GROUP group Group of unit.
-- @return #number Life of unit in percent.
function RAT:_GetLife(group)
local life=0.0
@@ -942,15 +1084,18 @@ end
--- Function is executed when a unit is spawned.
-- @param #RAT self
function RAT:_OnBirthDay(EventData)
- env.info(myid.."It's a birthday")
+
local SpawnGroup = EventData.IniGroup
+
if SpawnGroup then
+
local index=self:GetSpawnIndexFromGroup(SpawnGroup)
local EventPrefix = self:_GetPrefixFromGroup(SpawnGroup)
+
local text="Event: Group "..SpawnGroup:GetName().." was born."
env.info(myid..text)
- --MESSAGE:New(text, 180):ToAll()
- self:_SetStatus(SpawnGroup, "starting engines (born)")
+ self:_SetStatus(SpawnGroup, "starting engines (after birth)")
+
else
error("Group does not exist in RAT:_EngineStartup().")
end
@@ -959,16 +1104,19 @@ end
--- Function is executed when a unit starts its engines.
-- @param #RAT self
function RAT:_EngineStartup(EventData)
- local SpawnGroup = EventData.IniGroup
+
+ local SpawnGroup = EventData.IniGroup --Wrapper.Group#GROUP
+
if SpawnGroup then
+
local text="Event: Group "..SpawnGroup:GetName().." started engines. Life="..self:_GetLife(SpawnGroup)
env.info(myid..text)
- --MESSAGE:New(text, 180):ToAll()
+
local status
- if SpawnGroup:IsAir() then
+ if SpawnGroup:InAir() then
status="airborn"
else
- status="taxi (engines started)"
+ status="taxi (after engines started)"
end
self:_SetStatus(SpawnGroup, status)
else
@@ -979,12 +1127,15 @@ end
--- Function is executed when a unit takes off.
-- @param #RAT self
function RAT:_OnTakeoff(EventData)
- local SpawnGroup = EventData.IniGroup
+
+ local SpawnGroup = EventData.IniGroup --Wrapper.Group#GROUP
+
if SpawnGroup then
- local text="Event: Group "..SpawnGroup:GetName().." took off. Life="..self:_GetLife(SpawnGroup)
+
+ local text="Event: Group "..SpawnGroup:GetName().." is airborn. Life="..self:_GetLife(SpawnGroup)
env.info(myid..text)
- --MESSAGE:New(text, 180):ToAll()
- self:_SetStatus(SpawnGroup, "airborn (took off)")
+
+ self:_SetStatus(SpawnGroup, "airborn (after takeoff)")
else
error("Group does not exist in RAT:_OnTakeoff().")
end
@@ -993,16 +1144,23 @@ end
--- Function is executed when a unit lands.
-- @param #RAT self
function RAT:_OnLand(EventData)
- local SpawnGroup = EventData.IniGroup
+
+ local SpawnGroup = EventData.IniGroup --Wrapper.Group#GROUP
+
if SpawnGroup then
+
local text="Event: Group "..SpawnGroup:GetName().." landed. Life="..self:_GetLife(SpawnGroup)
env.info(myid..text)
- --MESSAGE:New(text, 180):ToAll()
- self:_SetStatus(SpawnGroup, "landed")
+
+ -- Set status.
+ self:_SetStatus(SpawnGroup, "taxi (after landing)")
+
+ -- Spawn new group.
+ self:_SpawnWithRoute()
+
text="Event: Group "..SpawnGroup:GetName().." will be respawned."
env.info(myid..text)
- --MESSAGE:New(text, 180):ToAll()
- self:_SpawnWithRoute()
+
else
error("Group does not exist in RAT:_OnLand().")
end
@@ -1011,15 +1169,22 @@ end
--- Function is executed when a unit shuts down its engines.
-- @param #RAT self
function RAT:_OnEngineShutdown(EventData)
- local SpawnGroup = EventData.IniGroup
+
+ local SpawnGroup = EventData.IniGroup --Wrapper.Group#GROUP
+
if SpawnGroup then
+
local text="Event: Group "..SpawnGroup:GetName().." shut down its engines. Life="..self:_GetLife(SpawnGroup)
env.info(myid..text)
- --MESSAGE:New(text, 180):ToAll()
- self:_SetStatus(SpawnGroup, "arrived (engines shut down)")
+
+ -- Set status.
+ self:_SetStatus(SpawnGroup, "parking (after engine shut down)")
+
text="Event: Group "..SpawnGroup:GetName().." will be destroyed now."
env.info(myid..text)
- --MESSAGE:New(text, 180):ToAll()
+
+ -- Destroy spawn group.
+ --TODO: Check what happens all the other arrays in #SPAWN and ratcarft. Maybe need to do more here.
SpawnGroup:Destroy()
else
error("Group does not exist in RAT:_OnEngineShutdown().")
@@ -1029,12 +1194,17 @@ end
--- Function is executed when a unit is dead.
-- @param #RAT self
function RAT:_OnDead(EventData)
- local SpawnGroup = EventData.IniGroup
+
+ local SpawnGroup = EventData.IniGroup --Wrapper.Group#GROUP
+
if SpawnGroup then
+
local text="Event: Group "..SpawnGroup:GetName().." was died. Life="..self:_GetLife(SpawnGroup)
env.info(myid..text)
- self:_SetStatus(SpawnGroup, "dead (died)")
- --MESSAGE:New(text, 180):ToAll()
+
+ -- Set status.
+ self:_SetStatus(SpawnGroup, "destroyed (after dead)")
+
else
error("Group does not exist in RAT:_OnDead().")
end
@@ -1043,13 +1213,19 @@ end
--- Function is executed when a unit crashes.
-- @param #RAT self
function RAT:_OnCrash(EventData)
- local SpawnGroup = EventData.IniGroup
+
+ local SpawnGroup = EventData.IniGroup --Wrapper.Group#GROUP
+
if SpawnGroup then
+
local text="Event: Group "..SpawnGroup:GetName().." crashed. Life="..self:_GetLife(SpawnGroup)
env.info(myid..text)
- --MESSAGE:New(text, 180):ToAll()
+
+ -- Set status.
self:_SetStatus(SpawnGroup, "crashed")
- --TODO: maybe spawn some people at the crash site and send a distress call. And define them as cargo which can be rescued.
+
+ --TODO: Maybe spawn some people at the crash site and send a distress call.
+ -- And define them as cargo which can be rescued.
else
error("Group does not exist in RAT:_OnCrash().")
end
@@ -1127,7 +1303,7 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport)
_alttype="RADIO"
_AID = Airport:GetID()
else
- error("Unknown waypoint type in RAT:Waypoint function!")
+ error("Unknown waypoint type in RAT:Waypoint() function!")
_Type="Turning Point"
_Action="Turning Point"
_alttype="RADIO"
@@ -1155,11 +1331,7 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport)
text=text..string.format("Airport = %s with ID %i.", Airport:GetName(), Airport:GetID())
end
else
- text=text..string.format("No (valid) airport specified.")
- end
- local debugmessage=false
- if debugmessage then
- MESSAGE:New(text, 30, "RAT Waypoint Debug"):ToAll()
+ text=text..string.format("No (valid) airport/zone specified.")
end
env.info(myid..text)
end
@@ -1204,7 +1376,8 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport)
RoutePoint.task.params = {}
RoutePoint.task.params.tasks = {}
end
- -- return the waypoint
+
+ -- Return waypoint.
return RoutePoint
end
@@ -1384,6 +1557,34 @@ function RAT:_AirportExists(name)
return false
end
+--- Set ROE for a group.
+-- @param #RAT self
+-- @param Wrapper.Group#GROUP group Group for which the ROE is set.
+function RAT:_SetROE(group)
+ if self.roe=="return" then
+ group:OptionROEReturnFire()
+ elseif self.roe=="free" then
+ group:OptionROEWeaponFree()
+ else
+ group:OptionROEHoldFire()
+ end
+end
+
+
+--- Set ROT for a group.
+-- @param #RAT self
+-- @param Wrapper.Group#GROUP group Group for which the ROT is set.
+function RAT:_SetROT(group)
+ if self.roe=="passive" then
+ group:OptionROTPassiveDefense()
+ elseif self.roe=="evade" then
+ group:OptionROTEvadeFire()
+ else
+ group:OptionROTNoReaction()
+ end
+end
+
+
--- Create a table with the valid coalitions for departure and destination airports.
-- @param #RAT self
function RAT:_SetCoalitionTable()
@@ -1405,6 +1606,7 @@ function RAT:_SetCoalitionTable()
elseif self.friendly=="sameonly" then
self.ctable={self.coalition}
else
+ error("Unknown friendly coalition in _SetCoalitionTable(). Defaulting to NEUTRAL.")
self.ctable={self.coalition, coalition.side.NEUTRAL}
end
-- debug info
@@ -1475,15 +1677,11 @@ function RAT:_Randomize(value, fac, lower, upper)
end
local r=math.random(min, max)
+
+ -- debug info
local text=string.format("Random: value = %6.2f, fac = %4.2f, min = %6.2f, max = %6.2f, r = %6.2f", value, fac, min, max, r)
env.info(myid..text)
- --local r=math.random(value-value*fac,value+value*fac)
--- if upper and r>upper then
--- r=upper
--- end
--- if lower and r
Date: Thu, 31 Aug 2017 21:56:24 +0200
Subject: [PATCH 10/28] Aircraft climb rapidly :(
---
Moose Development/Moose/AI/AI_RAT.lua | 169 ++++++++++++++------------
1 file changed, 94 insertions(+), 75 deletions(-)
diff --git a/Moose Development/Moose/AI/AI_RAT.lua b/Moose Development/Moose/AI/AI_RAT.lua
index f44b0d933..6c09e4499 100644
--- a/Moose Development/Moose/AI/AI_RAT.lua
+++ b/Moose Development/Moose/AI/AI_RAT.lua
@@ -13,7 +13,7 @@ myid="RAT | "
--- RAT class
-- @type RAT
--- @field #string ClassName Name of the class.
+-- @field #string ClassName
-- @field #boolean debug
-- @field #string prefix
-- @field #number spawndelay
@@ -134,26 +134,28 @@ RAT.unit={
--- Create a new RAT object.
-- @param #RAT self
--- @param #string prefix Prefix of the (template) group name defined in the mission editor.
--- @param #string friendly Friendly coalitions from which airports can be used.
+-- @param #string groupname Name of the group as defined in the mission editor. This group is serving as a template for all spawned units.
+-- @param #string friendly (Optional) Friendly coalitions from which airports can be used.
-- "all"=neutral+red+blue, "same"=spawn coalition+neutral, "sameonly"=spawn coalition, "blue"=blue+neutral, "blueonly"=blue, "red"=red+neutral, "redonly"=red, "neutral"=neutral.
-- Default is "same", so aircraft will use airports of the coalition their spawn template has plus all neutral airports.
-- @return #RAT Object of RAT class.
-- @return #nil Nil if the group does not exists in the mission editor.
-function RAT:New(prefix, friendly)
+-- @usage yak:RAT("RAT_YAK") will create a RAT object called "yak". The template group in the mission editor must have the name "RAT_YAK". By default aircraft will spawn at neutral and red airports if the template group is part of the red coaliton.
+-- @usage yak:RAT("RAT_YAK", "all") will spawn aircraft randomly on airports of any coaliton, i.e. red, blue and neutral.
+function RAT:New(groupname, friendly)
-- Inherit SPAWN clase.
- local self=BASE:Inherit(self, SPAWN:New(prefix)) -- #RAT
+ local self=BASE:Inherit(self, SPAWN:New(groupname)) -- #RAT
-- Set prefix.
--TODO: Replace this by SpawnTemplatePrefix.
- self.prefix=prefix
+ self.prefix=groupname
-- Set friendly coalitions. Default is "same", i.e. same coalition as template group plus neutrals.
self.friendly = friendly or "same"
-- Get template group defined in the mission editor.
- local DCSgroup=Group.getByName(prefix)
+ local DCSgroup=Group.getByName(groupname)
-- Check the group actually exists.
if DCSgroup==nil then
@@ -176,6 +178,10 @@ function RAT:New(prefix, friendly)
-- Get all airports of this map beloning to friendly coalition(s).
self:_GetAirportsOfCoalition()
+ -- Set random seed for reproduction purposes.
+ --math.randomseed(1234)
+ --math.randomseed(os.time())
+
self.marker0=math.random(1000000)
return self
@@ -531,19 +537,21 @@ function RAT:_InitAircraft(DCSgroup)
end
-- send debug message
- local text=string.format("Aircraft parameters:\n")
+ local text=string.format("\n\n******************************************************\n")
+ text=text..string.format("Aircraft parameters:\n")
text=text..string.format("Category = %s\n", self.category)
- text=text..string.format("Max speed = %6.1f m/s.\n", self.aircraft.Vmax)
- text=text..string.format("Max cruise speed = %6.1f m/s.\n", self.aircraft.Vcruise)
- text=text..string.format("Max climb speed = %6.1f m/s.\n", self.aircraft.Vymax)
- text=text..string.format("Climb speed = %6.1f m/s.\n", self.aircraft.Vclimb)
- text=text..string.format("Angle of climb = %6.1f Deg.\n", math.deg(self.aircraft.AlphaClimb))
- text=text..string.format("Angle of descent = %6.1f Deg.\n", math.deg(self.aircraft.AlphaDescent))
- 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("Ceiling = FL%3.0f = %6.1f km.\n", self.aircraft.ceiling/FL2m, self.aircraft.ceiling/1000)
- text=text..string.format("FL cruise = FL%3.0f = %6.1f km.", self.aircraft.FLcruise/FL2m, self.aircraft.FLcruise/1000)
+ text=text..string.format("Max speed = %6.1f m/s\n", self.aircraft.Vmax)
+ text=text..string.format("Max cruise speed = %6.1f m/s\n", self.aircraft.Vcruise)
+ text=text..string.format("Max climb speed = %6.1f m/s\n", self.aircraft.Vymax)
+ text=text..string.format("Climb speed = %6.1f m/s\n", self.aircraft.Vclimb)
+ text=text..string.format("Angle of climb = %6.1f Deg\n", math.deg(self.aircraft.AlphaClimb))
+ text=text..string.format("Angle of descent = %6.1f Deg\n", math.deg(self.aircraft.AlphaDescent))
+ 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("Ceiling = FL%3.0f = %6.1f km\n", self.aircraft.ceiling/FL2m, self.aircraft.ceiling/1000)
+ text=text..string.format("FL cruise = FL%3.0f = %6.1f km\n", self.aircraft.FLcruise/FL2m, self.aircraft.FLcruise/1000)
+ text=text..string.format("******************************************************\n")
env.info(myid..text)
if self.debug then
MESSAGE:New(text, 60):ToAll()
@@ -708,7 +716,7 @@ function RAT:_SetRoute()
-- This is the case where we only descent to the ground at the given descent angle.
-- TODO: should this not better be h_holding Pholding.y?
- FLmax=d_total*math.tan(AlphaDescent)+H_destination
+ FLmax=d_total*math.tan(AlphaDescent)+h_holding+Pholding.y
else
@@ -717,7 +725,7 @@ function RAT:_SetRoute()
end
-- If the route is very short we set FLmin a bit lower than FLmax.
if FLmin>FLmax then
- FLmin=FLmax*0.8
+ FLmin=FLmax*0.75
end
-- Again, if the route is too short to climb and descent, we set the default cruise alt at bit lower than the max we can reach.
@@ -747,6 +755,9 @@ function RAT:_SetRoute()
if self.takeoff=="air" then
H_departure=math.min(H_departure,FLmax)
end
+
+ --FLcruise=4000
+ --H_departure=2000
-- CLIMB
-- Height of climb relative to ASL height of departure airport.
@@ -758,7 +769,7 @@ function RAT:_SetRoute()
-- DESCENT
-- Height difference for descent form cruise alt to holding point.
- local h_descent=FLcruise-h_holding-Pholding.y
+ local h_descent=FLcruise-(h_holding+Pholding.y)
-- x-distance of descent part
local d_descent=math.abs(h_descent/math.tan(AlphaDescent))
@@ -767,13 +778,14 @@ function RAT:_SetRoute()
local d_cruise=d_total-d_climb-d_descent
-- debug message
- local text=string.format("Route distances:\n")
+ local text=string.format("\n\n******************************************************\n")
+ text=text..string.format("Route distances:\n")
text=text..string.format("d_climb = %6.1f km\n", d_climb/1000)
text=text..string.format("d_cruise = %6.1f km\n", d_cruise/1000)
text=text..string.format("d_descent = %6.1f km\n", d_descent/1000)
text=text..string.format("d_holding = %6.1f km\n", d_holding/1000)
text=text..string.format("d_total = %6.1f km\n", d_total/1000)
- text=text..string.format("Route heights:\n")
+ text=text..string.format("\nRoute heights:\n")
text=text..string.format("H_departure = %6.1f m ASL\n", H_departure)
text=text..string.format("H_destination = %6.1f m ASL\n", H_destination)
text=text..string.format("h_climb = %6.1f m AGL\n", h_climb)
@@ -785,7 +797,8 @@ function RAT:_SetRoute()
text=text..string.format("FLmin = %6.1f m ASL\n", FLmin)
text=text..string.format("FLmax = %6.1f m ASL\n", FLmax)
text=text..string.format("FLcruise = %6.1f m ASL\n", FLcruise)
- text=text..string.format("Heading = %6.1f Degrees", heading)
+ text=text..string.format("Heading = %6.1f Degrees\n", heading)
+ text=text..string.format("******************************************************\n")
env.info(myid..text)
if self.debug then
MESSAGE:New(text, 60):ToAll()
@@ -793,30 +806,36 @@ function RAT:_SetRoute()
-- 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, heading)
- local c2=c1:Translate(d_cruise, heading)
- local c3=c2:Translate(d_descent, heading)
- local c3=Pholding
- local c4=Pdestination
+ 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(self.takeoff, c0, self.aircraft.Vmin, H_departure, departure)
- local wp1=self:_Waypoint("climb", c1, self.aircraft.Vmax, FLcruise)
- local wp2=self:_Waypoint("cruise", c2, self.aircraft.Vcruise, FLcruise)
- --TODO: add the possibility for a holing point, i.e. we circle a bit before final approach.
- --local wp3=self:Waypoint("descent", c3, self.aircraft.Vmin, h_holding)
- local wp3=self:_Waypoint("holding", c3, self.aircraft.Vmin, h_holding)
- local wp4=self:_Waypoint("landing", c4, self.aircraft.Vmin, 2, destination)
+ local wp0=self:_Waypoint(self.takeoff, c0, self.aircraft.Vmin, H_departure, departure)
+ local wp1=self:_Waypoint("climb", c1, self.aircraft.Vmax, H_departure+(FLcruise-H_departure)/2)
+ local wp2=self:_Waypoint("climb", c2, self.aircraft.Vcruise, FLcruise)
+ local wp3=self:_Waypoint("cruise", c3, self.aircraft.Vcruise, FLcruise)
+ local wp4=self:_Waypoint("descent", c4, self.aircraft.Vcruise, FLcruise-(FLcruise-h_holding)/2)
+ local wp5=self:_Waypoint("holding", c5, self.aircraft.Vmin, h_holding)
+ local wp6=self:_Waypoint("landing", c6, self.aircraft.Vmin, H_destination, destination)
-- set waypoints
- local waypoints = {wp0, wp1, wp2, wp3, wp4}
-
- self:_SetMarker("Takeoff and begin of climb.", c0)
- self:_SetMarker("End of climb and begin of cruise", c1)
- self:_SetMarker("End of Cruise and begin of descent", c2)
- self:_SetMarker("Holding Point", c3)
- self:_SetMarker("Final Destination", c4)
+ local waypoints = {wp0, wp1, wp2, wp3, wp4, wp5, wp6}
+ -- Place markers of waypoints on F10 map.
+ if self.placemarkers then
+ self:_SetMarker("Takeoff", c0)
+ self:_SetMarker("Climb", c1)
+ self:_SetMarker("Begin of Cruise", c2)
+ self:_SetMarker("End of Cruise", c3)
+ self:_SetMarker("Descent", c4)
+ self:_SetMarker("Holding Point", c5)
+ self:_SetMarker("Destination", c6)
+ end
+
-- some info on the route as message
self:_Routeinfo(waypoints, "Waypoint info in set_route:")
@@ -1275,7 +1294,7 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport)
elseif Type:lower()=="takeoff-runway" or Type:lower()=="runway" then
-- take-off from runway
_Type="TakeOff"
- _Action="From Parking Area" --TODO: Is this correct for a runway start?
+ _Action="From Parking Area"
_Altitude = 2
_alttype="RADIO"
_AID = Airport:GetID()
@@ -1283,17 +1302,25 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport)
-- air start
_Type="Turning Point"
_Action="Turning Point"
- _alttype="BARO"
- elseif Type:lower()=="climb" or Type:lower()=="cruise" then
+ _alttype="RADIO"
+ elseif Type:lower()=="climb" then
+ _Type="Turning Point"
+ _Action="Turning Point"
+ _Action="Fly Over Point"
+ _alttype="RADIO"
+ elseif Type:lower()=="cruise" then
_Type="Turning Point"
_Action="Turning Point"
- _alttype="BARO"
+ _Action="Fly Over Point"
+ _alttype="RADIO"
elseif Type:lower()=="descent" then
_Type="Turning Point"
+ _Action="Turning Point"
_Action="Fly Over Point"
_alttype="RADIO"
elseif Type:lower()=="holding" then
_Type="Turning Point"
+ _Action="Turning Point"
_Action="Fly Over Point"
_alttype="RADIO"
elseif Type:lower()=="landing" or Type:lower()=="land" then
@@ -1310,31 +1337,24 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport)
end
-- some debug info about input parameters
- if self.debug then
- local at="unknown (oops!)"
- if _alttype=="BARO" then
- at="ASL"
- elseif _alttype=="RADIO" then
- at="AGL"
- end
- local text=string.format("\nType: %s.\n", Type)
- if _Action then
- text=text..string.format("Action: %s.\n", tostring(_Action))
- end
- 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)
- text=text..string.format("Speed = %6.1f m/s = %6.1f km/h = %6.1f knots.\n", Speed, Speed*3.6, Speed*1.94384)
- text=text..string.format("Altitude = %6.1f m "..at..".\n", _Altitude)
- if Airport then
- if Type:lower() == "air" then
- text=text..string.format("Zone = %s.", Airport:GetName())
- else
- text=text..string.format("Airport = %s with ID %i.", Airport:GetName(), Airport:GetID())
- end
+ local text=string.format("\n\n******************************************************\n")
+ text=text..string.format("Type: %s - %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)
+ text=text..string.format("Speed = %6.1f m/s = %6.1f km/h = %6.1f knots\n", Speed, Speed*3.6, Speed*1.94384)
+ text=text..string.format("Land = %6.1f m ASL\n", Hland)
+ text=text..string.format("Altitude = %6.1f m (%s)\n", _Altitude, _alttype)
+ if Airport then
+ if Type:lower() == "air" then
+ text=text..string.format("Zone = %s\n", Airport:GetName())
else
- text=text..string.format("No (valid) airport/zone specified.")
+ text=text..string.format("Airport = %s with ID %i\n", Airport:GetName(), Airport:GetID())
end
- env.info(myid..text)
+ else
+ text=text..string.format("No airport/zone specified\n")
end
+ text=text.."******************************************************\n\n"
+ env.info(myid..text)
-- define waypoint
local RoutePoint = {}
@@ -1345,15 +1365,14 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport)
-- altitude type: BARO=ASL or RADIO=AGL
RoutePoint.alt_type = _alttype
-- type
- RoutePoint.type = _Type or nil
- RoutePoint.action = _Action or nil
+ RoutePoint.type = _Type
+ RoutePoint.action = _Action
-- speed in m/s
RoutePoint.speed = Speed
RoutePoint.speed_locked = true
-- ETA (not used)
- --TODO: ETA check if this makes the DCS bug go away
- --RoutePoint.ETA=nil
- RoutePoint.ETA_locked=true
+ RoutePoint.ETA=nil
+ RoutePoint.ETA_locked = false
-- waypoint name (only for the mission editor)
RoutePoint.name="RAT waypoint"
if _AID then
From 023a7a17c506718234db227a6adcff54f7331b12 Mon Sep 17 00:00:00 2001
From: funkyfranky
Date: Fri, 1 Sep 2017 00:14:38 +0200
Subject: [PATCH 11/28] Improvements in following the fligh plan.
Adjusted some speed in route. Still much to do.
---
Moose Development/Moose/AI/AI_RAT.lua | 213 +++++++++++++++++---------
1 file changed, 141 insertions(+), 72 deletions(-)
diff --git a/Moose Development/Moose/AI/AI_RAT.lua b/Moose Development/Moose/AI/AI_RAT.lua
index 6c09e4499..e3f219642 100644
--- a/Moose Development/Moose/AI/AI_RAT.lua
+++ b/Moose Development/Moose/AI/AI_RAT.lua
@@ -499,35 +499,14 @@ function RAT:_InitAircraft(DCSgroup)
-- max airspeed from group
self.aircraft.Vmax = DCSdesc.speedMax
-
- -- min cruise airspeed = 60% of max speed
- self.aircraft.Vmin = self.aircraft.Vmax*0.60
-- max climb speed in m/s
self.aircraft.Vymax=DCSdesc.VyMax
-
- -- TODO: This should all NOT be done here!
-
- -- actual travel speed (random between ASmin and ASmax)
- --TODO: This needs to be placed somewhere else! Randomization should not happen here. Otherwise it is not changed for multiple spawns.
- self.aircraft.Vcruise = math.random(self.aircraft.Vmin, self.aircraft.Vmax)
-
- -- Limit travel speed to ~900 km/h for jets.
- self.aircraft.Vcruise = math.min(self.aircraft.Vcruise, self.aircraft.Vmax)
-
- -- Reasonably civil climb speed Vy=1500 ft/min but max aircraft specific climb rate.
- self.aircraft.Vclimb=math.min(self.Vclimb*ft2meter/60, self.aircraft.Vymax)
-
- -- Climb angle in rad.
- self.aircraft.AlphaClimb=math.asin(self.aircraft.Vclimb/self.aircraft.Vmax)
-
- -- Descent angle in rad.
- self.aircraft.AlphaDescent=math.rad(self.AlphaDescent)
-
+
-- service ceiling in meters
self.aircraft.ceiling=DCSdesc.Hmax
- -- Default flight level (ASL).
+ -- Default flight level (ASL).
if self.category=="plane" then
-- For planes: FL200 = 20000 ft = 6096 m.
self.aircraft.FLcruise=200*FL2m
@@ -535,17 +514,13 @@ function RAT:_InitAircraft(DCSgroup)
-- For helos: FL005 = 500 ft = 152 m.
self.aircraft.FLcruise=005*FL2m
end
-
+
-- send debug message
local text=string.format("\n\n******************************************************\n")
text=text..string.format("Aircraft parameters:\n")
text=text..string.format("Category = %s\n", self.category)
- text=text..string.format("Max speed = %6.1f m/s\n", self.aircraft.Vmax)
- text=text..string.format("Max cruise speed = %6.1f m/s\n", self.aircraft.Vcruise)
+ text=text..string.format("Max air speed = %6.1f m/s\n", self.aircraft.Vmax)
text=text..string.format("Max climb speed = %6.1f m/s\n", self.aircraft.Vymax)
- text=text..string.format("Climb speed = %6.1f m/s\n", self.aircraft.Vclimb)
- text=text..string.format("Angle of climb = %6.1f Deg\n", math.deg(self.aircraft.AlphaClimb))
- text=text..string.format("Angle of descent = %6.1f Deg\n", math.deg(self.aircraft.AlphaDescent))
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)
@@ -615,6 +590,57 @@ function RAT:_SetRoute()
local kmh2ms=0.278
local FL2m=30.48
local nm2km=1.852
+
+
+ -- Min cruise speed 70% of Vmax or 600 km/h whichever is lower.
+ local VxCruiseMin = math.min(self.aircraft.Vmax*0.70, 166)
+
+ -- Max cruise speed 90% of Vmax or 900 km/h whichever is lower.
+ local VxCruiseMax = math.min(self.aircraft.Vmax*0.90, 250)
+
+ -- Cruise speed (randomized).
+ local VxCruise = self:_Randomize((VxCruiseMax-VxCruiseMin)/2, 0.3 , VxCruiseMin, VxCruiseMax)
+
+ -- Climb speed 90% ov Vmax but max 720 km/h.
+ local VxClimb = math.min(self.aircraft.Vmax*0.90, 200)
+
+ -- Descent speed 50% of Vmax but max 400 km/h.
+ local VxDescent = math.min(self.aircraft.Vmax*0.50, 111)
+
+ local VxHolding = VxDescent*0.8
+ local VxFinal = VxHolding*0.8
+
+--[[
+ -- actual travel speed (random between ASmin and ASmax)
+ --TODO: This needs to be placed somewhere else! Randomization should not happen here. Otherwise it is not changed for multiple spawns.
+ self.aircraft.Vcruise = math.random(self.aircraft.Vmin, self.aircraft.Vmax)
+
+ -- Limit travel speed to ~900 km/h for jets.
+ self.aircraft.Vcruise = math.min(self.aircraft.Vcruise, self.aircraft.Vmax)
+
+ -- Reasonably civil climb speed Vy=1500 ft/min but max aircraft specific climb rate.
+ self.aircraft.Vclimb=math.min(self.Vclimb*ft2meter/60, self.aircraft.Vymax)
+
+ -- Climb angle in rad.
+ self.aircraft.AlphaClimb=math.asin(self.aircraft.Vclimb/self.aircraft.Vmax)
+
+ -- Descent angle in rad.
+ self.aircraft.AlphaDescent=math.rad(self.AlphaDescent)
+]]
+
+ -- Reasonably civil climb speed Vy=1500 ft/min but max aircraft specific climb rate.
+ local VyClimb=math.min(self.Vclimb*ft2meter/60, self.aircraft.Vymax)
+
+ -- CLIMB and DESCENT angles
+ --local AlphaClimb=self.aircraft.AlphaClimb
+ --local AlphaDescent=self.aircraft.AlphaDescent
+
+ -- Climb angle in rad.
+ local AlphaClimb=math.asin(VyClimb/VxClimb)
+
+ -- Descent angle in rad.
+ local AlphaDescent=math.rad(self.AlphaDescent)
+
-- DEPARTURE AIRPORT
-- Departure airport or zone.
@@ -695,12 +721,7 @@ function RAT:_SetRoute()
-- total distance between departure and holding point (+last bit to destination)
local d_total=Pdeparture:Get2DDistance(Pholding)
-
- -- CLIMB and DESCENT angles
- -- TODO: Randomize climb/descent angles. This did not work in rad. Need to convert to deg first.
- local AlphaClimb=self.aircraft.AlphaClimb
- local AlphaDescent=self.aircraft.AlphaDescent
-
+
--CRUISE
-- Set min/max cruise altitudes.
local FLmax
@@ -755,23 +776,18 @@ function RAT:_SetRoute()
if self.takeoff=="air" then
H_departure=math.min(H_departure,FLmax)
end
-
- --FLcruise=4000
- --H_departure=2000
-- 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=math.abs(h_climb/math.tan(AlphaClimb))
- -- time of climb in seconds
- local t_climb=h_climb/self.aircraft.Vclimb
+ local d_climb=h_climb/math.tan(AlphaClimb)
-- DESCENT
-- Height difference for descent form cruise alt to holding point.
local h_descent=FLcruise-(h_holding+Pholding.y)
-- x-distance of descent part
- local d_descent=math.abs(h_descent/math.tan(AlphaDescent))
+ local d_descent=h_descent/math.tan(AlphaDescent)
-- CRUISE
-- Distance of the cruising part. This should in principle not become negative, but can happen for very short legs.
@@ -792,8 +808,8 @@ function RAT:_SetRoute()
text=text..string.format("h_descent = %6.1f m\n", h_descent)
text=text..string.format("h_holding = %6.1f m AGL\n", h_holding)
text=text..string.format("P_holding alt = %6.1f m ASL\n", Pholding.y)
- 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("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("FLmin = %6.1f m ASL\n", FLmin)
text=text..string.format("FLmax = %6.1f m ASL\n", FLmax)
text=text..string.format("FLcruise = %6.1f m ASL\n", FLcruise)
@@ -814,13 +830,13 @@ function RAT:_SetRoute()
local c6=Pdestination
--Convert coordinates into route waypoints.
- local wp0=self:_Waypoint(self.takeoff, c0, self.aircraft.Vmin, H_departure, departure)
- local wp1=self:_Waypoint("climb", c1, self.aircraft.Vmax, H_departure+(FLcruise-H_departure)/2)
- local wp2=self:_Waypoint("climb", c2, self.aircraft.Vcruise, FLcruise)
- local wp3=self:_Waypoint("cruise", c3, self.aircraft.Vcruise, FLcruise)
- local wp4=self:_Waypoint("descent", c4, self.aircraft.Vcruise, FLcruise-(FLcruise-h_holding)/2)
- local wp5=self:_Waypoint("holding", c5, self.aircraft.Vmin, h_holding)
- local wp6=self:_Waypoint("landing", c6, self.aircraft.Vmin, H_destination, destination)
+ local wp0=self:_Waypoint(self.takeoff, c0, VxClimb, H_departure, departure)
+ local wp1=self:_Waypoint("climb", c1, VxClimb, H_departure+(FLcruise-H_departure)/2)
+ local wp2=self:_Waypoint("climb", c2, VxCruise, FLcruise)
+ local wp3=self:_Waypoint("cruise", c3, VxCruise, FLcruise)
+ local wp4=self:_Waypoint("descent", c4, VxDescent, FLcruise-(FLcruise-h_holding)/2)
+ local wp5=self:_Waypoint("holding", c5, VxHolding, h_holding)
+ local wp6=self:_Waypoint("landing", c6, VxFinal, H_destination, destination)
-- set waypoints
local waypoints = {wp0, wp1, wp2, wp3, wp4, wp5, wp6}
@@ -1277,6 +1293,7 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport)
local _alttype="RADIO"
local _AID=nil
+--[[
if Type:lower()=="takeoff-cold" or Type:lower()=="cold" then
-- take-off with engine off
_Type="TakeOffParking"
@@ -1302,25 +1319,17 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport)
-- air start
_Type="Turning Point"
_Action="Turning Point"
- _alttype="RADIO"
- elseif Type:lower()=="climb" then
- _Type="Turning Point"
- _Action="Turning Point"
- _Action="Fly Over Point"
- _alttype="RADIO"
- elseif Type:lower()=="cruise" then
+ _alttype="BARO"
+ elseif Type:lower()=="climb" or Type:lower()=="cruise" then
_Type="Turning Point"
_Action="Turning Point"
- _Action="Fly Over Point"
- _alttype="RADIO"
+ _alttype="BARO"
elseif Type:lower()=="descent" then
_Type="Turning Point"
- _Action="Turning Point"
_Action="Fly Over Point"
_alttype="RADIO"
elseif Type:lower()=="holding" then
_Type="Turning Point"
- _Action="Turning Point"
_Action="Fly Over Point"
_alttype="RADIO"
elseif Type:lower()=="landing" or Type:lower()=="land" then
@@ -1329,13 +1338,73 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport)
_Altitude = 2
_alttype="RADIO"
_AID = Airport:GetID()
+ else
+ error("Unknown waypoint type in RAT:Waypoint function!")
+ _Type="Turning Point"
+ _Action="Turning Point"
+ _alttype="RADIO"
+ end
+]]
+
+ if Type:lower()=="takeoff-cold" or Type:lower()=="cold" then
+ -- take-off with engine off
+ _Type="TakeOffParking"
+ _Action="From Parking Area"
+ _Altitude = 2
+ _alttype="RADIO"
+ _AID = Airport:GetID()
+ elseif Type:lower()=="takeoff-hot" or Type:lower()=="hot" then
+ -- take-off with engine on
+ _Type="TakeOffParkingHot"
+ _Action="From Parking Area"
+ _Altitude = 2
+ _alttype="RADIO"
+ _AID = Airport:GetID()
+ elseif Type:lower()=="takeoff-runway" or Type:lower()=="runway" then
+ -- take-off from runway
+ _Type="TakeOff"
+ _Action="From Parking Area"
+ _Altitude = 2
+ _alttype="RADIO"
+ _AID = Airport:GetID()
+ elseif Type:lower()=="air" then
+ -- air start
+ _Type="Turning Point"
+ _Action="Turning Point"
+ _alttype="BARO"
+ elseif Type:lower()=="climb" then
+ _Type="Turning Point"
+ _Action="Turning Point"
+ _Action="Fly Over Point"
+ _alttype="BARO"
+ elseif Type:lower()=="cruise" then
+ _Type="Turning Point"
+ _Action="Turning Point"
+ _Action="Fly Over Point"
+ _alttype="BARO"
+ elseif Type:lower()=="descent" then
+ _Type="Turning Point"
+ _Action="Turning Point"
+ _Action="Fly Over Point"
+ _alttype="BARO"
+ elseif Type:lower()=="holding" then
+ _Type="Turning Point"
+ _Action="Turning Point"
+ _Action="Fly Over Point"
+ _alttype="BARO"
+ elseif Type:lower()=="landing" or Type:lower()=="land" then
+ _Type="Land"
+ _Action="Landing"
+ _Altitude = 2
+ _alttype="RADIO"
+ _AID = Airport:GetID()
else
error("Unknown waypoint type in RAT:Waypoint() function!")
_Type="Turning Point"
_Action="Turning Point"
_alttype="RADIO"
end
-
+
-- some debug info about input parameters
local text=string.format("\n\n******************************************************\n")
text=text..string.format("Type: %s - %s\n", Type, _Type)
@@ -1371,21 +1440,21 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport)
RoutePoint.speed = Speed
RoutePoint.speed_locked = true
-- ETA (not used)
- RoutePoint.ETA=nil
- RoutePoint.ETA_locked = false
+ RoutePoint.ETA=0
+ RoutePoint.ETA_locked = true
-- waypoint name (only for the mission editor)
RoutePoint.name="RAT waypoint"
if _AID then
RoutePoint.airdromeId=_AID
end
-- properties
- RoutePoint.properties = {
- ["vnav"] = 1,
- ["scale"] = 0,
- ["angle"] = 0,
- ["vangle"] = 0,
- ["steer"] = 2,
- }
+-- RoutePoint.properties = {
+-- ["vnav"] = 1,
+-- ["scale"] = 0,
+-- ["angle"] = 0,
+-- ["vangle"] = 0,
+-- ["steer"] = 2,
+-- }
-- task
if Type:lower()=="holding" then
RoutePoint.task=self:_TaskHolding({x=Coord.x, y=Coord.z}, Altitude, Speed)
From 1f893fe544ee2a1d569c2702e9db78a2a9026dd9 Mon Sep 17 00:00:00 2001
From: funkyfranky
Date: Fri, 1 Sep 2017 16:56:32 +0200
Subject: [PATCH 12/28] Minor changes
---
Moose Development/Moose/AI/AI_RAT.lua | 87 ++++++++-------------------
1 file changed, 26 insertions(+), 61 deletions(-)
diff --git a/Moose Development/Moose/AI/AI_RAT.lua b/Moose Development/Moose/AI/AI_RAT.lua
index e3f219642..fe22e7b44 100644
--- a/Moose Development/Moose/AI/AI_RAT.lua
+++ b/Moose Development/Moose/AI/AI_RAT.lua
@@ -430,9 +430,6 @@ function RAT:Spawn(naircraft, name)
text=text.."Takeoff type: "..self.takeoff.."\n"
text=text.."Friendly airports: "..self.friendly
env.info(myid..text)
- if self.debug then
- MESSAGE:New(text, 60, "Info"):ToAll()
- end
-- Schedule spawning of aircraft.
local Tstart=self.spawndelay
@@ -528,9 +525,6 @@ function RAT:_InitAircraft(DCSgroup)
text=text..string.format("FL cruise = FL%3.0f = %6.1f km\n", self.aircraft.FLcruise/FL2m, self.aircraft.FLcruise/1000)
text=text..string.format("******************************************************\n")
env.info(myid..text)
- if self.debug then
- MESSAGE:New(text, 60):ToAll()
- end
end
@@ -599,7 +593,7 @@ function RAT:_SetRoute()
local VxCruiseMax = math.min(self.aircraft.Vmax*0.90, 250)
-- Cruise speed (randomized).
- local VxCruise = self:_Randomize((VxCruiseMax-VxCruiseMin)/2, 0.3 , VxCruiseMin, VxCruiseMax)
+ local VxCruise = self:_Randomize((VxCruiseMax-VxCruiseMin)/2+VxCruiseMin, 0.3 , VxCruiseMin, VxCruiseMax)
-- Climb speed 90% ov Vmax but max 720 km/h.
local VxClimb = math.min(self.aircraft.Vmax*0.90, 200)
@@ -610,31 +604,9 @@ function RAT:_SetRoute()
local VxHolding = VxDescent*0.8
local VxFinal = VxHolding*0.8
---[[
- -- actual travel speed (random between ASmin and ASmax)
- --TODO: This needs to be placed somewhere else! Randomization should not happen here. Otherwise it is not changed for multiple spawns.
- self.aircraft.Vcruise = math.random(self.aircraft.Vmin, self.aircraft.Vmax)
-
- -- Limit travel speed to ~900 km/h for jets.
- self.aircraft.Vcruise = math.min(self.aircraft.Vcruise, self.aircraft.Vmax)
-
- -- Reasonably civil climb speed Vy=1500 ft/min but max aircraft specific climb rate.
- self.aircraft.Vclimb=math.min(self.Vclimb*ft2meter/60, self.aircraft.Vymax)
-
- -- Climb angle in rad.
- self.aircraft.AlphaClimb=math.asin(self.aircraft.Vclimb/self.aircraft.Vmax)
-
- -- Descent angle in rad.
- self.aircraft.AlphaDescent=math.rad(self.AlphaDescent)
-]]
-
-- Reasonably civil climb speed Vy=1500 ft/min but max aircraft specific climb rate.
local VyClimb=math.min(self.Vclimb*ft2meter/60, self.aircraft.Vymax)
- -- CLIMB and DESCENT angles
- --local AlphaClimb=self.aircraft.AlphaClimb
- --local AlphaDescent=self.aircraft.AlphaDescent
-
-- Climb angle in rad.
local AlphaClimb=math.asin(VyClimb/VxClimb)
@@ -649,7 +621,7 @@ function RAT:_SetRoute()
-- Coordinates of departure point.
local Pdeparture
if self.takeoff=="air" then
- -- For an air start, we take a random point within the spawn zone.
+ -- 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)
@@ -712,14 +684,14 @@ function RAT:_SetRoute()
end
h_holding=self:_Randomize(h_holding, 0.2)
- -- Distance from holding point to destination.
+ -- Distance from holding point to final destination.
local d_holding=Pholding:Get2DDistance(Pdestination)
-- GENERAL
- -- heading from departure to holding point of destination
- local heading=self:_Course(Pdeparture, Pholding) -- heading from departure to destination
+ -- Heading from departure to holding point of destination.
+ local heading=self:_Course(Pdeparture, Pholding)
- -- total distance between departure and holding point (+last bit to destination)
+ -- Total distance between departure and holding point near destination.
local d_total=Pdeparture:Get2DDistance(Pholding)
--CRUISE
@@ -730,7 +702,7 @@ function RAT:_SetRoute()
if self.category=="plane" then
-- Min cruise alt is just above holding point at destination or departure height, whatever is larger.
- FLmin=math.max(H_departure, H_destination+h_holding)
+ FLmin=math.max(H_departure, Pholding.y+h_holding)
-- Check if the distance between the two airports is large enough to reach the desired FL and descent again at the given climb/descent rates.
if self.takeoff=="air" then
@@ -795,30 +767,28 @@ function RAT:_SetRoute()
-- debug message
local text=string.format("\n\n******************************************************\n")
- text=text..string.format("Route distances:\n")
- text=text..string.format("d_climb = %6.1f km\n", d_climb/1000)
- text=text..string.format("d_cruise = %6.1f km\n", d_cruise/1000)
- text=text..string.format("d_descent = %6.1f km\n", d_descent/1000)
- text=text..string.format("d_holding = %6.1f km\n", d_holding/1000)
- text=text..string.format("d_total = %6.1f km\n", d_total/1000)
- text=text..string.format("\nRoute heights:\n")
+ text=text..string.format("Distances:\n")
+ text=text..string.format("d_climb = %6.1f km\n", d_climb/1000)
+ text=text..string.format("d_cruise = %6.1f km\n", d_cruise/1000)
+ text=text..string.format("d_descent = %6.1f km\n", d_descent/1000)
+ text=text..string.format("d_holding = %6.1f km\n", d_holding/1000)
+ text=text..string.format("d_total = %6.1f km\n", d_total/1000)
+ text=text..string.format("\nHeights:\n")
text=text..string.format("H_departure = %6.1f m ASL\n", H_departure)
text=text..string.format("H_destination = %6.1f m ASL\n", H_destination)
+ text=text..string.format("H_holding = %6.1f m ASL\n", Pholding.y)
text=text..string.format("h_climb = %6.1f m AGL\n", h_climb)
text=text..string.format("h_descent = %6.1f m\n", h_descent)
text=text..string.format("h_holding = %6.1f m AGL\n", h_holding)
- text=text..string.format("P_holding alt = %6.1f m ASL\n", Pholding.y)
- 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("FLmin = %6.1f m ASL\n", FLmin)
text=text..string.format("FLmax = %6.1f m ASL\n", FLmax)
text=text..string.format("FLcruise = %6.1f m ASL\n", FLcruise)
- text=text..string.format("Heading = %6.1f Degrees\n", heading)
+ 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("Heading = %6.1f Deg\n", heading)
text=text..string.format("******************************************************\n")
env.info(myid..text)
- if self.debug then
- MESSAGE:New(text, 60):ToAll()
- end
-- Coordinates of route from departure (0) to cruise (1) to descent (2) to holing (3) to destination (4).
local c0=Pdeparture
@@ -924,7 +894,7 @@ function RAT:_SetDeparture()
text="Chosen departure airport: "..departure:GetName().." (ID "..departure:GetID()..")"
end
env.info(myid..text)
- MESSAGE:New(text, 60):ToAll()
+ MESSAGE:New(text, 30):ToAll()
return departure
end
@@ -958,9 +928,8 @@ function RAT:_SetDestination()
local destination=destinations[math.random(#destinations)] -- Wrapper.Airbase#AIRBASE
local text="Chosen destination airport: "..destination:GetName().." (ID "..destination:GetID()..")"
- self:E(destination:GetDesc())
env.info(myid..text)
- MESSAGE:New(text, 60):ToAll()
+ MESSAGE:New(text, 30):ToAll()
return destination
end
@@ -1473,7 +1442,7 @@ end
--- Provide information about the assigned flightplan.
-- @param #RAT self
--- @param #list waypoints Waypoints of the flight plan.
+-- @param #table waypoints Waypoints of the flight plan.
-- @param #string comment Some comment to identify the provided information.
-- @return #number total Total route length in meters.
function RAT:_Routeinfo(waypoints, comment)
@@ -1505,10 +1474,7 @@ function RAT:_Routeinfo(waypoints, comment)
text=text..string.format("Total distance = %6.1f km", total/1000)
-- send message
- env.info(text)
- if self.debug then
- MESSAGE:New(text, 60):ToAll()
- end
+ env.info(myid..text)
-- return total route length in meters
return total
@@ -1551,6 +1517,7 @@ function RAT:_ModifySpawnTemplate(waypoints)
SpawnTemplate.units[UnitID].x = TX
SpawnTemplate.units[UnitID].y = TY
SpawnTemplate.units[UnitID].alt = PointVec3.y
+ --TODO: Somehow this does not work. Initial heading of the units for air start is not equal to heading specified here.
SpawnTemplate.units[UnitID].heading = heading
self:T('After Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y)
end
@@ -1563,7 +1530,6 @@ function RAT:_ModifySpawnTemplate(waypoints)
-- Also modify x,y of the template. Not sure why.
SpawnTemplate.x = PointVec3.x
SpawnTemplate.y = PointVec3.z
- SpawnTemplate.heading = heading
-- Update modified template for spawn group.
self.SpawnGroups[self.SpawnIndex].SpawnTemplate=SpawnTemplate
@@ -1623,10 +1589,9 @@ function RAT:_FLmax(alpha, beta, d, h0)
local b=d*math.sin(beta)/math.sin(gamma)
local h1=b*math.sin(alpha)
local h2=a*math.sin(beta)
- local FL2m=30.48
-- h1 and h2 should be equal.
- local text=string.format("FLmax = FL%3.0f = %6.1f m.\n", h1/FL2m, h1)
- text=text..string.format("FLmax = FL%3.0f = %6.1f m.", h2/FL2m, h2)
+ local text=string.format("FLmax = FL%3.0f = %6.1f m.\n", h1/RAT.unit.FL2m, h1)
+ text=text..string.format("FLmax = FL%3.0f = %6.1f m.", h2/RAT.unit.FL2m, h2)
env.info(myid..text)
return b*math.sin(alpha)+h0
end
From d5a21ff60465aeca26b31502ad42ae96eb44fccd Mon Sep 17 00:00:00 2001
From: funkyfranky
Date: Sun, 3 Sep 2017 09:47:14 +0200
Subject: [PATCH 13/28] Improved flight plan calculation.
Included phi in flight plan.
Many other fixes and changes.
---
Moose Development/Moose/AI/AI_RAT.lua | 505 ++++++++++++++------------
1 file changed, 278 insertions(+), 227 deletions(-)
diff --git a/Moose Development/Moose/AI/AI_RAT.lua b/Moose Development/Moose/AI/AI_RAT.lua
index fe22e7b44..c1a6f811a 100644
--- a/Moose Development/Moose/AI/AI_RAT.lua
+++ b/Moose Development/Moose/AI/AI_RAT.lua
@@ -15,7 +15,7 @@ myid="RAT | "
-- @type RAT
-- @field #string ClassName
-- @field #boolean debug
--- @field #string prefix
+-- @field #string templatename
-- @field #number spawndelay
-- @field #number spawninterval
-- @field #number coalition
@@ -43,8 +43,6 @@ myid="RAT | "
-- @field #boolean reportstatus
-- @field #number statusinterval
-- @field #boolean placemarkers
--- @field #number markerid
--- @field #number marker0
-- @field #number FLuser
-- @field #number Vuser
-- @field #table RAT
@@ -56,9 +54,9 @@ myid="RAT | "
RAT={
ClassName = "RAT", -- Name of class: RAT = Random Air Traffic.
debug=false, -- Turn debug messages on or off.
- prefix=nil, -- Prefix of the template group defined in the mission editor.
+ templatename=nil, -- Name of the template group defined in the mission editor.
spawndelay=1, -- Delay time in seconds before first spawning happens.
- spawninterval=1, -- Interval between spawning units/groups. Note that we add a randomization of 10%.
+ spawninterval=2, -- Interval between spawning units/groups. Note that we add a randomization of 10%.
coalition = nil, -- Coalition of spawn 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.
@@ -84,8 +82,6 @@ RAT={
reportstatus=false, -- Aircraft report status.
statusinterval=30, -- Intervall between status reports.
placemarkers=false, -- Place markers of waypoints on F10 map.
- markerid=0, -- Running number of the ID of markers.
- marker0=nil, -- Specific randomized marker ID offset.
FLuser=nil, -- Flight level set by users explicitly.
Vuser=nil, -- Cruising speed set by user explicitly.
}
@@ -98,6 +94,7 @@ RAT.cat={
plane="plane",
heli="heli"
}
+
--- RAT unit conversions.
-- @field #RAT unit
-- @field #number ft2meter
@@ -109,6 +106,10 @@ RAT.unit={
nm2m=1852,
}
+--- Running number of placed markers on the F10 map.
+-- @field #RAT markerid
+RAT.markerid=0
+
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--TODO list:
@@ -116,7 +117,7 @@ RAT.unit={
--DONE: Add possibility to spawn in air.
--DONE: Add departure zones for air start.
--DONE: Make more functions to adjust/set RAT parameters.
---TODO: Clean up debug messages.
+--DONE: Clean up debug messages.
--DONE: Improve flight plan. Especially check FL against route length.
--DONE: Add event handlers.
--DONE: Respawn units when they have landed.
@@ -127,7 +128,7 @@ RAT.unit={
--TODO: Add possibility to continue journey at destination. Need "place" in event data for that.
--TODO: Add enumerators and get rid off error prone string comparisons.
--DONE: Check that FARPS are not used as airbases for planes.
---TODO: Add special cases for ships (similar to FARPs).
+--DONE: Add special cases for ships (similar to FARPs).
--DONE: Add cases for helicopters.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -135,31 +136,24 @@ RAT.unit={
--- Create a new RAT object.
-- @param #RAT self
-- @param #string groupname Name of the group as defined in the mission editor. This group is serving as a template for all spawned units.
--- @param #string friendly (Optional) Friendly coalitions from which airports can be used.
--- "all"=neutral+red+blue, "same"=spawn coalition+neutral, "sameonly"=spawn coalition, "blue"=blue+neutral, "blueonly"=blue, "red"=red+neutral, "redonly"=red, "neutral"=neutral.
--- Default is "same", so aircraft will use airports of the coalition their spawn template has plus all neutral airports.
-- @return #RAT Object of RAT class.
-- @return #nil Nil if the group does not exists in the mission editor.
--- @usage yak:RAT("RAT_YAK") will create a RAT object called "yak". The template group in the mission editor must have the name "RAT_YAK". By default aircraft will spawn at neutral and red airports if the template group is part of the red coaliton.
--- @usage yak:RAT("RAT_YAK", "all") will spawn aircraft randomly on airports of any coaliton, i.e. red, blue and neutral.
-function RAT:New(groupname, friendly)
+-- @usage yak:RAT("RAT_YAK") will create a RAT object called "yak". The template group in the mission editor must have the name "RAT_YAK".
+-- By default aircraft will spawn at neutral and red airports if the template group is part of the red coaliton.
+function RAT:New(groupname)
-- Inherit SPAWN clase.
local self=BASE:Inherit(self, SPAWN:New(groupname)) -- #RAT
- -- Set prefix.
- --TODO: Replace this by SpawnTemplatePrefix.
- self.prefix=groupname
+ -- Set template name.
+ self.templatename=groupname
- -- Set friendly coalitions. Default is "same", i.e. same coalition as template group plus neutrals.
- self.friendly = friendly or "same"
-
-- Get template group defined in the mission editor.
local DCSgroup=Group.getByName(groupname)
-- Check the group actually exists.
if DCSgroup==nil then
- error("Group with name "..prefix.." does not exist in the mission editor!")
+ env.error("Group with name "..groupname.." does not exist in the mission editor!")
return nil
end
@@ -171,24 +165,65 @@ function RAT:New(groupname, friendly)
-- Get all airports of current map (Caucasus, NTTR, Normandy, ...).
self:_GetAirportsOfMap()
-
- -- Set the coalition table based on choice of self.coalition and self.friendly.
- self:_SetCoalitionTable()
-
- -- Get all airports of this map beloning to friendly coalition(s).
- self:_GetAirportsOfCoalition()
-
- -- Set random seed for reproduction purposes.
- --math.randomseed(1234)
- --math.randomseed(os.time())
-
- self.marker0=math.random(1000000)
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+--- Spawn the AI aircraft.
+-- @param #RAT self
+-- @param #number naircraft (Optional) Number of aircraft to spawn. Default is one aircraft.
+function RAT:Spawn(naircraft)
+
+ -- Number of aircraft to spawn. Default is one.
+ naircraft=naircraft or 1
+
+ -- Set the coalition table based on choice of self.coalition and self.friendly.
+ self:_SetCoalitionTable()
+
+ -- Get all airports of this map beloning to friendly coalition(s).
+ self:_GetAirportsOfCoalition()
+
+ -- debug message
+ local text=string.format("\n******************************************************\n")
+ text=text..string.format("Spawning %i aircraft from template %s of type %s.\n", naircraft, self.templatename, self.aircraft.type)
+ text=text..string.format("Takeoff type: %s\n", self.takeoff)
+ text=text..string.format("Friendly coalitions: %s\n", self.friendly)
+ text=text..string.format("Number of friendly airports: %i\n", #self.airports)
+ text=text..string.format("******************************************************\n")
+ env.info(myid..text)
+
+ -- Schedule spawning of aircraft.
+ local Tstart=self.spawndelay
+ local dt=self.spawninterval
+ -- Ensure that interval is >= 180 seconds if spawn at runway is chosen. Aircraft need time to takeoff or the runway gets jammed.
+ if self.takeoff:lower()=="takeoff-runway" or self.takeoff:lower()=="runway" then
+ dt=math.max(dt, 180)
+ end
+ local Tstop=Tstart+dt*(naircraft-1)
+ SCHEDULER:New(nil, self._SpawnWithRoute, {self}, Tstart, dt, 0.1, Tstop)
+
+ -- Status report scheduler.
+ if self.reportstatus then
+ SCHEDULER:New(nil, self.Status, {self}, self.statusinterval, 30)
+ end
+
+end
+
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+
+--- Set the friendly coalitions from which the airports can be used as departure or destination.
+-- @param #RAT self
+-- @param #string friendly Possible choices:
+-- "all"=neutral+red+blue, "same"=spawn coalition+neutral, "sameonly"=spawn coalition, "blue"=blue+neutral, "blueonly"=blue, "red"=red+neutral, "redonly"=red, "neutral"=neutral.
+-- Default is "same", so aircraft will use airports of the coalition their spawn template has plus all neutral airports.
+-- @usage yak:SetCoalition("all") will spawn aircraft randomly on airports of any coaliton, i.e. red, blue and neutral.
+-- @usage yak:SetCoalition("redonly") will spawn aircraft randomly on airports belonging to the red coalition only.
+function RAT:SetCoalition(friendly)
+ self.friendly=friendly
+end
+
--- Set takeoff type. Starting cold at airport, starting hot at airport, starting at runway, starting in the air or randomly select one of the previous.
-- Default is "takeoff-hot" for a start at airport with engines already running.
-- @param #RAT self
@@ -258,7 +293,7 @@ function RAT:SetDeparture(names)
else
-- error message
- error("Input parameter must be a string or a table!")
+ env.error("Input parameter must be a string or a table!")
end
end
@@ -283,7 +318,7 @@ function RAT:SetDestination(names)
else
-- Error message.
- error("Input parameter must be a string or a table!")
+ env.error("Input parameter must be a string or a table!")
end
end
@@ -412,44 +447,6 @@ end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
---- Spawn the AI aircraft.
--- @param #RAT self
--- @param #number naircraft (Optional) Number of aircraft to spawn. Default is one aircraft.
--- @param #string name (Optional) Name of the spawn group (for debugging only).
-function RAT:Spawn(naircraft, name)
-
- -- Number of aircraft to spawn. Default is one.
- naircraft=naircraft or 1
-
- -- some of group for debugging
- --TODO: remove name from input parameter and make better unique RAT AI name
- name=name or "RAT AI "..self.aircraft.type
-
- -- debug message
- local text="Spawning "..naircraft.." aircraft of group "..self.prefix.." with name "..name.." of type "..self.aircraft.type..".\n"
- text=text.."Takeoff type: "..self.takeoff.."\n"
- text=text.."Friendly airports: "..self.friendly
- env.info(myid..text)
-
- -- Schedule spawning of aircraft.
- local Tstart=self.spawndelay
- local dt=self.spawninterval
- if self.takeoff:lower()=="takeoff-runway" or self.takeoff:lower()=="runway" then
- -- Ensure that interval is >= 180 seconds if spawn at runway is chosen. Aircraft need time to takeoff or the runway gets jammed.
- dt=math.max(dt, 180)
- end
- local Tstop=Tstart+dt*(naircraft-1)
- SCHEDULER:New(nil, self._SpawnWithRoute, {self}, Tstart, dt, 0.1, Tstop)
-
- -- Status report scheduler.
- if self.reportstatus then
- SCHEDULER:New(nil, self.Status, {self}, self.statusinterval, 30)
- end
-
-end
-
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-
--- Initialize basic parameters of the aircraft based on its (template) group in the mission editor.
-- @param #RAT self
-- @param Dcs.DCSWrapper.Group#Group DCSgroup Group of the aircraft in the mission editor.
@@ -465,13 +462,6 @@ function RAT:_InitAircraft(DCSgroup)
self:E({"DCSdesc", DCSdesc})
end
- -- unit conversions
- local ft2meter=0.305
- local kmh2ms=0.278
- local FL2m=30.48
- local nm2km=1.852
- local nm2m=1852
-
-- set category
if DCScategory==Group.Category.AIRPLANE then
self.category="plane"
@@ -479,7 +469,7 @@ function RAT:_InitAircraft(DCSgroup)
self.category="heli"
else
self.category="other"
- error(myid.."Group of RAT is neither airplane nor helicopter!")
+ env.error(myid.."Group of RAT is neither airplane nor helicopter!")
end
-- Get type of aircraft.
@@ -489,7 +479,7 @@ function RAT:_InitAircraft(DCSgroup)
self.aircraft.fuel=DCSunit:getFuel()
-- operational range in NM converted to m
- self.aircraft.Rmax = DCSdesc.range*nm2m
+ 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
@@ -506,23 +496,24 @@ function RAT:_InitAircraft(DCSgroup)
-- Default flight level (ASL).
if self.category=="plane" then
-- For planes: FL200 = 20000 ft = 6096 m.
- self.aircraft.FLcruise=200*FL2m
+ self.aircraft.FLcruise=200*RAT.unit.FL2m
else
-- For helos: FL005 = 500 ft = 152 m.
- self.aircraft.FLcruise=005*FL2m
+ self.aircraft.FLcruise=005*RAT.unit.FL2m
end
-- send debug message
- local text=string.format("\n\n******************************************************\n")
+ local text=string.format("\n******************************************************\n")
text=text..string.format("Aircraft parameters:\n")
- text=text..string.format("Category = %s\n", self.category)
- text=text..string.format("Max air speed = %6.1f m/s\n", self.aircraft.Vmax)
- 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("Ceiling = FL%3.0f = %6.1f km\n", self.aircraft.ceiling/FL2m, self.aircraft.ceiling/1000)
- text=text..string.format("FL cruise = FL%3.0f = %6.1f km\n", self.aircraft.FLcruise/FL2m, self.aircraft.FLcruise/1000)
+ text=text..string.format("Category = %s\n", self.category)
+ text=text..string.format("Type = %s\n", self.aircraft.type)
+ text=text..string.format("Max air speed = %6.1f m/s\n", self.aircraft.Vmax)
+ 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("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(myid..text)
@@ -556,10 +547,11 @@ function RAT:_SpawnWithRoute()
self.ratcraft[self.SpawnIndex]["group"]=group
self.ratcraft[self.SpawnIndex]["destination"]=destination
self.ratcraft[self.SpawnIndex]["departure"]=departure
+ self.ratcraft[self.SpawnIndex]["waypoints"]=waypoints
self.ratcraft[self.SpawnIndex]["status"]="spawned"
+ self.ratcraft[self.SpawnIndex]["airborn"]=false
-- Handle events.
- -- TODO: add hit event?
self:HandleEvent(EVENTS.Birth, self._OnBirthDay)
self:HandleEvent(EVENTS.EngineStartup, self._EngineStartup)
self:HandleEvent(EVENTS.Takeoff, self._OnTakeoff)
@@ -568,6 +560,7 @@ function RAT:_SpawnWithRoute()
self:HandleEvent(EVENTS.Dead, self._OnDead)
-- TODO: Crash needs to be handled better. Does it always occur when dead?
--self:HandleEvent(EVENTS.Crash, self._OnCrash)
+ -- TODO: add hit event?
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -655,7 +648,7 @@ function RAT:_SetRoute()
if destination:GetName()==departure:GetName() then
local text="Destination and departure airport are identical: "..destination:GetName().." with ID "..destination:GetID()
MESSAGE:New(text, 120):ToAll()
- error(myid..text)
+ env.error(myid..text)
end
-- Coordinates of destination airport.
@@ -664,10 +657,10 @@ function RAT:_SetRoute()
local H_destination=Pdestination.y
-- DESCENT/HOLDING POINT
- -- Get a random point between 10 and 20 km away from the destination.
+ -- Get a random point between 5 and 10 km away from the destination.
local Vholding
if self.category=="plane" then
- Vholding=destination:GetCoordinate():GetRandomVec2InRadius(20000, 10000)
+ Vholding=destination:GetCoordinate():GetRandomVec2InRadius(10000, 5000)
else
-- For helos we set a distance between 500 to 1000 m.
Vholding=destination:GetCoordinate():GetRandomVec2InRadius(1000, 500)
@@ -675,99 +668,98 @@ function RAT:_SetRoute()
-- Coordinates of the holding point. y is the land height at that point.
local Pholding=COORDINATE:NewFromVec2(Vholding)
- -- Holding point altitude. For planes between 800 and 1200 m AGL. For helos 80 to 120 m AGL.
+ -- AGL height of holding point.
+ local H_holding=Pholding.y
+
+ -- Holding point altitude. For planes between 1600 and 2400 m AGL. For helos 160 to 240 m AGL.
local h_holding
if self.category=="plane" then
- h_holding=1000
+ h_holding=2000
else
- h_holding=100
+ h_holding=200
end
h_holding=self:_Randomize(h_holding, 0.2)
-- Distance from holding point to final destination.
local d_holding=Pholding:Get2DDistance(Pdestination)
+ -- Height difference between departure and holding point.
+ local deltaH=h_holding+H_holding-H_departure
+
-- GENERAL
-- Heading from departure to holding point of destination.
local heading=self:_Course(Pdeparture, Pholding)
-- 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)
+
+ -- Corrected climb angle.
+ local PhiClimb=AlphaClimb+phi
+
+ -- Corrected descent angle.
+ local PhiDescent=AlphaDescent-phi
+
--CRUISE
- -- Set min/max cruise altitudes.
- local FLmax
- local FLmin
- local FLcruise=self.aircraft.FLcruise
- if self.category=="plane" then
+
+ -- 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.
- FLmin=math.max(H_departure, Pholding.y+h_holding)
-
- -- Check if the distance between the two airports is large enough to reach the desired FL and descent again at the given climb/descent rates.
- if self.takeoff=="air" then
-
- -- This is the case where we only descent to the ground at the given descent angle.
- -- TODO: should this not better be h_holding Pholding.y?
- FLmax=d_total*math.tan(AlphaDescent)+h_holding+Pholding.y
-
- else
-
- FLmax=self:_FLmax(AlphaClimb, AlphaDescent, d_total, H_departure)
-
- end
- -- If the route is very short we set FLmin a bit lower than FLmax.
- if FLmin>FLmax then
- FLmin=FLmax*0.75
- end
-
- -- Again, if the route is too short to climb and descent, we set the default cruise alt at bit lower than the max we can reach.
- if FLcruise>FLmax then
- FLcruise=FLmax*0.9
- end
- else
+ -- 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)
- -- For helicopters we take cruise alt between 50 to 1000 meters above ground. Default cruise alt is ~150 m.
+ -- If the route is very short we set FLmin a bit lower than FLmax.
+ if FLmin>FLmax then
+ FLmin=FLmax*0.75
+ end
+
+ -- For helicopters we take cruise alt between 50 to 1000 meters above ground. Default cruise alt is ~150 m.
+ if self.category=="heli" then
FLmin=math.max(H_departure, H_destination)+50
FLmax=math.max(H_departure, H_destination)+1000
-
end
- -- Ensure that FLmax not above 90% of service ceiling.
+ -- Ensure that FLmax not above 90% its service ceiling.
FLmax=math.min(FLmax, self.aircraft.ceiling*0.9)
- -- Set randomized cruise altitude: default +-50% but limited to FLmin and FLmax.
- FLcruise=self:_Randomize(FLcruise, 0.5, FLmin, FLmax)
+ -- Set cruise altitude: default with 100% randomization but limited to FLmin and FLmax.
+ local FLcruise=self:_Randomize(self.aircraft.FLcruise, 1.0, FLmin, FLmax)
-- Overrule setting if user specifies a flight level very explicitly.
if self.FLuser then
FLcruise=self.FLuser
end
-
- -- Ensure that departure height is not above FLmax
- if self.takeoff=="air" then
- H_departure=math.min(H_departure,FLmax)
- 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(AlphaClimb)
+ 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+Pholding.y)
+ local h_descent=FLcruise-(H_holding+h_holding)
-- x-distance of descent part
- local d_descent=h_descent/math.tan(AlphaDescent)
+ local d_descent=h_descent/math.tan(PhiDescent)
-- 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
-- debug message
- local text=string.format("\n\n******************************************************\n")
- text=text..string.format("Distances:\n")
+ local text=string.format("\n******************************************************\n")
+ 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)
+ text=text..string.format("VxCruise = %6.1f m/s = %5.1f km/h\n", VxCruise, VxCruise*3.6)
+ text=text..string.format("VxClimb = %6.1f m/s = %5.1f km/h\n", VxClimb, VxClimb*3.6)
+ text=text..string.format("VxDescent = %6.1f m/s = %5.1f km/h\n", VxDescent, VxDescent*3.6)
+ text=text..string.format("VxHolding = %6.1f m/s = %5.1f km/h\n", VxHolding, VxHolding*3.6)
+ text=text..string.format("VxFinal = %6.1f m/s = %5.1f km/h\n", VxFinal, VxFinal*3.6)
+ text=text..string.format("VyClimb = %6.1f m/s\n", VyClimb)
+ text=text..string.format("\nDistances:\n")
text=text..string.format("d_climb = %6.1f km\n", d_climb/1000)
text=text..string.format("d_cruise = %6.1f km\n", d_cruise/1000)
text=text..string.format("d_descent = %6.1f km\n", d_descent/1000)
@@ -776,20 +768,24 @@ function RAT:_SetRoute()
text=text..string.format("\nHeights:\n")
text=text..string.format("H_departure = %6.1f m ASL\n", H_departure)
text=text..string.format("H_destination = %6.1f m ASL\n", H_destination)
- text=text..string.format("H_holding = %6.1f m ASL\n", Pholding.y)
- text=text..string.format("h_climb = %6.1f m AGL\n", h_climb)
+ text=text..string.format("H_holding = %6.1f m ASL\n", H_holding)
+ text=text..string.format("h_climb = %6.1f m\n", h_climb)
text=text..string.format("h_descent = %6.1f m\n", h_descent)
- text=text..string.format("h_holding = %6.1f m AGL\n", h_holding)
+ text=text..string.format("h_holding = %6.1f m\n", h_holding)
text=text..string.format("FLmin = %6.1f m ASL\n", FLmin)
text=text..string.format("FLmax = %6.1f m ASL\n", FLmax)
text=text..string.format("FLcruise = %6.1f m ASL\n", FLcruise)
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("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 = %6.1f Deg\n", math.deg(phi))
text=text..string.format("Heading = %6.1f Deg\n", heading)
text=text..string.format("******************************************************\n")
env.info(myid..text)
+ -- Ensure that cruise distance is positve. Can be slightly negative in special cases. And we don't want to turn back.
+ d_cruise=math.abs(d_cruise)
+
-- 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)
@@ -802,10 +798,10 @@ function RAT:_SetRoute()
--Convert coordinates into route waypoints.
local wp0=self:_Waypoint(self.takeoff, c0, VxClimb, H_departure, departure)
local wp1=self:_Waypoint("climb", c1, VxClimb, H_departure+(FLcruise-H_departure)/2)
- local wp2=self:_Waypoint("climb", c2, VxCruise, FLcruise)
+ local wp2=self:_Waypoint("cruise", c2, VxCruise, FLcruise)
local wp3=self:_Waypoint("cruise", c3, VxCruise, FLcruise)
- local wp4=self:_Waypoint("descent", c4, VxDescent, FLcruise-(FLcruise-h_holding)/2)
- local wp5=self:_Waypoint("holding", c5, VxHolding, h_holding)
+ local wp4=self:_Waypoint("descent", c4, VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2)
+ local wp5=self:_Waypoint("holding", c5, VxHolding, H_holding+h_holding)
local wp6=self:_Waypoint("landing", c6, VxFinal, H_destination, destination)
-- set waypoints
@@ -1014,10 +1010,11 @@ function RAT:_GetAirportsOfCoalition()
for _,coalition in pairs(self.ctable) do
for _,airport in pairs(self.airports_map) do
if airport:GetCoalition()==coalition then
- airport:GetTypeName()
- -- Remember that planes cannot land on FARPs.
- -- TODO: Probably have to add ships as well!
- if not (self.category=="plane" and airport:GetTypeName()=="FARP") then
+ -- Planes cannot land on FARPs.
+ local condition1=self.category=="plane" and airport:GetTypeName()=="FARP"
+ -- Planes cannot land on ships.
+ local condition2=self.category=="plane" and airport:GetCategory()==1
+ if not (condition1 or condition2) then
table.insert(self.airports, airport)
end
end
@@ -1027,7 +1024,7 @@ function RAT:_GetAirportsOfCoalition()
if #self.airports==0 then
local text="No possible departure/destination airports found!"
MESSAGE:New(text, 180):ToAll()
- error(myid..text)
+ env.error(myid..text)
end
end
@@ -1037,22 +1034,38 @@ end
-- @param #RAT self
function RAT:Status()
- local ngroups=#self.SpawnGroups
- MESSAGE:New("Number of groups spawned = "..ngroups, 60):ToAll()
+ local ngroups=#self.ratcraft
+
+ local text=string.format("Groups spawned = %i\n", ngroups)
+ text=text..string.format("Spawn Template: %s\n", self.SpawnTemplatePrefix)
+ text=text..string.format("Spawn Index = %d\n", self.SpawnIndex)
+ text=text..string.format("Spawn Count = %d\n", self.SpawnCount)
+ text=text..string.format("Spawn Alive = %d", self.AliveUnits)
+ MESSAGE:New(text, 60):ToAll()
for i=1, ngroups do
- local group=self.SpawnGroups[i].Group
- local prefix=self:_GetPrefixFromGroup(group)
- local life=self:_GetLife(group)
-
- local text=string.format("Group %s ID %i:\n", prefix, i)
- text=text..string.format("Life = %3.0f\n", life)
- text=text..string.format("Status = %s\n", self.ratcraft[i].status)
- text=text..string.format("Flying from %s to %s.",self.ratcraft[i].departure:GetName(), self.ratcraft[i].destination:GetName())
- MESSAGE:New(text, 60):ToAll()
- env.info(myid..text)
+ --local group=self.SpawnGroups[i].Group
+ if self.ratcraft[i].group then
+ local group=self.ratcraft[i].group --Wrapper.Group#GROUP
+ local prefix=self:_GetPrefixFromGroup(group)
+ local life=self:_GetLife(group)
+ local fuel=group:GetFuel()
+ local airborn=group:InAir()
+ local text=string.format("Group %s ID %i:\n", prefix, i)
+ text=text..string.format("Life = %3.0f\n", life)
+ text=text..string.format("Fuel = %3.0f\n", fuel)
+ text=text..string.format("Status = %s\n", self.ratcraft[i].status)
+ text=text..string.format("Status = %s\n", tostring(airborn))
+ text=text..string.format("Flying from %s to %s.",self.ratcraft[i].departure:GetName(), self.ratcraft[i].destination:GetName())
+ MESSAGE:New(text, 60):ToAll()
+ env.info(myid..text)
+ else
+ local text=string.format("Group %i does not exist.", i)
+ MESSAGE:New(text, 60):ToAll()
+ env.info(myid..text)
+ end
end
end
@@ -1067,10 +1080,10 @@ function RAT:_GetLife(group)
if unit then
life=unit:GetLife()/unit:GetLife0()*100
else
- error(myid.."Unit does not exists in RAT_Getlife(). Returning zero.")
+ env.error(myid.."Unit does not exist in RAT_Getlife(). Returning zero.")
end
else
- env.info(myid.."Group does not exists in RAT_Getlife(). Returning zero.")
+ env.error(myid.."Group does not exist in RAT_Getlife(). Returning zero.")
end
return life
end
@@ -1101,7 +1114,7 @@ function RAT:_OnBirthDay(EventData)
self:_SetStatus(SpawnGroup, "starting engines (after birth)")
else
- error("Group does not exist in RAT:_EngineStartup().")
+ env.error("Group does not exist in RAT:_EngineStartup().")
end
end
@@ -1124,7 +1137,7 @@ function RAT:_EngineStartup(EventData)
end
self:_SetStatus(SpawnGroup, status)
else
- error("Group does not exist in RAT:_EngineStartup().")
+ env.error("Group does not exist in RAT:_EngineStartup().")
end
end
@@ -1141,7 +1154,7 @@ function RAT:_OnTakeoff(EventData)
self:_SetStatus(SpawnGroup, "airborn (after takeoff)")
else
- error("Group does not exist in RAT:_OnTakeoff().")
+ env.error("Group does not exist in RAT:_OnTakeoff().")
end
end
@@ -1166,7 +1179,7 @@ function RAT:_OnLand(EventData)
env.info(myid..text)
else
- error("Group does not exist in RAT:_OnLand().")
+ env.error("Group does not exist in RAT:_OnLand().")
end
end
@@ -1186,12 +1199,10 @@ function RAT:_OnEngineShutdown(EventData)
text="Event: Group "..SpawnGroup:GetName().." will be destroyed now."
env.info(myid..text)
-
- -- Destroy spawn group.
- --TODO: Check what happens all the other arrays in #SPAWN and ratcarft. Maybe need to do more here.
- SpawnGroup:Destroy()
+
+ self:_Despawn(SpawnGroup)
else
- error("Group does not exist in RAT:_OnEngineShutdown().")
+ env.error("Group does not exist in RAT:_OnEngineShutdown().")
end
end
@@ -1201,16 +1212,18 @@ function RAT:_OnDead(EventData)
local SpawnGroup = EventData.IniGroup --Wrapper.Group#GROUP
+ env.info("In function _OnDead()")
+
if SpawnGroup then
- local text="Event: Group "..SpawnGroup:GetName().." was died. Life="..self:_GetLife(SpawnGroup)
+ local text="Event: Group "..SpawnGroup:GetName().." died."
env.info(myid..text)
-- Set status.
self:_SetStatus(SpawnGroup, "destroyed (after dead)")
else
- error("Group does not exist in RAT:_OnDead().")
+ env.error("Group does not exist in RAT:_OnDead().")
end
end
@@ -1222,7 +1235,7 @@ function RAT:_OnCrash(EventData)
if SpawnGroup then
- local text="Event: Group "..SpawnGroup:GetName().." crashed. Life="..self:_GetLife(SpawnGroup)
+ local text="Event: Group "..SpawnGroup:GetName().." crashed."
env.info(myid..text)
-- Set status.
@@ -1231,10 +1244,22 @@ function RAT:_OnCrash(EventData)
--TODO: Maybe spawn some people at the crash site and send a distress call.
-- And define them as cargo which can be rescued.
else
- error("Group does not exist in RAT:_OnCrash().")
+ env.error("Group does not exist in RAT:_OnCrash().")
end
end
+--- Function is executed when a unit crashes.
+-- @param #RAT self
+function RAT:_Despawn(group)
+
+ local index=self:GetSpawnIndexFromGroup(group)
+ env.info(myid.."Number of ratcraft before = "..#self.ratcraft)
+ self.ratcraft[index].group:Destroy()
+ self.ratcraft[index].group=nil
+ --table.remove(self.ratcraft, index)
+ env.info(myid.."Number of ratcraft after = "..#self.ratcraft)
+end
+
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a waypoint that can be used with the Route command.
@@ -1261,8 +1286,10 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport)
local _Action=nil
local _alttype="RADIO"
local _AID=nil
-
---[[
+
+ local case=2
+
+if case==1 then
if Type:lower()=="takeoff-cold" or Type:lower()=="cold" then
-- take-off with engine off
_Type="TakeOffParking"
@@ -1277,6 +1304,11 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport)
_Altitude = 2
_alttype="RADIO"
_AID = Airport:GetID()
+ local ab=Airbase.getByName("Farp")
+ local p=ab:getPoint()
+ --self:_SetMarker("Farp Position", p)
+ self.E(ab:getDesc())
+ _AID = 1
elseif Type:lower()=="takeoff-runway" or Type:lower()=="runway" then
-- take-off from runway
_Type="TakeOff"
@@ -1308,12 +1340,13 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport)
_alttype="RADIO"
_AID = Airport:GetID()
else
- error("Unknown waypoint type in RAT:Waypoint function!")
+ env.error("Unknown waypoint type in RAT:Waypoint function!")
_Type="Turning Point"
_Action="Turning Point"
_alttype="RADIO"
end
-]]
+
+else
if Type:lower()=="takeoff-cold" or Type:lower()=="cold" then
-- take-off with engine off
@@ -1344,22 +1377,22 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport)
elseif Type:lower()=="climb" then
_Type="Turning Point"
_Action="Turning Point"
- _Action="Fly Over Point"
+ --_Action="Fly Over Point"
_alttype="BARO"
elseif Type:lower()=="cruise" then
_Type="Turning Point"
_Action="Turning Point"
- _Action="Fly Over Point"
+ --_Action="Fly Over Point"
_alttype="BARO"
elseif Type:lower()=="descent" then
_Type="Turning Point"
_Action="Turning Point"
- _Action="Fly Over Point"
+ --_Action="Fly Over Point"
_alttype="BARO"
elseif Type:lower()=="holding" then
_Type="Turning Point"
_Action="Turning Point"
- _Action="Fly Over Point"
+ --_Action="Fly Over Point"
_alttype="BARO"
elseif Type:lower()=="landing" or Type:lower()=="land" then
_Type="Land"
@@ -1368,14 +1401,16 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport)
_alttype="RADIO"
_AID = Airport:GetID()
else
- error("Unknown waypoint type in RAT:Waypoint() function!")
+ env.error("Unknown waypoint type in RAT:Waypoint() function!")
_Type="Turning Point"
_Action="Turning Point"
_alttype="RADIO"
end
+end
+
-- some debug info about input parameters
- local text=string.format("\n\n******************************************************\n")
+ local text=string.format("\n******************************************************\n")
text=text..string.format("Type: %s - %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)
@@ -1391,7 +1426,7 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport)
else
text=text..string.format("No airport/zone specified\n")
end
- text=text.."******************************************************\n\n"
+ text=text.."******************************************************\n"
env.info(myid..text)
-- define waypoint
@@ -1410,20 +1445,20 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport)
RoutePoint.speed_locked = true
-- ETA (not used)
RoutePoint.ETA=0
- RoutePoint.ETA_locked = true
+ RoutePoint.ETA_locked = false
-- waypoint name (only for the mission editor)
RoutePoint.name="RAT waypoint"
if _AID then
RoutePoint.airdromeId=_AID
end
-- properties
--- RoutePoint.properties = {
--- ["vnav"] = 1,
--- ["scale"] = 0,
--- ["angle"] = 0,
--- ["vangle"] = 0,
--- ["steer"] = 2,
--- }
+ RoutePoint.properties = {
+ ["vnav"] = 1,
+ ["scale"] = 0,
+ ["angle"] = 0,
+ ["vangle"] = 0,
+ ["steer"] = 2,
+ }
-- task
if Type:lower()=="holding" then
RoutePoint.task=self:_TaskHolding({x=Coord.x, y=Coord.z}, Altitude, Speed)
@@ -1447,9 +1482,9 @@ end
-- @return #number total Total route length in meters.
function RAT:_Routeinfo(waypoints, comment)
- local text=""
+ local text=string.format("\n******************************************************\n")
if comment then
- text=comment.."\n"
+ text=text..comment.."\n"
end
text=text..string.format("Number of waypoints = %i\n", #waypoints)
-- info on coordinate and altitude
@@ -1471,8 +1506,8 @@ function RAT:_Routeinfo(waypoints, comment)
total=total+d
text=text..string.format("Distance from WP %i-->%i = %6.1f km. Heading = %i.\n", i-1, i, d/1000, heading)
end
- text=text..string.format("Total distance = %6.1f km", total/1000)
-
+ text=text..string.format("Total distance = %6.1f km\n", total/1000)
+ local text=string.format("******************************************************\n")
-- send message
env.info(myid..text)
@@ -1494,7 +1529,7 @@ function RAT:_ModifySpawnTemplate(waypoints)
-- Heading from first to seconds waypoints
local heading = self:_Course(waypoints[1], waypoints[2])
- env.info(myid.."Heading wp1->wp2: ", heading)
+ env.info(myid.."Heading wp1->wp2: "..heading)
if self:_GetSpawnIndex(self.SpawnIndex+1) then
@@ -1518,7 +1553,7 @@ function RAT:_ModifySpawnTemplate(waypoints)
SpawnTemplate.units[UnitID].y = TY
SpawnTemplate.units[UnitID].alt = PointVec3.y
--TODO: Somehow this does not work. Initial heading of the units for air start is not equal to heading specified here.
- SpawnTemplate.units[UnitID].heading = heading
+ SpawnTemplate.units[UnitID].heading = math.rad(heading)
self:T('After Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y)
end
@@ -1550,14 +1585,19 @@ end
function RAT:_TaskHolding(P1, Altitude, Speed)
local LandHeight = land.getHeight(P1)
- --TODO: Add duration of holding. Otherwise it will hold until fuel is emtpy.
-
- -- second point is 10 km north of P1
--TODO: randomize P1
+ -- Second point is 10 km north of P1 and 200 m for helos.
+ local dx=10000
+ local dy=0
+ if self.category=="heli" then
+ dx=200
+ dy=0
+ end
+
local P2={}
- P2.x=P1.x
- P2.y=P1.y+10000
- local DCSTask = {
+ P2.x=P1.x+dx
+ P2.y=P1.y+dy
+ local Task = {
id = 'Orbit',
params = {
pattern = AI.Task.OrbitPattern.RACE_TRACK,
@@ -1568,6 +1608,15 @@ function RAT:_TaskHolding(P1, Altitude, Speed)
}
}
+ -- Duration of holing. Between 10 and 170 seconds.
+ local d=self:_Randomize(90,0.9)
+
+ local DCSTask={}
+ DCSTask.id="ControlledTask"
+ DCSTask.params={}
+ DCSTask.params.task=Task
+ DCSTask.params.stopCondition={duration=d}
+
return DCSTask
end
@@ -1580,20 +1629,23 @@ end
-- @param #number alpha Angle of climb [rad].
-- @param #number beta Angle of descent [rad].
-- @param #number d Distance between the two airports [m].
+-- @param #number phi Angle between departure and destination [rad].
-- @param #number h0 Height [m] of departure airport. Note we implicitly assume that the height difference between departure and destination is negligible.
-- @return #number Maximal flight level in meters.
-function RAT:_FLmax(alpha, beta, d, h0)
+function RAT:_FLmax(alpha, beta, d, phi, h0)
-- Solve ASA triangle for one side (d) and two adjacent angles (alpha, beta) given.
local gamma=math.rad(180)-alpha-beta
local a=d*math.sin(alpha)/math.sin(gamma)
local b=d*math.sin(beta)/math.sin(gamma)
local h1=b*math.sin(alpha)
local h2=a*math.sin(beta)
+ local h3=b*math.cos(math.pi/2-(alpha+phi))
-- h1 and h2 should be equal.
- local text=string.format("FLmax = FL%3.0f = %6.1f m.\n", h1/RAT.unit.FL2m, h1)
- text=text..string.format("FLmax = FL%3.0f = %6.1f m.", h2/RAT.unit.FL2m, h2)
+ local text=string.format("\nFLmax = FL%3.0f = %6.1f m.\n", h1/RAT.unit.FL2m, h1)
+ text=text..string.format( "FLmax = FL%3.0f = %6.1f m.\n", h2/RAT.unit.FL2m, h2)
+ text=text..string.format( "FLmax = FL%3.0f = %6.1f m.", h3/RAT.unit.FL2m, h3)
env.info(myid..text)
- return b*math.sin(alpha)+h0
+ return h3+h0
end
@@ -1659,7 +1711,7 @@ function RAT:_SetCoalitionTable()
elseif self.friendly=="sameonly" then
self.ctable={self.coalition}
else
- error("Unknown friendly coalition in _SetCoalitionTable(). Defaulting to NEUTRAL.")
+ env.error("Unknown friendly coalition in _SetCoalitionTable(). Defaulting to NEUTRAL.")
self.ctable={self.coalition, coalition.side.NEUTRAL}
end
-- debug info
@@ -1741,13 +1793,12 @@ end
--- Set a marker for all on the F10 map.
-- @param #RAT self
--- @param #number id Index of marker.
-- @param #string text Text of maker.
-- @param Core.Point#COORDINATE vec3 Position of marker.
function RAT:_SetMarker(text, vec3)
- self.markerid=self.markerid+1
- env.info(myid.."Placing marker with ID "..self.markerid.." and text "..text)
- trigger.action.markToAll(self.markerid, text, vec3)
+ RAT.markerid=RAT.markerid+1
+ env.info(myid.."Placing marker with ID "..RAT.markerid..": "..text)
+ trigger.action.markToAll(RAT.markerid, text, vec3)
end
--[[
From 27c51f8fe33d33861f13c88a7b0aa6db92eec665 Mon Sep 17 00:00:00 2001
From: funkyfranky
Date: Mon, 4 Sep 2017 00:37:13 +0200
Subject: [PATCH 14/28] First alpha version.
Added commute and continuejourney.
Improved status reports.
Added basic status menu.
Many other fixes.
---
Moose Development/Moose/AI/AI_RAT.lua | 678 ++++++++++++++++----------
1 file changed, 425 insertions(+), 253 deletions(-)
diff --git a/Moose Development/Moose/AI/AI_RAT.lua b/Moose Development/Moose/AI/AI_RAT.lua
index c1a6f811a..f069c3fc5 100644
--- a/Moose Development/Moose/AI/AI_RAT.lua
+++ b/Moose Development/Moose/AI/AI_RAT.lua
@@ -5,9 +5,7 @@
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
---- Some ID to identify where we are
--- #string myid
-myid="RAT | "
+-- TODO: Add description.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -45,6 +43,10 @@ myid="RAT | "
-- @field #boolean placemarkers
-- @field #number FLuser
-- @field #number Vuser
+-- @field #boolean commute
+-- @field #boolean continuejourney
+-- @field #number alive
+-- @field #table Menu
-- @field #table RAT
-- @extends Functional.Spawn#SPAWN
@@ -55,8 +57,8 @@ RAT={
ClassName = "RAT", -- Name of class: RAT = Random Air Traffic.
debug=false, -- Turn debug messages on or off.
templatename=nil, -- Name of the template group defined in the mission editor.
- spawndelay=1, -- Delay time in seconds before first spawning happens.
- spawninterval=2, -- Interval between spawning units/groups. Note that we add a randomization of 10%.
+ 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.
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.
@@ -80,10 +82,14 @@ RAT={
destination_ports={}, -- Array containing the names of the destination airports.
ratcraft={}, -- Array with the spawned RAT aircraft.
reportstatus=false, -- Aircraft report status.
- statusinterval=30, -- Intervall between status reports.
+ statusinterval=30, -- Intervall between status checks (and reports if enabled).
placemarkers=false, -- Place markers of waypoints on F10 map.
FLuser=nil, -- Flight level set by users explicitly.
Vuser=nil, -- Cruising speed set by user explicitly.
+ commute=false, -- Aircraft commute between departure and destination, i.e. when respawned the departure airport becomes the new destiation.
+ continuejourney=false, -- Aircraft will continue their journey, i.e. get respawned at their destination with a new random destination.
+ alive=0, -- Number of groups which are alive.
+ Menu={}, -- F10 menu for this RAT object.
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -110,6 +116,12 @@ RAT.unit={
-- @field #RAT markerid
RAT.markerid=0
+RAT.MenuF10=nil
+
+--- Some ID to identify where we are
+-- #string myid
+myid="RAT | "
+
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--TODO list:
@@ -123,9 +135,9 @@ RAT.markerid=0
--DONE: Respawn units when they have landed.
--DONE: Change ROE state.
--DONE: Make ROE state user function
---TODO: Improve status reports.
+--DONE: Improve status reports.
--TODO: Check compatibility with other #SPAWN functions.
---TODO: Add possibility to continue journey at destination. Need "place" in event data for that.
+--DONE: Add possibility to continue journey at destination. Need "place" in event data for that.
--TODO: Add enumerators and get rid off error prone string comparisons.
--DONE: Check that FARPS are not used as airbases for planes.
--DONE: Add special cases for ships (similar to FARPs).
@@ -165,6 +177,15 @@ function RAT:New(groupname)
-- Get all airports of current map (Caucasus, NTTR, Normandy, ...).
self:_GetAirportsOfMap()
+
+ -- Create F10 main menu if it does not exists yet.
+ if not RAT.MenuF10 then
+ RAT.MenuF10 = MENU_MISSION:New("RAT")
+ end
+
+ -- Crate submenu
+ self.Menu[self.SpawnTemplatePrefix]=MENU_MISSION:New(self.SpawnTemplatePrefix, RAT.MenuF10)
+ MENU_MISSION_COMMAND:New("Status report", self.Menu[self.SpawnTemplatePrefix], self.Status, self, true)
return self
end
@@ -191,6 +212,8 @@ function RAT:Spawn(naircraft)
text=text..string.format("Takeoff type: %s\n", self.takeoff)
text=text..string.format("Friendly coalitions: %s\n", self.friendly)
text=text..string.format("Number of friendly airports: %i\n", #self.airports)
+ text=text..string.format("Commuting: %s\n", tostring(self.commute))
+ text=text..string.format("Long Journey: %s\n", tostring(self.continuejourney))
text=text..string.format("******************************************************\n")
env.info(myid..text)
@@ -204,10 +227,18 @@ function RAT:Spawn(naircraft)
local Tstop=Tstart+dt*(naircraft-1)
SCHEDULER:New(nil, self._SpawnWithRoute, {self}, Tstart, dt, 0.1, Tstop)
- -- Status report scheduler.
- if self.reportstatus then
- SCHEDULER:New(nil, self.Status, {self}, self.statusinterval, 30)
- end
+ -- Status check and report scheduler.
+ SCHEDULER:New(nil, self.Status, {self}, Tstart+1, self.statusinterval)
+
+ -- Handle events.
+ self:HandleEvent(EVENTS.Birth, self._OnBirthDay)
+ self:HandleEvent(EVENTS.EngineStartup, self._EngineStartup)
+ self:HandleEvent(EVENTS.Takeoff, self._OnTakeoff)
+ self:HandleEvent(EVENTS.Land, self._OnLand)
+ self:HandleEvent(EVENTS.EngineShutdown, self._OnEngineShutdown)
+ self:HandleEvent(EVENTS.Dead, self._OnDead)
+ self:HandleEvent(EVENTS.Crash, self._OnCrash)
+ -- TODO: add hit event?
end
@@ -236,6 +267,7 @@ function RAT:SetTakeoff(type)
-- All possible types for random selection.
local types={"takeoff-cold", "takeoff-hot", "air"}
+ --TODO: Need to get rid of the string comparisons and introduce enumerators.
local _Type
if type:lower()=="takeoff-cold" or type:lower()=="cold" then
_Type="takeoff-cold"
@@ -323,6 +355,22 @@ function RAT:SetDestination(names)
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
+end
+
+--- Aircraft will commute between their departure and destination airports. This is not possible with takeoff in air.
+-- @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
+end
+
--- Set the delay before first group is spawned. Minimum delay is 0.5 seconds.
-- @param #RAT self
-- @param #number delay Delay in seconds.
@@ -502,9 +550,10 @@ function RAT:_InitAircraft(DCSgroup)
self.aircraft.FLcruise=005*RAT.unit.FL2m
end
- -- send debug message
+ -- info message
local text=string.format("\n******************************************************\n")
text=text..string.format("Aircraft parameters:\n")
+ text=text..string.format("Template group = %s\n", self.SpawnTemplatePrefix)
text=text..string.format("Category = %s\n", self.category)
text=text..string.format("Type = %s\n", self.aircraft.type)
text=text..string.format("Max air speed = %6.1f m/s\n", self.aircraft.Vmax)
@@ -527,16 +576,19 @@ end
-- Sets ROE/ROT.
-- Initializes the ratcraft array and event handlers.
-- @param #RAT self
-function RAT:_SpawnWithRoute()
+-- @param Wrapper.Airport#AIRBASE _departure (Optional) Departure airbase.
+-- @param Wrapper.Airport#AIRBASE _destination (Optional) Destination airbase.
+function RAT:_SpawnWithRoute(_departure, _destination)
-- Set flight plan.
- local departure, destination, waypoints = self:_SetRoute()
+ local departure, destination, waypoints = self:_SetRoute(_departure, _destination)
-- Modify the spawn template to follow the flight plan.
self:_ModifySpawnTemplate(waypoints)
-- Actually spawn the group.
local group=self:SpawnWithIndex(self.SpawnIndex) -- Wrapper.Group#GROUP
+ self.alive=self.alive+1
-- set ROE to "weapon hold" and ROT to "no reaction"
self:_SetROE(group)
@@ -549,35 +601,59 @@ function RAT:_SpawnWithRoute()
self.ratcraft[self.SpawnIndex]["departure"]=departure
self.ratcraft[self.SpawnIndex]["waypoints"]=waypoints
self.ratcraft[self.SpawnIndex]["status"]="spawned"
- self.ratcraft[self.SpawnIndex]["airborn"]=false
+ self.ratcraft[self.SpawnIndex]["airborn"]=group:InAir()
+ if group:InAir() then
+ self.ratcraft[self.SpawnIndex]["Tground"]=nil
+ self.ratcraft[self.SpawnIndex]["Pground"]=nil
+ else
+ self.ratcraft[self.SpawnIndex]["Tground"]=timer.getTime()
+ self.ratcraft[self.SpawnIndex]["Pground"]=group:GetCoordinate()
+ end
+ self.ratcraft[self.SpawnIndex]["P0"]=group:GetCoordinate()
+ self.ratcraft[self.SpawnIndex]["Pnow"]=group:GetCoordinate()
+ self.ratcraft[self.SpawnIndex]["Distance"]=0
- -- Handle events.
- self:HandleEvent(EVENTS.Birth, self._OnBirthDay)
- self:HandleEvent(EVENTS.EngineStartup, self._EngineStartup)
- self:HandleEvent(EVENTS.Takeoff, self._OnTakeoff)
- self:HandleEvent(EVENTS.Land, self._OnLand)
- self:HandleEvent(EVENTS.EngineShutdown, self._OnEngineShutdown)
- self:HandleEvent(EVENTS.Dead, self._OnDead)
- -- TODO: Crash needs to be handled better. Does it always occur when dead?
- --self:HandleEvent(EVENTS.Crash, self._OnCrash)
- -- TODO: add hit event?
end
+--- Respawn a group.
+-- @param #RAT self
+-- @param Wrapper.Group#GROUP group Group to be repawned.
+function RAT:_Respawn(group)
+
+ -- Get the spawn index from group
+ local index=self:GetSpawnIndexFromGroup(group)
+
+ -- Get departure and destination from previous journey.
+ local departure=self.ratcraft[index].departure
+ local destination=self.ratcraft[index].destination
+
+ local _departure=nil
+ local _destination=nil
+
+ if self.continuejourney then
+ -- We continue our journey from the old departure airport.
+ _departure=destination:GetName()
+ elseif self.commute then
+ -- We commute between departure and destination.
+ _departure=destination:GetName()
+ _destination=departure:GetName()
+ end
+
+ -- Spawn new group after 90 seconds.
+ --self:_SpawnWithRoute(_departure, _destination)
+ SCHEDULER:New(nil, self._SpawnWithRoute, {self, _departure, _destination}, 90)
+
+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 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.
-function RAT:_SetRoute()
-
- -- unit conversions
- local ft2meter=0.305
- local kmh2ms=0.278
- local FL2m=30.48
- local nm2km=1.852
-
+function RAT:_SetRoute(_departure, _destination)
-- Min cruise speed 70% of Vmax or 600 km/h whichever is lower.
local VxCruiseMin = math.min(self.aircraft.Vmax*0.70, 166)
@@ -591,14 +667,14 @@ function RAT:_SetRoute()
-- Climb speed 90% ov Vmax but max 720 km/h.
local VxClimb = math.min(self.aircraft.Vmax*0.90, 200)
- -- Descent speed 50% of Vmax but max 400 km/h.
- local VxDescent = math.min(self.aircraft.Vmax*0.50, 111)
+ -- Descent speed 60% of Vmax but max 500 km/h.
+ local VxDescent = math.min(self.aircraft.Vmax*0.60, 140)
- local VxHolding = VxDescent*0.8
- local VxFinal = VxHolding*0.8
+ local VxHolding = VxDescent*0.9
+ local VxFinal = VxHolding*0.9
-- Reasonably civil climb speed Vy=1500 ft/min but max aircraft specific climb rate.
- local VyClimb=math.min(self.Vclimb*ft2meter/60, self.aircraft.Vymax)
+ local VyClimb=math.min(self.Vclimb*RAT.unit.ft2meter/60, self.aircraft.Vymax)
-- Climb angle in rad.
local AlphaClimb=math.asin(VyClimb/VxClimb)
@@ -609,7 +685,12 @@ function RAT:_SetRoute()
-- DEPARTURE AIRPORT
-- Departure airport or zone.
- local departure=self:_SetDeparture()
+ local departure
+ if _departure then
+ departure=AIRBASE:FindByName(_departure)
+ else
+ departure=self:_SetDeparture()
+ end
-- Coordinates of departure point.
local Pdeparture
@@ -638,15 +719,20 @@ function RAT:_SetRoute()
end
-- DESTINATION AIRPORT
- -- Get all destination airports within reach and at least a bit away from departure.
- self:_GetDestinations(Pdeparture, self.mindist, math.min(self.aircraft.Reff, self.maxdist))
+ local destination
+ if _destination then
+ destination=AIRBASE:FindByName(_destination)
+ else
+ -- Get all destination airports within reach.
+ self:_GetDestinations(Pdeparture, self.mindist, math.min(self.aircraft.Reff, self.maxdist))
- -- Pick a destination airport.
- local destination=self:_SetDestination()
+ -- Pick a destination airport.
+ destination=self:_SetDestination()
+ end
-- Check that departure and destination are not the same. Should not happen due to mindist.
if destination:GetName()==departure:GetName() then
- local text="Destination and departure airport are identical: "..destination:GetName().." with ID "..destination:GetID()
+ local text=string.format("%s: Destination and departure airport are identical. Airport %s (ID %d).", self.SpawnTemplatePrefix, destination:GetName(), destination:GetID())
MESSAGE:New(text, 120):ToAll()
env.error(myid..text)
end
@@ -674,9 +760,9 @@ function RAT:_SetRoute()
-- Holding point altitude. For planes between 1600 and 2400 m AGL. For helos 160 to 240 m AGL.
local h_holding
if self.category=="plane" then
- h_holding=2000
+ h_holding=1500
else
- h_holding=200
+ h_holding=150
end
h_holding=self:_Randomize(h_holding, 0.2)
@@ -750,6 +836,7 @@ function RAT:_SetRoute()
-- debug message
local text=string.format("\n******************************************************\n")
+ text=text..string.format("Template = %s\n\n", self.SpawnTemplatePrefix)
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)
@@ -784,7 +871,9 @@ function RAT:_SetRoute()
env.info(myid..text)
-- Ensure that cruise distance is positve. Can be slightly negative in special cases. And we don't want to turn back.
- d_cruise=math.abs(d_cruise)
+ if d_cruise<0 then
+ 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
@@ -850,8 +939,6 @@ function RAT:_SetDeparture()
-- Put all specified zones in table.
for _,name in pairs(self.departure_zones) do
- self:E(self.departure_zones)
- env.info(myid.."Zone name: "..name)
table.insert(departures, ZONE:New(name))
end
-- Put all specified airport zones in table.
@@ -890,7 +977,9 @@ function RAT:_SetDeparture()
text="Chosen departure airport: "..departure:GetName().." (ID "..departure:GetID()..")"
end
env.info(myid..text)
- MESSAGE:New(text, 30):ToAll()
+ if self.debug then
+ MESSAGE:New(text, 30):ToAll()
+ end
return departure
end
@@ -923,9 +1012,15 @@ function RAT:_SetDestination()
-- Randomly select one possible destination.
local destination=destinations[math.random(#destinations)] -- Wrapper.Airbase#AIRBASE
- local text="Chosen destination airport: "..destination:GetName().." (ID "..destination:GetID()..")"
- env.info(myid..text)
- MESSAGE:New(text, 30):ToAll()
+ if destination then
+ local text="Chosen destination airport: "..destination:GetName().." (ID "..destination:GetID()..")"
+ env.info(myid..text)
+ if self.debug then
+ MESSAGE:New(text, 30):ToAll()
+ end
+ else
+ env.error(myid.."No destination airport found.")
+ end
return destination
end
@@ -995,9 +1090,11 @@ function RAT:_GetAirportsOfMap()
local _p=airbase:getPosition().p
local _name=airbase:getName()
local _myab=AIRBASE:FindByName(_name)
- local text="Airport ID = ".._myab:GetID().." and Name = ".._myab:GetName()..", Category = ".._myab:GetCategory()..", TypeName = ".._myab:GetTypeName()
- env.info(myid..text)
table.insert(self.airports_map, _myab)
+ local text="Airport ID = ".._myab:GetID().." and Name = ".._myab:GetName()..", Category = ".._myab:GetCategory()..", TypeName = ".._myab:GetTypeName()
+ if self.debug then
+ env.info(myid..text)
+ end
end
end
@@ -1023,7 +1120,7 @@ function RAT:_GetAirportsOfCoalition()
if #self.airports==0 then
local text="No possible departure/destination airports found!"
- MESSAGE:New(text, 180):ToAll()
+ MESSAGE:New(text, 60):ToAll()
env.error(myid..text)
end
end
@@ -1032,39 +1129,102 @@ end
--- Report status of RAT groups.
-- @param #RAT self
-function RAT:Status()
+function RAT:Status(message)
+ message=message or false
+
+ -- number of ratcraft spawned.
local ngroups=#self.ratcraft
- local text=string.format("Groups spawned = %i\n", ngroups)
- text=text..string.format("Spawn Template: %s\n", self.SpawnTemplatePrefix)
- text=text..string.format("Spawn Index = %d\n", self.SpawnIndex)
- text=text..string.format("Spawn Count = %d\n", self.SpawnCount)
- text=text..string.format("Spawn Alive = %d", self.AliveUnits)
- MESSAGE:New(text, 60):ToAll()
+ local text=string.format("Spawned %i groups of template %s.\n", ngroups, self.SpawnTemplatePrefix)
+ if self.debug then
+ env.info(myid..text)
+ end
+ text=string.format("Alive groups of template %s: %d", self.SpawnTemplatePrefix, self.alive)
+ env.info(myid..text)
for i=1, ngroups do
- --local group=self.SpawnGroups[i].Group
if self.ratcraft[i].group then
- local group=self.ratcraft[i].group --Wrapper.Group#GROUP
- local prefix=self:_GetPrefixFromGroup(group)
- local life=self:_GetLife(group)
- local fuel=group:GetFuel()
- local airborn=group:InAir()
-
- local text=string.format("Group %s ID %i:\n", prefix, i)
- text=text..string.format("Life = %3.0f\n", life)
- text=text..string.format("Fuel = %3.0f\n", fuel)
- text=text..string.format("Status = %s\n", self.ratcraft[i].status)
- text=text..string.format("Status = %s\n", tostring(airborn))
- text=text..string.format("Flying from %s to %s.",self.ratcraft[i].departure:GetName(), self.ratcraft[i].destination:GetName())
- MESSAGE:New(text, 60):ToAll()
- env.info(myid..text)
+ if self.ratcraft[i].group:IsAlive() then
+ local group=self.ratcraft[i].group --Wrapper.Group#GROUP
+ local prefix=self:_GetPrefixFromGroup(group)
+ local life=self:_GetLife(group)
+ local fuel=group:GetFuel()*100.0
+ local airborn=group:InAir()
+
+ -- Monitor time and distance on ground.
+ local Tg=0
+ local Dg=0
+ if airborn then
+ -- Aircraft is airborn.
+ self.ratcraft[i]["Tground"]=nil
+ self.ratcraft[i]["Pground"]=nil
+ else
+ --Aircraft is on ground.
+ if self.ratcraft[i]["Tground"] then
+ -- Aircraft was already on ground at last check. Calculate the time on ground.
+ Tg=timer.getTime()-self.ratcraft[i]["Tground"]
+ -- Distance on ground since first noticed aircraft is on ground.
+ Dg=group:GetCoordinate():Get2DDistance(self.ratcraft[i]["Pground"])
+ else
+ -- First time we see that aircraft is on ground. Initialize the time and position.
+ self.ratcraft[i]["Tground"]=timer.getTime()
+ self.ratcraft[i]["Pground"]=group:GetCoordinate()
+ end
+ end
+
+ -- Monitor travelled distance since last check.
+ local Pn=group:GetCoordinate()
+ local Dtravel=Pn:Get2DDistance(self.ratcraft[i]["Pnow"])
+ self.ratcraft[i]["Pnow"]=Pn
+
+ -- Add up the travelled distance.
+ self.ratcraft[i]["Distance"]=self.ratcraft[i]["Distance"]+Dtravel
+
+ -- Distance remaining to destiantion.
+ local Ddestination=Pn:Get2DDistance(self.ratcraft[i].destination:GetCoordinate())
+
+ -- Status report.
+ local text=string.format("Group %s (ID %i) of %s\n", prefix, i, self.aircraft.type)
+ text=text..string.format("Flying from %s to %s.\n",self.ratcraft[i].departure:GetName(), self.ratcraft[i].destination:GetName())
+ text=text..string.format("Status = %s (airborn %s)\n", self.ratcraft[i].status, tostring(airborn))
+ text=text..string.format("Fuel = %3.0f, life = %3.0f\n", fuel, life)
+ 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)
+ if not airborn then
+ text=text..string.format("\nTime on ground = %6.0f seconds\n", Tg)
+ text=text..string.format("Position change = %6.1f m", Dg)
+ end
+ if self.debug then
+ env.info(myid..text)
+ end
+ if self.reportstatus or message then
+ MESSAGE:New(text, 20):ToAll()
+ end
+
+ -- Despawn groups if they are on ground and don't move or are damaged.
+ if not airborn then
+
+ -- Despawn unit if it did not move more then 50 m in the last 120 seconds.
+ if Tg>120 and Dg<50 then
+ local text=string.format("Group %s is despawned after being %4.0f seconds inaktive on ground.", self.SpawnTemplatePrefix, Tg)
+ env.info(myid..text)
+ self:_Despawn(group)
+ end
+
+ if life<10 and Dtravel<100 then
+ local text=string.format("Damaged group %s is despawned. Life = %3.0f", self.SpawnTemplatePrefix, life)
+ self:_Despawn(group)
+ end
+
+ end
+ end
else
- local text=string.format("Group %i does not exist.", i)
- MESSAGE:New(text, 60):ToAll()
- env.info(myid..text)
+ if self.debug then
+ local text=string.format("Group %i does not exist.", i)
+ env.info(myid..text)
+ end
end
end
end
@@ -1080,10 +1240,14 @@ function RAT:_GetLife(group)
if unit then
life=unit:GetLife()/unit:GetLife0()*100
else
- env.error(myid.."Unit does not exist in RAT_Getlife(). Returning zero.")
+ if self.debug then
+ env.error(myid.."Unit does not exist in RAT_Getlife(). Returning zero.")
+ end
end
else
- env.error(myid.."Group does not exist in RAT_Getlife(). Returning zero.")
+ if self.debug then
+ env.error(myid.."Group does not exist in RAT_Getlife(). Returning zero.")
+ end
end
return life
end
@@ -1102,19 +1266,28 @@ end
-- @param #RAT self
function RAT:_OnBirthDay(EventData)
- local SpawnGroup = EventData.IniGroup
+ local SpawnGroup = EventData.IniGroup --Wrapper.Group#GROUP
if SpawnGroup then
- local index=self:GetSpawnIndexFromGroup(SpawnGroup)
+ -- Get the template name of the group. This can be nil if this was not a spawned group.
local EventPrefix = self:_GetPrefixFromGroup(SpawnGroup)
- local text="Event: Group "..SpawnGroup:GetName().." was born."
- env.info(myid..text)
- self:_SetStatus(SpawnGroup, "starting engines (after birth)")
+ if EventPrefix then
+ -- Check that the template name actually belongs to this object.
+ if EventPrefix == self.SpawnTemplatePrefix then
+
+ local text="Event: Group "..SpawnGroup:GetName().." was born."
+ env.info(myid..text)
+ self:_SetStatus(SpawnGroup, "Starting engines (after birth)")
+
+ end
+ end
else
- env.error("Group does not exist in RAT:_EngineStartup().")
+ if self.debug then
+ env.error("Group does not exist in RAT:_OnBirthDay().")
+ end
end
end
@@ -1126,18 +1299,32 @@ function RAT:_EngineStartup(EventData)
if SpawnGroup then
- local text="Event: Group "..SpawnGroup:GetName().." started engines. Life="..self:_GetLife(SpawnGroup)
- env.info(myid..text)
+ -- Get the template name of the group. This can be nil if this was not a spawned group.
+ local EventPrefix = self:_GetPrefixFromGroup(SpawnGroup)
- local status
- if SpawnGroup:InAir() then
- status="airborn"
- else
- status="taxi (after engines started)"
+ if EventPrefix then
+
+ -- Check that the template name actually belongs to this object.
+ if EventPrefix == self.SpawnTemplatePrefix then
+
+ local text="Event: Group "..SpawnGroup:GetName().." started engines. Life="..self:_GetLife(SpawnGroup)
+ env.info(myid..text)
+
+ local status
+ if SpawnGroup:InAir() then
+ status="airborn"
+ else
+ status="Taxiing (after engines started)"
+ end
+ self:_SetStatus(SpawnGroup, status)
+
+ end
end
- self:_SetStatus(SpawnGroup, status)
+
else
- env.error("Group does not exist in RAT:_EngineStartup().")
+ if self.debug then
+ env.error("Group does not exist in RAT:_EngineStartup().")
+ end
end
end
@@ -1149,12 +1336,27 @@ function RAT:_OnTakeoff(EventData)
if SpawnGroup then
- local text="Event: Group "..SpawnGroup:GetName().." is airborn. Life="..self:_GetLife(SpawnGroup)
- env.info(myid..text)
+ -- Get the template name of the group. This can be nil if this was not a spawned group.
+ local EventPrefix = self:_GetPrefixFromGroup(SpawnGroup)
+
+ if EventPrefix then
+
+ -- Check that the template name actually belongs to this object.
+ if EventPrefix == self.SpawnTemplatePrefix then
+
+ local text="Event: Group "..SpawnGroup:GetName().." is airborn. Life="..self:_GetLife(SpawnGroup)
+ env.info(myid..text)
+
+ -- Set status.
+ self:_SetStatus(SpawnGroup, "On journey (after takeoff)")
+
+ end
+ end
- self:_SetStatus(SpawnGroup, "airborn (after takeoff)")
else
- env.error("Group does not exist in RAT:_OnTakeoff().")
+ if self.debug then
+ env.error("Group does not exist in RAT:_OnTakeoff().")
+ end
end
end
@@ -1166,20 +1368,33 @@ function RAT:_OnLand(EventData)
if SpawnGroup then
- local text="Event: Group "..SpawnGroup:GetName().." landed. Life="..self:_GetLife(SpawnGroup)
- env.info(myid..text)
+ -- Get the template name of the group. This can be nil if this was not a spawned group.
+ local EventPrefix = self:_GetPrefixFromGroup(SpawnGroup)
- -- Set status.
- self:_SetStatus(SpawnGroup, "taxi (after landing)")
+ if EventPrefix then
- -- Spawn new group.
- self:_SpawnWithRoute()
+ -- Check that the template name actually belongs to this object.
+ if EventPrefix == self.SpawnTemplatePrefix then
+
+ local text="Event: Group "..SpawnGroup:GetName().." landed. Life="..self:_GetLife(SpawnGroup)
+ env.info(myid..text)
- text="Event: Group "..SpawnGroup:GetName().." will be respawned."
- env.info(myid..text)
+ -- Set status.
+ self:_SetStatus(SpawnGroup, "Taxiing (after landing)")
+
+ text="Event: Group "..SpawnGroup:GetName().." will be respawned."
+ env.info(myid..text)
+
+ -- Respawn group.
+ self:_Respawn(SpawnGroup)
+
+ end
+ end
else
- env.error("Group does not exist in RAT:_OnLand().")
+ if self.debug then
+ env.error("Group does not exist in RAT:_OnLand().")
+ end
end
end
@@ -1191,18 +1406,33 @@ function RAT:_OnEngineShutdown(EventData)
if SpawnGroup then
- local text="Event: Group "..SpawnGroup:GetName().." shut down its engines. Life="..self:_GetLife(SpawnGroup)
- env.info(myid..text)
+ -- Get the template name of the group. This can be nil if this was not a spawned group.
+ local EventPrefix = self:_GetPrefixFromGroup(SpawnGroup)
- -- Set status.
- self:_SetStatus(SpawnGroup, "parking (after engine shut down)")
+ if EventPrefix then
- text="Event: Group "..SpawnGroup:GetName().." will be destroyed now."
- env.info(myid..text)
+ -- Check that the template name actually belongs to this object.
+ if EventPrefix == self.SpawnTemplatePrefix then
+
+ local text="Event: Group "..SpawnGroup:GetName().." shut down its engines. Life="..self:_GetLife(SpawnGroup)
+ env.info(myid..text)
+
+ -- Set status.
+ self:_SetStatus(SpawnGroup, "Parking (shutting down engines)")
+
+ text="Event: Group "..SpawnGroup:GetName().." will be destroyed now."
+ env.info(myid..text)
- self:_Despawn(SpawnGroup)
+ -- Despawn group.
+ self:_Despawn(SpawnGroup)
+
+ end
+ end
+
else
- env.error("Group does not exist in RAT:_OnEngineShutdown().")
+ if self.debug then
+ env.error("Group does not exist in RAT:_OnEngineShutdown().")
+ end
end
end
@@ -1212,18 +1442,29 @@ function RAT:_OnDead(EventData)
local SpawnGroup = EventData.IniGroup --Wrapper.Group#GROUP
- env.info("In function _OnDead()")
-
if SpawnGroup then
- local text="Event: Group "..SpawnGroup:GetName().." died."
- env.info(myid..text)
+ -- Get the template name of the group. This can be nil if this was not a spawned group.
+ local EventPrefix = self:_GetPrefixFromGroup(SpawnGroup)
- -- Set status.
- self:_SetStatus(SpawnGroup, "destroyed (after dead)")
+ if EventPrefix then
+
+ -- Check that the template name actually belongs to this object.
+ if EventPrefix == self.SpawnTemplatePrefix then
+
+ local text="Event: Group "..SpawnGroup:GetName().." died."
+ env.info(myid..text)
+
+ -- Set status.
+ self:_SetStatus(SpawnGroup, "Destroyed (after dead)")
+
+ end
+ end
else
- env.error("Group does not exist in RAT:_OnDead().")
+ if self.debug then
+ env.error("Group does not exist in RAT:_OnDead().")
+ end
end
end
@@ -1234,30 +1475,47 @@ function RAT:_OnCrash(EventData)
local SpawnGroup = EventData.IniGroup --Wrapper.Group#GROUP
if SpawnGroup then
+
+ -- Get the template name of the group. This can be nil if this was not a spawned group.
+ local EventPrefix = self:_GetPrefixFromGroup(SpawnGroup)
+
+ if EventPrefix then
+
+ -- Check that the template name actually belongs to this object.
+ if EventPrefix == self.SpawnTemplatePrefix then
- local text="Event: Group "..SpawnGroup:GetName().." crashed."
- env.info(myid..text)
+ local text="Event: Group "..SpawnGroup:GetName().." crashed."
+ env.info(myid..text)
- -- Set status.
- self:_SetStatus(SpawnGroup, "crashed")
+ -- Set status.
+ self:_SetStatus(SpawnGroup, "Crashed")
+
+ --TODO: Maybe spawn some people at the crash site and send a distress call.
+ -- And define them as cargo which can be rescued.
+ end
+ end
- --TODO: Maybe spawn some people at the crash site and send a distress call.
- -- And define them as cargo which can be rescued.
else
- env.error("Group does not exist in RAT:_OnCrash().")
+ if self.debug then
+ env.error("Group does not exist in RAT:_OnCrash().")
+ end
end
end
---- Function is executed when a unit crashes.
+--- Despawn unit. Unit gets destoyed and group is set to nil.
+-- Index of ratcraft array is taken from spawned group name.
-- @param #RAT self
+-- @param Wrapper.Group#GROUP group Group to be despawned.
function RAT:_Despawn(group)
local index=self:GetSpawnIndexFromGroup(group)
- env.info(myid.."Number of ratcraft before = "..#self.ratcraft)
self.ratcraft[index].group:Destroy()
self.ratcraft[index].group=nil
- --table.remove(self.ratcraft, index)
- env.info(myid.."Number of ratcraft after = "..#self.ratcraft)
+
+ -- Decreas group alive counter.
+ self.alive=self.alive+1
+
+ --TODO: Maybe here could be some more arrays deleted?
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -1276,7 +1534,6 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport)
local _Altitude=Altitude or Coord.y
--TODO: _Type should be generalized to Grouptemplate.Type
- --TODO: Only use _alttype="BARO" and add landheight for _alttype="RADIO". Don't know if "RADIO" really works well.
-- Land height at given coordinate.
local Hland=Coord:GetLandHeight()
@@ -1287,67 +1544,6 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport)
local _alttype="RADIO"
local _AID=nil
- local case=2
-
-if case==1 then
- if Type:lower()=="takeoff-cold" or Type:lower()=="cold" then
- -- take-off with engine off
- _Type="TakeOffParking"
- _Action="From Parking Area"
- _Altitude = 2
- _alttype="RADIO"
- _AID = Airport:GetID()
- elseif Type:lower()=="takeoff-hot" or Type:lower()=="hot" then
- -- take-off with engine on
- _Type="TakeOffParkingHot"
- _Action="From Parking Area"
- _Altitude = 2
- _alttype="RADIO"
- _AID = Airport:GetID()
- local ab=Airbase.getByName("Farp")
- local p=ab:getPoint()
- --self:_SetMarker("Farp Position", p)
- self.E(ab:getDesc())
- _AID = 1
- elseif Type:lower()=="takeoff-runway" or Type:lower()=="runway" then
- -- take-off from runway
- _Type="TakeOff"
- _Action="From Parking Area"
- _Altitude = 2
- _alttype="RADIO"
- _AID = Airport:GetID()
- elseif Type:lower()=="air" then
- -- air start
- _Type="Turning Point"
- _Action="Turning Point"
- _alttype="BARO"
- elseif Type:lower()=="climb" or Type:lower()=="cruise" then
- _Type="Turning Point"
- _Action="Turning Point"
- _alttype="BARO"
- elseif Type:lower()=="descent" then
- _Type="Turning Point"
- _Action="Fly Over Point"
- _alttype="RADIO"
- elseif Type:lower()=="holding" then
- _Type="Turning Point"
- _Action="Fly Over Point"
- _alttype="RADIO"
- elseif Type:lower()=="landing" or Type:lower()=="land" then
- _Type="Land"
- _Action="Landing"
- _Altitude = 2
- _alttype="RADIO"
- _AID = Airport:GetID()
- else
- env.error("Unknown waypoint type in RAT:Waypoint function!")
- _Type="Turning Point"
- _Action="Turning Point"
- _alttype="RADIO"
- end
-
-else
-
if Type:lower()=="takeoff-cold" or Type:lower()=="cold" then
-- take-off with engine off
_Type="TakeOffParking"
@@ -1375,7 +1571,7 @@ else
_Action="Turning Point"
_alttype="BARO"
elseif Type:lower()=="climb" then
- _Type="Turning Point"
+ _Type="Turning Point"
_Action="Turning Point"
--_Action="Fly Over Point"
_alttype="BARO"
@@ -1407,10 +1603,9 @@ else
_alttype="RADIO"
end
-end
-
-- some debug info about input parameters
local text=string.format("\n******************************************************\n")
+ text=text..string.format("Template = %s\n", self.SpawnTemplatePrefix)
text=text..string.format("Type: %s - %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)
@@ -1427,7 +1622,9 @@ end
text=text..string.format("No airport/zone specified\n")
end
text=text.."******************************************************\n"
- env.info(myid..text)
+ if self.debug then
+ env.info(myid..text)
+ end
-- define waypoint
local RoutePoint = {}
@@ -1444,7 +1641,7 @@ end
RoutePoint.speed = Speed
RoutePoint.speed_locked = true
-- ETA (not used)
- RoutePoint.ETA=0
+ RoutePoint.ETA=nil
RoutePoint.ETA_locked = false
-- waypoint name (only for the mission editor)
RoutePoint.name="RAT waypoint"
@@ -1483,6 +1680,7 @@ end
function RAT:_Routeinfo(waypoints, comment)
local text=string.format("\n******************************************************\n")
+ text=text..string.format("Template = %s\n", self.SpawnTemplatePrefix)
if comment then
text=text..comment.."\n"
end
@@ -1508,8 +1706,11 @@ function RAT:_Routeinfo(waypoints, comment)
end
text=text..string.format("Total distance = %6.1f km\n", total/1000)
local text=string.format("******************************************************\n")
+
-- send message
- env.info(myid..text)
+ if self.debug then
+ env.info(myid..text)
+ end
-- return total route length in meters
return total
@@ -1527,9 +1728,8 @@ function RAT:_ModifySpawnTemplate(waypoints)
-- 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
+ -- Heading from first to seconds waypoints to align units in case of air start.
local heading = self:_Course(waypoints[1], waypoints[2])
- env.info(myid.."Heading wp1->wp2: "..heading)
if self:_GetSpawnIndex(self.SpawnIndex+1) then
@@ -1537,7 +1737,7 @@ function RAT:_ModifySpawnTemplate(waypoints)
local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate
if SpawnTemplate then
- self:E(SpawnTemplate)
+ self:T(SpawnTemplate)
-- Translate the position of the Group Template to the Vec3.
for UnitID = 1, #SpawnTemplate.units do
@@ -1552,7 +1752,6 @@ function RAT:_ModifySpawnTemplate(waypoints)
SpawnTemplate.units[UnitID].x = TX
SpawnTemplate.units[UnitID].y = TY
SpawnTemplate.units[UnitID].alt = PointVec3.y
- --TODO: Somehow this does not work. Initial heading of the units for air start is not equal to heading specified here.
SpawnTemplate.units[UnitID].heading = math.rad(heading)
self:T('After Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y)
end
@@ -1569,7 +1768,7 @@ function RAT:_ModifySpawnTemplate(waypoints)
-- Update modified template for spawn group.
self.SpawnGroups[self.SpawnIndex].SpawnTemplate=SpawnTemplate
- self:E(SpawnTemplate)
+ self:T(SpawnTemplate)
end
end
end
@@ -1586,8 +1785,8 @@ function RAT:_TaskHolding(P1, Altitude, Speed)
local LandHeight = land.getHeight(P1)
--TODO: randomize P1
- -- Second point is 10 km north of P1 and 200 m for helos.
- local dx=10000
+ -- Second point is 3 km north of P1 and 200 m for helos.
+ local dx=3000
local dy=0
if self.category=="heli" then
dx=200
@@ -1637,14 +1836,18 @@ function RAT:_FLmax(alpha, beta, d, phi, h0)
local gamma=math.rad(180)-alpha-beta
local a=d*math.sin(alpha)/math.sin(gamma)
local b=d*math.sin(beta)/math.sin(gamma)
+ -- h1 and h2 should be equal.
local h1=b*math.sin(alpha)
local h2=a*math.sin(beta)
+ -- We also take the slope between departure and destination into account.
local h3=b*math.cos(math.pi/2-(alpha+phi))
- -- h1 and h2 should be equal.
+ -- Debug message.
local text=string.format("\nFLmax = FL%3.0f = %6.1f m.\n", h1/RAT.unit.FL2m, h1)
text=text..string.format( "FLmax = FL%3.0f = %6.1f m.\n", h2/RAT.unit.FL2m, h2)
text=text..string.format( "FLmax = FL%3.0f = %6.1f m.", h3/RAT.unit.FL2m, h3)
- env.info(myid..text)
+ if self.debug then
+ env.info(myid..text)
+ end
return h3+h0
end
@@ -1718,17 +1921,6 @@ function RAT:_SetCoalitionTable()
self:T({"Coalition table: ", self.ctable})
end
-
---- Convert 3D waypoint to 3D coordinate. x==>x, alt==>y, y==>z
--- @param #RAT self
--- @param #table wp Containing .x, .y and .alt
--- @return Core.Point#COORDINATE Coordinates of the waypoint.
-function RAT:_WP2COORD(wp)
- local _coord = COORDINATE:New(wp.x, wp.alt, wp.y) -- Core.Point#COORDINATE
- return _coord
-end
-
-
---Determine the heading from point a to point b.
--@param #RAT self
--@param Core.Point#COORDINATE a Point from.
@@ -1784,8 +1976,10 @@ function RAT:_Randomize(value, fac, lower, upper)
local r=math.random(min, max)
-- debug info
- local text=string.format("Random: value = %6.2f, fac = %4.2f, min = %6.2f, max = %6.2f, r = %6.2f", value, fac, min, max, r)
- env.info(myid..text)
+ if self.debug then
+ local text=string.format("Random: value = %6.2f, fac = %4.2f, min = %6.2f, max = %6.2f, r = %6.2f", value, fac, min, max, r)
+ env.info(myid..text)
+ end
return r
end
@@ -1797,32 +1991,10 @@ end
-- @param Core.Point#COORDINATE vec3 Position of marker.
function RAT:_SetMarker(text, vec3)
RAT.markerid=RAT.markerid+1
- env.info(myid.."Placing marker with ID "..RAT.markerid..": "..text)
+ if self.debug then
+ env.info(myid.."Placing marker with ID "..RAT.markerid..": "..text)
+ end
trigger.action.markToAll(RAT.markerid, text, vec3)
end
---[[
---- @type RATPORT
--- @extends Wrapper.Positionable#POSITIONABLE
-
---- #RATPORT class, extends @{Positionable#POSITIONABLE}
--- @field #RATPORT RATPORT
-RATPORT={
- ClassName="RATPORT",
-}
-
---- Creates a new RATPORT object.
--- @param #RATPORT self
--- @param #string name Name of airport or zone.
--- @return #RATPORT self
-function RATPORT:New(name)
- local self = BASE:Inherit(self, POSITIONABLE:New(name)) -- #RATPORT
- return self
-end
-
---function RATCRAFT:New(name)
--- local self = BASE:Inherit(self, GROUP:New(name))
---end
-]]
-
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
\ No newline at end of file
From b490412f63b320b7fd7ce9c4da8a80a19001e769 Mon Sep 17 00:00:00 2001
From: funkyfranky
Date: Mon, 4 Sep 2017 16:43:51 +0200
Subject: [PATCH 15/28] F10 menu update (untested)
---
Moose Development/Moose/AI/AI_RAT.lua | 32 +++++++++++++++++++++------
1 file changed, 25 insertions(+), 7 deletions(-)
diff --git a/Moose Development/Moose/AI/AI_RAT.lua b/Moose Development/Moose/AI/AI_RAT.lua
index f069c3fc5..fe8993803 100644
--- a/Moose Development/Moose/AI/AI_RAT.lua
+++ b/Moose Development/Moose/AI/AI_RAT.lua
@@ -46,6 +46,7 @@
-- @field #boolean commute
-- @field #boolean continuejourney
-- @field #number alive
+-- @field #boolean f10menu
-- @field #table Menu
-- @field #table RAT
-- @extends Functional.Spawn#SPAWN
@@ -89,6 +90,7 @@ RAT={
commute=false, -- Aircraft commute between departure and destination, i.e. when respawned the departure airport becomes the new destiation.
continuejourney=false, -- Aircraft will continue their journey, i.e. get respawned at their destination with a new random destination.
alive=0, -- Number of groups which are alive.
+ f10menu=true, -- Add an F10 menu for RAT.
Menu={}, -- F10 menu for this RAT object.
}
@@ -142,6 +144,7 @@ myid="RAT | "
--DONE: Check that FARPS are not used as airbases for planes.
--DONE: Add special cases for ships (similar to FARPs).
--DONE: Add cases for helicopters.
+--TODO: Add F10 menu.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -179,13 +182,17 @@ function RAT:New(groupname)
self:_GetAirportsOfMap()
-- Create F10 main menu if it does not exists yet.
- if not RAT.MenuF10 then
+ if not RAT.MenuF10 and self.f10menu then
RAT.MenuF10 = MENU_MISSION:New("RAT")
end
- -- Crate submenu
- self.Menu[self.SpawnTemplatePrefix]=MENU_MISSION:New(self.SpawnTemplatePrefix, RAT.MenuF10)
- MENU_MISSION_COMMAND:New("Status report", self.Menu[self.SpawnTemplatePrefix], self.Status, self, true)
+ -- Craete submenus.
+ if self.f10menu then
+ self.Menu[self.SpawnTemplatePrefix]=MENU_MISSION:New(self.SpawnTemplatePrefix, RAT.MenuF10)
+ self.Menu[self.SpawnTemplatePrefix]["groups"]=MENU_MISSION:New("Groups", self.Menu[self.SpawnTemplatePrefix])
+ MENU_MISSION_COMMAND:New("Status report", self.Menu[self.SpawnTemplatePrefix], self.Status, self, true)
+ MENU_MISSION_COMMAND:New("Spawn new group", self.Menu[self.SpawnTemplatePrefix], self._SpawnWithRoute, self)
+ end
return self
end
@@ -613,6 +620,12 @@ function RAT:_SpawnWithRoute(_departure, _destination)
self.ratcraft[self.SpawnIndex]["Pnow"]=group:GetCoordinate()
self.ratcraft[self.SpawnIndex]["Distance"]=0
+ -- Create submenu for this group.
+ if self.f10menu then
+ local name=self.aircraft.type.." ID "..tostring(self.SpawnIndex)
+ self.Menu[self.SpawnTemplatePrefix]["groups"][self.SpawnIndex]=MENU_MISSION:New(name, self.Menu[self.SpawnTemplatePrefix]["groups"])
+ end
+
end
--- Respawn a group.
@@ -760,7 +773,7 @@ function RAT:_SetRoute(_departure, _destination)
-- Holding point altitude. For planes between 1600 and 2400 m AGL. For helos 160 to 240 m AGL.
local h_holding
if self.category=="plane" then
- h_holding=1500
+ h_holding=1200
else
h_holding=150
end
@@ -1512,8 +1525,13 @@ function RAT:_Despawn(group)
self.ratcraft[index].group:Destroy()
self.ratcraft[index].group=nil
- -- Decreas group alive counter.
- self.alive=self.alive+1
+ -- Decrease group alive counter.
+ self.alive=self.alive-1
+
+ -- Remove submenu for this group.
+ if self.f10menu then
+ self.Menu[self.SpawnTemplatePrefix]["groups"][index]:Remove()
+ end
--TODO: Maybe here could be some more arrays deleted?
end
From 9fc00dd9c364cb85e582c34217fb47d07bfafce0 Mon Sep 17 00:00:00 2001
From: funkyfranky
Date: Tue, 5 Sep 2017 00:23:06 +0200
Subject: [PATCH 16/28] Some fixes and improvements.
Added some menu stuff.
Fixed things in journey case.
---
Moose Development/Moose/AI/AI_RAT.lua | 132 ++++++++++++++++++--------
1 file changed, 92 insertions(+), 40 deletions(-)
diff --git a/Moose Development/Moose/AI/AI_RAT.lua b/Moose Development/Moose/AI/AI_RAT.lua
index fe8993803..ed4fdac38 100644
--- a/Moose Development/Moose/AI/AI_RAT.lua
+++ b/Moose Development/Moose/AI/AI_RAT.lua
@@ -31,7 +31,6 @@
-- @field #number maxdist
-- @field #table airports_map
-- @field #table airports
--- @field #table airports_destination
-- @field #boolean random_departure
-- @field #boolean random_destination
-- @field #table departure_zones
@@ -75,7 +74,6 @@ RAT={
maxdist = 500000, -- 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.
- airports_destination={}, -- Possible destination airports which are in range of the chosen departure airport/zone.
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.
@@ -623,7 +621,8 @@ function RAT:_SpawnWithRoute(_departure, _destination)
-- Create submenu for this group.
if self.f10menu then
local name=self.aircraft.type.." ID "..tostring(self.SpawnIndex)
- self.Menu[self.SpawnTemplatePrefix]["groups"][self.SpawnIndex]=MENU_MISSION:New(name, self.Menu[self.SpawnTemplatePrefix]["groups"])
+ self.Menu[self.SpawnTemplatePrefix].groups[self.SpawnIndex]=MENU_MISSION:New(name, self.Menu[self.SpawnTemplatePrefix].groups)
+ MENU_MISSION_COMMAND:New("Status report", self.Menu[self.SpawnTemplatePrefix].groups[self.SpawnIndex], self.Status, self, true, self.SpawnIndex)
end
end
@@ -642,14 +641,20 @@ function RAT:_Respawn(group)
local _departure=nil
local _destination=nil
+
+ env.info(myid.."_Respawn: departure name = "..departure:GetName())
+ env.info(myid.."_Respawn: destination name = "..destination:GetName())
if self.continuejourney then
-- We continue our journey from the old departure airport.
_departure=destination:GetName()
+ env.info(myid.."_Respawn: journey, _departure = ".._departure)
elseif self.commute then
-- We commute between departure and destination.
_departure=destination:GetName()
_destination=departure:GetName()
+ env.info(myid.."_Respawn: commute, _departure = ".._departure)
+ env.info(myid.."_Respawn: commute, _destination = ".._destination)
end
-- Spawn new group after 90 seconds.
@@ -667,6 +672,9 @@ end
-- @return Wrapper.Airport#AIRBASE Destination airbase.
-- @return #table Table of flight plan waypoints.
function RAT:_SetRoute(_departure, _destination)
+
+ env.info(myid.."_SetRoute: commute, _departure = "..tostring(_departure))
+ env.info(myid.."_SetRoute: commute, _destination = "..tostring(_destination))
-- Min cruise speed 70% of Vmax or 600 km/h whichever is lower.
local VxCruiseMin = math.min(self.aircraft.Vmax*0.70, 166)
@@ -702,7 +710,7 @@ function RAT:_SetRoute(_departure, _destination)
if _departure then
departure=AIRBASE:FindByName(_departure)
else
- departure=self:_SetDeparture()
+ departure=self:_PickDeparture()
end
-- Coordinates of departure point.
@@ -737,10 +745,15 @@ function RAT:_SetRoute(_departure, _destination)
destination=AIRBASE:FindByName(_destination)
else
-- Get all destination airports within reach.
- self:_GetDestinations(Pdeparture, self.mindist, math.min(self.aircraft.Reff, self.maxdist))
+ local destinations=self:_GetDestinations(Pdeparture, self.mindist, math.min(self.aircraft.Reff, self.maxdist))
+
+ local random_destination=false
+ if self.continuejourney and _departure then
+ random_destination=true
+ end
-- Pick a destination airport.
- destination=self:_SetDestination()
+ destination=self:_PickDestination(destinations, random_destination)
end
-- Check that departure and destination are not the same. Should not happen due to mindist.
@@ -934,7 +947,7 @@ end
-- If takeoff style is set to "air", we use zones around the airports or the zones specified by user input.
-- @param #RAT self
-- @return Wrapper.Airbase#AIRBASE Departure airport if spawning at airport.
-function RAT:_SetDeparture()
+function RAT:_PickDeparture()
-- Array of possible departure airports or zones.
local departures={}
@@ -1000,21 +1013,26 @@ end
--- Set the destination airport of the AI. 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:_SetDestination()
-
- -- Array of possible destination airports.
- local destinations={}
-
- if self.random_destination then
+function RAT:_PickDestination(destinations, _random)
+ env.info(myid.."pickdestinations _random = "..tostring(_random))
+ self:E(destinations)
+--[[
+ if self.random_destination or _random then
-- All airports of friendly coalitons.
- for _,airport in pairs(self.airports_destination) do
+ for _,airport in pairs(destinations) do
table.insert(destinations, airport)
end
else
+]]
+ if not (self.random_destination or _random) then
+ destinations=nil
+ destinations={}
-- All airports specified by user.
for _,name in pairs(self.destination_ports) do
table.insert(destinations, AIRBASE:FindByName(name))
@@ -1046,10 +1064,15 @@ 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 Nil if no airports could be found.
function RAT:_GetDestinations(q, minrange, maxrange)
minrange=minrange or self.mindist
maxrange=maxrange or self.maxdist
+
+ -- Initialize array.
+ local destinations={}
-- loop over all friendly airports
for _,airport in pairs(self.airports) do
@@ -1057,12 +1080,13 @@ function RAT:_GetDestinations(q, minrange, maxrange)
local distance=q:Get2DDistance(p)
-- check if distance form departure to destination is within min/max range
if distance>=minrange and distance<=maxrange then
- table.insert(self.airports_destination, airport)
+ table.insert(destinations, airport)
+ env.info("Possible destination = "..airport:GetName())
end
end
- env.info(myid.."Number of possible destination airports = "..#self.airports_destination)
+ env.info(myid.."Number of possible destination airports = "..#destinations)
- if #self.airports_destination > 1 then
+ if #destinations > 1 then
--- Compare distance of destination airports.
-- @param Core.Point#COORDINATE a Coordinate of point a.
-- @param Core.Point#COORDINATE b Coordinate of point b.
@@ -1072,9 +1096,15 @@ function RAT:_GetDestinations(q, minrange, maxrange)
local qb=q:Get2DDistance(b:GetCoordinate())
return qa < qb
end
- table.sort(self.airports_destination, compare)
+ table.sort(destinations, compare)
+ else
+ env.error(myid.."No possible destination airports found!")
+ destinations=nil
end
+ -- Return table with destination airports.
+ return destinations
+
end
@@ -1142,9 +1172,12 @@ end
--- Report status of RAT groups.
-- @param #RAT self
-function RAT:Status(message)
+-- @param #boolean message (Optional) Send message if true.
+-- @param #number forID (Optional) Send message only for this ID.
+function RAT:Status(message, forID)
message=message or false
+ forID=forID or false
-- number of ratcraft spawned.
local ngroups=#self.ratcraft
@@ -1160,11 +1193,19 @@ function RAT:Status(message)
if self.ratcraft[i].group then
if self.ratcraft[i].group:IsAlive() then
+
+ -- Gather some information.
local group=self.ratcraft[i].group --Wrapper.Group#GROUP
local prefix=self:_GetPrefixFromGroup(group)
local life=self:_GetLife(group)
local fuel=group:GetFuel()*100.0
local airborn=group:InAir()
+ local coords=group:GetCoordinate()
+ --local alt=group:GetAltitude()
+ --local vel=group:GetVelocityKMH()
+ local departure=self.ratcraft[i].departure:GetName()
+ local destination=self.ratcraft[i].destination:GetName()
+ local type=self.aircraft.type
-- Monitor time and distance on ground.
local Tg=0
@@ -1179,43 +1220,52 @@ function RAT:Status(message)
-- Aircraft was already on ground at last check. Calculate the time on ground.
Tg=timer.getTime()-self.ratcraft[i]["Tground"]
-- Distance on ground since first noticed aircraft is on ground.
- Dg=group:GetCoordinate():Get2DDistance(self.ratcraft[i]["Pground"])
+ Dg=coords:Get2DDistance(self.ratcraft[i]["Pground"])
else
-- First time we see that aircraft is on ground. Initialize the time and position.
self.ratcraft[i]["Tground"]=timer.getTime()
- self.ratcraft[i]["Pground"]=group:GetCoordinate()
+ self.ratcraft[i]["Pground"]=coords
end
end
-- Monitor travelled distance since last check.
- local Pn=group:GetCoordinate()
+ local Pn=coords
local Dtravel=Pn:Get2DDistance(self.ratcraft[i]["Pnow"])
self.ratcraft[i]["Pnow"]=Pn
-- Add up the travelled distance.
self.ratcraft[i]["Distance"]=self.ratcraft[i]["Distance"]+Dtravel
- -- Distance remaining to destiantion.
+ -- Distance remaining to destination.
local Ddestination=Pn:Get2DDistance(self.ratcraft[i].destination:GetCoordinate())
- -- Status report.
- local text=string.format("Group %s (ID %i) of %s\n", prefix, i, self.aircraft.type)
- text=text..string.format("Flying from %s to %s.\n",self.ratcraft[i].departure:GetName(), self.ratcraft[i].destination:GetName())
- text=text..string.format("Status = %s (airborn %s)\n", self.ratcraft[i].status, tostring(airborn))
- text=text..string.format("Fuel = %3.0f, life = %3.0f\n", fuel, life)
- 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)
- if not airborn then
- text=text..string.format("\nTime on ground = %6.0f seconds\n", Tg)
- text=text..string.format("Position change = %6.1f m", Dg)
+ -- Status report.
+ if (forID and i==forID) or not forID then
+ local text=string.format("ID %i of group %s\n", i, prefix)
+ text=text..string.format("%s travelling from %s to %s\n", type, departure, destination)
+ text=text..string.format("Status: %s", self.ratcraft[i].status)
+ if airborn then
+ text=text.." (airborn)\n"
+ else
+ text=text.." (on ground)\n"
+ end
+ text=text..string.format("Fuel = %3.0f\n", fuel)
+ text=text..string.format("Life = %3.0f\n", life)
+ --text=text..string.format("FL%d03, v = %i km/h\n", alt/RAT.unit.FL2m, vel)
+ --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)
+ if not airborn then
+ text=text..string.format("\nTime on ground = %6.0f seconds\n", Tg)
+ text=text..string.format("Position change = %6.1f m since last check.", Dg)
+ end
+ if self.debug then
+ env.info(myid..text)
+ end
+ if self.reportstatus or message then
+ MESSAGE:New(text, 20):ToAll()
+ end
end
- if self.debug then
- env.info(myid..text)
- end
- if self.reportstatus or message then
- MESSAGE:New(text, 20):ToAll()
- end
-
-- Despawn groups if they are on ground and don't move or are damaged.
if not airborn then
@@ -1279,6 +1329,8 @@ end
-- @param #RAT self
function RAT:_OnBirthDay(EventData)
+ env.info(myid.."It's a birthday!")
+
local SpawnGroup = EventData.IniGroup --Wrapper.Group#GROUP
if SpawnGroup then
From c84df9bf5ad489cc90884182b0df7d946c894590 Mon Sep 17 00:00:00 2001
From: funkyfranky
Date: Tue, 5 Sep 2017 16:56:06 +0200
Subject: [PATCH 17/28] Added enumerators (untested)
---
Moose Development/Moose/AI/AI_RAT.lua | 142 ++++++++++++++------------
1 file changed, 74 insertions(+), 68 deletions(-)
diff --git a/Moose Development/Moose/AI/AI_RAT.lua b/Moose Development/Moose/AI/AI_RAT.lua
index ed4fdac38..3a806fa7b 100644
--- a/Moose Development/Moose/AI/AI_RAT.lua
+++ b/Moose Development/Moose/AI/AI_RAT.lua
@@ -101,6 +101,20 @@ RAT.cat={
heli="heli"
}
+--- RAT takeoff style.
+-- @field #RAT waypoint
+RAT.waypoint={
+ air=1,
+ runway=2,
+ hot=3,
+ cold=4,
+ climb=5,
+ cruise=6,
+ descent=7,
+ holding=8,
+ landing=9,
+}
+
--- RAT unit conversions.
-- @field #RAT unit
-- @field #number ft2meter
@@ -116,6 +130,8 @@ RAT.unit={
-- @field #RAT markerid
RAT.markerid=0
+--- Main F10 menu.
+-- @field #RAT MenuF10
RAT.MenuF10=nil
--- Some ID to identify where we are
@@ -138,7 +154,7 @@ myid="RAT | "
--DONE: Improve status reports.
--TODO: Check compatibility with other #SPAWN functions.
--DONE: Add possibility to continue journey at destination. Need "place" in event data for that.
---TODO: Add enumerators and get rid off error prone string comparisons.
+--DONE: Add enumerators and get rid off error prone string comparisons.
--DONE: Check that FARPS are not used as airbases for planes.
--DONE: Add special cases for ships (similar to FARPs).
--DONE: Add cases for helicopters.
@@ -226,7 +242,7 @@ function RAT:Spawn(naircraft)
local Tstart=self.spawndelay
local dt=self.spawninterval
-- Ensure that interval is >= 180 seconds if spawn at runway is chosen. Aircraft need time to takeoff or the runway gets jammed.
- if self.takeoff:lower()=="takeoff-runway" or self.takeoff:lower()=="runway" then
+ if self.takeoff==RAT.waypoint.runway then
dt=math.max(dt, 180)
end
local Tstop=Tstart+dt*(naircraft-1)
@@ -268,24 +284,18 @@ end
-- @usage RAT:Takeoff("cold") will spawn RAT objects at airports with engines off.
-- @usage RAT:Takeoff("air") will spawn RAT objects in air over random airports or within pre-defined zones.
function RAT:SetTakeoff(type)
-
- -- All possible types for random selection.
- local types={"takeoff-cold", "takeoff-hot", "air"}
- --TODO: Need to get rid of the string comparisons and introduce enumerators.
local _Type
if type:lower()=="takeoff-cold" or type:lower()=="cold" then
- _Type="takeoff-cold"
+ _Type=RAT.waypoint.cold
elseif type:lower()=="takeoff-hot" or type:lower()=="hot" then
- _Type="takeoff-hot"
+ _Type=RAT.waypoint.hot
elseif type:lower()=="takeoff-runway" or type:lower()=="runway" then
- _Type="takeoff-runway"
+ _Type=RAT.waypoint.runway
elseif type:lower()=="air" then
- _Type="air"
- elseif type:lower()=="random" then
- _Type=types[math.random(#types)]
+ _Type=RAT.waypoint.air
else
- _Type="takeoff-hot"
+ _Type=RAT.waypoint.hot
end
self.takeoff=_Type
@@ -517,9 +527,9 @@ function RAT:_InitAircraft(DCSgroup)
-- set category
if DCScategory==Group.Category.AIRPLANE then
- self.category="plane"
+ self.category=RAT.cat.plane
elseif DCScategory==Group.Category.HELICOPTER then
- self.category="heli"
+ self.category=RAT.cat.heli
else
self.category="other"
env.error(myid.."Group of RAT is neither airplane nor helicopter!")
@@ -547,7 +557,7 @@ function RAT:_InitAircraft(DCSgroup)
self.aircraft.ceiling=DCSdesc.Hmax
-- Default flight level (ASL).
- if self.category=="plane" then
+ if self.category==RAT.cat.plane then
-- For planes: FL200 = 20000 ft = 6096 m.
self.aircraft.FLcruise=200*RAT.unit.FL2m
else
@@ -623,6 +633,9 @@ function RAT:_SpawnWithRoute(_departure, _destination)
local name=self.aircraft.type.." ID "..tostring(self.SpawnIndex)
self.Menu[self.SpawnTemplatePrefix].groups[self.SpawnIndex]=MENU_MISSION:New(name, self.Menu[self.SpawnTemplatePrefix].groups)
MENU_MISSION_COMMAND:New("Status report", self.Menu[self.SpawnTemplatePrefix].groups[self.SpawnIndex], self.Status, self, true, self.SpawnIndex)
+ --TODO: no sure if it works with group as argument.
+ --MENU_MISSION_COMMAND:New("Despawn group", self.Menu[self.SpawnTemplatePrefix].groups[self.SpawnIndex], self._Despawn, self, self.SpawnIndex)
+ MENU_MISSION_COMMAND:New("Despawn group", self.Menu[self.SpawnTemplatePrefix].groups[self.SpawnIndex], self._Despawn, self, group)
end
end
@@ -715,7 +728,7 @@ function RAT:_SetRoute(_departure, _destination)
-- Coordinates of departure point.
local Pdeparture
- if self.takeoff=="air" then
+ if self.takeoff==RAT.waypoint.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)
@@ -726,10 +739,10 @@ function RAT:_SetRoute(_departure, _destination)
-- Height ASL of departure point.
local H_departure
- if self.takeoff=="air" then
+ if self.takeoff==RAT.waypoint.air then
-- Departure altitude is 70% of default cruise with 30% variation and limited to 1000 m AGL (50 m for helos).
local Hmin
- if self.category=="plane" then
+ if self.category==RAT.cat.plane then
Hmin=1000
else
Hmin=50
@@ -771,7 +784,7 @@ function RAT:_SetRoute(_departure, _destination)
-- DESCENT/HOLDING POINT
-- Get a random point between 5 and 10 km away from the destination.
local Vholding
- if self.category=="plane" then
+ if self.category==RAT.cat.plane then
Vholding=destination:GetCoordinate():GetRandomVec2InRadius(10000, 5000)
else
-- For helos we set a distance between 500 to 1000 m.
@@ -785,7 +798,7 @@ function RAT:_SetRoute(_departure, _destination)
-- Holding point altitude. For planes between 1600 and 2400 m AGL. For helos 160 to 240 m AGL.
local h_holding
- if self.category=="plane" then
+ if self.category==RAT.cat.plane then
h_holding=1200
else
h_holding=150
@@ -828,7 +841,7 @@ function RAT:_SetRoute(_departure, _destination)
end
-- For helicopters we take cruise alt between 50 to 1000 meters above ground. Default cruise alt is ~150 m.
- if self.category=="heli" then
+ if self.category==RAT.cat.heli then
FLmin=math.max(H_departure, H_destination)+50
FLmax=math.max(H_departure, H_destination)+1000
end
@@ -911,13 +924,13 @@ function RAT:_SetRoute(_departure, _destination)
local c6=Pdestination
--Convert coordinates into route waypoints.
- local wp0=self:_Waypoint(self.takeoff, c0, VxClimb, H_departure, departure)
- local wp1=self:_Waypoint("climb", c1, VxClimb, H_departure+(FLcruise-H_departure)/2)
- local wp2=self:_Waypoint("cruise", c2, VxCruise, FLcruise)
- local wp3=self:_Waypoint("cruise", c3, VxCruise, FLcruise)
- local wp4=self:_Waypoint("descent", c4, VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2)
- local wp5=self:_Waypoint("holding", c5, VxHolding, H_holding+h_holding)
- local wp6=self:_Waypoint("landing", c6, VxFinal, H_destination, destination)
+ local wp0=self:_Waypoint(self.takeoff, c0, VxClimb, H_departure, departure)
+ local wp1=self:_Waypoint(RAT.waypoint.climb, c1, VxClimb, H_departure+(FLcruise-H_departure)/2)
+ local wp2=self:_Waypoint(RAT.waypoint.cruise, c2, VxCruise, FLcruise)
+ local wp3=self:_Waypoint(RAT.waypoint.cruise, c3, VxCruise, FLcruise)
+ local wp4=self:_Waypoint(RAT.waypoint.descent, c4, VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2)
+ local wp5=self:_Waypoint(RAT.waypoint.holding, c5, VxHolding, H_holding+h_holding)
+ local wp6=self:_Waypoint(RAT.waypoint.landing, c6, VxFinal, H_destination, destination)
-- set waypoints
local waypoints = {wp0, wp1, wp2, wp3, wp4, wp5, wp6}
@@ -952,7 +965,7 @@ function RAT:_PickDeparture()
-- Array of possible departure airports or zones.
local departures={}
- if self.takeoff=="air" then
+ if self.takeoff==RAT.waypoint.air then
if self.random_departure then
@@ -997,7 +1010,7 @@ function RAT:_PickDeparture()
local departure=departures[math.random(#departures)]
local text
- if self.takeoff=="air" then
+ if self.takeoff==RAT.waypoint.air then
text="Chosen departure zone: "..departure:GetName()
else
text="Chosen departure airport: "..departure:GetName().." (ID "..departure:GetID()..")"
@@ -1019,17 +1032,8 @@ end
function RAT:_PickDestination(destinations, _random)
env.info(myid.."pickdestinations _random = "..tostring(_random))
self:E(destinations)
---[[
- if self.random_destination or _random then
-
- -- All airports of friendly coalitons.
- for _,airport in pairs(destinations) do
- table.insert(destinations, airport)
- end
-
- else
-]]
-
+
+ --
if not (self.random_destination or _random) then
destinations=nil
destinations={}
@@ -1134,6 +1138,7 @@ function RAT:_GetAirportsOfMap()
local _name=airbase:getName()
local _myab=AIRBASE:FindByName(_name)
table.insert(self.airports_map, _myab)
+ --TODO: check here if MOOSE gives the same ID as the native DCS API
local text="Airport ID = ".._myab:GetID().." and Name = ".._myab:GetName()..", Category = ".._myab:GetCategory()..", TypeName = ".._myab:GetTypeName()
if self.debug then
env.info(myid..text)
@@ -1151,9 +1156,9 @@ function RAT:_GetAirportsOfCoalition()
for _,airport in pairs(self.airports_map) do
if airport:GetCoalition()==coalition then
-- Planes cannot land on FARPs.
- local condition1=self.category=="plane" and airport:GetTypeName()=="FARP"
+ local condition1=self.category==RAT.cat.plane and airport:GetTypeName()=="FARP"
-- Planes cannot land on ships.
- local condition2=self.category=="plane" and airport:GetCategory()==1
+ local condition2=self.category==RAT.cat.plane and airport:GetCategory()==1
if not (condition1 or condition2) then
table.insert(self.airports, airport)
end
@@ -1603,8 +1608,6 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport)
-- Altitude of input parameter or y-component of 3D-coordinate.
local _Altitude=Altitude or Coord.y
- --TODO: _Type should be generalized to Grouptemplate.Type
-
-- Land height at given coordinate.
local Hland=Coord:GetLandHeight()
@@ -1614,53 +1617,53 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport)
local _alttype="RADIO"
local _AID=nil
- if Type:lower()=="takeoff-cold" or Type:lower()=="cold" then
+ if Type==RAT.waypoint.cold then
-- take-off with engine off
_Type="TakeOffParking"
_Action="From Parking Area"
_Altitude = 2
_alttype="RADIO"
_AID = Airport:GetID()
- elseif Type:lower()=="takeoff-hot" or Type:lower()=="hot" then
+ elseif Type==RAT.waypoint.hot then
-- take-off with engine on
_Type="TakeOffParkingHot"
_Action="From Parking Area"
_Altitude = 2
_alttype="RADIO"
_AID = Airport:GetID()
- elseif Type:lower()=="takeoff-runway" or Type:lower()=="runway" then
+ elseif Type==RAT.waypoint.runway then
-- take-off from runway
_Type="TakeOff"
_Action="From Parking Area"
_Altitude = 2
_alttype="RADIO"
_AID = Airport:GetID()
- elseif Type:lower()=="air" then
+ elseif Type==RAT.waypoint.air then
-- air start
_Type="Turning Point"
_Action="Turning Point"
_alttype="BARO"
- elseif Type:lower()=="climb" then
+ elseif Type==RAT.waypoint.climb then
_Type="Turning Point"
_Action="Turning Point"
--_Action="Fly Over Point"
_alttype="BARO"
- elseif Type:lower()=="cruise" then
+ elseif Type==RAT.waypoint.cruise then
_Type="Turning Point"
_Action="Turning Point"
--_Action="Fly Over Point"
_alttype="BARO"
- elseif Type:lower()=="descent" then
+ elseif Type==RAT.waypoint.descent then
_Type="Turning Point"
_Action="Turning Point"
--_Action="Fly Over Point"
_alttype="BARO"
- elseif Type:lower()=="holding" then
+ elseif Type==RAT.waypoint.holding then
_Type="Turning Point"
_Action="Turning Point"
--_Action="Fly Over Point"
_alttype="BARO"
- elseif Type:lower()=="landing" or Type:lower()=="land" then
+ elseif Type==RAT.waypoint.landing then
_Type="Land"
_Action="Landing"
_Altitude = 2
@@ -1676,14 +1679,14 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport)
-- some debug info about input parameters
local text=string.format("\n******************************************************\n")
text=text..string.format("Template = %s\n", self.SpawnTemplatePrefix)
- text=text..string.format("Type: %s - %s\n", Type, _Type)
+ 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)
text=text..string.format("Speed = %6.1f m/s = %6.1f km/h = %6.1f knots\n", Speed, Speed*3.6, Speed*1.94384)
text=text..string.format("Land = %6.1f m ASL\n", Hland)
text=text..string.format("Altitude = %6.1f m (%s)\n", _Altitude, _alttype)
if Airport then
- if Type:lower() == "air" then
+ if Type==RAT.waypoint.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())
@@ -1727,8 +1730,10 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport)
["steer"] = 2,
}
-- task
- if Type:lower()=="holding" then
- RoutePoint.task=self:_TaskHolding({x=Coord.x, y=Coord.z}, Altitude, Speed)
+ if Type==RAT.waypoint.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)
else
RoutePoint.task = {}
RoutePoint.task.id = "ComboTask"
@@ -1848,17 +1853,19 @@ end
--- Orbit at a specified position at a specified alititude with a specified speed.
-- @param #RAT self
-- @param Dcs.DCSTypes#Vec2 P1 The point to hold the position.
--- @param #number Altitude The altitude AGL to hold the position.
+-- @param #number Altitude The altitude ASL at which to hold the position.
-- @param #number Speed The speed flying when holding the position in m/s.
+-- @param #number Duration Duration of holding pattern in seconds.
-- @return Dcs.DCSTasking.Task#Task DCSTask
-function RAT:_TaskHolding(P1, Altitude, Speed)
- local LandHeight = land.getHeight(P1)
+function RAT:_TaskHolding(P1, Altitude, Speed, Duration)
+
+ --local LandHeight = land.getHeight(P1)
--TODO: randomize P1
-- Second point is 3 km north of P1 and 200 m for helos.
local dx=3000
local dy=0
- if self.category=="heli" then
+ if self.category==RAT.cat.heli then
dx=200
dy=0
end
@@ -1873,18 +1880,15 @@ function RAT:_TaskHolding(P1, Altitude, Speed)
point = P1,
point2 = P2,
speed = Speed,
- altitude = Altitude + LandHeight
+ altitude = Altitude
}
}
- -- Duration of holing. Between 10 and 170 seconds.
- local d=self:_Randomize(90,0.9)
-
local DCSTask={}
DCSTask.id="ControlledTask"
DCSTask.params={}
DCSTask.params.task=Task
- DCSTask.params.stopCondition={duration=d}
+ DCSTask.params.stopCondition={duration=Duration}
return DCSTask
end
@@ -1935,6 +1939,7 @@ function RAT:_AirportExists(name)
return false
end
+
--- Set ROE for a group.
-- @param #RAT self
-- @param Wrapper.Group#GROUP group Group for which the ROE is set.
@@ -1991,6 +1996,7 @@ function RAT:_SetCoalitionTable()
self:T({"Coalition table: ", self.ctable})
end
+
---Determine the heading from point a to point b.
--@param #RAT self
--@param Core.Point#COORDINATE a Point from.
From 1d1f8d8a01847145158c567c7c2974a7eed533ad Mon Sep 17 00:00:00 2001
From: funkyfranky
Date: Wed, 6 Sep 2017 00:15:55 +0200
Subject: [PATCH 18/28] Enhancement and minor fixes.
Added possibility to create markers from F10 menu.
Minor bug fixes.
Cleaned up messages.
---
Moose Development/Moose/AI/AI_RAT.lua | 74 +++++++++++++--------------
1 file changed, 37 insertions(+), 37 deletions(-)
diff --git a/Moose Development/Moose/AI/AI_RAT.lua b/Moose Development/Moose/AI/AI_RAT.lua
index 3a806fa7b..1b8310183 100644
--- a/Moose Development/Moose/AI/AI_RAT.lua
+++ b/Moose Development/Moose/AI/AI_RAT.lua
@@ -69,7 +69,7 @@ RAT={
AlphaDescent=3.6, -- 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.
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 = "hot", -- Takeoff type: "hot", "cold", "runway", "air", "random".
+ takeoff = 3, -- Takeoff type.
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, ...).
@@ -158,7 +158,9 @@ myid="RAT | "
--DONE: Check that FARPS are not used as airbases for planes.
--DONE: Add special cases for ships (similar to FARPs).
--DONE: Add cases for helicopters.
---TODO: Add F10 menu.
+--DONE: Add F10 menu.
+--TODO: Add markers to F10 menu.
+--TODO: Add
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -308,7 +310,6 @@ end
-- @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)
- self:E({"SetDeparture Names", names})
-- Random departure is deactivated now that user specified departure ports.
self.random_departure=false
@@ -633,8 +634,7 @@ function RAT:_SpawnWithRoute(_departure, _destination)
local name=self.aircraft.type.." ID "..tostring(self.SpawnIndex)
self.Menu[self.SpawnTemplatePrefix].groups[self.SpawnIndex]=MENU_MISSION:New(name, self.Menu[self.SpawnTemplatePrefix].groups)
MENU_MISSION_COMMAND:New("Status report", self.Menu[self.SpawnTemplatePrefix].groups[self.SpawnIndex], self.Status, self, true, self.SpawnIndex)
- --TODO: no sure if it works with group as argument.
- --MENU_MISSION_COMMAND:New("Despawn group", self.Menu[self.SpawnTemplatePrefix].groups[self.SpawnIndex], self._Despawn, self, self.SpawnIndex)
+ MENU_MISSION_COMMAND:New("Place markers", self.Menu[self.SpawnTemplatePrefix].groups[self.SpawnIndex], self._PlaceMarkers, self, waypoints)
MENU_MISSION_COMMAND:New("Despawn group", self.Menu[self.SpawnTemplatePrefix].groups[self.SpawnIndex], self._Despawn, self, group)
end
@@ -654,25 +654,19 @@ function RAT:_Respawn(group)
local _departure=nil
local _destination=nil
-
- env.info(myid.."_Respawn: departure name = "..departure:GetName())
- env.info(myid.."_Respawn: destination name = "..destination:GetName())
if self.continuejourney then
-- We continue our journey from the old departure airport.
_departure=destination:GetName()
- env.info(myid.."_Respawn: journey, _departure = ".._departure)
elseif self.commute then
-- We commute between departure and destination.
_departure=destination:GetName()
_destination=departure:GetName()
- env.info(myid.."_Respawn: commute, _departure = ".._departure)
- env.info(myid.."_Respawn: commute, _destination = ".._destination)
end
- -- Spawn new group after 90 seconds.
+ -- Spawn new group after 180 seconds.
--self:_SpawnWithRoute(_departure, _destination)
- SCHEDULER:New(nil, self._SpawnWithRoute, {self, _departure, _destination}, 90)
+ SCHEDULER:New(nil, self._SpawnWithRoute, {self, _departure, _destination}, 180)
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -685,9 +679,6 @@ end
-- @return Wrapper.Airport#AIRBASE Destination airbase.
-- @return #table Table of flight plan waypoints.
function RAT:_SetRoute(_departure, _destination)
-
- env.info(myid.."_SetRoute: commute, _departure = "..tostring(_departure))
- env.info(myid.."_SetRoute: commute, _destination = "..tostring(_destination))
-- Min cruise speed 70% of Vmax or 600 km/h whichever is lower.
local VxCruiseMin = math.min(self.aircraft.Vmax*0.70, 166)
@@ -937,13 +928,7 @@ function RAT:_SetRoute(_departure, _destination)
-- Place markers of waypoints on F10 map.
if self.placemarkers then
- self:_SetMarker("Takeoff", c0)
- self:_SetMarker("Climb", c1)
- self:_SetMarker("Begin of Cruise", c2)
- self:_SetMarker("End of Cruise", c3)
- self:_SetMarker("Descent", c4)
- self:_SetMarker("Holding Point", c5)
- self:_SetMarker("Destination", c6)
+ self:_PlaceMarkers(waypoints)
end
-- some info on the route as message
@@ -1030,10 +1015,8 @@ end
-- @param #boolean _random Optional switch to activate a random selection of airports.
-- @return Wrapper.Airbase#AIRBASE Destination airport.
function RAT:_PickDestination(destinations, _random)
- env.info(myid.."pickdestinations _random = "..tostring(_random))
- self:E(destinations)
- --
+ -- Take destinations from user input.
if not (self.random_destination or _random) then
destinations=nil
destinations={}
@@ -1085,7 +1068,6 @@ function RAT:_GetDestinations(q, minrange, maxrange)
-- check if distance form departure to destination is within min/max range
if distance>=minrange and distance<=maxrange then
table.insert(destinations, airport)
- env.info("Possible destination = "..airport:GetName())
end
end
env.info(myid.."Number of possible destination airports = "..#destinations)
@@ -1133,16 +1115,21 @@ function RAT:_GetAirportsOfMap()
-- loop over airbases and put them in a table
for _,airbase in pairs(ab) do
+
local _id=airbase:getID()
local _p=airbase:getPosition().p
local _name=airbase:getName()
local _myab=AIRBASE:FindByName(_name)
+
table.insert(self.airports_map, _myab)
- --TODO: check here if MOOSE gives the same ID as the native DCS API
- local text="Airport ID = ".._myab:GetID().." and Name = ".._myab:GetName()..", Category = ".._myab:GetCategory()..", TypeName = ".._myab:GetTypeName()
+
+ local text1="Airport ID = ".._myab:GetID().." and Name = ".._myab:GetName()..", Category = ".._myab:GetCategory()..", TypeName = ".._myab:GetTypeName()
+ local text2="Airport ID = "..airbase:getID().." and Name = "..airbase:getName()..", Category = "..airbase:getCategory()..", TypeName = "..airbase:getTypeName()
if self.debug then
- env.info(myid..text)
+ env.info(myid..text1)
+ env.info(myid..text2)
end
+
end
end
@@ -1187,12 +1174,11 @@ function RAT:Status(message, forID)
-- number of ratcraft spawned.
local ngroups=#self.ratcraft
- local text=string.format("Spawned %i groups of template %s.\n", ngroups, self.SpawnTemplatePrefix)
- if self.debug then
+ 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(myid..text)
+ MESSAGE:New(text, 20):ToAll()
end
- text=string.format("Alive groups of template %s: %d", self.SpawnTemplatePrefix, self.alive)
- env.info(myid..text)
for i=1, ngroups do
@@ -1245,7 +1231,7 @@ function RAT:Status(message, forID)
local Ddestination=Pn:Get2DDistance(self.ratcraft[i].destination:GetCoordinate())
-- Status report.
- if (forID and i==forID) or not forID then
+ if (forID and i==forID) or (not forID) then
local text=string.format("ID %i of group %s\n", i, prefix)
text=text..string.format("%s travelling from %s to %s\n", type, departure, destination)
text=text..string.format("Status: %s", self.ratcraft[i].status)
@@ -1627,7 +1613,7 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport)
elseif Type==RAT.waypoint.hot then
-- take-off with engine on
_Type="TakeOffParkingHot"
- _Action="From Parking Area"
+ _Action="From Parking Area Hot"
_Altitude = 2
_alttype="RADIO"
_AID = Airport:GetID()
@@ -2060,6 +2046,19 @@ function RAT:_Randomize(value, fac, lower, upper)
return r
end
+--- Place markers of the waypoints
+-- @param #RAT self
+-- @param #table waypoints Table with waypoints.
+function RAT:_PlaceMarkers(waypoints)
+ self:_SetMarker("Takeoff", waypoints[1])
+ 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])
+end
+
--- Set a marker for all on the F10 map.
-- @param #RAT self
@@ -2070,7 +2069,8 @@ function RAT:_SetMarker(text, vec3)
if self.debug then
env.info(myid.."Placing marker with ID "..RAT.markerid..": "..text)
end
- trigger.action.markToAll(RAT.markerid, text, vec3)
+ local vec={x=vec3.x, y=vec3.alt, z=vec3.y}
+ trigger.action.markToAll(RAT.markerid, text, vec)
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
\ No newline at end of file
From 07878d4b6e9a4e778f037bae7724d201af97f335 Mon Sep 17 00:00:00 2001
From: funkyfranky
Date: Wed, 6 Sep 2017 16:59:11 +0200
Subject: [PATCH 19/28] Added enumerators (untested version)
---
Moose Development/Moose/AI/AI_RAT.lua | 168 +++++++++++++++++---------
1 file changed, 109 insertions(+), 59 deletions(-)
diff --git a/Moose Development/Moose/AI/AI_RAT.lua b/Moose Development/Moose/AI/AI_RAT.lua
index 1b8310183..5e04cdcaa 100644
--- a/Moose Development/Moose/AI/AI_RAT.lua
+++ b/Moose Development/Moose/AI/AI_RAT.lua
@@ -69,7 +69,7 @@ RAT={
AlphaDescent=3.6, -- 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.
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 = 3, -- Takeoff type.
+ takeoff = 3, -- Takeoff type. 3=hot.
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, ...).
@@ -104,6 +104,7 @@ RAT.cat={
--- RAT takeoff style.
-- @field #RAT waypoint
RAT.waypoint={
+ random=0,
air=1,
runway=2,
hot=3,
@@ -115,9 +116,21 @@ RAT.waypoint={
landing=9,
}
+--- RAT friendly coalitions.
+-- @field #RAT coal
+RAT.coal={
+ same="same",
+ sameonly="sameonly",
+ all="all",
+ blue="blue",
+ blueonly="blueonly",
+ red="red",
+ redonly="redonly",
+ neutral="neutral",
+}
+
--- RAT unit conversions.
-- @field #RAT unit
--- @field #number ft2meter
RAT.unit={
ft2meter=0.305,
kmh2ms=0.278,
@@ -159,8 +172,9 @@ myid="RAT | "
--DONE: Add special cases for ships (similar to FARPs).
--DONE: Add cases for helicopters.
--DONE: Add F10 menu.
---TODO: Add markers to F10 menu.
---TODO: Add
+--DONE: Add markers to F10 menu.
+--TODO: Add respawn limit.
+--TODO: Make takeoff method random.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -168,9 +182,8 @@ myid="RAT | "
-- @param #RAT self
-- @param #string groupname Name of the group as defined in the mission editor. This group is serving as a template for all spawned units.
-- @return #RAT Object of RAT class.
--- @return #nil Nil if the group does not exists in the mission editor.
+-- @return #nil If the group does not exist in the mission editor.
-- @usage yak:RAT("RAT_YAK") will create a RAT object called "yak". The template group in the mission editor must have the name "RAT_YAK".
--- By default aircraft will spawn at neutral and red airports if the template group is part of the red coaliton.
function RAT:New(groupname)
-- Inherit SPAWN clase.
@@ -215,9 +228,10 @@ end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
---- Spawn the AI aircraft.
+--- Triggers the spawning of AI aircraft. Note that all additional options should be set before giving the spawn command.
-- @param #RAT self
-- @param #number naircraft (Optional) Number of aircraft to spawn. Default is one aircraft.
+-- @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)
-- Number of aircraft to spawn. Default is one.
@@ -232,7 +246,7 @@ function RAT:Spawn(naircraft)
-- debug message
local text=string.format("\n******************************************************\n")
text=text..string.format("Spawning %i aircraft from template %s of type %s.\n", naircraft, self.templatename, self.aircraft.type)
- text=text..string.format("Takeoff type: %s\n", self.takeoff)
+ text=text..string.format("Takeoff type: %i\n", self.takeoff)
text=text..string.format("Friendly coalitions: %s\n", self.friendly)
text=text..string.format("Number of friendly airports: %i\n", #self.airports)
text=text..string.format("Commuting: %s\n", tostring(self.commute))
@@ -269,19 +283,36 @@ end
--- Set the friendly coalitions from which the airports can be used as departure or destination.
-- @param #RAT self
--- @param #string friendly Possible choices:
--- "all"=neutral+red+blue, "same"=spawn coalition+neutral, "sameonly"=spawn coalition, "blue"=blue+neutral, "blueonly"=blue, "red"=red+neutral, "redonly"=red, "neutral"=neutral.
+-- @param #string friendly "same"=own coalition+neutral (default), "all"=neutral+red+blue", "sameonly"=own coalition only, "blue"=blue+neutral, "blueonly"=blue, "red"=red+neutral, "redonly"=red, "neutral"=neutral.
-- Default is "same", so aircraft will use airports of the coalition their spawn template has plus all neutral airports.
--- @usage yak:SetCoalition("all") will spawn aircraft randomly on airports of any coaliton, i.e. red, blue and neutral.
--- @usage yak:SetCoalition("redonly") will spawn aircraft randomly on airports belonging to the red coalition only.
+-- @usage yak:SetCoalition("all") will spawn aircraft randomly on airports of any coaliton, i.e. red, blue and neutral, regardless of its own coalition.
+-- @usage yak:SetCoalition("redonly") will spawn aircraft randomly on airports belonging to the red coalition _only_.
function RAT:SetCoalition(friendly)
- self.friendly=friendly
+ if friendly:lower()=="all" then
+ self.friendly=RAT.coal.all
+ elseif friendly:lower()=="sameonly" then
+ self.friendly=RAT.coal.sameonly
+ elseif friendly:lower()=="blue" then
+ self.friendly=RAT.coal.blue
+ elseif friendly:lower()=="blueonly" then
+ self.friendly=RAT.coal.blueonly
+ elseif friendly:lower()=="red" then
+ self.friendly=RAT.coal.red
+ elseif friendly:lower()=="redonly" then
+ self.friendly=RAT.coal.red
+ elseif friendly:lower()=="blue" then
+ self.friendly=RAT.coal.blue
+ elseif friendly:lower()=="neutral" then
+ self.friendly=RAT.coal.neutral
+ else
+ self.friendly=RAT.coal.same
+ end
end
---- Set takeoff type. Starting cold at airport, starting hot at airport, starting at runway, starting in the air or randomly select one of the previous.
+--- Set takeoff type. Starting cold at airport, starting hot at airport, starting at runway, starting in the air.
-- Default is "takeoff-hot" for a start at airport with engines already running.
-- @param #RAT self
--- @param #string type Type can be "takeoff-cold" or "cold", "takeoff-hot" or "hot", "takeoff-runway" or "runway", "air", "random".
+-- @param #string type Type can be "takeoff-cold" or "cold", "takeoff-hot" or "hot", "takeoff-runway" or "runway", "air".
-- @usage RAT:Takeoff("hot") will spawn RAT objects at airports with engines started.
-- @usage RAT:Takeoff("cold") will spawn RAT objects at airports with engines off.
-- @usage RAT:Takeoff("air") will spawn RAT objects in air over random airports or within pre-defined zones.
@@ -346,9 +377,10 @@ function RAT:SetDeparture(names)
end
---- Set name of destination airport for the AI aircraft. If no name is given an airport from the coalition is chosen randomly.
+--- Set name of destination airport for the AI aircraft. If no name is given an airport from the friendly coalition(s) is chosen randomly.
-- @param #RAT self
-- @param #string names 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)
-- Random departure is deactivated now that user specified departure ports.
@@ -371,7 +403,7 @@ function RAT:SetDestination(names)
end
---- Aircraft will continue their journey from their destination. This means they are respawned at their destination and get a new random destination.
+--- 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)
@@ -379,7 +411,8 @@ function RAT:ContinueJourney(switch)
self.continuejourney=switch
end
---- Aircraft will commute between their departure and destination airports. This is not possible with takeoff in air.
+--- 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.
-- @param #RAT self
-- @param #boolean switch Turn commute on=true or off=false. If no value is given switch=true.
function RAT:Commute(switch)
@@ -414,21 +447,28 @@ end
function RAT:SetClimbRate(rate)
-- Convert from ft/min to m/s.
- self.Vclimb=rate*RAT.unit.ft2m/60
+ self.Vclimb=rate --*RAT.unit.ft2m/60
-- Climb rate in m/s. Max is aircraft specific.
- self.aircraft.Vclimb=math.min(self.Vclimb, self.aircraft.Vymax)
+ --self.aircraft.Vclimb=math.min(self.Vclimb, self.aircraft.Vymax)
-- Climb angle in rad.
- self.aircraft.AlphaClimb=math.asin(self.aircraft.Vclimb/self.aircraft.Vmax)
+ --self.aircraft.AlphaClimb=math.asin(self.aircraft.Vclimb/self.aircraft.Vmax)
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.
function RAT:SetDescentAngle(angle)
- -- Convert to rad.
- self.aircraft.AlphaDescent=math.rad(angle)
+ self.AlphaDescent=angle
+end
+
+--- Set the descent rate.
+-- @param #RAT self
+-- @param #number rate Descent rate in ft/min.
+function RAT:SetDescentRate(rate)
+ -- Convert to m/s.
+ self.Vdescent=rate*RAT.unit.ft2m/60
end
--- Set rules of engagement (ROE). Default is weapon hold. This is a peaceful class.
@@ -590,12 +630,15 @@ end
-- Sets the departure and destination airports and waypoints.
-- Modifies the spawn template.
-- Sets ROE/ROT.
--- Initializes the ratcraft array and event handlers.
+-- Initializes the ratcraft array and group menu.
-- @param #RAT self
--- @param Wrapper.Airport#AIRBASE _departure (Optional) Departure airbase.
--- @param Wrapper.Airport#AIRBASE _destination (Optional) Destination airbase.
+-- @param #string _departure (Optional) Name of departure airbase.
+-- @param #string _destination (Optional) Name of destination airbase.
function RAT:_SpawnWithRoute(_departure, _destination)
+ -- Set takeoff type.
+ --if self.takeoff==""
+
-- Set flight plan.
local departure, destination, waypoints = self:_SetRoute(_departure, _destination)
@@ -695,17 +738,20 @@ function RAT:_SetRoute(_departure, _destination)
-- Descent speed 60% of Vmax but max 500 km/h.
local VxDescent = math.min(self.aircraft.Vmax*0.60, 140)
+ -- Holding speed is 90% of descent speed.
local VxHolding = VxDescent*0.9
- local VxFinal = VxHolding*0.9
- -- Reasonably civil climb speed Vy=1500 ft/min but max aircraft specific climb rate.
+ -- Final leg is 90% of holding speed.
+ local VxFinal = VxHolding*0.9
+
+ -- Reasonably civil climb speed Vy=1500 ft/min = 7.6 m/s but max aircraft specific climb rate.
local VyClimb=math.min(self.Vclimb*RAT.unit.ft2meter/60, self.aircraft.Vymax)
-- Climb angle in rad.
local AlphaClimb=math.asin(VyClimb/VxClimb)
-- Descent angle in rad.
- local AlphaDescent=math.rad(self.AlphaDescent)
+ local AlphaDescent=math.rad(self.AlphaDescent)
-- DEPARTURE AIRPORT
@@ -843,7 +889,7 @@ function RAT:_SetRoute(_departure, _destination)
-- Set cruise altitude: default with 100% randomization but limited to FLmin and FLmax.
local FLcruise=self:_Randomize(self.aircraft.FLcruise, 1.0, FLmin, FLmax)
- -- Overrule setting if user specifies a flight level very explicitly.
+ -- Overrule setting if user specified a flight level explicitly.
if self.FLuser then
FLcruise=self.FLuser
end
@@ -895,7 +941,7 @@ function RAT:_SetRoute(_departure, _destination)
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 = %6.1f Deg\n", math.deg(phi))
+ text=text..string.format("Phi (slope) = %6.1f Deg\n", math.deg(phi))
text=text..string.format("Heading = %6.1f Deg\n", heading)
text=text..string.format("******************************************************\n")
env.info(myid..text)
@@ -945,6 +991,7 @@ end
-- If takeoff style is set to "air", we use zones around the airports or the zones specified by user input.
-- @param #RAT self
-- @return Wrapper.Airbase#AIRBASE Departure airport if spawning at airport.
+-- @return Coore.Zone#ZONE Departure zone if spawning in air.
function RAT:_PickDeparture()
-- Array of possible departure airports or zones.
@@ -1052,7 +1099,7 @@ end
-- @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 Nil if no airports could be found.
+-- @return #nil If no airports could be found.
function RAT:_GetDestinations(q, minrange, maxrange)
minrange=minrange or self.mindist
@@ -1121,11 +1168,12 @@ function RAT:_GetAirportsOfMap()
local _name=airbase:getName()
local _myab=AIRBASE:FindByName(_name)
+ -- Add airport to table.
table.insert(self.airports_map, _myab)
- local text1="Airport ID = ".._myab:GetID().." and Name = ".._myab:GetName()..", Category = ".._myab:GetCategory()..", TypeName = ".._myab:GetTypeName()
- local text2="Airport ID = "..airbase:getID().." and Name = "..airbase:getName()..", Category = "..airbase:getCategory()..", TypeName = "..airbase:getTypeName()
if self.debug then
+ local text1="Airport ID = ".._myab:GetID().." and Name = ".._myab:GetName()..", Category = ".._myab:GetCategory()..", TypeName = ".._myab:GetTypeName()
+ local text2="Airport ID = "..airbase:getID().." and Name = "..airbase:getName()..", Category = "..airbase:getCategory()..", TypeName = "..airbase:getTypeName()
env.info(myid..text1)
env.info(myid..text2)
end
@@ -1192,7 +1240,7 @@ function RAT:Status(message, forID)
local fuel=group:GetFuel()*100.0
local airborn=group:InAir()
local coords=group:GetCoordinate()
- --local alt=group:GetAltitude()
+ local alt=coords.y
--local vel=group:GetVelocityKMH()
local departure=self.ratcraft[i].departure:GetName()
local destination=self.ratcraft[i].destination:GetName()
@@ -1236,13 +1284,13 @@ function RAT:Status(message, forID)
text=text..string.format("%s travelling from %s to %s\n", type, departure, destination)
text=text..string.format("Status: %s", self.ratcraft[i].status)
if airborn then
- text=text.." (airborn)\n"
+ text=text.." [airborn]\n"
else
- text=text.." (on ground)\n"
+ text=text.." [on ground]\n"
end
text=text..string.format("Fuel = %3.0f\n", fuel)
text=text..string.format("Life = %3.0f\n", life)
- --text=text..string.format("FL%d03, v = %i km/h\n", alt/RAT.unit.FL2m, vel)
+ text=text..string.format("FL%d03 = %5.0f 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", Ddestination/1000)
@@ -1363,12 +1411,12 @@ function RAT:_EngineStartup(EventData)
-- Check that the template name actually belongs to this object.
if EventPrefix == self.SpawnTemplatePrefix then
- local text="Event: Group "..SpawnGroup:GetName().." started engines. Life="..self:_GetLife(SpawnGroup)
+ local text="Event: Group "..SpawnGroup:GetName().." started engines."
env.info(myid..text)
local status
if SpawnGroup:InAir() then
- status="airborn"
+ status="On journey (after air start)"
else
status="Taxiing (after engines started)"
end
@@ -1400,7 +1448,7 @@ function RAT:_OnTakeoff(EventData)
-- Check that the template name actually belongs to this object.
if EventPrefix == self.SpawnTemplatePrefix then
- local text="Event: Group "..SpawnGroup:GetName().." is airborn. Life="..self:_GetLife(SpawnGroup)
+ local text="Event: Group "..SpawnGroup:GetName().." is airborn."
env.info(myid..text)
-- Set status.
@@ -1432,7 +1480,7 @@ function RAT:_OnLand(EventData)
-- Check that the template name actually belongs to this object.
if EventPrefix == self.SpawnTemplatePrefix then
- local text="Event: Group "..SpawnGroup:GetName().." landed. Life="..self:_GetLife(SpawnGroup)
+ local text="Event: Group "..SpawnGroup:GetName().." landed."
env.info(myid..text)
-- Set status.
@@ -1470,7 +1518,7 @@ function RAT:_OnEngineShutdown(EventData)
-- Check that the template name actually belongs to this object.
if EventPrefix == self.SpawnTemplatePrefix then
- local text="Event: Group "..SpawnGroup:GetName().." shut down its engines. Life="..self:_GetLife(SpawnGroup)
+ local text="Event: Group "..SpawnGroup:GetName().." shut down its engines."
env.info(myid..text)
-- Set status.
@@ -1545,6 +1593,8 @@ function RAT:_OnCrash(EventData)
-- Set status.
self:_SetStatus(SpawnGroup, "Crashed")
+
+ --TODO: Aircraft are not respawned if they crash. Should they?
--TODO: Maybe spawn some people at the crash site and send a distress call.
-- And define them as cargo which can be rescued.
@@ -1958,28 +2008,26 @@ end
-- @param #RAT self
function RAT:_SetCoalitionTable()
-- get all possible departures/destinations depending on coalition
- if self.friendly=="all" then
+ if self.friendly==RAT.coal.all then
self.ctable={coalition.side.BLUE, coalition.side.RED, coalition.side.NEUTRAL}
- elseif self.friendly=="blue" then
+ elseif self.friendly==RAT.coal.blue then
self.ctable={coalition.side.BLUE, coalition.side.NEUTRAL}
- elseif self.friendly=="blueonly" then
+ elseif self.friendly==RAT.coal.blueonly then
self.ctable={coalition.side.BLUE}
- elseif self.friendly=="red" then
+ elseif self.friendly==RAT.coal.red then
self.ctable={coalition.side.RED, coalition.side.NEUTRAL}
- elseif self.friendly=="redonly" then
+ elseif self.friendly==RAT.coal.redonly then
self.ctable={coalition.side.RED}
- elseif self.friendly=="neutral" then
+ elseif self.friendly==RAT.coal.neutral then
self.ctable={coalition.side.NEUTRAL}
- elseif self.friendly=="same" then
+ elseif self.friendly==RAT.coal.same then
self.ctable={self.coalition, coalition.side.NEUTRAL}
- elseif self.friendly=="sameonly" then
+ elseif self.friendly==RAT.coal.sameonly then
self.ctable={self.coalition}
else
env.error("Unknown friendly coalition in _SetCoalitionTable(). Defaulting to NEUTRAL.")
self.ctable={self.coalition, coalition.side.NEUTRAL}
end
- -- debug info
- self:T({"Coalition table: ", self.ctable})
end
@@ -2046,7 +2094,7 @@ function RAT:_Randomize(value, fac, lower, upper)
return r
end
---- Place markers of the waypoints
+--- 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)
@@ -2060,16 +2108,18 @@ function RAT:_PlaceMarkers(waypoints)
end
---- Set a marker for all on the F10 map.
+--- Set a marker visible for all on the F10 map.
-- @param #RAT self
--- @param #string text Text of maker.
--- @param Core.Point#COORDINATE vec3 Position of marker.
-function RAT:_SetMarker(text, vec3)
+-- @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)
RAT.markerid=RAT.markerid+1
if self.debug then
env.info(myid.."Placing marker with ID "..RAT.markerid..": "..text)
end
- local vec={x=vec3.x, y=vec3.alt, z=vec3.y}
+ -- Convert to coordinate.
+ local vec={x=wp.x, y=wp.alt, z=wp.y}
+ -- Place maker visible for all on the F10 map.
trigger.action.markToAll(RAT.markerid, text, vec)
end
From f79143095ef545a0fe690f205337521fbc7ea853 Mon Sep 17 00:00:00 2001
From: funkyfranky
Date: Thu, 7 Sep 2017 08:02:08 +0200
Subject: [PATCH 20/28] Added min/max fligh level.
---
Moose Development/Moose/AI/AI_RAT.lua | 29 ++++++++++++++++++++++++++-
1 file changed, 28 insertions(+), 1 deletion(-)
diff --git a/Moose Development/Moose/AI/AI_RAT.lua b/Moose Development/Moose/AI/AI_RAT.lua
index 5e04cdcaa..8fa983674 100644
--- a/Moose Development/Moose/AI/AI_RAT.lua
+++ b/Moose Development/Moose/AI/AI_RAT.lua
@@ -83,6 +83,8 @@ RAT={
reportstatus=false, -- Aircraft report status.
statusinterval=30, -- Intervall between status checks (and reports if enabled).
placemarkers=false, -- Place markers of waypoints on F10 map.
+ FLminuser=nil, -- Minimum flight level set by user.
+ FLmaxuser=nil, -- Minimum flight level set by user.
FLuser=nil, -- Flight level set by users explicitly.
Vuser=nil, -- Cruising speed set by user explicitly.
commute=false, -- Aircraft commute between departure and destination, i.e. when respawned the departure airport becomes the new destiation.
@@ -541,6 +543,20 @@ function RAT:SetFL(height)
self.FLuser=height*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
+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
+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
@@ -886,8 +902,19 @@ function RAT:_SetRoute(_departure, _destination)
-- Ensure that FLmax not above 90% its service ceiling.
FLmax=math.min(FLmax, self.aircraft.ceiling*0.9)
+ -- Overrule setting if user specified min/max flight level explicitly.
+ if self.FLminuser then
+ FLmin=self.FLminuser
+ env.info(myid.."FLmin user = "..FLmin)
+ end
+ if self.FLmaxuser then
+ FLmax=self.FLmaxuser
+ env.info(myid.."FLmax user = "..FLmax)
+ end
+
-- Set cruise altitude: default with 100% randomization but limited to FLmin and FLmax.
local FLcruise=self:_Randomize(self.aircraft.FLcruise, 1.0, FLmin, FLmax)
+ env.info(myid.."FLcruise = "..FLcruise)
-- Overrule setting if user specified a flight level explicitly.
if self.FLuser then
@@ -1290,7 +1317,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%d03 = %5.0f m\n", alt/RAT.unit.FL2m, alt)
+ 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", Ddestination/1000)
From e4e1990657801104535c50ba369f420d1c26f2fb Mon Sep 17 00:00:00 2001
From: funkyfranky
Date: Fri, 8 Sep 2017 00:33:31 +0200
Subject: [PATCH 21/28] Added option to respawn after landing.
Changed default to respawn after engine shut-down.
---
Moose Development/Moose/AI/AI_RAT.lua | 55 ++++++++++++++++++---------
1 file changed, 37 insertions(+), 18 deletions(-)
diff --git a/Moose Development/Moose/AI/AI_RAT.lua b/Moose Development/Moose/AI/AI_RAT.lua
index 8fa983674..fded623af 100644
--- a/Moose Development/Moose/AI/AI_RAT.lua
+++ b/Moose Development/Moose/AI/AI_RAT.lua
@@ -47,6 +47,8 @@
-- @field #number alive
-- @field #boolean f10menu
-- @field #table Menu
+-- @field #boolean respawn_after_landing
+-- @field #number respawn_delay
-- @field #table RAT
-- @extends Functional.Spawn#SPAWN
@@ -92,6 +94,8 @@ RAT={
alive=0, -- Number of groups which are alive.
f10menu=true, -- Add an F10 menu for RAT.
Menu={}, -- F10 menu for this RAT object.
+ respawn_at_landing=false, -- Respawn aircraft the moment they land rather than at engine shutdown.
+ respawn_delay=nil, -- Delay in seconds until repawn happens after landing.
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -436,6 +440,15 @@ function RAT:SetSpawnInterval(interval)
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.
+function RAT:RespawnAfterLanding(delay)
+ delay = delay or 180
+ self.respawn_after_landing=true
+ self.respawn_delay=delay
+end
+
--- Set the maximum cruise speed of the aircraft.
-- @param #RAT self
-- @param #number speed Speed in km/h.
@@ -451,11 +464,6 @@ function RAT:SetClimbRate(rate)
-- Convert from ft/min to m/s.
self.Vclimb=rate --*RAT.unit.ft2m/60
- -- Climb rate in m/s. Max is aircraft specific.
- --self.aircraft.Vclimb=math.min(self.Vclimb, self.aircraft.Vymax)
-
- -- Climb angle in rad.
- --self.aircraft.AlphaClimb=math.asin(self.aircraft.Vclimb/self.aircraft.Vmax)
end
--- Set the angle of descent. Default is 3.6 degrees, which corresponds to 3000 ft descent after one mile of travel.
@@ -723,11 +731,15 @@ function RAT:_Respawn(group)
_destination=departure:GetName()
end
- -- Spawn new group after 180 seconds.
- --self:_SpawnWithRoute(_departure, _destination)
- SCHEDULER:New(nil, self._SpawnWithRoute, {self, _departure, _destination}, 180)
+ -- Spawn new group.
+ if self.respawn_delay then
+ SCHEDULER:New(nil, self._SpawnWithRoute, {self, _departure, _destination}, self.respawn_delay)
+ else
+ self:_SpawnWithRoute(_departure, _destination)
+ end
end
+
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Set the route of the AI plane. Due to DCS landing bug, this has to be done before the unit is spawned.
@@ -905,16 +917,13 @@ function RAT:_SetRoute(_departure, _destination)
-- Overrule setting if user specified min/max flight level explicitly.
if self.FLminuser then
FLmin=self.FLminuser
- env.info(myid.."FLmin user = "..FLmin)
end
if self.FLmaxuser then
FLmax=self.FLmaxuser
- env.info(myid.."FLmax user = "..FLmax)
end
-- Set cruise altitude: default with 100% randomization but limited to FLmin and FLmax.
local FLcruise=self:_Randomize(self.aircraft.FLcruise, 1.0, FLmin, FLmax)
- env.info(myid.."FLcruise = "..FLcruise)
-- Overrule setting if user specified a flight level explicitly.
if self.FLuser then
@@ -1513,11 +1522,14 @@ function RAT:_OnLand(EventData)
-- Set status.
self:_SetStatus(SpawnGroup, "Taxiing (after landing)")
- text="Event: Group "..SpawnGroup:GetName().." will be respawned."
- env.info(myid..text)
- -- Respawn group.
- self:_Respawn(SpawnGroup)
+ if self.respawn_after_landing then
+ text="Event: Group "..SpawnGroup:GetName().." will be respawned."
+ env.info(myid..text)
+
+ -- Respawn group.
+ self:_Respawn(SpawnGroup)
+ end
end
end
@@ -1550,11 +1562,18 @@ function RAT:_OnEngineShutdown(EventData)
-- Set status.
self:_SetStatus(SpawnGroup, "Parking (shutting down engines)")
-
- text="Event: Group "..SpawnGroup:GetName().." will be destroyed now."
- env.info(myid..text)
+
+ if not self.respawn_after_landing then
+ text="Event: Group "..SpawnGroup:GetName().." will be respawned."
+ env.info(myid..text)
+
+ -- Respawn group.
+ self:_Respawn(SpawnGroup)
+ end
-- Despawn group.
+ text="Event: Group "..SpawnGroup:GetName().." will be destroyed now."
+ env.info(myid..text)
self:_Despawn(SpawnGroup)
end
From 5992c852da11b2fb20f77cc18be6a30b8c76d21d Mon Sep 17 00:00:00 2001
From: funkyfranky
Date: Fri, 8 Sep 2017 16:27:51 +0200
Subject: [PATCH 22/28] Improved menu, added enumerators for ROE/ROT, added
random takeoff (untested)
---
Moose Development/Moose/AI/AI_RAT.lua | 189 +++++++++++++++++---------
1 file changed, 123 insertions(+), 66 deletions(-)
diff --git a/Moose Development/Moose/AI/AI_RAT.lua b/Moose Development/Moose/AI/AI_RAT.lua
index fded623af..a594b5c16 100644
--- a/Moose Development/Moose/AI/AI_RAT.lua
+++ b/Moose Development/Moose/AI/AI_RAT.lua
@@ -13,7 +13,6 @@
-- @type RAT
-- @field #string ClassName
-- @field #boolean debug
--- @field #string templatename
-- @field #number spawndelay
-- @field #number spawninterval
-- @field #number coalition
@@ -41,12 +40,14 @@
-- @field #number statusinterval
-- @field #boolean placemarkers
-- @field #number FLuser
--- @field #number Vuser
+-- @field #number FLminuser
+-- @field #number FLmaxuser
-- @field #boolean commute
-- @field #boolean continuejourney
-- @field #number alive
-- @field #boolean f10menu
-- @field #table Menu
+-- @field #string SubMenuName
-- @field #boolean respawn_after_landing
-- @field #number respawn_delay
-- @field #table RAT
@@ -58,7 +59,6 @@
RAT={
ClassName = "RAT", -- Name of class: RAT = Random Air Traffic.
debug=false, -- Turn debug messages on or off.
- templatename=nil, -- Name of the template group defined in the mission editor.
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.
@@ -71,7 +71,7 @@ RAT={
AlphaDescent=3.6, -- 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.
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 = 3, -- Takeoff type. 3=hot.
+ takeoff = 0, -- Takeoff type. 0=coldorhot.
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, ...).
@@ -88,12 +88,12 @@ RAT={
FLminuser=nil, -- Minimum flight level set by user.
FLmaxuser=nil, -- Minimum flight level set by user.
FLuser=nil, -- Flight level set by users explicitly.
- Vuser=nil, -- Cruising speed set by user explicitly.
commute=false, -- Aircraft commute between departure and destination, i.e. when respawned the departure airport becomes the new destiation.
continuejourney=false, -- Aircraft will continue their journey, i.e. get respawned at their destination with a new random destination.
alive=0, -- Number of groups which are alive.
f10menu=true, -- Add an F10 menu for RAT.
- Menu={}, -- F10 menu for this RAT object.
+ 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.
respawn_delay=nil, -- Delay in seconds until repawn happens after landing.
}
@@ -110,7 +110,7 @@ RAT.cat={
--- RAT takeoff style.
-- @field #RAT waypoint
RAT.waypoint={
- random=0,
+ coldorhot=0,
air=1,
runway=2,
hot=3,
@@ -145,6 +145,22 @@ RAT.unit={
nm2m=1852,
}
+--- RAT rules of engagement.
+-- @field #RAT roe
+RAT.roe={
+ weaponhold="hold",
+ weaponfree="free",
+ returnfire="return",
+}
+
+--- RAT reaction to threat.
+-- @field #RAT rot
+RAT.rot{
+ evade="evade",
+ passive="passive",
+ noreaction="noreaction"
+}
+
--- Running number of placed markers on the F10 map.
-- @field #RAT markerid
RAT.markerid=0
@@ -180,7 +196,7 @@ myid="RAT | "
--DONE: Add F10 menu.
--DONE: Add markers to F10 menu.
--TODO: Add respawn limit.
---TODO: Make takeoff method random.
+--DONE: Make takeoff method random between cold and hot start.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -195,9 +211,6 @@ function RAT:New(groupname)
-- Inherit SPAWN clase.
local self=BASE:Inherit(self, SPAWN:New(groupname)) -- #RAT
- -- Set template name.
- self.templatename=groupname
-
-- Get template group defined in the mission editor.
local DCSgroup=Group.getByName(groupname)
@@ -217,18 +230,10 @@ function RAT:New(groupname)
self:_GetAirportsOfMap()
-- Create F10 main menu if it does not exists yet.
- if not RAT.MenuF10 and self.f10menu then
+ if self.f10menu and not RAT.MenuF10 then
RAT.MenuF10 = MENU_MISSION:New("RAT")
end
-
- -- Craete submenus.
- if self.f10menu then
- self.Menu[self.SpawnTemplatePrefix]=MENU_MISSION:New(self.SpawnTemplatePrefix, RAT.MenuF10)
- self.Menu[self.SpawnTemplatePrefix]["groups"]=MENU_MISSION:New("Groups", self.Menu[self.SpawnTemplatePrefix])
- MENU_MISSION_COMMAND:New("Status report", self.Menu[self.SpawnTemplatePrefix], self.Status, self, true)
- MENU_MISSION_COMMAND:New("Spawn new group", self.Menu[self.SpawnTemplatePrefix], self._SpawnWithRoute, self)
- end
-
+
return self
end
@@ -251,15 +256,46 @@ function RAT:Spawn(naircraft)
-- debug message
local text=string.format("\n******************************************************\n")
- text=text..string.format("Spawning %i aircraft from template %s of type %s.\n", naircraft, self.templatename, self.aircraft.type)
+ text=text..string.format("Spawning %i aircraft from template %s of type %s.\n", naircraft, self.SpawnTemplatePrefix, self.aircraft.type)
text=text..string.format("Takeoff type: %i\n", self.takeoff)
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("Commuting: %s\n", tostring(self.commute))
- text=text..string.format("Long Journey: %s\n", tostring(self.continuejourney))
+ text=text..string.format("Commute: %s\n", tostring(self.commute))
+ text=text..string.format("Journey: %s\n", tostring(self.continuejourney))
+ 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("Category: %s\n", self.category)
+ text=text..string.format("Vcruisemax: %4.1f\n", self.Vcruisemax)
+ text=text..string.format("Vclimb: %4.1f\n", self.Vclimb)
+ text=text..string.format("Vcruisemax: %4.1f\n", self.Vcruisemax)
+ text=text..string.format("AlphaDescent: %4.2f\n", self.AlphaDescent)
+ text=text..string.format("ROE: %s\n", self.roe)
+ text=text..string.format("ROT: %s\n", self.rot)
+ text=text..string.format("Min dist: %4.1f\n", self.mindist)
+ text=text..string.format("Max dist: %4.1f\n", self.maxdist)
+ text=text..string.format("Report status: %s\n", tostring(self.airports))
+ text=text..string.format("Status interval: %4.1f\n", self.statusinterval)
+ text=text..string.format("Place markers: %s\n", tostring(self.placemarkers))
+ 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("Respawn after landing: %s\n", tostring(self.respawn_after_landing))
+ text=text..string.format("Respawn after landing: %s\n", tostring(self.respawn_delay))
+ -- @field #number FLminuser
text=text..string.format("******************************************************\n")
env.info(myid..text)
+
+ -- Create submenus.
+ if self.f10menu then
+ self.SubMenuName=self.SpawnTemplatePrefix
+ self.Menu[self.SubMenuName]=MENU_MISSION:New(self.SubMenuName, RAT.MenuF10)
+ self.Menu[self.SubMenuName]["groups"]=MENU_MISSION:New("Groups", self.Menu[self.SubMenuName])
+ MENU_MISSION_COMMAND:New("Status report", self.Menu[self.SubMenuName], self.Status, self, true)
+ MENU_MISSION_COMMAND:New("Spawn new group", self.Menu[self.SubMenuName], self._SpawnWithRoute, self)
+ end
+
-- Schedule spawning of aircraft.
local Tstart=self.spawndelay
local dt=self.spawninterval
@@ -334,7 +370,7 @@ function RAT:SetTakeoff(type)
elseif type:lower()=="air" then
_Type=RAT.waypoint.air
else
- _Type=RAT.waypoint.hot
+ _Type=RAT.waypoint.coldorhot
end
self.takeoff=_Type
@@ -460,10 +496,7 @@ end
-- @param #RAT self
-- @param #number rate Climb rate in ft/min.
function RAT:SetClimbRate(rate)
-
- -- Convert from ft/min to m/s.
- self.Vclimb=rate --*RAT.unit.ft2m/60
-
+ 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.
@@ -473,22 +506,16 @@ function RAT:SetDescentAngle(angle)
self.AlphaDescent=angle
end
---- Set the descent rate.
--- @param #RAT self
--- @param #number rate Descent rate in ft/min.
-function RAT:SetDescentRate(rate)
- -- Convert to m/s.
- self.Vdescent=rate*RAT.unit.ft2m/60
-end
-
--- Set rules of engagement (ROE). Default is weapon hold. This is a peaceful class.
-- @param #RAT self
-- @param #string roe "hold" = weapon hold, "return" = return fire, "free" = weapons free.
function RAT:SetROE(roe)
- if roe=="hold" or roe=="return" or roe=="free" then
- self.roe=roe
+ if roe=="return" then
+ self.roe=RAT.roe.returnfire
+ elseif roe=="free" then
+ self.roe=RAT.roe.weaponfree
else
- self.roe="hold"
+ self.roe=RAT.roe.weaponhold
end
end
@@ -496,10 +523,12 @@ end
-- @param #RAT self
-- @param #string rot "noreaction = no reactino, "passive" = passive defence, "evade" = weapons free.
function RAT:SetROT(rot)
- if rot=="noreaction" or rot=="passive" or rot=="evade" then
- self.rot=rot
+ if rot=="passive" then
+ self.rot=RAT.rot.passive
+ elseif rot=="evade" then
+ self.rot=RAT.rot.evade
else
- self.rot="noreaction"
+ self.rot=RAT.rot.noreaction
end
end
@@ -661,10 +690,14 @@ end
function RAT:_SpawnWithRoute(_departure, _destination)
-- Set takeoff type.
- --if self.takeoff==""
+ local _takeoff=self.takeoff
+ if self.takeoff==RAT.waypoint.coldorhot then
+ local temp={RAT.waypoint.cold, RAT.waypoint.hot}
+ _takeoff=temp[math.random(2)]
+ end
-- Set flight plan.
- local departure, destination, waypoints = self:_SetRoute(_departure, _destination)
+ local departure, destination, waypoints = self:_SetRoute(_takeoff, _departure, _destination)
-- Modify the spawn template to follow the flight plan.
self:_ModifySpawnTemplate(waypoints)
@@ -673,9 +706,11 @@ function RAT:_SpawnWithRoute(_departure, _destination)
local group=self:SpawnWithIndex(self.SpawnIndex) -- Wrapper.Group#GROUP
self.alive=self.alive+1
- -- set ROE to "weapon hold" and ROT to "no reaction"
- self:_SetROE(group)
- self:_SetROT(group)
+ -- Set ROE, default is "weapon hold".
+ self:_SetROE(group, self.roe)
+
+ -- Set ROT, default is "no reaction".
+ self:_SetROT(group, self.rot)
-- Init ratcraft array.
self.ratcraft[self.SpawnIndex]={}
@@ -685,6 +720,7 @@ function RAT:_SpawnWithRoute(_departure, _destination)
self.ratcraft[self.SpawnIndex]["waypoints"]=waypoints
self.ratcraft[self.SpawnIndex]["status"]="spawned"
self.ratcraft[self.SpawnIndex]["airborn"]=group:InAir()
+ -- Time and position on ground. For check if aircraft is stuck somewhere.
if group:InAir() then
self.ratcraft[self.SpawnIndex]["Tground"]=nil
self.ratcraft[self.SpawnIndex]["Pground"]=nil
@@ -692,17 +728,34 @@ function RAT:_SpawnWithRoute(_departure, _destination)
self.ratcraft[self.SpawnIndex]["Tground"]=timer.getTime()
self.ratcraft[self.SpawnIndex]["Pground"]=group:GetCoordinate()
end
+ -- Initial and current position. For calculating the travelled distance.
self.ratcraft[self.SpawnIndex]["P0"]=group:GetCoordinate()
self.ratcraft[self.SpawnIndex]["Pnow"]=group:GetCoordinate()
self.ratcraft[self.SpawnIndex]["Distance"]=0
+ -- Each aircraft gets its own takeoff type and randomized route.
+ self.ratcraft[self.SpawnIndex]["takeoff"]=_takeoff
+ self.ratcraft[self.SpawnIndex]["random_departure"]=self.random_departure
+ self.ratcraft[self.SpawnIndex]["random_destination"]=self.random_destination
-- Create submenu for this group.
if self.f10menu then
local name=self.aircraft.type.." ID "..tostring(self.SpawnIndex)
- self.Menu[self.SpawnTemplatePrefix].groups[self.SpawnIndex]=MENU_MISSION:New(name, self.Menu[self.SpawnTemplatePrefix].groups)
- MENU_MISSION_COMMAND:New("Status report", self.Menu[self.SpawnTemplatePrefix].groups[self.SpawnIndex], self.Status, self, true, self.SpawnIndex)
- MENU_MISSION_COMMAND:New("Place markers", self.Menu[self.SpawnTemplatePrefix].groups[self.SpawnIndex], self._PlaceMarkers, self, waypoints)
- MENU_MISSION_COMMAND:New("Despawn group", self.Menu[self.SpawnTemplatePrefix].groups[self.SpawnIndex], self._Despawn, self, group)
+ -- F10/RAT//Group X
+ self.Menu[self.SubMenuName].groups[self.SpawnIndex]=MENU_MISSION:New(name, self.Menu[self.SubMenuName].groups)
+ -- F10/RAT//Group X/ROT
+ self.Menu[self.SubMenuName].groups[self.SpawnIndex].roe=MENU_MISSION:New("Set ROE", self.Menu[self.SubMenuName].groups)
+ MENU_MISSION_COMMAND:New("Weapons hold", self.Menu[self.SubMenuName].groups[self.SpawnIndex].roe, self._SetROE, self, RAT.roe.weaponhold)
+ MENU_MISSION_COMMAND:New("Weapons free", self.Menu[self.SubMenuName].groups[self.SpawnIndex].roe, self._SetROE, self, RAT.roe.weaponfree)
+ MENU_MISSION_COMMAND:New("Return fire", self.Menu[self.SubMenuName].groups[self.SpawnIndex].roe, self._SetROE, self, RAT.roe.returnfire)
+ -- F10/RAT//Group X/ROT
+ self.Menu[self.SubMenuName].groups[self.SpawnIndex].rot=MENU_MISSION:New("Set ROT", self.Menu[self.SubMenuName].groups)
+ MENU_MISSION_COMMAND:New("Weapons hold", self.Menu[self.SubMenuName].groups[self.SpawnIndex].rot, self._SetROT, self, RAT.rot.noreaction)
+ MENU_MISSION_COMMAND:New("Weapons free", self.Menu[self.SubMenuName].groups[self.SpawnIndex].rot, self._SetROT, self, RAT.rot.passive)
+ MENU_MISSION_COMMAND:New("Return fire", self.Menu[self.SubMenuName].groups[self.SpawnIndex].rot, self._SetROT, self, RAT.rot.evade)
+ -- F10/RAT//Group X/
+ MENU_MISSION_COMMAND:New("Status report", self.Menu[self.SubMenuName].groups[self.SpawnIndex], self.Status, self, true, self.SpawnIndex)
+ MENU_MISSION_COMMAND:New("Place markers", self.Menu[self.SubMenuName].groups[self.SpawnIndex], self._PlaceMarkers, self, waypoints)
+ MENU_MISSION_COMMAND:New("Despawn group", self.Menu[self.SubMenuName].groups[self.SpawnIndex], self._Despawn, self, group)
end
end
@@ -744,12 +797,13 @@ 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.waypoint Takeoff type.
-- @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.
-function RAT:_SetRoute(_departure, _destination)
+function RAT:_SetRoute(takeoff, _departure, _destination)
-- Min cruise speed 70% of Vmax or 600 km/h whichever is lower.
local VxCruiseMin = math.min(self.aircraft.Vmax*0.70, 166)
@@ -788,12 +842,12 @@ function RAT:_SetRoute(_departure, _destination)
if _departure then
departure=AIRBASE:FindByName(_departure)
else
- departure=self:_PickDeparture()
+ departure=self:_PickDeparture(takeoff)
end
-- Coordinates of departure point.
local Pdeparture
- if self.takeoff==RAT.waypoint.air then
+ if takeoff==RAT.waypoint.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)
@@ -804,7 +858,7 @@ function RAT:_SetRoute(_departure, _destination)
-- Height ASL of departure point.
local H_departure
- if self.takeoff==RAT.waypoint.air then
+ if takeoff==RAT.waypoint.air then
-- Departure altitude is 70% of default cruise with 30% variation and limited to 1000 m AGL (50 m for helos).
local Hmin
if self.category==RAT.cat.plane then
@@ -997,7 +1051,7 @@ function RAT:_SetRoute(_departure, _destination)
local c6=Pdestination
--Convert coordinates into route waypoints.
- local wp0=self:_Waypoint(self.takeoff, c0, VxClimb, H_departure, departure)
+ local wp0=self:_Waypoint(takeoff, c0, VxClimb, H_departure, departure)
local wp1=self:_Waypoint(RAT.waypoint.climb, c1, VxClimb, H_departure+(FLcruise-H_departure)/2)
local wp2=self:_Waypoint(RAT.waypoint.cruise, c2, VxCruise, FLcruise)
local wp3=self:_Waypoint(RAT.waypoint.cruise, c3, VxCruise, FLcruise)
@@ -1026,14 +1080,15 @@ end
--- Set the departure airport of the AI. If no airport name is given explicitly an airport from the coalition is chosen randomly.
-- If takeoff style is set to "air", we use zones around the airports or the zones specified by user input.
-- @param #RAT self
+-- @param #number takeoff Takeoff type.
-- @return Wrapper.Airbase#AIRBASE Departure airport if spawning at airport.
-- @return Coore.Zone#ZONE Departure zone if spawning in air.
-function RAT:_PickDeparture()
+function RAT:_PickDeparture(takeoff)
-- Array of possible departure airports or zones.
local departures={}
- if self.takeoff==RAT.waypoint.air then
+ if takeoff==RAT.waypoint.air then
if self.random_departure then
@@ -1078,7 +1133,7 @@ function RAT:_PickDeparture()
local departure=departures[math.random(#departures)]
local text
- if self.takeoff==RAT.waypoint.air then
+ if takeoff==RAT.waypoint.air then
text="Chosen departure zone: "..departure:GetName()
else
text="Chosen departure airport: "..departure:GetName().." (ID "..departure:GetID()..")"
@@ -2025,10 +2080,11 @@ end
--- Set ROE for a group.
-- @param #RAT self
-- @param Wrapper.Group#GROUP group Group for which the ROE is set.
-function RAT:_SetROE(group)
- if self.roe=="return" then
+-- @param #string roe ROE of group.
+function RAT:_SetROE(group, roe)
+ if self.roe==RAT.roe.returnfire then
group:OptionROEReturnFire()
- elseif self.roe=="free" then
+ elseif self.roe==RAT.roe.weaponfree then
group:OptionROEWeaponFree()
else
group:OptionROEHoldFire()
@@ -2039,10 +2095,11 @@ end
--- Set ROT for a group.
-- @param #RAT self
-- @param Wrapper.Group#GROUP group Group for which the ROT is set.
-function RAT:_SetROT(group)
- if self.roe=="passive" then
+-- @param #string rot ROT of group.
+function RAT:_SetROT(group, rot)
+ if self.rot==RAT.rot.passive then
group:OptionROTPassiveDefense()
- elseif self.roe=="evade" then
+ elseif self.rot==RAT.rot.evade then
group:OptionROTEvadeFire()
else
group:OptionROTNoReaction()
From e205af75ca0eeef4e1b3c18e26903645b73382ea Mon Sep 17 00:00:00 2001
From: funkyfranky
Date: Fri, 8 Sep 2017 23:22:33 +0200
Subject: [PATCH 23/28] Fixed bugs introduced in last update.
Added delete markers to F10 menu.
---
Moose Development/Moose/AI/AI_RAT.lua | 99 ++++++++++++++++++---------
1 file changed, 65 insertions(+), 34 deletions(-)
diff --git a/Moose Development/Moose/AI/AI_RAT.lua b/Moose Development/Moose/AI/AI_RAT.lua
index a594b5c16..8e944b35a 100644
--- a/Moose Development/Moose/AI/AI_RAT.lua
+++ b/Moose Development/Moose/AI/AI_RAT.lua
@@ -50,6 +50,7 @@
-- @field #string SubMenuName
-- @field #boolean respawn_after_landing
-- @field #number respawn_delay
+-- @field #table markerids
-- @field #table RAT
-- @extends Functional.Spawn#SPAWN
@@ -96,6 +97,7 @@ RAT={
SubMenuName=nil, -- Submenu name for RAT object.
respawn_at_landing=false, -- Respawn aircraft the moment they land rather than at engine shutdown.
respawn_delay=nil, -- Delay in seconds until repawn happens after landing.
+ markerids={}, -- Array with marker IDs.
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -146,19 +148,19 @@ RAT.unit={
}
--- RAT rules of engagement.
--- @field #RAT roe
-RAT.roe={
+-- @field #RAT ROT
+RAT.ROE={
weaponhold="hold",
weaponfree="free",
returnfire="return",
}
--- RAT reaction to threat.
--- @field #RAT rot
-RAT.rot{
+-- @field #RAT ROT
+RAT.ROT={
evade="evade",
passive="passive",
- noreaction="noreaction"
+ noreaction="noreaction",
}
--- Running number of placed markers on the F10 map.
@@ -197,6 +199,7 @@ myid="RAT | "
--DONE: Add markers to F10 menu.
--TODO: Add respawn limit.
--DONE: Make takeoff method random between cold and hot start.
+--TODO: Check out uncontrolled spawning.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -208,6 +211,9 @@ myid="RAT | "
-- @usage yak:RAT("RAT_YAK") will create a RAT object called "yak". The template group in the mission editor must have the name "RAT_YAK".
function RAT:New(groupname)
+ -- Welcome message.
+ env.info(myid.."Creating new RAT object from template: "..groupname)
+
-- Inherit SPAWN clase.
local self=BASE:Inherit(self, SPAWN:New(groupname)) -- #RAT
@@ -270,18 +276,18 @@ function RAT:Spawn(naircraft)
text=text..string.format("Vclimb: %4.1f\n", self.Vclimb)
text=text..string.format("Vcruisemax: %4.1f\n", self.Vcruisemax)
text=text..string.format("AlphaDescent: %4.2f\n", self.AlphaDescent)
- text=text..string.format("ROE: %s\n", self.roe)
- text=text..string.format("ROT: %s\n", self.rot)
+ text=text..string.format("ROE: %s\n", tostring(self.roe))
+ text=text..string.format("ROT: %s\n", tostring(self.rot))
text=text..string.format("Min dist: %4.1f\n", self.mindist)
text=text..string.format("Max dist: %4.1f\n", self.maxdist)
- text=text..string.format("Report status: %s\n", tostring(self.airports))
+ text=text..string.format("Report status: %s\n", tostring(self.reportstatus))
text=text..string.format("Status interval: %4.1f\n", self.statusinterval)
text=text..string.format("Place markers: %s\n", tostring(self.placemarkers))
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("Respawn after landing: %s\n", tostring(self.respawn_after_landing))
- text=text..string.format("Respawn after landing: %s\n", tostring(self.respawn_delay))
+ text=text..string.format("Respawn delay: %s\n", tostring(self.respawn_delay))
-- @field #number FLminuser
text=text..string.format("******************************************************\n")
env.info(myid..text)
@@ -292,8 +298,9 @@ function RAT:Spawn(naircraft)
self.SubMenuName=self.SpawnTemplatePrefix
self.Menu[self.SubMenuName]=MENU_MISSION:New(self.SubMenuName, RAT.MenuF10)
self.Menu[self.SubMenuName]["groups"]=MENU_MISSION:New("Groups", self.Menu[self.SubMenuName])
- MENU_MISSION_COMMAND:New("Status report", self.Menu[self.SubMenuName], self.Status, self, true)
MENU_MISSION_COMMAND:New("Spawn new group", self.Menu[self.SubMenuName], self._SpawnWithRoute, self)
+ MENU_MISSION_COMMAND:New("Delete markers", self.Menu[self.SubMenuName], self._DeleteMarkers, self, self.markerids)
+ MENU_MISSION_COMMAND:New("Status report", self.Menu[self.SubMenuName], self.Status, self, true)
end
-- Schedule spawning of aircraft.
@@ -511,11 +518,11 @@ end
-- @param #string roe "hold" = weapon hold, "return" = return fire, "free" = weapons free.
function RAT:SetROE(roe)
if roe=="return" then
- self.roe=RAT.roe.returnfire
+ self.roe=RAT.ROE.returnfire
elseif roe=="free" then
- self.roe=RAT.roe.weaponfree
+ self.roe=RAT.ROE.weaponfree
else
- self.roe=RAT.roe.weaponhold
+ self.roe=RAT.ROE.weaponhold
end
end
@@ -524,11 +531,11 @@ end
-- @param #string rot "noreaction = no reactino, "passive" = passive defence, "evade" = weapons free.
function RAT:SetROT(rot)
if rot=="passive" then
- self.rot=RAT.rot.passive
+ self.rot=RAT.ROT.passive
elseif rot=="evade" then
- self.rot=RAT.rot.evade
+ self.rot=RAT.ROT.evade
else
- self.rot=RAT.rot.noreaction
+ self.rot=RAT.ROT.noreaction
end
end
@@ -694,6 +701,7 @@ function RAT:_SpawnWithRoute(_departure, _destination)
if self.takeoff==RAT.waypoint.coldorhot then
local temp={RAT.waypoint.cold, RAT.waypoint.hot}
_takeoff=temp[math.random(2)]
+ env.info(myid.."Random takeoff type: ".._takeoff)
end
-- Set flight plan.
@@ -743,19 +751,20 @@ function RAT:_SpawnWithRoute(_departure, _destination)
-- F10/RAT//Group X
self.Menu[self.SubMenuName].groups[self.SpawnIndex]=MENU_MISSION:New(name, self.Menu[self.SubMenuName].groups)
-- F10/RAT//Group X/ROT
- self.Menu[self.SubMenuName].groups[self.SpawnIndex].roe=MENU_MISSION:New("Set ROE", self.Menu[self.SubMenuName].groups)
- MENU_MISSION_COMMAND:New("Weapons hold", self.Menu[self.SubMenuName].groups[self.SpawnIndex].roe, self._SetROE, self, RAT.roe.weaponhold)
- MENU_MISSION_COMMAND:New("Weapons free", self.Menu[self.SubMenuName].groups[self.SpawnIndex].roe, self._SetROE, self, RAT.roe.weaponfree)
- MENU_MISSION_COMMAND:New("Return fire", self.Menu[self.SubMenuName].groups[self.SpawnIndex].roe, self._SetROE, self, RAT.roe.returnfire)
+ self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"]=MENU_MISSION:New("Set ROE", self.Menu[self.SubMenuName].groups[self.SpawnIndex])
+ MENU_MISSION_COMMAND:New("Weapons hold", self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"], self._SetROE, self, group, RAT.ROE.weaponhold)
+ MENU_MISSION_COMMAND:New("Weapons free", self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"], self._SetROE, self, group, RAT.ROE.weaponfree)
+ MENU_MISSION_COMMAND:New("Return fire", self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"], self._SetROE, self, group, RAT.ROE.returnfire)
-- F10/RAT//Group X/ROT
- self.Menu[self.SubMenuName].groups[self.SpawnIndex].rot=MENU_MISSION:New("Set ROT", self.Menu[self.SubMenuName].groups)
- MENU_MISSION_COMMAND:New("Weapons hold", self.Menu[self.SubMenuName].groups[self.SpawnIndex].rot, self._SetROT, self, RAT.rot.noreaction)
- MENU_MISSION_COMMAND:New("Weapons free", self.Menu[self.SubMenuName].groups[self.SpawnIndex].rot, self._SetROT, self, RAT.rot.passive)
- MENU_MISSION_COMMAND:New("Return fire", self.Menu[self.SubMenuName].groups[self.SpawnIndex].rot, self._SetROT, self, RAT.rot.evade)
+ self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"]=MENU_MISSION:New("Set ROT", self.Menu[self.SubMenuName].groups[self.SpawnIndex])
+ MENU_MISSION_COMMAND:New("No reaction", self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"], self._SetROT, self, group, RAT.ROT.noreaction)
+ MENU_MISSION_COMMAND:New("Passive defense", self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"], self._SetROT, self, group, RAT.ROT.passive)
+ MENU_MISSION_COMMAND:New("Evade on fire", self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"], self._SetROT, self, group, RAT.ROT.evade)
-- F10/RAT//Group X/
- MENU_MISSION_COMMAND:New("Status report", self.Menu[self.SubMenuName].groups[self.SpawnIndex], self.Status, self, true, self.SpawnIndex)
- MENU_MISSION_COMMAND:New("Place markers", self.Menu[self.SubMenuName].groups[self.SpawnIndex], self._PlaceMarkers, self, waypoints)
- MENU_MISSION_COMMAND:New("Despawn group", self.Menu[self.SubMenuName].groups[self.SpawnIndex], self._Despawn, self, group)
+ 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("Delete markers", self.Menu[self.SubMenuName].groups[self.SpawnIndex], self._DelateMarkers, self, markers)
+ MENU_MISSION_COMMAND:New("Status report", self.Menu[self.SubMenuName].groups[self.SpawnIndex], self.Status, self, true, self.SpawnIndex)
end
end
@@ -1025,9 +1034,9 @@ function RAT:_SetRoute(takeoff, _departure, _destination)
text=text..string.format("h_climb = %6.1f m\n", h_climb)
text=text..string.format("h_descent = %6.1f m\n", h_descent)
text=text..string.format("h_holding = %6.1f m\n", h_holding)
- text=text..string.format("FLmin = %6.1f m ASL\n", FLmin)
- text=text..string.format("FLmax = %6.1f m ASL\n", FLmax)
- text=text..string.format("FLcruise = %6.1f m ASL\n", FLcruise)
+ text=text..string.format("FLmin = %6.1f m ASL = FL%03d\n", FLmin, FLmin/RAT.unit.FL2m)
+ text=text..string.format("FLcruise = %6.1f m ASL = FL%03d\n", FLcruise, FLcruise/RAT.unit.FL2m)
+ text=text..string.format("FLmax = %6.1f m ASL = FL%03d\n", FLmax, FLmax/RAT.unit.FL2m)
text=text..string.format("\nAngles:\n")
text=text..string.format("Alpha climb = %6.1f Deg\n", math.deg(AlphaClimb))
text=text..string.format("Alpha descent = %6.1f Deg\n", math.deg(AlphaDescent))
@@ -1976,6 +1985,7 @@ function RAT:_ModifySpawnTemplate(waypoints)
-- Also modify x,y of the template. Not sure why.
SpawnTemplate.x = PointVec3.x
SpawnTemplate.y = PointVec3.z
+ --SpawnTemplate.uncontrolled=true
-- Update modified template for spawn group.
self.SpawnGroups[self.SpawnIndex].SpawnTemplate=SpawnTemplate
@@ -2082,12 +2092,16 @@ end
-- @param Wrapper.Group#GROUP group Group for which the ROE is set.
-- @param #string roe ROE of group.
function RAT:_SetROE(group, roe)
- if self.roe==RAT.roe.returnfire then
+ env.info(myid.."Setting ROE to "..roe.." for group "..group:GetName())
+ if self.roe==RAT.ROE.returnfire then
group:OptionROEReturnFire()
- elseif self.roe==RAT.roe.weaponfree then
+ env.info(myid.."ROE return fire")
+ elseif self.roe==RAT.ROE.weaponfree then
group:OptionROEWeaponFree()
+ env.info(myid.."ROE weapons free")
else
group:OptionROEHoldFire()
+ env.info(myid.."ROE hold fire")
end
end
@@ -2097,9 +2111,10 @@ end
-- @param Wrapper.Group#GROUP group Group for which the ROT is set.
-- @param #string rot ROT of group.
function RAT:_SetROT(group, rot)
- if self.rot==RAT.rot.passive then
+ env.info(myid.."Setting ROT to "..rot.." for group "..group:GetName())
+ if self.rot==RAT.ROT.passive then
group:OptionROTPassiveDefense()
- elseif self.rot==RAT.rot.evade then
+ elseif self.rot==RAT.ROT.evade then
group:OptionROTEvadeFire()
else
group:OptionROTNoReaction()
@@ -2217,6 +2232,7 @@ end
-- @param #table wp Position of marker coming in as waypoint, i.e. has x, y and alt components.
function RAT:_SetMarker(text, wp)
RAT.markerid=RAT.markerid+1
+ table.insert(self.markerids,RAT.markerid)
if self.debug then
env.info(myid.."Placing marker with ID "..RAT.markerid..": "..text)
end
@@ -2226,4 +2242,19 @@ function RAT:_SetMarker(text, wp)
trigger.action.markToAll(RAT.markerid, text, vec)
end
+--- Delete all markers on F10 map.
+-- @param #RAT self
+-- @param #table ids (Optional) Table holding the marker IDs to be deleted.
+function RAT:_DeleteMarkers(ids)
+ if ids then
+ for k,v in pairs(ids) do
+ trigger.action.removeMark(k)
+ end
+ else
+ for i=1,RAT.markerid do
+ trigger.action.removeMark(i)
+ end
+ end
+end
+
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
\ No newline at end of file
From 5d627d91d85469e98b60cbc0017042e3d85d05c2 Mon Sep 17 00:00:00 2001
From: funkyfranky
Date: Sat, 9 Sep 2017 18:23:57 +0200
Subject: [PATCH 24/28] Moved RAT to functional.
---
.../{AI/AI_RAT.lua => Functional/RAT.lua} | 148 +-
Moose Mission Setup/Moose.files | 2 +-
Moose Mission Setup/Moose.lua | 4 +-
docs/Documentation/AI_A2A.html | 1 +
docs/Documentation/AI_A2A_Cap.html | 1 +
docs/Documentation/AI_A2A_Dispatcher.html | 33 +-
docs/Documentation/AI_A2A_GCI.html | 1 +
docs/Documentation/AI_A2A_Patrol.html | 1 +
docs/Documentation/AI_BAI.html | 1 +
docs/Documentation/AI_Balancer.html | 1 +
docs/Documentation/AI_Cap.html | 1 +
docs/Documentation/AI_Cas.html | 1 +
docs/Documentation/AI_Formation.html | 1 +
docs/Documentation/AI_Patrol.html | 1 +
docs/Documentation/AI_RAT.html | 3196 ++++++++++++++++
docs/Documentation/Account.html | 1 +
docs/Documentation/Airbase.html | 1 +
docs/Documentation/AirbasePolice.html | 1 +
docs/Documentation/Assign.html | 1 +
docs/Documentation/Base.html | 1 +
docs/Documentation/Cargo.html | 2 +-
docs/Documentation/CleanUp.html | 1 +
docs/Documentation/Client.html | 1 +
docs/Documentation/CommandCenter.html | 1 +
docs/Documentation/Controllable.html | 1 +
docs/Documentation/DCSAirbase.html | 1 +
docs/Documentation/DCSCoalitionObject.html | 1 +
docs/Documentation/DCSCommand.html | 1 +
docs/Documentation/DCSController.html | 1 +
docs/Documentation/DCSGroup.html | 1 +
docs/Documentation/DCSObject.html | 1 +
docs/Documentation/DCSTask.html | 1 +
docs/Documentation/DCSTypes.html | 1 +
docs/Documentation/DCSUnit.html | 1 +
docs/Documentation/DCSVec3.html | 1 +
docs/Documentation/DCSWorld.html | 1 +
docs/Documentation/DCSZone.html | 1 +
docs/Documentation/DCScountry.html | 1 +
docs/Documentation/DCStimer.html | 1 +
docs/Documentation/DCStrigger.html | 1 +
docs/Documentation/Database.html | 1 +
docs/Documentation/Designate.html | 22 +
docs/Documentation/Detection.html | 3 +-
docs/Documentation/DetectionManager.html | 1 +
docs/Documentation/Escort.html | 1 +
docs/Documentation/Event.html | 1 +
docs/Documentation/Fsm.html | 4 +-
docs/Documentation/Group.html | 1 +
docs/Documentation/Identifiable.html | 1 +
docs/Documentation/Menu.html | 1 +
docs/Documentation/Message.html | 1 +
docs/Documentation/MissileTrainer.html | 1 +
docs/Documentation/Mission.html | 1 +
docs/Documentation/Movement.html | 1 +
docs/Documentation/Object.html | 1 +
docs/Documentation/Point.html | 1 +
docs/Documentation/Positionable.html | 2 +-
docs/Documentation/Process_JTAC.html | 1 +
docs/Documentation/Process_Pickup.html | 1 +
docs/Documentation/RAT.html | 3219 +++++++++++++++++
docs/Documentation/Radio.html | 1 +
docs/Documentation/Route.html | 1 +
docs/Documentation/Scenery.html | 1 +
docs/Documentation/ScheduleDispatcher.html | 1 +
docs/Documentation/Scheduler.html | 1 +
docs/Documentation/Scoring.html | 1 +
docs/Documentation/Sead.html | 1 +
docs/Documentation/Set.html | 1 +
docs/Documentation/Settings.html | 1 +
docs/Documentation/Smoke.html | 1 +
docs/Documentation/Spawn.html | 36 +-
docs/Documentation/SpawnStatic.html | 1 +
docs/Documentation/Spot.html | 5 +-
docs/Documentation/Static.html | 1 +
docs/Documentation/StaticObject.html | 1 +
docs/Documentation/Task.html | 1 +
docs/Documentation/Task_A2A.html | 1 +
docs/Documentation/Task_A2A_Dispatcher.html | 1 +
docs/Documentation/Task_A2G.html | 1 +
docs/Documentation/Task_A2G_Dispatcher.html | 1 +
docs/Documentation/Task_Cargo.html | 5 +-
docs/Documentation/Task_PICKUP.html | 1 +
docs/Documentation/Unit.html | 1 +
docs/Documentation/Utils.html | 1 +
docs/Documentation/Zone.html | 1 +
docs/Documentation/env.html | 1 +
docs/Documentation/index.html | 9 +
docs/Documentation/land.html | 1 +
docs/Documentation/routines.html | 1 +
docs/Presentations/RAT/RAT.png | Bin 0 -> 623771 bytes
90 files changed, 6679 insertions(+), 85 deletions(-)
rename Moose Development/Moose/{AI/AI_RAT.lua => Functional/RAT.lua} (95%)
create mode 100644 docs/Documentation/AI_RAT.html
create mode 100644 docs/Documentation/RAT.html
create mode 100644 docs/Presentations/RAT/RAT.png
diff --git a/Moose Development/Moose/AI/AI_RAT.lua b/Moose Development/Moose/Functional/RAT.lua
similarity index 95%
rename from Moose Development/Moose/AI/AI_RAT.lua
rename to Moose Development/Moose/Functional/RAT.lua
index 8e944b35a..2194704de 100644
--- a/Moose Development/Moose/AI/AI_RAT.lua
+++ b/Moose Development/Moose/Functional/RAT.lua
@@ -1,7 +1,37 @@
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-
---- ** AI **
--- @module AI_RAT
+--- **Functional** -- Create random airtraffic in your missions.
+--
+-- 
+--
+-- ====
+--
+-- The documentation of the RAT class can be found further in this document.
+--
+-- ====
+--
+-- # Demo Missions
+--
+-- ### [RAT Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/SPA%20-%20Spawning)
+--
+-- ### [RAT Demo Missions, only for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SPA%20-%20Spawning)
+--
+-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
+--
+-- ====
+--
+-- # YouTube Channel
+--
+-- ### [RAT 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 Rat
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -11,7 +41,7 @@
--- RAT class
-- @type RAT
--- @field #string ClassName
+-- @field #string ClassName Name of the Class
-- @field #boolean debug
-- @field #number spawndelay
-- @field #number spawninterval
@@ -54,6 +84,11 @@
-- @field #table RAT
-- @extends Functional.Spawn#SPAWN
+--- # RAT class, extends @{Spawn#SPAWN}
+--
+-- The RAT class allows to easily create random air traffic in your missions.
+--
+
--- RAT class
-- @field #RAT RAT
@@ -102,16 +137,16 @@ RAT={
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
---- RAT categories.
+--- Categories of the RAT class.
-- @field #RAT cat
RAT.cat={
plane="plane",
- heli="heli"
+ heli="heli",
}
---- RAT takeoff style.
--- @field #RAT waypoint
-RAT.waypoint={
+--- RAT waypoint type.
+-- @field #RAT wp
+RAT.wp={
coldorhot=0,
air=1,
runway=2,
@@ -148,7 +183,7 @@ RAT.unit={
}
--- RAT rules of engagement.
--- @field #RAT ROT
+-- @field #RAT ROE
RAT.ROE={
weaponhold="hold",
weaponfree="free",
@@ -200,6 +235,12 @@ myid="RAT | "
--TODO: Add respawn limit.
--DONE: Make takeoff method random between cold and hot start.
--TODO: Check out uncontrolled spawning.
+--TODO: Check aircraft spawning in air at Sochi after third aircraft was spawned.
+--TODO: Improve despawn after stationary. Might lead to despawning if many aircraft spawn at the same time.
+--TODO: Check why birth event is not handled.
+--TODO: Improve behaviour when no destination or departure airports were found. Leads to crash, e.g. 1184: attempt to get length of local 'destinations' (a nil value)
+--TODO: Check cases where aircraft get shot down. Respawn?
+--TODO: 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!
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -217,7 +258,7 @@ function RAT:New(groupname)
-- Inherit SPAWN clase.
local self=BASE:Inherit(self, SPAWN:New(groupname)) -- #RAT
- -- Get template group defined in the mission editor.
+ -- Get template group defined in the mission editor.
local DCSgroup=Group.getByName(groupname)
-- Check the group actually exists.
@@ -307,7 +348,7 @@ function RAT:Spawn(naircraft)
local Tstart=self.spawndelay
local dt=self.spawninterval
-- Ensure that interval is >= 180 seconds if spawn at runway is chosen. Aircraft need time to takeoff or the runway gets jammed.
- if self.takeoff==RAT.waypoint.runway then
+ if self.takeoff==RAT.wp.runway then
dt=math.max(dt, 180)
end
local Tstop=Tstart+dt*(naircraft-1)
@@ -317,7 +358,7 @@ function RAT:Spawn(naircraft)
SCHEDULER:New(nil, self.Status, {self}, Tstart+1, self.statusinterval)
-- Handle events.
- self:HandleEvent(EVENTS.Birth, self._OnBirthDay)
+ self:HandleEvent(EVENTS.Birth, self._OnBirth)
self:HandleEvent(EVENTS.EngineStartup, self._EngineStartup)
self:HandleEvent(EVENTS.Takeoff, self._OnTakeoff)
self:HandleEvent(EVENTS.Land, self._OnLand)
@@ -369,15 +410,15 @@ function RAT:SetTakeoff(type)
local _Type
if type:lower()=="takeoff-cold" or type:lower()=="cold" then
- _Type=RAT.waypoint.cold
+ _Type=RAT.wp.cold
elseif type:lower()=="takeoff-hot" or type:lower()=="hot" then
- _Type=RAT.waypoint.hot
+ _Type=RAT.wp.hot
elseif type:lower()=="takeoff-runway" or type:lower()=="runway" then
- _Type=RAT.waypoint.runway
+ _Type=RAT.wp.runway
elseif type:lower()=="air" then
- _Type=RAT.waypoint.air
+ _Type=RAT.wp.air
else
- _Type=RAT.waypoint.coldorhot
+ _Type=RAT.wp.coldorhot
end
self.takeoff=_Type
@@ -698,11 +739,12 @@ function RAT:_SpawnWithRoute(_departure, _destination)
-- Set takeoff type.
local _takeoff=self.takeoff
- if self.takeoff==RAT.waypoint.coldorhot then
- local temp={RAT.waypoint.cold, RAT.waypoint.hot}
+ if self.takeoff==RAT.wp.coldorhot then
+ local temp={RAT.wp.cold, RAT.wp.hot}
_takeoff=temp[math.random(2)]
env.info(myid.."Random takeoff type: ".._takeoff)
end
+ env.info(myid.."Takeoff type: ".._takeoff)
-- Set flight plan.
local departure, destination, waypoints = self:_SetRoute(_takeoff, _departure, _destination)
@@ -750,12 +792,12 @@ function RAT:_SpawnWithRoute(_departure, _destination)
local name=self.aircraft.type.." ID "..tostring(self.SpawnIndex)
-- F10/RAT//Group X
self.Menu[self.SubMenuName].groups[self.SpawnIndex]=MENU_MISSION:New(name, self.Menu[self.SubMenuName].groups)
- -- F10/RAT//Group X/ROT
+ -- F10/RAT//Group X/Set ROE
self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"]=MENU_MISSION:New("Set ROE", self.Menu[self.SubMenuName].groups[self.SpawnIndex])
MENU_MISSION_COMMAND:New("Weapons hold", self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"], self._SetROE, self, group, RAT.ROE.weaponhold)
MENU_MISSION_COMMAND:New("Weapons free", self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"], self._SetROE, self, group, RAT.ROE.weaponfree)
MENU_MISSION_COMMAND:New("Return fire", self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"], self._SetROE, self, group, RAT.ROE.returnfire)
- -- F10/RAT//Group X/ROT
+ -- F10/RAT//Group X/Set ROT
self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"]=MENU_MISSION:New("Set ROT", self.Menu[self.SubMenuName].groups[self.SpawnIndex])
MENU_MISSION_COMMAND:New("No reaction", self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"], self._SetROT, self, group, RAT.ROT.noreaction)
MENU_MISSION_COMMAND:New("Passive defense", self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"], self._SetROT, self, group, RAT.ROT.passive)
@@ -806,13 +848,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.waypoint Takeoff type.
+-- @param takeoff #RAT.wp Takeoff type.
-- @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.
function RAT:_SetRoute(takeoff, _departure, _destination)
+
+ env.info(myid.."takeoff in _setroute: "..takeoff)
-- Min cruise speed 70% of Vmax or 600 km/h whichever is lower.
local VxCruiseMin = math.min(self.aircraft.Vmax*0.70, 166)
@@ -856,7 +900,7 @@ function RAT:_SetRoute(takeoff, _departure, _destination)
-- Coordinates of departure point.
local Pdeparture
- if takeoff==RAT.waypoint.air then
+ 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)
@@ -867,7 +911,7 @@ function RAT:_SetRoute(takeoff, _departure, _destination)
-- Height ASL of departure point.
local H_departure
- if takeoff==RAT.waypoint.air then
+ 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).
local Hmin
if self.category==RAT.cat.plane then
@@ -1061,12 +1105,12 @@ function RAT:_SetRoute(takeoff, _departure, _destination)
--Convert coordinates into route waypoints.
local wp0=self:_Waypoint(takeoff, c0, VxClimb, H_departure, departure)
- local wp1=self:_Waypoint(RAT.waypoint.climb, c1, VxClimb, H_departure+(FLcruise-H_departure)/2)
- local wp2=self:_Waypoint(RAT.waypoint.cruise, c2, VxCruise, FLcruise)
- local wp3=self:_Waypoint(RAT.waypoint.cruise, c3, VxCruise, FLcruise)
- local wp4=self:_Waypoint(RAT.waypoint.descent, c4, VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2)
- local wp5=self:_Waypoint(RAT.waypoint.holding, c5, VxHolding, H_holding+h_holding)
- local wp6=self:_Waypoint(RAT.waypoint.landing, c6, VxFinal, H_destination, destination)
+ 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
local waypoints = {wp0, wp1, wp2, wp3, wp4, wp5, wp6}
@@ -1097,7 +1141,7 @@ function RAT:_PickDeparture(takeoff)
-- Array of possible departure airports or zones.
local departures={}
- if takeoff==RAT.waypoint.air then
+ if takeoff==RAT.wp.air then
if self.random_departure then
@@ -1142,7 +1186,7 @@ function RAT:_PickDeparture(takeoff)
local departure=departures[math.random(#departures)]
local text
- if takeoff==RAT.waypoint.air then
+ if takeoff==RAT.wp.air then
text="Chosen departure zone: "..departure:GetName()
else
text="Chosen departure airport: "..departure:GetName().." (ID "..departure:GetID()..")"
@@ -1466,7 +1510,7 @@ end
--- Function is executed when a unit is spawned.
-- @param #RAT self
-function RAT:_OnBirthDay(EventData)
+function RAT:_OnBirth(EventData)
env.info(myid.."It's a birthday!")
@@ -1743,7 +1787,7 @@ end
--- Create a waypoint that can be used with the Route command.
-- @param #RAT self
--- @param #string Type Type of waypoint. takeoff-cold, takeoff-hot, takeoff-runway, climb, cruise, descent, holding, land, landing.
+-- @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.
@@ -1751,6 +1795,8 @@ end
-- @return #table Waypoints for DCS task route or spawn template.
function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport)
+ env.info(myid.."takeoff in _waypoint: "..Type)
+
-- Altitude of input parameter or y-component of 3D-coordinate.
local _Altitude=Altitude or Coord.y
@@ -1763,53 +1809,53 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport)
local _alttype="RADIO"
local _AID=nil
- if Type==RAT.waypoint.cold then
+ if Type==RAT.wp.cold then
-- take-off with engine off
_Type="TakeOffParking"
_Action="From Parking Area"
_Altitude = 2
_alttype="RADIO"
_AID = Airport:GetID()
- elseif Type==RAT.waypoint.hot then
+ elseif Type==RAT.wp.hot then
-- take-off with engine on
_Type="TakeOffParkingHot"
_Action="From Parking Area Hot"
_Altitude = 2
_alttype="RADIO"
_AID = Airport:GetID()
- elseif Type==RAT.waypoint.runway then
+ elseif Type==RAT.wp.runway then
-- take-off from runway
_Type="TakeOff"
_Action="From Parking Area"
_Altitude = 2
_alttype="RADIO"
_AID = Airport:GetID()
- elseif Type==RAT.waypoint.air then
+ elseif Type==RAT.wp.air then
-- air start
_Type="Turning Point"
_Action="Turning Point"
_alttype="BARO"
- elseif Type==RAT.waypoint.climb then
+ elseif Type==RAT.wp.climb then
_Type="Turning Point"
_Action="Turning Point"
--_Action="Fly Over Point"
_alttype="BARO"
- elseif Type==RAT.waypoint.cruise then
+ elseif Type==RAT.wp.cruise then
_Type="Turning Point"
_Action="Turning Point"
--_Action="Fly Over Point"
_alttype="BARO"
- elseif Type==RAT.waypoint.descent then
+ elseif Type==RAT.wp.descent then
_Type="Turning Point"
_Action="Turning Point"
--_Action="Fly Over Point"
_alttype="BARO"
- elseif Type==RAT.waypoint.holding then
+ elseif Type==RAT.wp.holding then
_Type="Turning Point"
_Action="Turning Point"
--_Action="Fly Over Point"
_alttype="BARO"
- elseif Type==RAT.waypoint.landing then
+ elseif Type==RAT.wp.landing then
_Type="Land"
_Action="Landing"
_Altitude = 2
@@ -1832,7 +1878,7 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport)
text=text..string.format("Land = %6.1f m ASL\n", Hland)
text=text..string.format("Altitude = %6.1f m (%s)\n", _Altitude, _alttype)
if Airport then
- if Type==RAT.waypoint.air then
+ 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())
@@ -1876,7 +1922,7 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport)
["steer"] = 2,
}
-- task
- if Type==RAT.waypoint.holding then
+ 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)
@@ -2233,9 +2279,9 @@ end
function RAT:_SetMarker(text, wp)
RAT.markerid=RAT.markerid+1
table.insert(self.markerids,RAT.markerid)
- if self.debug then
- env.info(myid.."Placing marker with ID "..RAT.markerid..": "..text)
- end
+ --if self.debug then
+ env.info(myid..self.SpawnTemplatePrefix..": placing marker with ID "..RAT.markerid..": "..text)
+ --end
-- Convert to coordinate.
local vec={x=wp.x, y=wp.alt, z=wp.y}
-- Place maker visible for all on the F10 map.
@@ -2248,10 +2294,12 @@ end
function RAT:_DeleteMarkers(ids)
if ids then
for k,v in pairs(ids) do
- trigger.action.removeMark(k)
+ env.info("Deleting maker id v= "..v)
+ trigger.action.removeMark(v)
end
else
for i=1,RAT.markerid do
+ env.info("Deleting maker id i= "..i)
trigger.action.removeMark(i)
end
end
diff --git a/Moose Mission Setup/Moose.files b/Moose Mission Setup/Moose.files
index 1794a4ff2..b50c54d88 100644
--- a/Moose Mission Setup/Moose.files
+++ b/Moose Mission Setup/Moose.files
@@ -40,6 +40,7 @@ Functional/MissileTrainer.lua
Functional/AirbasePolice.lua
Functional/Detection.lua
Functional/Designate.lua
+Functional/RAT.lua
AI/AI_Balancer.lua
AI/AI_A2A.lua
@@ -52,7 +53,6 @@ AI/AI_Cap.lua
AI/AI_Cas.lua
AI/AI_Bai.lua
AI/AI_Formation.lua
-AI/AI_RAT.lua
Actions/Act_Assign.lua
Actions/Act_Route.lua
diff --git a/Moose Mission Setup/Moose.lua b/Moose Mission Setup/Moose.lua
index 0365ba7b0..314c98d50 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: 20170811_0800 funkyfranky' )
+env.info( 'Moose Generation Timestamp: 20170909_1801' )
local base = _G
@@ -60,6 +60,7 @@ __Moose.Include( 'Functional/MissileTrainer.lua' )
__Moose.Include( 'Functional/AirbasePolice.lua' )
__Moose.Include( 'Functional/Detection.lua' )
__Moose.Include( 'Functional/Designate.lua' )
+__Moose.Include( 'Functional/RAT.lua' )
__Moose.Include( 'AI/AI_Balancer.lua' )
__Moose.Include( 'AI/AI_A2A.lua' )
__Moose.Include( 'AI/AI_A2A_Patrol.lua' )
@@ -70,7 +71,6 @@ __Moose.Include( 'AI/AI_Patrol.lua' )
__Moose.Include( 'AI/AI_Cap.lua' )
__Moose.Include( 'AI/AI_Cas.lua' )
__Moose.Include( 'AI/AI_Bai.lua' )
-__Moose.Include( 'AI/AI_RAT.lua' )
__Moose.Include( 'AI/AI_Formation.lua' )
__Moose.Include( 'Actions/Act_Assign.lua' )
__Moose.Include( 'Actions/Act_Route.lua' )
diff --git a/docs/Documentation/AI_A2A.html b/docs/Documentation/AI_A2A.html
index 6d716e063..f8b56c8f1 100644
--- a/docs/Documentation/AI_A2A.html
+++ b/docs/Documentation/AI_A2A.html
@@ -73,6 +73,7 @@
Set the friendly coalitions from which the airports can be used as departure or destination.
+
+
Parameter
+
+
+
+
#string friendly :
+"same"=own coalition+neutral (default), "all"=neutral+red+blue", "sameonly"=own coalition only, "blue"=blue+neutral, "blueonly"=blue, "red"=red+neutral, "redonly"=red, "neutral"=neutral.
+Default is "same", so aircraft will use airports of the coalition their spawn template has plus all neutral airports.
+
+
+
+
Usages:
+
+
yak:SetCoalition("all") will spawn aircraft randomly on airports of any coaliton, i.e. red, blue and neutral, regardless of its own coalition.
+
yak:SetCoalition("redonly") will spawn aircraft randomly on airports belonging to the red coalition _only_.
This can be an airport or a zone defined in the mission editor.
+
+
Parameter
+
+
+
+
#string names :
+Name or table of names of departure airports or zones.
+
+
+
+
Usages:
+
+
RAT:SetDeparture("Sochi-Adler") will spawn RAT objects at Sochi-Adler airport.
+
RAT:SetDeparture({"Sochi-Adler", "Gudauta"}) will spawn RAT aircraft radomly at Sochi-Adler or Gudauta airport.
+
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.
Starting cold at airport, starting hot at airport, starting at runway, starting in the air.
+Default is "takeoff-hot" for a start at airport with engines already running.
+
+
Parameter
+
+
+
+
#string type :
+Type can be "takeoff-cold" or "cold", "takeoff-hot" or "hot", "takeoff-runway" or "runway", "air".
+
+
+
+
Usages:
+
+
RAT:Takeoff("hot") will spawn RAT objects at airports with engines started.
+
RAT:Takeoff("cold") will spawn RAT objects at airports with engines off.
+
RAT:Takeoff("air") will spawn RAT objects in air over random airports or within pre-defined zones.
Calculate the max flight level for a given distance and fixed climb and descent rates.
+
+
+
In other words we have a distance between two airports and want to know how high we
+can climb before we must descent again to arrive at the destination without any level/cruising part.
+
+
Parameters
+
+
+
+
#number alpha :
+Angle of climb [rad].
+
+
+
+
+
#number beta :
+Angle of descent [rad].
+
+
+
+
+
#number d :
+Distance between the two airports [m].
+
+
+
+
+
#number phi :
+Angle between departure and destination [rad].
+
+
+
+
+
#number h0 :
+Height [m] of departure airport. Note we implicitly assume that the height difference between departure and destination is negligible.
In particular, the waypoints of the group's flight plan are copied into the spawn template.
+This allows to spawn at airports and also land at other airports, i.e. circumventing the DCS "landing bug".
+
+
Parameter
+
+
+
+
#table waypoints :
+The waypoints of the AI flight plan.
If no airport name is given explicitly an airport from the coalition is chosen randomly.
+If takeoff style is set to "air", we use zones around the airports or the zones specified by user input.
Sets the departure and destination airports and waypoints.
+Modifies the spawn template.
+Sets ROE/ROT.
+Initializes the ratcraft array and group menu.
+
+
Parameters
+
+
+
+
#string _departure :
+(Optional) Name of departure airbase.
+
+
+
+
+
#string _destination :
+(Optional) Name of destination airbase.
Set the friendly coalitions from which the airports can be used as departure or destination.
+
+
Parameter
+
+
+
+
#string friendly :
+"same"=own coalition+neutral (default), "all"=neutral+red+blue", "sameonly"=own coalition only, "blue"=blue+neutral, "blueonly"=blue, "red"=red+neutral, "redonly"=red, "neutral"=neutral.
+Default is "same", so aircraft will use airports of the coalition their spawn template has plus all neutral airports.
+
+
+
+
Usages:
+
+
yak:SetCoalition("all") will spawn aircraft randomly on airports of any coaliton, i.e. red, blue and neutral, regardless of its own coalition.
+
yak:SetCoalition("redonly") will spawn aircraft randomly on airports belonging to the red coalition _only_.
This can be an airport or a zone defined in the mission editor.
+
+
Parameter
+
+
+
+
#string names :
+Name or table of names of departure airports or zones.
+
+
+
+
Usages:
+
+
RAT:SetDeparture("Sochi-Adler") will spawn RAT objects at Sochi-Adler airport.
+
RAT:SetDeparture({"Sochi-Adler", "Gudauta"}) will spawn RAT aircraft radomly at Sochi-Adler or Gudauta airport.
+
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.
Starting cold at airport, starting hot at airport, starting at runway, starting in the air.
+Default is "takeoff-hot" for a start at airport with engines already running.
+
+
Parameter
+
+
+
+
#string type :
+Type can be "takeoff-cold" or "cold", "takeoff-hot" or "hot", "takeoff-runway" or "runway", "air".
+
+
+
+
Usages:
+
+
RAT:Takeoff("hot") will spawn RAT objects at airports with engines started.
+
RAT:Takeoff("cold") will spawn RAT objects at airports with engines off.
+
RAT:Takeoff("air") will spawn RAT objects in air over random airports or within pre-defined zones.
Calculate the max flight level for a given distance and fixed climb and descent rates.
+
+
+
In other words we have a distance between two airports and want to know how high we
+can climb before we must descent again to arrive at the destination without any level/cruising part.
+
+
Parameters
+
+
+
+
#number alpha :
+Angle of climb [rad].
+
+
+
+
+
#number beta :
+Angle of descent [rad].
+
+
+
+
+
#number d :
+Distance between the two airports [m].
+
+
+
+
+
#number phi :
+Angle between departure and destination [rad].
+
+
+
+
+
#number h0 :
+Height [m] of departure airport. Note we implicitly assume that the height difference between departure and destination is negligible.
In particular, the waypoints of the group's flight plan are copied into the spawn template.
+This allows to spawn at airports and also land at other airports, i.e. circumventing the DCS "landing bug".
+
+
Parameter
+
+
+
+
#table waypoints :
+The waypoints of the AI flight plan.
If no airport name is given explicitly an airport from the coalition is chosen randomly.
+If takeoff style is set to "air", we use zones around the airports or the zones specified by user input.
Sets the departure and destination airports and waypoints.
+Modifies the spawn template.
+Sets ROE/ROT.
+Initializes the ratcraft array and group menu.
+
+
Parameters
+
+
+
+
#string _departure :
+(Optional) Name of departure airbase.
+
+
+
+
+
#string _destination :
+(Optional) Name of destination airbase.
Core -- The RADIO Module is responsible for everything that is related to radio transmission and you can hear in DCS, be it TACAN beacons, Radio transmissions...