Merge branch 'develop' into Functional-ATC_Ground_AlertFreqCustom_PersianGulf

This commit is contained in:
Frank
2019-01-31 18:25:25 +01:00
committed by GitHub
43 changed files with 26609 additions and 2163 deletions

View File

@@ -7,7 +7,7 @@
-- === -- ===
-- --
-- @module AI.AI_A2A -- @module AI.AI_A2A
-- @image AI_Air_To_Air_Dispatching.JPG -- @image MOOSE.JPG
--BASE:TraceClass("AI_A2A") --BASE:TraceClass("AI_A2A")
@@ -489,7 +489,7 @@ function AI_A2A:onafterStatus()
not self:Is( "Fuel" ) and not self:Is( "Fuel" ) and
not self:Is( "Damaged" ) and not self:Is( "Damaged" ) and
not self:Is( "Home" ) then not self:Is( "Home" ) then
if self.IdleCount >= 2 then if self.IdleCount >= 3 then
if Damage ~= InitialLife then if Damage ~= InitialLife then
self:Damaged() self:Damaged()
else else

View File

@@ -274,7 +274,7 @@ do -- AI_A2A_DISPATCHER
-- A2ADispatcher_Red = AI_A2A_DISPATCHER:New( EWR_Red ) -- A2ADispatcher_Red = AI_A2A_DISPATCHER:New( EWR_Red )
-- A2ADispatcher_Blue = AI_A2A_DISPATCHER:New( EWR_Blue ) -- A2ADispatcher_Blue = AI_A2A_DISPATCHER:New( EWR_Blue )
-- --
-- ### 2. Define the detected **target grouping radius**: -- ### 1.2. Define the detected **target grouping radius**:
-- --
-- The target grouping radius is a property of the Detection object, that was passed to the AI\_A2A\_DISPATCHER object, but can be changed. -- The target grouping radius is a property of the Detection object, that was passed to the AI\_A2A\_DISPATCHER object, but can be changed.
-- The grouping radius should not be too small, but also depends on the types of planes and the era of the simulation. -- The grouping radius should not be too small, but also depends on the types of planes and the era of the simulation.

View File

@@ -345,7 +345,7 @@ function AI_A2A_PATROL:onafterRoute( AIPatrol, From, Event, To )
AIPatrol:OptionROEReturnFire() AIPatrol:OptionROEReturnFire()
AIPatrol:OptionROTEvadeFire() AIPatrol:OptionROTEvadeFire()
AIPatrol:Route( PatrolRoute, 0.5 ) AIPatrol:Route( PatrolRoute, 0.5)
end end
end end

View File

@@ -0,0 +1,69 @@
--- **AI** -- Models the process of air to ground operations for airplanes and helicopters.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_A2G
-- @image AI_Air_To_Ground_Dispatching.JPG
--- @type AI_A2G
-- @extends AI.AI_Air#AI_AIR
--- The AI_A2G class implements the core functions to operate an AI @{Wrapper.Group} A2G tasking.
--
--
-- # 1) AI_A2G constructor
--
-- * @{#AI_A2G.New}(): Creates a new AI_A2G object.
--
-- # 2) AI_A2G is a Finite State Machine.
--
-- This section must be read as follows. Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed.
-- The first column is the **From** state, the second column the **Event**, and the third column the **To** state.
--
-- So, each of the rows have the following structure.
--
-- * **From** => **Event** => **To**
--
-- Important to know is that an event can only be executed if the **current state** is the **From** state.
-- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed,
-- and the resulting state will be the **To** state.
--
-- These are the different possible state transitions of this state machine implementation:
--
-- * Idle => Start => Monitoring
--
-- ## 2.1) AI_A2G States.
--
-- * **Idle**: The process is idle.
--
-- ## 2.2) AI_A2G Events.
--
-- * **Start**: Start the transport process.
-- * **Stop**: Stop the transport process.
-- * **Monitor**: Monitor and take action.
--
-- @field #AI_A2G
AI_A2G = {
ClassName = "AI_A2G",
}
--- Creates a new AI_A2G process.
-- @param #AI_A2G self
-- @param Wrapper.Group#GROUP AIGroup The group object to receive the A2G Process.
-- @return #AI_A2G
function AI_A2G:New( AIGroup )
-- Inherits from BASE
local self = BASE:Inherit( self, AI_AIR:New( AIGroup ) ) -- #AI_A2G
self:SetFuelThreshold( .2, 60 )
self:SetDamageThreshold( 0.95 )
self:SetDisengageRadius( 70000 )
return self
end

View File

@@ -0,0 +1,162 @@
--- **AI** -- Models the process of air to ground BAI engagement for airplanes and helicopters.
--
-- This is a class used in the @{AI_A2G_Dispatcher}.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_A2G_BAI
-- @image AI_Air_To_Ground_Engage.JPG
--- @type AI_A2G_BAI
-- @extends AI.AI_A2A_Engage#AI_A2A_Engage
--- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders.
--
-- ===
--
-- @field #AI_A2G_BAI
AI_A2G_BAI = {
ClassName = "AI_A2G_BAI",
}
--- Creates a new AI_A2G_BAI object
-- @param #AI_A2G_BAI self
-- @param Wrapper.Group#GROUP AIGroup
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
-- @return #AI_A2G_BAI
function AI_A2G_BAI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
-- Inherits from BASE
local self = BASE:Inherit( self, AI_A2G_PATROL:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_A2G_BAI
local RTBSpeedMax = AIGroup:GetSpeedMax() or 9999
self:SetRTBSpeed( RTBSpeedMax * 0.50, RTBSpeedMax * 0.75 )
return self
end
--- @param #AI_A2G_BAI self
-- @param Wrapper.Group#GROUP DefenderGroup The GroupGroup managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2G_BAI:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit )
self:F( { DefenderGroup, From, Event, To, AttackSetUnit} )
local DefenderGroupName = DefenderGroup:GetName()
self.AttackSetUnit = AttackSetUnit or self.AttackSetUnit -- Core.Set#SET_UNIT
local AttackCount = self.AttackSetUnit:Count()
if AttackCount > 0 then
if DefenderGroup:IsAlive() then
local EngageAltitude = math.random( self.EngageFloorAltitude, self.EngageCeilingAltitude )
local EngageSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed )
-- Determine the distance to the target.
-- If it is less than 10km, then attack without a route.
-- Otherwise perform a route attack.
local DefenderCoord = DefenderGroup:GetPointVec3()
DefenderCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude.
local TargetCoord = self.AttackSetUnit:GetFirst():GetPointVec3()
TargetCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude.
local TargetDistance = DefenderCoord:Get2DDistance( TargetCoord )
local EngageRoute = {}
--- Calculate the target route point.
local FromWP = DefenderCoord:WaypointAir(
self.PatrolAltType or "RADIO",
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
EngageSpeed,
true
)
EngageRoute[#EngageRoute+1] = FromWP
self:SetTargetDistance( TargetCoord ) -- For RTB status check
local FromEngageAngle = TargetCoord:GetAngleDegrees( TargetCoord:GetDirectionVec3( DefenderCoord ) )
local EngageDistance = ( DefenderGroup:IsHelicopter() and 5000 ) or ( DefenderGroup:IsAirPlane() and 10000 )
--- Create a route point of type air.
local ToWP = TargetCoord:Translate( EngageDistance, FromEngageAngle ):WaypointAir(
self.PatrolAltType or "RADIO",
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
EngageSpeed,
true
)
EngageRoute[#EngageRoute+1] = ToWP
local AttackTasks = {}
self.AttackSetUnit.AttackIndex = self.AttackSetUnit.AttackIndex and self.AttackSetUnit.AttackIndex + 1 or 1
local AttackSetUnitPerThreatLevel = self.AttackSetUnit:GetSetPerThreatLevel( 10, 0 )
local AttackUnit = AttackSetUnitPerThreatLevel[self.AttackSetUnit.AttackIndex]
if not AttackUnit then
self.AttackSetUnit.AttackIndex = 1
AttackUnit = AttackSetUnitPerThreatLevel[self.AttackSetUnit.AttackIndex]
end
if AttackUnit then
if AttackUnit:IsAlive() and AttackUnit:IsGround() then
self:T( { "BAI Unit:", AttackUnit:GetName() } )
AttackTasks[#AttackTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit, false, false, nil, nil, EngageAltitude )
end
end
if #AttackTasks == 0 then
self:E( DefenderGroupName .. ": No targets found -> Going RTB")
self:Return()
self:__RTB( self.TaskDelay )
else
DefenderGroup:OptionROEOpenFire()
DefenderGroup:OptionROTEvadeFire()
DefenderGroup:OptionKeepWeaponsOnThreat()
AttackTasks[#AttackTasks+1] = DefenderGroup:TaskFunction( "AI_A2G_ENGAGE.EngageRoute", self )
EngageRoute[#EngageRoute].task = DefenderGroup:TaskCombo( AttackTasks )
end
DefenderGroup:Route( EngageRoute, self.TaskDelay )
end
else
self:E( DefenderGroupName .. ": No targets found -> Going RTB")
self:Return()
self:__RTB( self.TaskDelay )
end
end

View File

@@ -0,0 +1,159 @@
--- **AI** -- Models the process of air to ground engagement for airplanes and helicopters.
--
-- This is a class used in the @{AI_A2G_Dispatcher}.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_A2G_CAS
-- @image AI_Air_To_Ground_Engage.JPG
--- @type AI_A2G_CAS
-- @extends AI.AI_A2G_Patrol#AI_A2G_PATROL
--- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders.
--
-- ===
--
-- @field #AI_A2G_CAS
AI_A2G_CAS = {
ClassName = "AI_A2G_CAS",
}
--- Creates a new AI_A2G_CAS object
-- @param #AI_A2G_CAS self
-- @param Wrapper.Group#GROUP AIGroup
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
-- @return #AI_A2G_CAS
function AI_A2G_CAS:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
-- Inherits from BASE
local self = BASE:Inherit( self, AI_A2G_PATROL:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_A2G_CAS
local RTBSpeedMax = AIGroup:GetSpeedMax() or 9999
self:SetRTBSpeed( RTBSpeedMax * 0.50, RTBSpeedMax * 0.75 )
return self
end
--- @param #AI_A2G_CAS self
-- @param Wrapper.Group#GROUP DefenderGroup The GroupGroup managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2G_CAS:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit )
self:F( { DefenderGroup, From, Event, To, AttackSetUnit} )
local DefenderGroupName = DefenderGroup:GetName()
self.AttackSetUnit = AttackSetUnit or self.AttackSetUnit -- Core.Set#SET_UNIT
local AttackCount = self.AttackSetUnit:Count()
if AttackCount > 0 then
if DefenderGroup:IsAlive() then
local EngageAltitude = math.random( self.EngageFloorAltitude, self.EngageCeilingAltitude )
local EngageSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed )
local DefenderCoord = DefenderGroup:GetPointVec3()
DefenderCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude.
local TargetCoord = self.AttackSetUnit:GetFirst():GetPointVec3()
TargetCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude.
local TargetDistance = DefenderCoord:Get2DDistance( TargetCoord )
local EngageRoute = {}
--- Calculate the target route point.
local FromWP = DefenderCoord:WaypointAir(
self.PatrolAltType or "RADIO",
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
EngageSpeed,
true
)
EngageRoute[#EngageRoute+1] = FromWP
self:SetTargetDistance( TargetCoord ) -- For RTB status check
local FromEngageAngle = TargetCoord:GetAngleDegrees( TargetCoord:GetDirectionVec3( DefenderCoord ) )
local EngageDistance = ( DefenderGroup:IsHelicopter() and 5000 ) or ( DefenderGroup:IsAirPlane() and 10000 )
--- Create a route point of type air.
local ToWP = TargetCoord:Translate( EngageDistance, FromEngageAngle, true ):WaypointAir(
self.PatrolAltType or "RADIO",
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
EngageSpeed,
true
)
EngageRoute[#EngageRoute+1] = ToWP
local AttackTasks = {}
self.AttackSetUnit.AttackIndex = self.AttackSetUnit.AttackIndex and self.AttackSetUnit.AttackIndex + 1 or 1
local AttackSetUnitPerThreatLevel = self.AttackSetUnit:GetSetPerThreatLevel( 10, 0 )
local AttackUnit = AttackSetUnitPerThreatLevel[self.AttackSetUnit.AttackIndex]
if not AttackUnit then
self.AttackSetUnit.AttackIndex = 1
AttackUnit = AttackSetUnitPerThreatLevel[self.AttackSetUnit.AttackIndex]
end
if AttackUnit then
if AttackUnit:IsAlive() and AttackUnit:IsGround() then
self:F( { "CAS Unit:", AttackUnit:GetName() } )
AttackTasks[#AttackTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit, false, false, nil, nil, EngageAltitude )
end
end
if #AttackTasks == 0 then
self:E( DefenderGroupName .. ": No targets found -> Going RTB")
self:Return()
self:__RTB( self.TaskDelay )
else
DefenderGroup:OptionROEOpenFire()
DefenderGroup:OptionROTEvadeFire()
DefenderGroup:OptionKeepWeaponsOnThreat()
AttackTasks[#AttackTasks+1] = DefenderGroup:TaskFunction( "AI_A2G_ENGAGE.EngageRoute", self )
EngageRoute[#EngageRoute].task = DefenderGroup:TaskCombo( AttackTasks )
end
DefenderGroup:Route( EngageRoute, self.TaskDelay )
end
else
self:E( DefenderGroupName .. ": No targets found -> Going RTB")
self:Return()
self:__RTB( self.TaskDelay )
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,378 @@
--- **AI** -- Models the process of air to ground engagement for airplanes and helicopters.
--
-- This is a class used in the @{AI_A2G_Dispatcher}.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_A2G_Engage
-- @image AI_Air_To_Ground_Engage.JPG
--- @type AI_A2G_ENGAGE
-- @extends AI.AI_A2G#AI_A2G
--- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders.
--
-- ![Process](..\Presentations\AI_GCI\Dia3.JPG)
--
-- The AI_A2G_ENGAGE is assigned a @{Wrapper.Group} and this must be done before the AI_A2G_ENGAGE process can be started using the **Start** event.
--
-- ![Process](..\Presentations\AI_GCI\Dia4.JPG)
--
-- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits.
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
--
-- ![Process](..\Presentations\AI_GCI\Dia5.JPG)
--
-- This cycle will continue.
--
-- ![Process](..\Presentations\AI_GCI\Dia6.JPG)
--
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
--
-- ![Process](..\Presentations\AI_GCI\Dia9.JPG)
--
-- When enemies are detected, the AI will automatically engage the enemy.
--
-- ![Process](..\Presentations\AI_GCI\Dia10.JPG)
--
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
--
-- ![Process](..\Presentations\AI_GCI\Dia13.JPG)
--
-- ## 1. AI_A2G_ENGAGE constructor
--
-- * @{#AI_A2G_ENGAGE.New}(): Creates a new AI_A2G_ENGAGE object.
--
-- ## 3. Set the Range of Engagement
--
-- ![Range](..\Presentations\AI_GCI\Dia11.JPG)
--
-- An optional range can be set in meters,
-- that will define when the AI will engage with the detected airborne enemy targets.
-- The range can be beyond or smaller than the range of the Patrol Zone.
-- The range is applied at the position of the AI.
-- Use the method @{AI.AI_GCI#AI_A2G_ENGAGE.SetEngageRange}() to define that range.
--
-- ## 4. Set the Zone of Engagement
--
-- ![Zone](..\Presentations\AI_GCI\Dia12.JPG)
--
-- An optional @{Zone} can be set,
-- that will define when the AI will engage with the detected airborne enemy targets.
-- Use the method @{AI.AI_Cap#AI_A2G_ENGAGE.SetEngageZone}() to define that Zone.
--
-- ===
--
-- @field #AI_A2G_ENGAGE
AI_A2G_ENGAGE = {
ClassName = "AI_A2G_ENGAGE",
}
--- Creates a new AI_A2G_ENGAGE object
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup
-- @param DCS#Speed EngageMinSpeed (optional, default = 50% of max speed) The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#Speed EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#Altitude EngageFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the engagement.
-- @param DCS#Altitude EngageCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the engagement.
-- @return #AI_A2G_ENGAGE
function AI_A2G_ENGAGE:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude )
-- Inherits from BASE
local self = BASE:Inherit( self, AI_A2G:New( AIGroup ) ) -- #AI_A2G_ENGAGE
self.Accomplished = false
self.Engaging = false
local SpeedMax = AIGroup:GetSpeedMax()
self.EngageMinSpeed = EngageMinSpeed or SpeedMax * 0.5
self.EngageMaxSpeed = EngageMaxSpeed or SpeedMax * 0.75
self.EngageFloorAltitude = EngageFloorAltitude or 1000
self.EngageCeilingAltitude = EngageCeilingAltitude or 1500
self:AddTransition( { "Started", "Engaging", "Returning", "Airborne", "Patrolling" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_A2G_ENGAGE.
--- OnBefore Transition Handler for Event Engage.
-- @function [parent=#AI_A2G_ENGAGE] OnBeforeEngage
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Engage.
-- @function [parent=#AI_A2G_ENGAGE] OnAfterEngage
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Engage.
-- @function [parent=#AI_A2G_ENGAGE] Engage
-- @param #AI_A2G_ENGAGE self
--- Asynchronous Event Trigger for Event Engage.
-- @function [parent=#AI_A2G_ENGAGE] __Engage
-- @param #AI_A2G_ENGAGE self
-- @param #number Delay The delay in seconds.
--- OnLeave Transition Handler for State Engaging.
-- @function [parent=#AI_A2G_ENGAGE] OnLeaveEngaging
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnEnter Transition Handler for State Engaging.
-- @function [parent=#AI_A2G_ENGAGE] OnEnterEngaging
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
self:AddTransition( "Engaging", "Fired", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_A2G_ENGAGE.
--- OnBefore Transition Handler for Event Fired.
-- @function [parent=#AI_A2G_ENGAGE] OnBeforeFired
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Fired.
-- @function [parent=#AI_A2G_ENGAGE] OnAfterFired
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Fired.
-- @function [parent=#AI_A2G_ENGAGE] Fired
-- @param #AI_A2G_ENGAGE self
--- Asynchronous Event Trigger for Event Fired.
-- @function [parent=#AI_A2G_ENGAGE] __Fired
-- @param #AI_A2G_ENGAGE self
-- @param #number Delay The delay in seconds.
self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_A2G_ENGAGE.
--- OnBefore Transition Handler for Event Destroy.
-- @function [parent=#AI_A2G_ENGAGE] OnBeforeDestroy
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Destroy.
-- @function [parent=#AI_A2G_ENGAGE] OnAfterDestroy
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Destroy.
-- @function [parent=#AI_A2G_ENGAGE] Destroy
-- @param #AI_A2G_ENGAGE self
--- Asynchronous Event Trigger for Event Destroy.
-- @function [parent=#AI_A2G_ENGAGE] __Destroy
-- @param #AI_A2G_ENGAGE self
-- @param #number Delay The delay in seconds.
self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2G_ENGAGE.
--- OnBefore Transition Handler for Event Abort.
-- @function [parent=#AI_A2G_ENGAGE] OnBeforeAbort
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Abort.
-- @function [parent=#AI_A2G_ENGAGE] OnAfterAbort
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Abort.
-- @function [parent=#AI_A2G_ENGAGE] Abort
-- @param #AI_A2G_ENGAGE self
--- Asynchronous Event Trigger for Event Abort.
-- @function [parent=#AI_A2G_ENGAGE] __Abort
-- @param #AI_A2G_ENGAGE self
-- @param #number Delay The delay in seconds.
self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2G_ENGAGE.
--- OnBefore Transition Handler for Event Accomplish.
-- @function [parent=#AI_A2G_ENGAGE] OnBeforeAccomplish
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Accomplish.
-- @function [parent=#AI_A2G_ENGAGE] OnAfterAccomplish
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Accomplish.
-- @function [parent=#AI_A2G_ENGAGE] Accomplish
-- @param #AI_A2G_ENGAGE self
--- Asynchronous Event Trigger for Event Accomplish.
-- @function [parent=#AI_A2G_ENGAGE] __Accomplish
-- @param #AI_A2G_ENGAGE self
-- @param #number Delay The delay in seconds.
return self
end
--- onafter event handler for Start event.
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The AI group managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2G_ENGAGE:onafterStart( AIGroup, From, Event, To )
self:GetParent( self, AI_A2G_ENGAGE ).onafterStart( self, AIGroup, From, Event, To )
AIGroup:HandleEvent( EVENTS.Takeoff, nil, self )
end
--- onafter event handler for Engage event.
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The AI Group managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2G_ENGAGE:onafterEngage( AIGroup, From, Event, To )
self:HandleEvent( EVENTS.Dead )
end
-- todo: need to fix this global function
--- @param Wrapper.Group#GROUP AIControllable
function AI_A2G_ENGAGE.EngageRoute( AIGroup, Fsm )
AIGroup:I( { "AI_A2G_ENGAGE.EngageRoute:", AIGroup:GetName() } )
if AIGroup:IsAlive() then
Fsm:__Engage( Fsm.TaskDelay )
--local Task = AIGroup:TaskOrbitCircle( 4000, 400 )
--AIGroup:SetTask( Task )
end
end
--- onbefore event handler for Engage event.
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2G_ENGAGE:onbeforeEngage( AIGroup, From, Event, To )
if self.Accomplished == true then
return false
end
end
--- onafter event handler for Abort event.
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The AI Group managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2G_ENGAGE:onafterAbort( AIGroup, From, Event, To )
AIGroup:ClearTasks()
self:Return()
self:__RTB( self.TaskDelay )
end
--- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP DefenderGroup The GroupGroup managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2G_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit )
self:F( { DefenderGroup, From, Event, To, AttackSetUnit} )
end
--- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2G_ENGAGE:onafterAccomplish( AIGroup, From, Event, To )
self.Accomplished = true
self:SetDetectionOff()
end
--- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param Core.Event#EVENTDATA EventData
function AI_A2G_ENGAGE:onafterDestroy( AIGroup, From, Event, To, EventData )
if EventData.IniUnit then
self.AttackUnits[EventData.IniUnit] = nil
end
end
--- @param #AI_A2G_ENGAGE self
-- @param Core.Event#EVENTDATA EventData
function AI_A2G_ENGAGE:OnEventDead( EventData )
self:F( { "EventDead", EventData } )
if EventData.IniDCSUnit then
if self.AttackUnits and self.AttackUnits[EventData.IniUnit] then
self:__Destroy( self.TaskDelay, EventData )
end
end
end

View File

@@ -0,0 +1,323 @@
--- **AI** -- Models the process of A2G patrolling and engaging ground targets for airplanes and helicopters.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_A2G_Patrol
-- @image AI_Air_To_Ground_Patrol.JPG
--- @type AI_A2G_PATROL
-- @extends AI.AI_A2G_Engage#AI_A2G_ENGAGE
--- The AI_A2G_PATROL class implements the core functions to patrol a @{Zone} by an AI @{Wrapper.Group} or @{Wrapper.Group}
-- and automatically engage any airborne enemies that are within a certain range or within a certain zone.
--
-- ![Process](..\Presentations\AI_CAP\Dia3.JPG)
--
-- The AI_A2G_PATROL is assigned a @{Wrapper.Group} and this must be done before the AI_A2G_PATROL process can be started using the **Start** event.
--
-- ![Process](..\Presentations\AI_CAP\Dia4.JPG)
--
-- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits.
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
--
-- ![Process](..\Presentations\AI_CAP\Dia5.JPG)
--
-- This cycle will continue.
--
-- ![Process](..\Presentations\AI_CAP\Dia6.JPG)
--
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
--
-- ![Process](..\Presentations\AI_CAP\Dia9.JPG)
--
-- When enemies are detected, the AI will automatically engage the enemy.
--
-- ![Process](..\Presentations\AI_CAP\Dia10.JPG)
--
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
--
-- ![Process](..\Presentations\AI_CAP\Dia13.JPG)
--
-- ## 1. AI_A2G_PATROL constructor
--
-- * @{#AI_A2G_PATROL.New}(): Creates a new AI_A2G_PATROL object.
--
-- ## 2. AI_A2G_PATROL is a FSM
--
-- ![Process](..\Presentations\AI_CAP\Dia2.JPG)
--
-- ### 2.1 AI_A2G_PATROL States
--
-- * **None** ( Group ): The process is not started yet.
-- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone.
-- * **Engaging** ( Group ): The AI is engaging the bogeys.
-- * **Returning** ( Group ): The AI is returning to Base..
--
-- ### 2.2 AI_A2G_PATROL Events
--
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process.
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone.
-- * **@{#AI_A2G_PATROL.Engage}**: Let the AI engage the bogeys.
-- * **@{#AI_A2G_PATROL.Abort}**: Aborts the engagement and return patrolling in the patrol zone.
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base.
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets.
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
-- * **@{#AI_A2G_PATROL.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}.
-- * **@{#AI_A2G_PATROL.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task.
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB.
--
-- ## 3. Set the Range of Engagement
--
-- ![Range](..\Presentations\AI_CAP\Dia11.JPG)
--
-- An optional range can be set in meters,
-- that will define when the AI will engage with the detected airborne enemy targets.
-- The range can be beyond or smaller than the range of the Patrol Zone.
-- The range is applied at the position of the AI.
-- Use the method @{AI.AI_CAP#AI_A2G_PATROL.SetEngageRange}() to define that range.
--
-- ## 4. Set the Zone of Engagement
--
-- ![Zone](..\Presentations\AI_CAP\Dia12.JPG)
--
-- An optional @{Zone} can be set,
-- that will define when the AI will engage with the detected airborne enemy targets.
-- Use the method @{AI.AI_Cap#AI_A2G_PATROL.SetEngageZone}() to define that Zone.
--
-- ===
--
-- @field #AI_A2G_PATROL
AI_A2G_PATROL = {
ClassName = "AI_A2G_PATROL",
}
--- Creates a new AI_A2G_PATROL object
-- @param #AI_A2G_PATROL self
-- @param Wrapper.Group#GROUP AIGroup
-- @param DCS#Speed EngageMinSpeed (optional, default = 50% of max speed) The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#Speed EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#Altitude EngageFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the engagement.
-- @param DCS#Altitude EngageCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the engagement.
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
-- @param DCS#Altitude PatrolFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the patrol.
-- @param DCS#Altitude PatrolCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the patrol.
-- @param DCS#Speed PatrolMinSpeed (optional, default = 50% of max speed) The minimum speed of the @{Wrapper.Group} in km/h.
-- @param DCS#Speed PatrolMaxSpeed (optional, default = 75% of max speed) The maximum speed of the @{Wrapper.Group} in km/h.
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO.
-- @return #AI_A2G_PATROL
function AI_A2G_PATROL:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
-- Inherits from BASE
local self = BASE:Inherit( self, AI_A2G_ENGAGE:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude ) ) -- #AI_A2G_PATROL
local SpeedMax = AIGroup:GetSpeedMax()
self.PatrolZone = PatrolZone
self.PatrolFloorAltitude = PatrolFloorAltitude or 1000
self.PatrolCeilingAltitude = PatrolCeilingAltitude or 1500
self.PatrolMinSpeed = PatrolMinSpeed or SpeedMax * 0.5
self.PatrolMaxSpeed = PatrolMaxSpeed or SpeedMax * 0.75
-- defafult PatrolAltType to "RADIO" if not specified
self.PatrolAltType = PatrolAltType or "RADIO"
self:AddTransition( { "Started", "Airborne", "Refuelling" }, "Patrol", "Patrolling" )
--- OnBefore Transition Handler for Event Patrol.
-- @function [parent=#AI_A2G_PATROL] OnBeforePatrol
-- @param #AI_A2G_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Patrol.
-- @function [parent=#AI_A2G_PATROL] OnAfterPatrol
-- @param #AI_A2G_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Patrol.
-- @function [parent=#AI_A2G_PATROL] Patrol
-- @param #AI_A2G_PATROL self
--- Asynchronous Event Trigger for Event Patrol.
-- @function [parent=#AI_A2G_PATROL] __Patrol
-- @param #AI_A2G_PATROL self
-- @param #number Delay The delay in seconds.
--- OnLeave Transition Handler for State Patrolling.
-- @function [parent=#AI_A2G_PATROL] OnLeavePatrolling
-- @param #AI_A2G_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnEnter Transition Handler for State Patrolling.
-- @function [parent=#AI_A2G_PATROL] OnEnterPatrolling
-- @param #AI_A2G_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
self:AddTransition( "Patrolling", "Route", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2G_PATROL.
--- OnBefore Transition Handler for Event Route.
-- @function [parent=#AI_A2G_PATROL] OnBeforeRoute
-- @param #AI_A2G_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Route.
-- @function [parent=#AI_A2G_PATROL] OnAfterRoute
-- @param #AI_A2G_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Route.
-- @function [parent=#AI_A2G_PATROL] Route
-- @param #AI_A2G_PATROL self
--- Asynchronous Event Trigger for Event Route.
-- @function [parent=#AI_A2G_PATROL] __Route
-- @param #AI_A2G_PATROL self
-- @param #number Delay The delay in seconds.
self:AddTransition( "*", "Reset", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2G_PATROL.
return self
end
--- Set the Engage Range when the AI will engage with airborne enemies.
-- @param #AI_A2G_PATROL self
-- @param #number EngageRange The Engage Range.
-- @return #AI_A2G_PATROL self
function AI_A2G_PATROL:SetEngageRange( EngageRange )
self:F2()
if EngageRange then
self.EngageRange = EngageRange
else
self.EngageRange = nil
end
end
--- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings.
-- @param #AI_A2G_PATROL self
-- @return #AI_A2G_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2G_PATROL:onafterPatrol( AIPatrol, From, Event, To )
self:F2()
self:ClearTargetDistance()
self:__Route( self.TaskDelay )
AIPatrol:OnReSpawn(
function( PatrolGroup )
self:__Reset( self.TaskDelay )
self:__Route( self.TaskDelay )
end
)
end
--- @param Wrapper.Group#GROUP AIPatrol
-- This statis method is called from the route path within the last task at the last waaypoint of the AIPatrol.
-- Note that this method is required, as triggers the next route when patrolling for the AIPatrol.
function AI_A2G_PATROL.PatrolRoute( AIPatrol, Fsm )
AIPatrol:F( { "AI_A2G_PATROL.PatrolRoute:", AIPatrol:GetName() } )
if AIPatrol:IsAlive() then
Fsm:Route()
end
end
--- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings.
-- @param #AI_A2G_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2G_PATROL:onafterRoute( AIPatrol, From, Event, To )
self:F2()
-- When RTB, don't allow anymore the routing.
if From == "RTB" then
return
end
if AIPatrol:IsAlive() then
local PatrolRoute = {}
--- Calculate the target route point.
local CurrentCoord = AIPatrol:GetCoordinate()
local ToTargetCoord = self.PatrolZone:GetRandomPointVec2()
ToTargetCoord:SetAlt( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ) )
self:SetTargetDistance( ToTargetCoord ) -- For RTB status check
local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed )
--- Create a route point of type air.
local ToPatrolRoutePoint = ToTargetCoord:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
ToTargetSpeed,
true
)
PatrolRoute[#PatrolRoute+1] = ToPatrolRoutePoint
PatrolRoute[#PatrolRoute+1] = ToPatrolRoutePoint
local Tasks = {}
Tasks[#Tasks+1] = AIPatrol:TaskFunction( "AI_A2G_PATROL.PatrolRoute", self )
PatrolRoute[#PatrolRoute].task = AIPatrol:TaskCombo( Tasks )
AIPatrol:OptionROEReturnFire()
AIPatrol:OptionROTEvadeFire()
AIPatrol:Route( PatrolRoute, self.TaskDelay )
end
end
--- @param Wrapper.Group#GROUP AIPatrol
function AI_A2G_PATROL.Resume( AIPatrol, Fsm )
AIPatrol:F( { "AI_A2G_PATROL.Resume:", AIPatrol:GetName() } )
if AIPatrol:IsAlive() then
Fsm:__Reset( self.TaskDelay )
Fsm:__Route( self.TaskDelay )
end
end

View File

@@ -0,0 +1,221 @@
--- **AI** -- Models the process of air to ground SEAD engagement for airplanes and helicopters.
--
-- This is a class used in the @{AI_A2G_Dispatcher}.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_A2G_SEAD
-- @image AI_Air_To_Ground_Engage.JPG
--- @type AI_A2G_SEAD
-- @extends AI.AI_A2G_Patrol#AI_A2G_PATROL
--- Implements the core functions to SEAD intruders. Use the Engage trigger to intercept intruders.
--
-- ![Process](..\Presentations\AI_GCI\Dia3.JPG)
--
-- The AI_A2G_SEAD is assigned a @{Wrapper.Group} and this must be done before the AI_A2G_SEAD process can be started using the **Start** event.
--
-- ![Process](..\Presentations\AI_GCI\Dia4.JPG)
--
-- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits.
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
--
-- ![Process](..\Presentations\AI_GCI\Dia5.JPG)
--
-- This cycle will continue.
--
-- ![Process](..\Presentations\AI_GCI\Dia6.JPG)
--
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
--
-- ![Process](..\Presentations\AI_GCI\Dia9.JPG)
--
-- When enemies are detected, the AI will automatically engage the enemy.
--
-- ![Process](..\Presentations\AI_GCI\Dia10.JPG)
--
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
--
-- ![Process](..\Presentations\AI_GCI\Dia13.JPG)
--
-- ## 1. AI_A2G_SEAD constructor
--
-- * @{#AI_A2G_SEAD.New}(): Creates a new AI_A2G_SEAD object.
--
-- ## 3. Set the Range of Engagement
--
-- ![Range](..\Presentations\AI_GCI\Dia11.JPG)
--
-- An optional range can be set in meters,
-- that will define when the AI will engage with the detected airborne enemy targets.
-- The range can be beyond or smaller than the range of the Patrol Zone.
-- The range is applied at the position of the AI.
-- Use the method @{AI.AI_GCI#AI_A2G_SEAD.SetEngageRange}() to define that range.
--
-- ## 4. Set the Zone of Engagement
--
-- ![Zone](..\Presentations\AI_GCI\Dia12.JPG)
--
-- An optional @{Zone} can be set,
-- that will define when the AI will engage with the detected airborne enemy targets.
-- Use the method @{AI.AI_Cap#AI_A2G_SEAD.SetEngageZone}() to define that Zone.
--
-- ===
--
-- @field #AI_A2G_SEAD
AI_A2G_SEAD = {
ClassName = "AI_A2G_SEAD",
}
--- Creates a new AI_A2G_SEAD object
-- @param #AI_A2G_SEAD self
-- @param Wrapper.Group#GROUP AIGroup
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
-- @return #AI_A2G_SEAD
function AI_A2G_SEAD:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
-- Inherits from BASE
local self = BASE:Inherit( self, AI_A2G_PATROL:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_A2G_SEAD
local RTBSpeedMax = AIGroup:GetSpeedMax() or 9999
self:SetRTBSpeed( RTBSpeedMax * 0.50, RTBSpeedMax * 0.75 )
return self
end
--- @param #AI_A2G_SEAD self
-- @param Wrapper.Group#GROUP DefenderGroup The GroupGroup managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2G_SEAD:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit )
self:F( { DefenderGroup, From, Event, To, AttackSetUnit} )
local DefenderGroupName = DefenderGroup:GetName()
self.AttackSetUnit = AttackSetUnit or self.AttackSetUnit -- Core.Set#SET_UNIT
local AttackCount = self.AttackSetUnit:Count()
if AttackCount > 0 then
if DefenderGroup:IsAlive() then
-- Determine the distance to the target.
-- If it is less than 50km, then attack without a route.
-- Otherwise perform a route attack.
local EngageAltitude = math.random( self.EngageFloorAltitude, self.EngageCeilingAltitude )
local EngageSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed )
local DefenderCoord = DefenderGroup:GetPointVec3()
DefenderCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude.
local TargetCoord = self.AttackSetUnit:GetFirst():GetPointVec3()
TargetCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude.
local TargetDistance = DefenderCoord:Get2DDistance( TargetCoord )
local EngageRoute = {}
--- Calculate the target route point.
local FromWP = DefenderCoord:WaypointAir(
self.PatrolAltType or "RADIO",
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
EngageSpeed,
true
)
EngageRoute[#EngageRoute+1] = FromWP
self:SetTargetDistance( TargetCoord ) -- For RTB status check
local FromEngageAngle = TargetCoord:GetAngleDegrees( TargetCoord:GetDirectionVec3( DefenderCoord ) )
local EngageDistance = ( DefenderGroup:IsHelicopter() and 5000 ) or ( DefenderGroup:IsAirPlane() and 25000 )
--- Create a route point of type air, 50km from the center of the attack point.
local ToWP = TargetCoord:Translate( EngageDistance, FromEngageAngle, true ):WaypointAir(
self.PatrolAltType or "RADIO",
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
EngageSpeed,
true
)
EngageRoute[#EngageRoute+1] = ToWP
local AttackTasks = {}
self.AttackSetUnit.AttackIndex = self.AttackSetUnit.AttackIndex and self.AttackSetUnit.AttackIndex + 1 or 1
if self.AttackSetUnit.AttackIndex > self.AttackSetUnit:Count() then
self.AttackSetUnit.AttackIndex = 1
end
local AttackSetUnitPerThreatLevel = self.AttackSetUnit:GetSetPerThreatLevel( 10, 0 )
for AttackUnitID, AttackUnit in ipairs( AttackSetUnitPerThreatLevel ) do
if AttackUnitID >= self.AttackSetUnit.AttackIndex then
if AttackUnit then
if AttackUnit:IsAlive() and AttackUnit:IsGround() then
local HasRadar = AttackUnit:HasSEAD()
if HasRadar then
self:F( { "SEAD Unit:", AttackUnit:GetName() } )
AttackTasks[#AttackTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit, false, false, nil, nil, EngageAltitude )
end
end
end
end
end
if #AttackTasks == 0 then
self:E( DefenderGroupName .. ": No targets found -> Going RTB")
self:Return()
self:__RTB( self.TaskDelay )
else
DefenderGroup:OptionROEOpenFire()
DefenderGroup:OptionROTVertical()
DefenderGroup:OptionKeepWeaponsOnThreat()
--DefenderGroup:OptionRTBAmmo( Weapon.flag.AnyASM )
AttackTasks[#AttackTasks+1] = DefenderGroup:TaskFunction( "AI_A2G_ENGAGE.EngageRoute", self )
EngageRoute[#EngageRoute].task = DefenderGroup:TaskCombo( AttackTasks )
end
DefenderGroup:Route( EngageRoute, self.TaskDelay )
end
else
self:E( DefenderGroupName .. ": No targets found -> Going RTB")
self:Return()
self:__RTB( self.TaskDelay )
end
end

View File

@@ -0,0 +1,772 @@
--- **AI** -- Models the process of AI air operations.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_Air
-- @image MOOSE.JPG
--- @type AI_AIR
-- @extends Core.Fsm#FSM_CONTROLLABLE
--- The AI_AIR class implements the core functions to operate an AI @{Wrapper.Group}.
--
--
-- # 1) AI_AIR constructor
--
-- * @{#AI_AIR.New}(): Creates a new AI_AIR object.
--
-- # 2) AI_AIR is a Finite State Machine.
--
-- This section must be read as follows. Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed.
-- The first column is the **From** state, the second column the **Event**, and the third column the **To** state.
--
-- So, each of the rows have the following structure.
--
-- * **From** => **Event** => **To**
--
-- Important to know is that an event can only be executed if the **current state** is the **From** state.
-- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed,
-- and the resulting state will be the **To** state.
--
-- These are the different possible state transitions of this state machine implementation:
--
-- * Idle => Start => Monitoring
--
-- ## 2.1) AI_AIR States.
--
-- * **Idle**: The process is idle.
--
-- ## 2.2) AI_AIR Events.
--
-- * **Start**: Start the transport process.
-- * **Stop**: Stop the transport process.
-- * **Monitor**: Monitor and take action.
--
-- @field #AI_AIR
AI_AIR = {
ClassName = "AI_AIR",
}
AI_AIR.TaskDelay = 0.5 -- The delay of each task given to the AI.
--- Creates a new AI_AIR process.
-- @param #AI_AIR self
-- @param Wrapper.Group#GROUP AIGroup The group object to receive the A2G Process.
-- @return #AI_AIR
function AI_AIR:New( AIGroup )
-- Inherits from BASE
local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- #AI_AIR
self:SetControllable( AIGroup )
self:SetStartState( "Stopped" )
self:AddTransition( "*", "Queue", "Queued" )
self:AddTransition( "*", "Start", "Started" )
--- Start Handler OnBefore for AI_AIR
-- @function [parent=#AI_AIR] OnBeforeStart
-- @param #AI_AIR self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Start Handler OnAfter for AI_AIR
-- @function [parent=#AI_AIR] OnAfterStart
-- @param #AI_AIR self
-- @param #string From
-- @param #string Event
-- @param #string To
--- Start Trigger for AI_AIR
-- @function [parent=#AI_AIR] Start
-- @param #AI_AIR self
--- Start Asynchronous Trigger for AI_AIR
-- @function [parent=#AI_AIR] __Start
-- @param #AI_AIR self
-- @param #number Delay
self:AddTransition( "*", "Stop", "Stopped" )
--- OnLeave Transition Handler for State Stopped.
-- @function [parent=#AI_AIR] OnLeaveStopped
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnEnter Transition Handler for State Stopped.
-- @function [parent=#AI_AIR] OnEnterStopped
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- OnBefore Transition Handler for Event Stop.
-- @function [parent=#AI_AIR] OnBeforeStop
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Stop.
-- @function [parent=#AI_AIR] OnAfterStop
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Stop.
-- @function [parent=#AI_AIR] Stop
-- @param #AI_AIR self
--- Asynchronous Event Trigger for Event Stop.
-- @function [parent=#AI_AIR] __Stop
-- @param #AI_AIR self
-- @param #number Delay The delay in seconds.
self:AddTransition( "*", "Status", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR.
--- OnBefore Transition Handler for Event Status.
-- @function [parent=#AI_AIR] OnBeforeStatus
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Status.
-- @function [parent=#AI_AIR] OnAfterStatus
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Status.
-- @function [parent=#AI_AIR] Status
-- @param #AI_AIR self
--- Asynchronous Event Trigger for Event Status.
-- @function [parent=#AI_AIR] __Status
-- @param #AI_AIR self
-- @param #number Delay The delay in seconds.
self:AddTransition( "*", "RTB", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR.
--- OnBefore Transition Handler for Event RTB.
-- @function [parent=#AI_AIR] OnBeforeRTB
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event RTB.
-- @function [parent=#AI_AIR] OnAfterRTB
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event RTB.
-- @function [parent=#AI_AIR] RTB
-- @param #AI_AIR self
--- Asynchronous Event Trigger for Event RTB.
-- @function [parent=#AI_AIR] __RTB
-- @param #AI_AIR self
-- @param #number Delay The delay in seconds.
--- OnLeave Transition Handler for State Returning.
-- @function [parent=#AI_AIR] OnLeaveReturning
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnEnter Transition Handler for State Returning.
-- @function [parent=#AI_AIR] OnEnterReturning
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
self:AddTransition( "Patrolling", "Refuel", "Refuelling" )
--- Refuel Handler OnBefore for AI_AIR
-- @function [parent=#AI_AIR] OnBeforeRefuel
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Refuel Handler OnAfter for AI_AIR
-- @function [parent=#AI_AIR] OnAfterRefuel
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From
-- @param #string Event
-- @param #string To
--- Refuel Trigger for AI_AIR
-- @function [parent=#AI_AIR] Refuel
-- @param #AI_AIR self
--- Refuel Asynchronous Trigger for AI_AIR
-- @function [parent=#AI_AIR] __Refuel
-- @param #AI_AIR self
-- @param #number Delay
self:AddTransition( "*", "Takeoff", "Airborne" )
self:AddTransition( "*", "Return", "Returning" )
self:AddTransition( "*", "Hold", "Holding" )
self:AddTransition( "*", "Home", "Home" )
self:AddTransition( "*", "LostControl", "LostControl" )
self:AddTransition( "*", "Fuel", "Fuel" )
self:AddTransition( "*", "Damaged", "Damaged" )
self:AddTransition( "*", "Eject", "*" )
self:AddTransition( "*", "Crash", "Crashed" )
self:AddTransition( "*", "PilotDead", "*" )
self.IdleCount = 0
return self
end
--- @param Wrapper.Group#GROUP self
-- @param Core.Event#EVENTDATA EventData
function GROUP:OnEventTakeoff( EventData, Fsm )
Fsm:Takeoff()
self:UnHandleEvent( EVENTS.Takeoff )
end
function AI_AIR:SetDispatcher( Dispatcher )
self.Dispatcher = Dispatcher
end
function AI_AIR:GetDispatcher()
return self.Dispatcher
end
function AI_AIR:SetTargetDistance( Coordinate )
local CurrentCoord = self.Controllable:GetCoordinate()
self.TargetDistance = CurrentCoord:Get2DDistance( Coordinate )
self.ClosestTargetDistance = ( not self.ClosestTargetDistance or self.ClosestTargetDistance > self.TargetDistance ) and self.TargetDistance or self.ClosestTargetDistance
end
function AI_AIR:ClearTargetDistance()
self.TargetDistance = nil
self.ClosestTargetDistance = nil
end
--- Sets (modifies) the minimum and maximum speed of the patrol.
-- @param #AI_AIR self
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h.
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h.
-- @return #AI_AIR self
function AI_AIR:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed )
self:F2( { PatrolMinSpeed, PatrolMaxSpeed } )
self.PatrolMinSpeed = PatrolMinSpeed
self.PatrolMaxSpeed = PatrolMaxSpeed
end
--- Sets (modifies) the minimum and maximum RTB speed of the patrol.
-- @param #AI_AIR self
-- @param DCS#Speed RTBMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h.
-- @param DCS#Speed RTBMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h.
-- @return #AI_AIR self
function AI_AIR:SetRTBSpeed( RTBMinSpeed, RTBMaxSpeed )
self:F2( { RTBMinSpeed, RTBMaxSpeed } )
self.RTBMinSpeed = RTBMinSpeed
self.RTBMaxSpeed = RTBMaxSpeed
end
--- Sets the floor and ceiling altitude of the patrol.
-- @param #AI_AIR self
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @return #AI_AIR self
function AI_AIR:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude )
self:F2( { PatrolFloorAltitude, PatrolCeilingAltitude } )
self.PatrolFloorAltitude = PatrolFloorAltitude
self.PatrolCeilingAltitude = PatrolCeilingAltitude
end
--- Sets the home airbase.
-- @param #AI_AIR self
-- @param Wrapper.Airbase#AIRBASE HomeAirbase
-- @return #AI_AIR self
function AI_AIR:SetHomeAirbase( HomeAirbase )
self:F2( { HomeAirbase } )
self.HomeAirbase = HomeAirbase
end
--- Sets to refuel at the given tanker.
-- @param #AI_AIR self
-- @param Wrapper.Group#GROUP TankerName The group name of the tanker as defined within the Mission Editor or spawned.
-- @return #AI_AIR self
function AI_AIR:SetTanker( TankerName )
self:F2( { TankerName } )
self.TankerName = TankerName
end
--- Sets the disengage range, that when engaging a target beyond the specified range, the engagement will be cancelled and the plane will RTB.
-- @param #AI_AIR self
-- @param #number DisengageRadius The disengage range.
-- @return #AI_AIR self
function AI_AIR:SetDisengageRadius( DisengageRadius )
self:F2( { DisengageRadius } )
self.DisengageRadius = DisengageRadius
end
--- Set the status checking off.
-- @param #AI_AIR self
-- @return #AI_AIR self
function AI_AIR:SetStatusOff()
self:F2()
self.CheckStatus = false
end
--- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base.
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated.
-- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_AIR.
-- Once the time is finished, the old AI will return to the base.
-- @param #AI_AIR self
-- @param #number FuelThresholdPercentage The treshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel.
-- @param #number OutOfFuelOrbitTime The amount of seconds the out of fuel AIControllable will orbit before returning to the base.
-- @return #AI_AIR self
function AI_AIR:SetFuelThreshold( FuelThresholdPercentage, OutOfFuelOrbitTime )
self.FuelThresholdPercentage = FuelThresholdPercentage
self.OutOfFuelOrbitTime = OutOfFuelOrbitTime
self.Controllable:OptionRTBBingoFuel( false )
return self
end
--- When the AI is damaged beyond a certain treshold, it is required that the AI returns to the home base.
-- However, damage cannot be foreseen early on.
-- Therefore, when the damage treshold is reached,
-- the AI will return immediately to the home base (RTB).
-- Note that for groups, the average damage of the complete group will be calculated.
-- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage treshold will be 0.25.
-- @param #AI_AIR self
-- @param #number PatrolDamageThreshold The treshold in percentage (between 0 and 1) when the AI is considered to be damaged.
-- @return #AI_AIR self
function AI_AIR:SetDamageThreshold( PatrolDamageThreshold )
self.PatrolManageDamage = true
self.PatrolDamageThreshold = PatrolDamageThreshold
return self
end
--- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings.
-- @param #AI_AIR self
-- @return #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_AIR:onafterStart( Controllable, From, Event, To )
self:__Status( 10 ) -- Check status status every 30 seconds.
self:HandleEvent( EVENTS.PilotDead, self.OnPilotDead )
self:HandleEvent( EVENTS.Crash, self.OnCrash )
self:HandleEvent( EVENTS.Ejection, self.OnEjection )
Controllable:OptionROEHoldFire()
Controllable:OptionROTVertical()
end
--- @param #AI_AIR self
function AI_AIR:onbeforeStatus()
return self.CheckStatus
end
--- @param #AI_AIR self
function AI_AIR:onafterStatus()
if self.Controllable and self.Controllable:IsAlive() then
local RTB = false
local DistanceFromHomeBase = self.HomeAirbase:GetCoordinate():Get2DDistance( self.Controllable:GetCoordinate() )
if not self:Is( "Holding" ) and not self:Is( "Returning" ) then
local DistanceFromHomeBase = self.HomeAirbase:GetCoordinate():Get2DDistance( self.Controllable:GetCoordinate() )
if DistanceFromHomeBase > self.DisengageRadius then
self:E( self.Controllable:GetName() .. " is too far from home base, RTB!" )
self:Hold( 300 )
RTB = false
end
end
-- I think this code is not requirement anymore after release 2.5.
-- if self:Is( "Fuel" ) or self:Is( "Damaged" ) or self:Is( "LostControl" ) then
-- if DistanceFromHomeBase < 5000 then
-- self:E( self.Controllable:GetName() .. " is near the home base, RTB!" )
-- self:Home( "Destroy" )
-- end
-- end
if not self:Is( "Fuel" ) and not self:Is( "Home" ) then
local Fuel = self.Controllable:GetFuelMin()
-- If the fuel in the controllable is below the treshold percentage,
-- then send for refuel in case of a tanker, otherwise RTB.
if Fuel < self.FuelThresholdPercentage then
if self.TankerName then
self:E( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... Refuelling at Tanker!" )
self:Refuel()
else
self:E( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... RTB!" )
local OldAIControllable = self.Controllable
local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed )
local TimedOrbitTask = OldAIControllable:TaskControlled( OrbitTask, OldAIControllable:TaskCondition(nil,nil,nil,nil,self.OutOfFuelOrbitTime,nil ) )
OldAIControllable:SetTask( TimedOrbitTask, 10 )
self:Fuel()
RTB = true
end
else
end
end
-- TODO: Check GROUP damage function.
local Damage = self.Controllable:GetLife()
local InitialLife = self.Controllable:GetLife0()
-- If the group is damaged, then RTB.
-- Note that a group can consist of more units, so if one unit is damaged of a group, the mission may continue.
-- The damaged unit will RTB due to DCS logic, and the others will continue to engage.
if ( Damage / InitialLife ) < self.PatrolDamageThreshold then
self:E( self.Controllable:GetName() .. " is damaged: " .. Damage .. " ... RTB!" )
self:Damaged()
RTB = true
self:SetStatusOff()
end
-- Check if planes went RTB and are out of control.
-- We only check if planes are out of control, when they are in duty.
if self.Controllable:HasTask() == false then
if not self:Is( "Started" ) and
not self:Is( "Stopped" ) and
not self:Is( "Fuel" ) and
not self:Is( "Damaged" ) and
not self:Is( "Home" ) then
if self.IdleCount >= 10 then
if Damage ~= InitialLife then
self:Damaged()
else
self:E( self.Controllable:GetName() .. " control lost! " )
self:LostControl()
end
else
self.IdleCount = self.IdleCount + 1
end
end
else
self.IdleCount = 0
end
if RTB == true then
self:__RTB( self.TaskDelay )
end
if not self:Is("Home") then
self:__Status( 10 )
end
end
end
--- @param Wrapper.Group#GROUP AIGroup
function AI_AIR.RTBRoute( AIGroup, Fsm )
AIGroup:F( { "AI_AIR.RTBRoute:", AIGroup:GetName() } )
if AIGroup:IsAlive() then
Fsm:RTB()
end
end
--- @param Wrapper.Group#GROUP AIGroup
function AI_AIR.RTBHold( AIGroup, Fsm )
AIGroup:F( { "AI_AIR.RTBHold:", AIGroup:GetName() } )
if AIGroup:IsAlive() then
Fsm:__RTB( Fsm.TaskDelay )
Fsm:Return()
local Task = AIGroup:TaskOrbitCircle( 4000, 400 )
AIGroup:SetTask( Task )
end
end
--- @param #AI_AIR self
-- @param Wrapper.Group#GROUP AIGroup
function AI_AIR:onafterRTB( AIGroup, From, Event, To )
self:F( { AIGroup, From, Event, To } )
if AIGroup and AIGroup:IsAlive() then
self:E( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" )
self:ClearTargetDistance()
--AIGroup:ClearTasks()
local EngageRoute = {}
--- Calculate the target route point.
local FromCoord = AIGroup:GetCoordinate()
local ToTargetCoord = self.HomeAirbase:GetCoordinate()
local ToTargetSpeed = math.random( self.RTBMinSpeed, self.RTBMaxSpeed )
local ToAirbaseAngle = FromCoord:GetAngleDegrees( FromCoord:GetDirectionVec3( ToTargetCoord ) )
local Distance = FromCoord:Get2DDistance( ToTargetCoord )
local ToAirbaseCoord = FromCoord:Translate( 5000, ToAirbaseAngle )
if Distance < 5000 then
self:E( "RTB and near the airbase!" )
self:Home()
return
end
--- Create a route point of type air.
local FromRTBRoutePoint = FromCoord:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
ToTargetSpeed,
true
)
--- Create a route point of type air.
local ToRTBRoutePoint = ToAirbaseCoord:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
ToTargetSpeed,
true
)
EngageRoute[#EngageRoute+1] = FromRTBRoutePoint
EngageRoute[#EngageRoute+1] = ToRTBRoutePoint
local Tasks = {}
Tasks[#Tasks+1] = AIGroup:TaskFunction( "AI_AIR.RTBRoute", self )
EngageRoute[#EngageRoute].task = AIGroup:TaskCombo( Tasks )
AIGroup:OptionROEHoldFire()
AIGroup:OptionROTEvadeFire()
--- NOW ROUTE THE GROUP!
AIGroup:Route( EngageRoute, self.TaskDelay )
end
end
--- @param #AI_AIR self
-- @param Wrapper.Group#GROUP AIGroup
function AI_AIR:onafterHome( AIGroup, From, Event, To )
self:F( { AIGroup, From, Event, To } )
self:E( "Group " .. self.Controllable:GetName() .. " ... Home! ( " .. self:GetState() .. " )" )
if AIGroup and AIGroup:IsAlive() then
end
end
--- @param #AI_AIR self
-- @param Wrapper.Group#GROUP AIGroup
function AI_AIR:onafterHold( AIGroup, From, Event, To, HoldTime )
self:F( { AIGroup, From, Event, To } )
self:E( "Group " .. self.Controllable:GetName() .. " ... Holding! ( " .. self:GetState() .. " )" )
if AIGroup and AIGroup:IsAlive() then
local OrbitTask = AIGroup:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed )
local TimedOrbitTask = AIGroup:TaskControlled( OrbitTask, AIGroup:TaskCondition( nil, nil, nil, nil, HoldTime , nil ) )
local RTBTask = AIGroup:TaskFunction( "AI_AIR.RTBHold", self )
local OrbitHoldTask = AIGroup:TaskOrbitCircle( 4000, self.PatrolMinSpeed )
--AIGroup:SetState( AIGroup, "AI_AIR", self )
AIGroup:SetTask( AIGroup:TaskCombo( { TimedOrbitTask, RTBTask, OrbitHoldTask } ), 1 )
end
end
--- @param Wrapper.Group#GROUP AIGroup
function AI_AIR.Resume( AIGroup, Fsm )
AIGroup:I( { "AI_AIR.Resume:", AIGroup:GetName() } )
if AIGroup:IsAlive() then
Fsm:__RTB( Fsm.TaskDelay )
end
end
--- @param #AI_AIR self
-- @param Wrapper.Group#GROUP AIGroup
function AI_AIR:onafterRefuel( AIGroup, From, Event, To )
self:F( { AIGroup, From, Event, To } )
self:E( "Group " .. self.Controllable:GetName() .. " ... Refuelling! ( " .. self:GetState() .. " )" )
if AIGroup and AIGroup:IsAlive() then
local Tanker = GROUP:FindByName( self.TankerName )
if Tanker:IsAlive() and Tanker:IsAirPlane() then
local RefuelRoute = {}
--- Calculate the target route point.
local FromRefuelCoord = AIGroup:GetCoordinate()
local ToRefuelCoord = Tanker:GetCoordinate()
local ToRefuelSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed )
--- Create a route point of type air.
local FromRefuelRoutePoint = FromRefuelCoord:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
ToRefuelSpeed,
true
)
--- Create a route point of type air.
local ToRefuelRoutePoint = ToRefuelCoord:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
ToRefuelSpeed,
true
)
self:F( { ToRefuelSpeed = ToRefuelSpeed } )
RefuelRoute[#RefuelRoute+1] = FromRefuelRoutePoint
RefuelRoute[#RefuelRoute+1] = ToRefuelRoutePoint
AIGroup:OptionROEHoldFire()
AIGroup:OptionROTEvadeFire()
local Tasks = {}
Tasks[#Tasks+1] = AIGroup:TaskRefueling()
Tasks[#Tasks+1] = AIGroup:TaskFunction( self:GetClassName() .. ".Resume", self )
RefuelRoute[#RefuelRoute].task = AIGroup:TaskCombo( Tasks )
AIGroup:Route( RefuelRoute, self.TaskDelay )
else
self:RTB()
end
end
end
--- @param #AI_AIR self
function AI_AIR:onafterDead()
self:SetStatusOff()
end
--- @param #AI_AIR self
-- @param Core.Event#EVENTDATA EventData
function AI_AIR:OnCrash( EventData )
if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then
self:E( self.Controllable:GetUnits() )
if #self.Controllable:GetUnits() == 1 then
self:__Crash( self.TaskDelay, EventData )
end
end
end
--- @param #AI_AIR self
-- @param Core.Event#EVENTDATA EventData
function AI_AIR:OnEjection( EventData )
if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then
self:__Eject( self.TaskDelay, EventData )
end
end
--- @param #AI_AIR self
-- @param Core.Event#EVENTDATA EventData
function AI_AIR:OnPilotDead( EventData )
if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then
self:__PilotDead( self.TaskDelay, EventData )
end
end

View File

@@ -36,6 +36,7 @@
-- @field #boolean ReportTargets If true, nearby targets are reported. -- @field #boolean ReportTargets If true, nearby targets are reported.
-- @Field DCSTypes#AI.Option.Air.val.ROE OptionROE Which ROE is set to the FollowGroup. -- @Field DCSTypes#AI.Option.Air.val.ROE OptionROE Which ROE is set to the FollowGroup.
-- @field DCSTypes#AI.Option.Air.val.REACTION_ON_THREAT OptionReactionOnThreat Which REACTION_ON_THREAT is set to the FollowGroup. -- @field DCSTypes#AI.Option.Air.val.REACTION_ON_THREAT OptionReactionOnThreat Which REACTION_ON_THREAT is set to the FollowGroup.
-- @field #number dtFollow Time step between position updates.
--- Build large formations, make AI follow a @{Wrapper.Client#CLIENT} (player) leader or a @{Wrapper.Unit#UNIT} (AI) leader. --- Build large formations, make AI follow a @{Wrapper.Client#CLIENT} (player) leader or a @{Wrapper.Unit#UNIT} (AI) leader.
@@ -106,6 +107,7 @@ AI_FORMATION = {
FollowScheduler = nil, FollowScheduler = nil,
OptionROE = AI.Option.Air.val.ROE.OPEN_FIRE, OptionROE = AI.Option.Air.val.ROE.OPEN_FIRE,
OptionReactionOnThreat = AI.Option.Air.val.REACTION_ON_THREAT.ALLOW_ABORT_MISSION, OptionReactionOnThreat = AI.Option.Air.val.REACTION_ON_THREAT.ALLOW_ABORT_MISSION,
dtFollow = 0.5,
} }
--- AI_FORMATION.Mode class --- AI_FORMATION.Mode class
@@ -125,6 +127,7 @@ AI_FORMATION = {
-- @param Wrapper.Unit#UNIT FollowUnit The UNIT leading the FolllowGroupSet. -- @param Wrapper.Unit#UNIT FollowUnit The UNIT leading the FolllowGroupSet.
-- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. -- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit.
-- @param #string FollowName Name of the escort. -- @param #string FollowName Name of the escort.
-- @param #string FollowBriefing Briefing.
-- @return #AI_FORMATION self -- @return #AI_FORMATION self
function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefing ) --R2.1 function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefing ) --R2.1
local self = BASE:Inherit( self, FSM_SET:New( FollowGroupSet ) ) local self = BASE:Inherit( self, FSM_SET:New( FollowGroupSet ) )
@@ -139,7 +142,7 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
self:AddTransition( "*", "Stop", "Stopped" ) self:AddTransition( "*", "Stop", "Stopped" )
self:AddTransition( "None", "Start", "Following" ) self:AddTransition( {"None", "Stopped"}, "Start", "Following" )
self:AddTransition( "*", "FormationLine", "*" ) self:AddTransition( "*", "FormationLine", "*" )
--- FormationLine Handler OnBefore for AI_FORMATION --- FormationLine Handler OnBefore for AI_FORMATION
@@ -620,6 +623,16 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
return self return self
end end
--- Set time interval between updates of the formation.
-- @param #AI_FORMATION self
-- @param #number dt Time step in seconds between formation updates. Default is every 0.5 seconds.
-- @return #AI_FORMATION
function AI_FORMATION:SetFollowTimeInterval(dt) --R2.1
self.dtFollow=dt or 0.5
return self
end
--- This function is for test, it will put on the frequency of the FollowScheduler a red smoke at the direction vector calculated for the escort to fly to. --- This function is for test, it will put on the frequency of the FollowScheduler a red smoke at the direction vector calculated for the escort to fly to.
-- This allows to visualize where the escort is flying to. -- This allows to visualize where the escort is flying to.
-- @param #AI_FORMATION self -- @param #AI_FORMATION self
@@ -893,7 +906,30 @@ function AI_FORMATION:SetFlightRandomization( FlightRandomization ) --R2.1
end end
--- @param Follow#AI_FORMATION self --- Stop function. Formation will not be updated any more.
-- @param #AI_FORMATION self
-- @param Core.Set#SET_GROUP FollowGroupSet The following set of groups.
-- @param #string From From state.
-- @param #string Event Event.
-- @pram #string To The to state.
function AI_FORMATION:onafterStop(FollowGroupSet, From, Event, To) --R2.1
self:E("Stopping formation.")
end
--- Follow event fuction. Check if coming from state "stopped". If so the transition is rejected.
-- @param #AI_FORMATION self
-- @param Core.Set#SET_GROUP FollowGroupSet The following set of groups.
-- @param #string From From state.
-- @param #string Event Event.
-- @pram #string To The to state.
function AI_FORMATION:onbeforeFollow( FollowGroupSet, From, Event, To ) --R2.1
if From=="Stopped" then
return false -- Deny transition.
end
return true
end
--- @param #AI_FORMATION self
function AI_FORMATION:onenterFollowing( FollowGroupSet ) --R2.1 function AI_FORMATION:onenterFollowing( FollowGroupSet ) --R2.1
self:F( ) self:F( )
@@ -1032,8 +1068,8 @@ function AI_FORMATION:onenterFollowing( FollowGroupSet ) --R2.1
end, end,
self, ClientUnit, CT1, CV1, CT2, CV2 self, ClientUnit, CT1, CV1, CT2, CV2
) )
self:__Follow( -0.5 ) self:__Follow( -self.dtFollow )
end end
end end

View File

@@ -342,18 +342,18 @@ do -- COORDINATE
return x - Precision <= self.x and x + Precision >= self.x and z - Precision <= self.z and z + Precision >= self.z return x - Precision <= self.x and x + Precision >= self.x and z - Precision <= self.z and z + Precision >= self.z
end end
--- Returns if the 2 coordinates are at the same 2D position. --- Scan/find objects (units, statics, scenery) within a certain radius around the coordinate using the world.searchObjects() DCS API function.
-- @param #COORDINATE self -- @param #COORDINATE self
-- @param #number radius (Optional) Scan radius in meters. Default 100 m. -- @param #number radius (Optional) Scan radius in meters. Default 100 m.
-- @param #boolean scanunits (Optional) If true scan for units. Default true. -- @param #boolean scanunits (Optional) If true scan for units. Default true.
-- @param #boolean scanstatics (Optional) If true scan for static objects. Default true. -- @param #boolean scanstatics (Optional) If true scan for static objects. Default true.
-- @param #boolean scanscenery (Optional) If true scan for scenery objects. Default false. -- @param #boolean scanscenery (Optional) If true scan for scenery objects. Default false.
-- @return True if units were found. -- @return #boolean True if units were found.
-- @return True if statics were found. -- @return #boolean True if statics were found.
-- @return True if scenery objects were found. -- @return #boolean True if scenery objects were found.
-- @return Unit objects found. -- @return #table Table of MOOSE @[#Wrapper.Unit#UNIT} objects found.
-- @return Static objects found. -- @return #table Table of DCS static objects found.
-- @return Scenery objects found. -- @return #table Table of DCS scenery objects found.
function COORDINATE:ScanObjects(radius, scanunits, scanstatics, scanscenery) function COORDINATE:ScanObjects(radius, scanunits, scanstatics, scanscenery)
self:F(string.format("Scanning in radius %.1f m.", radius)) self:F(string.format("Scanning in radius %.1f m.", radius))
@@ -405,18 +405,17 @@ do -- COORDINATE
local ObjectCategory = ZoneObject:getCategory() local ObjectCategory = ZoneObject:getCategory()
-- Check for unit or static objects -- Check for unit or static objects
--if (ObjectCategory == Object.Category.UNIT and ZoneObject:isExist() and ZoneObject:isActive()) then if ObjectCategory==Object.Category.UNIT and ZoneObject:isExist() then
if (ObjectCategory == Object.Category.UNIT and ZoneObject:isExist()) then
table.insert(Units, UNIT:Find(ZoneObject)) table.insert(Units, UNIT:Find(ZoneObject))
gotunits=true gotunits=true
elseif (ObjectCategory == Object.Category.STATIC and ZoneObject:isExist()) then elseif ObjectCategory==Object.Category.STATIC and ZoneObject:isExist() then
table.insert(Statics, ZoneObject) table.insert(Statics, ZoneObject)
gotstatics=true gotstatics=true
elseif ObjectCategory == Object.Category.SCENERY then elseif ObjectCategory==Object.Category.SCENERY then
table.insert(Scenery, ZoneObject) table.insert(Scenery, ZoneObject)
gotscenery=true gotscenery=true
@@ -460,18 +459,47 @@ do -- COORDINATE
--- Add a Distance in meters from the COORDINATE orthonormal plane, with the given angle, and calculate the new COORDINATE. --- Add a Distance in meters from the COORDINATE orthonormal plane, with the given angle, and calculate the new COORDINATE.
-- @param #COORDINATE self -- @param #COORDINATE self
-- @param DCS#Distance Distance The Distance to be added in meters. -- @param DCS#Distance Distance The Distance to be added in meters.
-- @param DCS#Angle Angle The Angle in degrees. -- @param DCS#Angle Angle The Angle in degrees. Defaults to 0 if not specified (nil).
-- @return #COORDINATE The new calculated COORDINATE. -- @param #boolean Keepalt If true, keep altitude of original coordinate. Default is that the new coordinate is created at the translated land height.
function COORDINATE:Translate( Distance, Angle ) -- @return Core.Point#COORDINATE The new calculated COORDINATE.
function COORDINATE:Translate( Distance, Angle, Keepalt )
local SX = self.x local SX = self.x
local SY = self.z local SY = self.z
local Radians = Angle / 180 * math.pi local Radians = (Angle or 0) / 180 * math.pi
local TX = Distance * math.cos( Radians ) + SX local TX = Distance * math.cos( Radians ) + SX
local TY = Distance * math.sin( Radians ) + SY local TY = Distance * math.sin( Radians ) + SY
return COORDINATE:NewFromVec2( { x = TX, y = TY } ) if Keepalt then
return COORDINATE:NewFromVec3( { x = TX, y=self.y, z = TY } )
else
return COORDINATE:NewFromVec2( { x = TX, y = TY } )
end
end end
--- Rotate coordinate in 2D (x,z) space.
-- @param #COORDINATE self
-- @param DCS#Angle Angle Angle of rotation in degrees.
-- @return Core.Point#COORDINATE The rotated coordinate.
function COORDINATE:Rotate2D(Angle)
if not Angle then
return self
end
local phi=math.rad(Angle)
local X=self.z
local Y=self.x
--slocal R=math.sqrt(X*X+Y*Y)
local x=X*math.cos(phi)-Y*math.sin(phi)
local y=X*math.sin(phi)+Y*math.cos(phi)
-- Coordinate assignment looks bit strange but is correct.
return COORDINATE:NewFromVec3({x=y, y=self.y, z=x})
end
--- Return a random Vec2 within an Outer Radius and optionally NOT within an Inner Radius of the COORDINATE. --- Return a random Vec2 within an Outer Radius and optionally NOT within an Inner Radius of the COORDINATE.
-- @param #COORDINATE self -- @param #COORDINATE self
-- @param DCS#Distance OuterRadius -- @param DCS#Distance OuterRadius
@@ -1003,11 +1031,15 @@ do -- COORDINATE
function COORDINATE:WaypointAir( AltType, Type, Action, Speed, SpeedLocked, airbase, DCSTasks, description ) function COORDINATE:WaypointAir( AltType, Type, Action, Speed, SpeedLocked, airbase, DCSTasks, description )
self:F2( { AltType, Type, Action, Speed, SpeedLocked } ) self:F2( { AltType, Type, Action, Speed, SpeedLocked } )
-- Defaults -- Set alttype or "RADIO" which is AGL.
AltType=AltType or "RADIO" AltType=AltType or "RADIO"
-- Speedlocked by default
if SpeedLocked==nil then if SpeedLocked==nil then
SpeedLocked=true SpeedLocked=true
end end
-- Speed or default 500 km/h.
Speed=Speed or 500 Speed=Speed or 500
-- Waypoint array. -- Waypoint array.
@@ -1016,19 +1048,26 @@ do -- COORDINATE
-- Coordinates. -- Coordinates.
RoutePoint.x = self.x RoutePoint.x = self.x
RoutePoint.y = self.z RoutePoint.y = self.z
-- Altitude. -- Altitude.
RoutePoint.alt = self.y RoutePoint.alt = self.y
RoutePoint.alt_type = AltType RoutePoint.alt_type = AltType
-- Waypoint type. -- Waypoint type.
RoutePoint.type = Type or nil RoutePoint.type = Type or nil
RoutePoint.action = Action or nil RoutePoint.action = Action or nil
-- Set speed/ETA.
-- Speed.
RoutePoint.speed = Speed/3.6 RoutePoint.speed = Speed/3.6
RoutePoint.speed_locked = SpeedLocked RoutePoint.speed_locked = SpeedLocked
RoutePoint.ETA=nil
RoutePoint.ETA_locked = false -- ETA.
RoutePoint.ETA=0
RoutePoint.ETA_locked=true
-- Waypoint description. -- Waypoint description.
RoutePoint.name=description RoutePoint.name=description
-- Airbase parameters for takeoff and landing points. -- Airbase parameters for takeoff and landing points.
if airbase then if airbase then
local AirbaseID = airbase:GetID() local AirbaseID = airbase:GetID()
@@ -1037,31 +1076,29 @@ do -- COORDINATE
RoutePoint.linkUnit = AirbaseID RoutePoint.linkUnit = AirbaseID
RoutePoint.helipadId = AirbaseID RoutePoint.helipadId = AirbaseID
elseif AirbaseCategory == Airbase.Category.AIRDROME then elseif AirbaseCategory == Airbase.Category.AIRDROME then
RoutePoint.airdromeId = AirbaseID RoutePoint.airdromeId = AirbaseID
else else
self:T("ERROR: Unknown airbase category in COORDINATE:WaypointAir()!") self:T("ERROR: Unknown airbase category in COORDINATE:WaypointAir()!")
end end
end
--self:MarkToAll(string.format("Landing waypoint at airbase %s, ID=%d, Category=%d", airbase:GetName(), AirbaseID, AirbaseCategory ))
end
-- ["task"] =
-- {
-- ["id"] = "ComboTask",
-- ["params"] =
-- {
-- ["tasks"] =
-- {
-- }, -- end of ["tasks"]
-- }, -- end of ["params"]
-- }, -- end of ["task"]
-- Waypoint tasks. -- Waypoint tasks.
RoutePoint.task = {} RoutePoint.task = {}
RoutePoint.task.id = "ComboTask" RoutePoint.task.id = "ComboTask"
RoutePoint.task.params = {} RoutePoint.task.params = {}
RoutePoint.task.params.tasks = DCSTasks or {} RoutePoint.task.params.tasks = DCSTasks or {}
--RoutePoint.properties={}
--RoutePoint.properties.addopt={}
--RoutePoint.formation_template=""
-- Debug.
self:T({RoutePoint=RoutePoint}) self:T({RoutePoint=RoutePoint})
-- Return waypoint.
return RoutePoint return RoutePoint
end end
@@ -1121,6 +1158,9 @@ do -- COORDINATE
--- Build a Waypoint Air "Landing". --- Build a Waypoint Air "Landing".
-- @param #COORDINATE self -- @param #COORDINATE self
-- @param DCS#Speed Speed Airspeed in km/h. -- @param DCS#Speed Speed Airspeed in km/h.
-- @param Wrapper.Airbase#AIRBASE airbase The airbase for takeoff and landing points.
-- @param #table DCSTasks A table of @{DCS#Task} items which are executed at the waypoint.
-- @param #string description A text description of the waypoint, which will be shown on the F10 map.
-- @return #table The route point. -- @return #table The route point.
-- @usage -- @usage
-- --
@@ -1129,8 +1169,8 @@ do -- COORDINATE
-- LandingWaypoint = LandingCoord:WaypointAirLanding( 60 ) -- LandingWaypoint = LandingCoord:WaypointAirLanding( 60 )
-- HeliGroup:Route( { LandWaypoint }, 1 ) -- Start landing the helicopter in one second. -- HeliGroup:Route( { LandWaypoint }, 1 ) -- Start landing the helicopter in one second.
-- --
function COORDINATE:WaypointAirLanding( Speed ) function COORDINATE:WaypointAirLanding( Speed, airbase, DCSTasks, description )
return self:WaypointAir( nil, COORDINATE.WaypointType.Land, COORDINATE.WaypointAction.Landing, Speed ) return self:WaypointAir(nil, COORDINATE.WaypointType.Land, COORDINATE.WaypointAction.Landing, Speed, false, airbase, DCSTasks, description)
end end

View File

@@ -9,12 +9,12 @@
-- --
-- The Radio contains 2 classes : RADIO and BEACON -- The Radio contains 2 classes : RADIO and BEACON
-- --
-- What are radio communications in DCS ? -- What are radio communications in DCS?
-- --
-- * Radio transmissions consist of **sound files** that are broadcasted on a specific **frequency** (e.g. 115MHz) and **modulation** (e.g. AM), -- * Radio transmissions consist of **sound files** that are broadcasted on a specific **frequency** (e.g. 115MHz) and **modulation** (e.g. AM),
-- * They can be **subtitled** for a specific **duration**, the **power** in Watts of the transmiter's antenna can be set, and the transmission can be **looped**. -- * They can be **subtitled** for a specific **duration**, the **power** in Watts of the transmiter's antenna can be set, and the transmission can be **looped**.
-- --
-- How to supply DCS my own Sound Files ? -- How to supply DCS my own Sound Files?
-- --
-- * Your sound files need to be encoded in **.ogg** or .wav, -- * Your sound files need to be encoded in **.ogg** or .wav,
-- * Your sound files should be **as tiny as possible**. It is suggested you encode in .ogg with low bitrate and sampling settings, -- * Your sound files should be **as tiny as possible**. It is suggested you encode in .ogg with low bitrate and sampling settings,
@@ -23,7 +23,7 @@
-- --
-- Due to weird DCS quirks, **radio communications behave differently** if sent by a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP} or by any other @{Wrapper.Positionable#POSITIONABLE} -- Due to weird DCS quirks, **radio communications behave differently** if sent by a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP} or by any other @{Wrapper.Positionable#POSITIONABLE}
-- --
-- * If the transmitter is a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}, DCS will set the power of the transmission automatically, -- * If the transmitter is a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}, DCS will set the power of the transmission automatically,
-- * If the transmitter is any other @{Wrapper.Positionable#POSITIONABLE}, the transmisison can't be subtitled or looped. -- * If the transmitter is any other @{Wrapper.Positionable#POSITIONABLE}, the transmisison can't be subtitled or looped.
-- --
-- Note that obviously, the **frequency** and the **modulation** of the transmission are important only if the players are piloting an **Advanced System Modelling** enabled aircraft, -- Note that obviously, the **frequency** and the **modulation** of the transmission are important only if the players are piloting an **Advanced System Modelling** enabled aircraft,
@@ -33,7 +33,7 @@
-- --
-- === -- ===
-- --
-- ### Author: Hugues "Grey_Echo" Bousquet -- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky
-- --
-- @module Core.Radio -- @module Core.Radio
-- @image Core_Radio.JPG -- @image Core_Radio.JPG
@@ -66,24 +66,25 @@
-- * @{#RADIO.SetPower}() : Sets the power of the antenna in Watts -- * @{#RADIO.SetPower}() : Sets the power of the antenna in Watts
-- * @{#RADIO.NewGenericTransmission}() : Shortcut to set all the relevant parameters in one method call -- * @{#RADIO.NewGenericTransmission}() : Shortcut to set all the relevant parameters in one method call
-- --
-- What is this power thing ? -- What is this power thing?
-- --
-- * If your transmission is sent by a @{Wrapper.Positionable#POSITIONABLE} other than a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}, you can set the power of the antenna, -- * If your transmission is sent by a @{Wrapper.Positionable#POSITIONABLE} other than a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}, you can set the power of the antenna,
-- * Otherwise, DCS sets it automatically, depending on what's available on your Unit, -- * Otherwise, DCS sets it automatically, depending on what's available on your Unit,
-- * If the player gets **too far** from the transmiter, or if the antenna is **too weak**, the transmission will **fade** and **become noisyer**, -- * If the player gets **too far** from the transmitter, or if the antenna is **too weak**, the transmission will **fade** and **become noisyer**,
-- * This an automated DCS calculation you have no say on, -- * This an automated DCS calculation you have no say on,
-- * For reference, a standard VOR station has a 100W antenna, a standard AA TACAN has a 120W antenna, and civilian ATC's antenna usually range between 300 and 500W, -- * For reference, a standard VOR station has a 100 W antenna, a standard AA TACAN has a 120 W antenna, and civilian ATC's antenna usually range between 300 and 500 W,
-- * Note that if the transmission has a subtitle, it will be readable, regardless of the quality of the transmission. -- * Note that if the transmission has a subtitle, it will be readable, regardless of the quality of the transmission.
-- --
-- @type RADIO -- @type RADIO
-- @field Positionable#POSITIONABLE Positionable The transmiter -- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will transmit the radio calls.
-- @field #string FileName Name of the sound file -- @field #string FileName Name of the sound file played.
-- @field #number Frequency Frequency of the transmission in Hz -- @field #number Frequency Frequency of the transmission in Hz.
-- @field #number Modulation Modulation of the transmission (either radio.modulation.AM or radio.modulation.FM) -- @field #number Modulation Modulation of the transmission (either radio.modulation.AM or radio.modulation.FM).
-- @field #string Subtitle Subtitle of the transmission -- @field #string Subtitle Subtitle of the transmission.
-- @field #number SubtitleDuration Duration of the Subtitle in seconds -- @field #number SubtitleDuration Duration of the Subtitle in seconds.
-- @field #number Power Power of the antenna is Watts -- @field #number Power Power of the antenna is Watts.
-- @field #boolean Loop (default true) -- @field #boolean Loop Transmission is repeated (default true).
-- @field #string alias Name of the radio transmitter.
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
RADIO = { RADIO = {
ClassName = "RADIO", ClassName = "RADIO",
@@ -93,19 +94,19 @@ RADIO = {
Subtitle = "", Subtitle = "",
SubtitleDuration = 0, SubtitleDuration = 0,
Power = 100, Power = 100,
Loop = true, Loop = false,
alias=nil,
} }
--- Create a new RADIO Object. This doesn't broadcast a transmission, though, use @{#RADIO.Broadcast} to actually broadcast --- Create a new RADIO Object. This doesn't broadcast a transmission, though, use @{#RADIO.Broadcast} to actually broadcast.
-- If you want to create a RADIO, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetRadio}() instead -- If you want to create a RADIO, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetRadio}() instead.
-- @param #RADIO self -- @param #RADIO self
-- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities. -- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities.
-- @return #RADIO Radio -- @return #RADIO The RADIO object or #nil if Positionable is invalid.
-- @return #nil If Positionable is invalid
function RADIO:New(Positionable) function RADIO:New(Positionable)
-- Inherit base
local self = BASE:Inherit( self, BASE:New() ) -- Core.Radio#RADIO local self = BASE:Inherit( self, BASE:New() ) -- Core.Radio#RADIO
self.Loop = true -- default Loop to true (not sure the above RADIO definition actually is working)
self:F(Positionable) self:F(Positionable)
if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid
@@ -113,11 +114,27 @@ function RADIO:New(Positionable)
return self return self
end end
self:E({"The passed positionable is invalid, no RADIO created", Positionable}) self:E({error="The passed positionable is invalid, no RADIO created!", positionable=Positionable})
return nil return nil
end end
--- Check validity of the filename passed and sets RADIO.FileName --- Set alias of the transmitter.
-- @param #RADIO self
-- @param #string alias Name of the radio transmitter.
-- @return #RADIO self
function RADIO:SetAlias(alias)
self.alias=tostring(alias)
return self
end
--- Get alias of the transmitter.
-- @param #RADIO self
-- @return #string Name of the transmitter.
function RADIO:GetAlias()
return tostring(self.alias)
end
--- Set the file name for the radio transmission.
-- @param #RADIO self -- @param #RADIO self
-- @param #string FileName File name of the sound file (i.e. "Noise.ogg") -- @param #string FileName File name of the sound file (i.e. "Noise.ogg")
-- @return #RADIO self -- @return #RADIO self
@@ -125,49 +142,63 @@ function RADIO:SetFileName(FileName)
self:F2(FileName) self:F2(FileName)
if type(FileName) == "string" then if type(FileName) == "string" then
if FileName:find(".ogg") or FileName:find(".wav") then if FileName:find(".ogg") or FileName:find(".wav") then
if not FileName:find("l10n/DEFAULT/") then if not FileName:find("l10n/DEFAULT/") then
FileName = "l10n/DEFAULT/" .. FileName FileName = "l10n/DEFAULT/" .. FileName
end end
self.FileName = FileName self.FileName = FileName
return self return self
end end
end end
self:E({"File name invalid. Maybe something wrong with the extension ?", self.FileName}) self:E({"File name invalid. Maybe something wrong with the extension?", FileName})
return self return self
end end
--- Check validity of the frequency passed and sets RADIO.Frequency --- Set the frequency for the radio transmission.
-- If the transmitting positionable is a unit or group, this also set the command "SetFrequency" with the defined frequency and modulation.
-- @param #RADIO self -- @param #RADIO self
-- @param #number Frequency in MHz (Ranges allowed for radio transmissions in DCS : 30-88 / 108-152 / 225-400MHz) -- @param #number Frequency Frequency in MHz. Ranges allowed for radio transmissions in DCS : 30-88 / 108-152 / 225-400MHz.
-- @return #RADIO self -- @return #RADIO self
function RADIO:SetFrequency(Frequency) function RADIO:SetFrequency(Frequency)
self:F2(Frequency) self:F2(Frequency)
if type(Frequency) == "number" then if type(Frequency) == "number" then
-- If frequency is in range -- If frequency is in range
if (Frequency >= 30 and Frequency < 88) or (Frequency >= 108 and Frequency < 152) or (Frequency >= 225 and Frequency < 400) then if (Frequency >= 30 and Frequency < 88) or (Frequency >= 108 and Frequency < 152) or (Frequency >= 225 and Frequency < 400) then
self.Frequency = Frequency * 1000000 -- Conversion in Hz
-- Convert frequency from MHz to Hz
self.Frequency = Frequency * 1000000
-- If the RADIO is attached to a UNIT or a GROUP, we need to send the DCS Command "SetFrequency" to change the UNIT or GROUP frequency -- If the RADIO is attached to a UNIT or a GROUP, we need to send the DCS Command "SetFrequency" to change the UNIT or GROUP frequency
if self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then if self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then
self.Positionable:SetCommand({
local commandSetFrequency={
id = "SetFrequency", id = "SetFrequency",
params = { params = {
frequency = self.Frequency, frequency = self.Frequency,
modulation = self.Modulation, modulation = self.Modulation,
} }
}) }
self:T2(commandSetFrequency)
self.Positionable:SetCommand(commandSetFrequency)
end end
return self return self
end end
end end
self:E({"Frequency is outside of DCS Frequency ranges (30-80, 108-152, 225-400). Frequency unchanged.", self.Frequency})
self:E({"Frequency is outside of DCS Frequency ranges (30-80, 108-152, 225-400). Frequency unchanged.", Frequency})
return self return self
end end
--- Check validity of the frequency passed and sets RADIO.Modulation --- Set AM or FM modulation of the radio transmitter.
-- @param #RADIO self -- @param #RADIO self
-- @param #number Modulation either radio.modulation.AM or radio.modulation.FM -- @param #number Modulation Modulation is either radio.modulation.AM or radio.modulation.FM.
-- @return #RADIO self -- @return #RADIO self
function RADIO:SetModulation(Modulation) function RADIO:SetModulation(Modulation)
self:F2(Modulation) self:F2(Modulation)
@@ -183,23 +214,24 @@ end
--- Check validity of the power passed and sets RADIO.Power --- Check validity of the power passed and sets RADIO.Power
-- @param #RADIO self -- @param #RADIO self
-- @param #number Power in W -- @param #number Power Power in W.
-- @return #RADIO self -- @return #RADIO self
function RADIO:SetPower(Power) function RADIO:SetPower(Power)
self:F2(Power) self:F2(Power)
if type(Power) == "number" then if type(Power) == "number" then
self.Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that self.Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that
return self else
self:E({"Power is invalid. Power unchanged.", self.Power})
end end
self:E({"Power is invalid. Power unchanged.", self.Power})
return self return self
end end
--- Check validity of the loop passed and sets RADIO.Loop --- Set message looping on or off.
-- @param #RADIO self -- @param #RADIO self
-- @param #boolean Loop -- @param #boolean Loop If true, message is repeated indefinitely.
-- @return #RADIO self -- @return #RADIO self
-- @usage
function RADIO:SetLoop(Loop) function RADIO:SetLoop(Loop)
self:F2(Loop) self:F2(Loop)
if type(Loop) == "boolean" then if type(Loop) == "boolean" then
@@ -232,13 +264,12 @@ function RADIO:SetSubtitle(Subtitle, SubtitleDuration)
self:E({"Subtitle is invalid. Subtitle reset.", self.Subtitle}) self:E({"Subtitle is invalid. Subtitle reset.", self.Subtitle})
end end
if type(SubtitleDuration) == "number" then if type(SubtitleDuration) == "number" then
if math.floor(math.abs(SubtitleDuration)) == SubtitleDuration then self.SubtitleDuration = SubtitleDuration
self.SubtitleDuration = SubtitleDuration else
return self self.SubtitleDuration = 0
end self:E({"SubtitleDuration is invalid. SubtitleDuration reset.", self.SubtitleDuration})
end end
self.SubtitleDuration = 0 return self
self:E({"SubtitleDuration is invalid. SubtitleDuration reset.", self.SubtitleDuration})
end end
--- Create a new transmission, that is to say, populate the RADIO with relevant data --- Create a new transmission, that is to say, populate the RADIO with relevant data
@@ -246,10 +277,10 @@ end
-- but it will work with a UNIT or a GROUP anyway. -- but it will work with a UNIT or a GROUP anyway.
-- Only the #RADIO and the Filename are mandatory -- Only the #RADIO and the Filename are mandatory
-- @param #RADIO self -- @param #RADIO self
-- @param #string FileName -- @param #string FileName Name of the sound file that will be transmitted.
-- @param #number Frequency in MHz -- @param #number Frequency Frequency in MHz.
-- @param #number Modulation either radio.modulation.AM or radio.modulation.FM -- @param #number Modulation Modulation of frequency, which is either radio.modulation.AM or radio.modulation.FM.
-- @param #number Power in W -- @param #number Power Power in W.
-- @return #RADIO self -- @return #RADIO self
function RADIO:NewGenericTransmission(FileName, Frequency, Modulation, Power, Loop) function RADIO:NewGenericTransmission(FileName, Frequency, Modulation, Power, Loop)
self:F({FileName, Frequency, Modulation, Power}) self:F({FileName, Frequency, Modulation, Power})
@@ -269,31 +300,43 @@ end
-- but it will work for any @{Wrapper.Positionable#POSITIONABLE}. -- but it will work for any @{Wrapper.Positionable#POSITIONABLE}.
-- Only the RADIO and the Filename are mandatory. -- Only the RADIO and the Filename are mandatory.
-- @param #RADIO self -- @param #RADIO self
-- @param #string FileName -- @param #string FileName Name of sound file.
-- @param #string Subtitle -- @param #string Subtitle Subtitle to be displayed with sound file.
-- @param #number SubtitleDuration in s -- @param #number SubtitleDuration Duration of subtitle display in seconds.
-- @param #number Frequency in MHz -- @param #number Frequency Frequency in MHz.
-- @param #number Modulation either radio.modulation.AM or radio.modulation.FM -- @param #number Modulation Modulation which can be either radio.modulation.AM or radio.modulation.FM
-- @param #boolean Loop -- @param #boolean Loop If true, loop message.
-- @return #RADIO self -- @return #RADIO self
function RADIO:NewUnitTransmission(FileName, Subtitle, SubtitleDuration, Frequency, Modulation, Loop) function RADIO:NewUnitTransmission(FileName, Subtitle, SubtitleDuration, Frequency, Modulation, Loop)
self:F({FileName, Subtitle, SubtitleDuration, Frequency, Modulation, Loop}) self:F({FileName, Subtitle, SubtitleDuration, Frequency, Modulation, Loop})
-- Set file name.
self:SetFileName(FileName) self:SetFileName(FileName)
local Duration = 5
if SubtitleDuration then Duration = SubtitleDuration end -- Set modulation AM/FM.
-- SubtitleDuration argument was missing, adding it if Modulation then
if Subtitle then self:SetSubtitle(Subtitle, Duration) end self:SetModulation(Modulation)
-- self:SetSubtitleDuration is non existent, removing faulty line end
-- if SubtitleDuration then self:SetSubtitleDuration(SubtitleDuration) end
if Frequency then self:SetFrequency(Frequency) end -- Set frequency.
if Modulation then self:SetModulation(Modulation) end if Frequency then
if Loop then self:SetLoop(Loop) end self:SetFrequency(Frequency)
end
-- Set subtitle.
if Subtitle then
self:SetSubtitle(Subtitle, SubtitleDuration or 0)
end
-- Set Looping.
if Loop then
self:SetLoop(Loop)
end
return self return self
end end
--- Actually Broadcast the transmission --- Broadcast the transmission.
-- * The Radio has to be populated with the new transmission before broadcasting. -- * The Radio has to be populated with the new transmission before broadcasting.
-- * Please use RADIO setters or either @{#RADIO.NewGenericTransmission} or @{#RADIO.NewUnitTransmission} -- * Please use RADIO setters or either @{#RADIO.NewGenericTransmission} or @{#RADIO.NewUnitTransmission}
-- * This class is in fact pretty smart, it determines the right DCS function to use depending on the type of POSITIONABLE -- * This class is in fact pretty smart, it determines the right DCS function to use depending on the type of POSITIONABLE
@@ -302,31 +345,38 @@ end
-- * If your POSITIONABLE is a UNIT or a GROUP, the Power is ignored. -- * If your POSITIONABLE is a UNIT or a GROUP, the Power is ignored.
-- * If your POSITIONABLE is not a UNIT or a GROUP, the Subtitle, SubtitleDuration are ignored -- * If your POSITIONABLE is not a UNIT or a GROUP, the Subtitle, SubtitleDuration are ignored
-- @param #RADIO self -- @param #RADIO self
-- @param #boolean viatrigger Use trigger.action.radioTransmission() in any case, i.e. also for UNITS and GROUPS.
-- @return #RADIO self -- @return #RADIO self
function RADIO:Broadcast() function RADIO:Broadcast(viatrigger)
self:F() self:F({viatrigger=viatrigger})
-- If the POSITIONABLE is actually a UNIT or a GROUP, use the more complicated DCS command system -- If the POSITIONABLE is actually a UNIT or a GROUP, use the more complicated DCS command system.
if self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then if (self.Positionable.ClassName=="UNIT" or self.Positionable.ClassName=="GROUP") and (not viatrigger) then
self:T2("Broadcasting from a UNIT or a GROUP") self:T("Broadcasting from a UNIT or a GROUP")
self.Positionable:SetCommand({
local commandTransmitMessage={
id = "TransmitMessage", id = "TransmitMessage",
params = { params = {
file = self.FileName, file = self.FileName,
duration = self.SubtitleDuration, duration = self.SubtitleDuration,
subtitle = self.Subtitle, subtitle = self.Subtitle,
loop = self.Loop, loop = self.Loop,
} }}
})
self:T3(commandTransmitMessage)
self.Positionable:SetCommand(commandTransmitMessage)
else else
-- If the POSITIONABLE is anything else, we revert to the general singleton function -- If the POSITIONABLE is anything else, we revert to the general singleton function
-- I need to give it a unique name, so that the transmission can be stopped later. I use the class ID -- I need to give it a unique name, so that the transmission can be stopped later. I use the class ID
self:T2("Broadcasting from a POSITIONABLE") self:T("Broadcasting from a POSITIONABLE")
trigger.action.radioTransmission(self.FileName, self.Positionable:GetPositionVec3(), self.Modulation, self.Loop, self.Frequency, self.Power, tostring(self.ID)) trigger.action.radioTransmission(self.FileName, self.Positionable:GetPositionVec3(), self.Modulation, self.Loop, self.Frequency, self.Power, tostring(self.ID))
end end
return self return self
end end
--- Stops a transmission --- Stops a transmission
-- This function is especially usefull to stop the broadcast of looped transmissions -- This function is especially usefull to stop the broadcast of looped transmissions
-- @param #RADIO self -- @param #RADIO self
@@ -335,10 +385,10 @@ function RADIO:StopBroadcast()
self:F() self:F()
-- If the POSITIONABLE is a UNIT or a GROUP, stop the transmission with the DCS "StopTransmission" command -- If the POSITIONABLE is a UNIT or a GROUP, stop the transmission with the DCS "StopTransmission" command
if self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then if self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then
self.Positionable:SetCommand({
id = "StopTransmission", local commandStopTransmission={id="StopTransmission", params={}}
params = {}
}) self.Positionable:SetCommand(commandStopTransmission)
else else
-- Else, we use the appropriate singleton funciton -- Else, we use the appropriate singleton funciton
trigger.action.stopRadioTransmission(tostring(self.ID)) trigger.action.stopRadioTransmission(tostring(self.ID))
@@ -364,22 +414,86 @@ end
-- Use @{#BEACON:StopRadioBeacon}() to stop it. -- Use @{#BEACON:StopRadioBeacon}() to stop it.
-- --
-- @type BEACON -- @type BEACON
-- @field #string ClassName Name of the class "BEACON".
-- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will receive radio capabilities.
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
BEACON = { BEACON = {
ClassName = "BEACON", ClassName = "BEACON",
Positionable = nil,
} }
--- Create a new BEACON Object. This doesn't activate the beacon, though, use @{#BEACON.AATACAN} or @{#BEACON.Generic} --- Beacon types supported by DCS.
-- @type BEACON.Type
-- @field #number NULL
-- @field #number VOR
-- @field #number DME
-- @field #number VOR_DME
-- @field #number TACAN
-- @field #number VORTAC
-- @field #number RSBN
-- @field #number BROADCAST_STATION
-- @field #number HOMER
-- @field #number AIRPORT_HOMER
-- @field #number AIRPORT_HOMER_WITH_MARKER
-- @field #number ILS_FAR_HOMER
-- @field #number ILS_NEAR_HOMER
-- @field #number ILS_LOCALIZER
-- @field #number ILS_GLIDESLOPE
-- @field #number NAUTICAL_HOMER
-- @field #number ICLS
BEACON.Type={
NULL = 0,
VOR = 1,
DME = 2,
VOR_DME = 3,
TACAN = 4,
VORTAC = 5,
RSBN = 32,
BROADCAST_STATION = 1024,
HOMER = 8,
AIRPORT_HOMER = 4104,
AIRPORT_HOMER_WITH_MARKER = 4136,
ILS_FAR_HOMER = 16408,
ILS_NEAR_HOMER = 16456,
ILS_LOCALIZER = 16640,
ILS_GLIDESLOPE = 16896,
NAUTICAL_HOMER = 32776,
ICLS = 131584,
}
--- Beacon systems supported by DCS. https://wiki.hoggitworld.com/view/DCS_command_activateBeacon
-- @type BEACON.System
-- @field #number PAR_10
-- @field #number RSBN_5
-- @field #number TACAN
-- @field #number TACAN_TANKER
-- @field #number ILS_LOCALIZER (This is the one to be used for AA TACAN Tanker!)
-- @field #number ILS_GLIDESLOPE
-- @field #number BROADCAST_STATION
BEACON.System={
PAR_10 = 1,
RSBN_5 = 2,
TACAN = 3,
TACAN_TANKER = 4,
ILS_LOCALIZER = 5,
ILS_GLIDESLOPE = 6,
BROADCAST_STATION = 7,
}
--- Create a new BEACON Object. This doesn't activate the beacon, though, use @{#BEACON.ActivateTACAN} etc.
-- If you want to create a BEACON, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetBeacon}() instead. -- If you want to create a BEACON, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetBeacon}() instead.
-- @param #BEACON self -- @param #BEACON self
-- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities. -- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities.
-- @return #BEACON Beacon -- @return #BEACON Beacon object or #nil if the positionable is invalid.
-- @return #nil If Positionable is invalid
function BEACON:New(Positionable) function BEACON:New(Positionable)
local self = BASE:Inherit(self, BASE:New())
-- Inherit BASE.
local self=BASE:Inherit(self, BASE:New()) --#BEACON
-- Debug.
self:F(Positionable) self:F(Positionable)
-- Set positionable.
if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid
self.Positionable = Positionable self.Positionable = Positionable
return self return self
@@ -390,44 +504,95 @@ function BEACON:New(Positionable)
end end
--- Converts a TACAN Channel/Mode couple into a frequency in Hz --- Activates a TACAN BEACON.
-- @param #BEACON self -- @param #BEACON self
-- @param #number TACANChannel -- @param #number Channel TACAN channel, i.e. the "10" part in "10Y".
-- @param #string TACANMode -- @param #string Mode TACAN mode, i.e. the "Y" part in "10Y".
-- @return #number Frequecy -- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon.
-- @return #nil if parameters are invalid -- @param #boolean Bearing If true, beacon provides bearing information. If false (or nil), only distance information is available.
function BEACON:_TACANToFrequency(TACANChannel, TACANMode) -- @param #number Duration How long will the beacon last in seconds. Omit for forever.
self:F3({TACANChannel, TACANMode}) -- @return #BEACON self
-- @usage
if type(TACANChannel) ~= "number" then -- -- Let's create a TACAN Beacon for a tanker
if TACANMode ~= "X" and TACANMode ~= "Y" then -- local myUnit = UNIT:FindByName("MyUnit")
return nil -- error in arguments -- local myBeacon = myUnit:GetBeacon() -- Creates the beacon
end --
-- myBeacon:TACAN(20, "Y", "TEXACO", true) -- Activate the beacon
function BEACON:ActivateTACAN(Channel, Mode, Message, Bearing, Duration)
self:T({channel=Channel, mode=Mode, callsign=Message, bearing=Bearing, duration=Duration})
-- Get frequency.
local Frequency=UTILS.TACANToFrequency(Channel, Mode)
-- Check.
if not Frequency then
self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
return self
end end
-- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137. -- Beacon type.
-- I have no idea what it does but it seems to work local Type=BEACON.Type.TACAN
local A = 1151 -- 'X', channel >= 64
local B = 64 -- channel >= 64
if TACANChannel < 64 then -- Beacon system.
B = 1 local System=BEACON.System.TACAN
end
if TACANMode == 'Y' then -- Check if unit is an aircraft and set system accordingly.
A = 1025 local AA=self.Positionable:IsAir()
if TACANChannel < 64 then if AA then
A = 1088 System=5 --NOTE: 5 is how you cat the correct tanker behaviour! --BEACON.System.TACAN_TANKER
end -- Check if "Y" mode is selected for aircraft.
else -- 'X' if Mode~="Y" then
if TACANChannel < 64 then self:E({"WARNING: The POSITIONABLE you want to attach the AA Tacan Beacon is an aircraft: Mode should Y !The BEACON is not emitting.", self.Positionable})
A = 962
end end
end end
return (A + TACANChannel - B) * 1000000 -- Attached unit.
local UnitID=self.Positionable:GetID()
-- Debug.
self:T({"TACAN BEACON started!"})
-- Start beacon.
self.Positionable:CommandActivateBeacon(Type, System, Frequency, UnitID, Channel, Mode, AA, Message, Bearing)
-- Stop sheduler.
if Duration then
self.Positionable:DeactivateBeacon(Duration)
end
return self
end end
--- Activates an ICLS BEACON. The unit the BEACON is attached to should be an aircraft carrier supporting this system.
-- @param #BEACON self
-- @param #number Channel ICLS channel.
-- @param #string Callsign The Message that is going to be coded in Morse and broadcasted by the beacon.
-- @param #number Duration How long will the beacon last in seconds. Omit for forever.
-- @return #BEACON self
function BEACON:ActivateICLS(Channel, Callsign, Duration)
self:F({Channel=Channel, Callsign=Callsign, Duration=Duration})
-- Attached unit.
local UnitID=self.Positionable:GetID()
-- Debug
self:T2({"ICLS BEACON started!"})
-- Start beacon.
self.Positionable:CommandActivateICLS(Channel, UnitID, Callsign)
-- Stop sheduler
if Duration then -- Schedule the stop of the BEACON if asked by the MD
self.Positionable:DeactivateBeacon(Duration)
end
return self
end
--- Activates a TACAN BEACON on an Aircraft. --- Activates a TACAN BEACON on an Aircraft.
-- @param #BEACON self -- @param #BEACON self
@@ -480,7 +645,7 @@ function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration)
}) })
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
SCHEDULER:New( nil, SCHEDULER:New(nil,
function() function()
self:StopAATACAN() self:StopAATACAN()
end, {}, BeaconDuration) end, {}, BeaconDuration)
@@ -591,4 +756,44 @@ function BEACON:StopRadioBeacon()
self:F() self:F()
-- The unique name of the transmission is the class ID -- The unique name of the transmission is the class ID
trigger.action.stopRadioTransmission(tostring(self.ID)) trigger.action.stopRadioTransmission(tostring(self.ID))
end return self
end
--- Converts a TACAN Channel/Mode couple into a frequency in Hz
-- @param #BEACON self
-- @param #number TACANChannel
-- @param #string TACANMode
-- @return #number Frequecy
-- @return #nil if parameters are invalid
function BEACON:_TACANToFrequency(TACANChannel, TACANMode)
self:F3({TACANChannel, TACANMode})
if type(TACANChannel) ~= "number" then
if TACANMode ~= "X" and TACANMode ~= "Y" then
return nil -- error in arguments
end
end
-- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137.
-- I have no idea what it does but it seems to work
local A = 1151 -- 'X', channel >= 64
local B = 64 -- channel >= 64
if TACANChannel < 64 then
B = 1
end
if TACANMode == 'Y' then
A = 1025
if TACANChannel < 64 then
A = 1088
end
else -- 'X'
if TACANChannel < 64 then
A = 962
end
end
return (A + TACANChannel - B) * 1000000
end

View File

@@ -409,9 +409,9 @@ do -- SET_BASE
for ObjectID, ObjectData in pairs( self.Set ) do for ObjectID, ObjectData in pairs( self.Set ) do
if NearestObject == nil then if NearestObject == nil then
NearestObject = ObjectData NearestObject = ObjectData
ClosestDistance = PointVec2:DistanceFromPointVec2( ObjectData:GetVec2() ) ClosestDistance = PointVec2:DistanceFromPointVec2( ObjectData:GetCoordinate() )
else else
local Distance = PointVec2:DistanceFromPointVec2( ObjectData:GetVec2() ) local Distance = PointVec2:DistanceFromPointVec2( ObjectData:GetCoordinate() )
if Distance < ClosestDistance then if Distance < ClosestDistance then
NearestObject = ObjectData NearestObject = ObjectData
ClosestDistance = Distance ClosestDistance = Distance
@@ -2033,6 +2033,54 @@ do -- SET_UNIT
return self return self
end end
--- Get the SET of the SET_UNIT **sorted per Threat Level**.
--
-- @param #SET_UNIT self
-- @param #number FromThreatLevel The TreatLevel to start the evaluation **From** (this must be a value between 0 and 10).
-- @param #number ToThreatLevel The TreatLevel to stop the evaluation **To** (this must be a value between 0 and 10).
-- @return #SET_UNIT self
-- @usage
--
--
function SET_UNIT:GetSetPerThreatLevel( FromThreatLevel, ToThreatLevel )
self:F2( arg )
local ThreatLevelSet = {}
if self:Count() ~= 0 then
for UnitName, UnitObject in pairs( self.Set ) do
local Unit = UnitObject -- Wrapper.Unit#UNIT
local ThreatLevel = Unit:GetThreatLevel()
ThreatLevelSet[ThreatLevel] = ThreatLevelSet[ThreatLevel] or {}
ThreatLevelSet[ThreatLevel].Set = ThreatLevelSet[ThreatLevel].Set or {}
ThreatLevelSet[ThreatLevel].Set[UnitName] = UnitObject
self:F( { ThreatLevel = ThreatLevel, ThreatLevelSet = ThreatLevelSet[ThreatLevel].Set } )
end
local OrderedPerThreatLevelSet = {}
local ThreatLevelIncrement = FromThreatLevel <= ToThreatLevel and 1 or -1
for ThreatLevel = FromThreatLevel, ToThreatLevel, ThreatLevelIncrement do
self:F( { ThreatLevel = ThreatLevel } )
local ThreatLevelItem = ThreatLevelSet[ThreatLevel]
if ThreatLevelItem then
for UnitName, UnitObject in pairs( ThreatLevelItem.Set ) do
table.insert( OrderedPerThreatLevelSet, UnitObject )
end
end
end
return OrderedPerThreatLevelSet
end
end
--- Iterate the SET_UNIT **sorted *per Threat Level** and call an interator function for each **alive** UNIT, providing the UNIT and optional parameters. --- Iterate the SET_UNIT **sorted *per Threat Level** and call an interator function for each **alive** UNIT, providing the UNIT and optional parameters.
-- --
-- @param #SET_UNIT self -- @param #SET_UNIT self

View File

@@ -8,7 +8,7 @@
-- * Schedule spawning of new groups. -- * Schedule spawning of new groups.
-- * Put limits on the amount of groups that can be spawned, and the amount of units that can be alive at the same time. -- * Put limits on the amount of groups that can be spawned, and the amount of units that can be alive at the same time.
-- * Randomize the spawning location between different zones. -- * Randomize the spawning location between different zones.
-- * Randomize the intial positions within the zones. -- * Randomize the initial positions within the zones.
-- * Spawn in array formation. -- * Spawn in array formation.
-- * Spawn uncontrolled (for planes or helos only). -- * Spawn uncontrolled (for planes or helos only).
-- * Clean up inactive helicopters that "crashed". -- * Clean up inactive helicopters that "crashed".
@@ -322,6 +322,10 @@ function SPAWN:New( SpawnTemplatePrefix )
self.Grouping = nil -- No grouping. self.Grouping = nil -- No grouping.
self.SpawnInitLivery = nil -- No special livery. self.SpawnInitLivery = nil -- No special livery.
self.SpawnInitSkill = nil -- No special skill. self.SpawnInitSkill = nil -- No special skill.
self.SpawnInitFreq = nil -- No special frequency.
self.SpawnInitModu = nil -- No special modulation.
self.SpawnInitRadio = nil -- No radio comms setting.
self.SpawnInitModex = nil
self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned. self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned.
else else
@@ -370,7 +374,11 @@ function SPAWN:NewWithAlias( SpawnTemplatePrefix, SpawnAliasPrefix )
self.Grouping = nil -- No grouping. self.Grouping = nil -- No grouping.
self.SpawnInitLivery = nil -- No special livery. self.SpawnInitLivery = nil -- No special livery.
self.SpawnInitSkill = nil -- No special skill. self.SpawnInitSkill = nil -- No special skill.
self.SpawnInitFreq = nil -- No special frequency.
self.SpawnInitModu = nil -- No special modulation.
self.SpawnInitRadio = nil -- No radio comms setting.
self.SpawnInitModex = nil
self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned. self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned.
else else
error( "SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" ) error( "SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" )
@@ -421,7 +429,11 @@ function SPAWN:NewFromTemplate( SpawnTemplate, SpawnTemplatePrefix, SpawnAliasPr
self.Grouping = nil -- No grouping. self.Grouping = nil -- No grouping.
self.SpawnInitLivery = nil -- No special livery. self.SpawnInitLivery = nil -- No special livery.
self.SpawnInitSkill = nil -- No special skill. self.SpawnInitSkill = nil -- No special skill.
self.SpawnInitFreq = nil -- No special frequency.
self.SpawnInitModu = nil -- No special modulation.
self.SpawnInitRadio = nil -- No radio comms setting.
self.SpawnInitModex = nil
self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned. self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned.
else else
error( "There is no template provided for SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" ) error( "There is no template provided for SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" )
@@ -597,6 +609,55 @@ function SPAWN:InitSkill( Skill )
return self return self
end end
--- Sets the radio comms on or off. Same as checking/unchecking the COMM box in the mission editor.
-- @param #SPAWN self
-- @param #number switch If true (or nil), enables the radio comms. If false, disables the radio for the spawned group.
-- @return #SPAWN self
function SPAWN:InitRadioCommsOnOff(switch)
self:F({switch=switch} )
self.SpawnInitRadio=switch or true
return self
end
--- Sets the radio frequency of the group.
-- @param #SPAWN self
-- @param #number frequency The frequency in MHz.
-- @return #SPAWN self
function SPAWN:InitRadioFrequency(frequency)
self:F({frequency=frequency} )
self.SpawnInitFreq=frequency
return self
end
--- Set radio modulation. Default is AM.
-- @param #SPAWN self
-- @param #string modulation Either "FM" or "AM". If no value is given, modulation is set to AM.
-- @return #SPAWN self
function SPAWN:InitRadioModulation(modulation)
self:F({modulation=modulation})
if modulation and modulation:lower()=="fm" then
self.SpawnInitModu=radio.modulation.FM
else
self.SpawnInitModu=radio.modulation.AM
end
return self
end
--- Sets the modex of the first unit of the group. If more units are in the group, the number is increased by one with every unit.
-- @param #SPAWN self
-- @param #number modex Modex of the first unit.
-- @return #SPAWN self
function SPAWN:InitModex(modex)
if modex then
self.SpawnInitModex=tonumber(modex)
end
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. --- 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 #SPAWN self
@@ -1173,6 +1234,28 @@ function SPAWN:SpawnWithIndex( SpawnIndex )
SpawnTemplate.units[UnitID].skill = self.SpawnInitSkill SpawnTemplate.units[UnitID].skill = self.SpawnInitSkill
end end
end end
-- Set tail number.
if self.SpawnInitModex then
for UnitID = 1, #SpawnTemplate.units do
SpawnTemplate.units[UnitID].onboard_num = string.format("%03d", self.SpawnInitModex+(UnitID-1))
end
end
-- Set radio comms on/off.
if self.SpawnInitRadio then
SpawnTemplate.communication=self.SpawnInitRadio
end
-- Set radio frequency.
if self.SpawnInitFreq then
SpawnTemplate.frequency=self.SpawnInitFreq
end
-- Set radio modulation.
if self.SpawnInitModu then
SpawnTemplate.modulation=self.SpawnInitModu
end
-- Set country, coaliton and categroy. -- Set country, coaliton and categroy.
SpawnTemplate.CategoryID = self.SpawnInitCategory or SpawnTemplate.CategoryID SpawnTemplate.CategoryID = self.SpawnInitCategory or SpawnTemplate.CategoryID
@@ -2611,7 +2694,10 @@ function SPAWN:_OnLand( EventData )
if self.RepeatOnLanding then if self.RepeatOnLanding then
local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup ) local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup )
self:T( { "Landed:", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } ) self:T( { "Landed:", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } )
self:ReSpawn( SpawnGroupIndex ) --self:ReSpawn( SpawnGroupIndex )
-- Delay respawn by three seconds due to DCS 2.5.4.26368 OB bug https://github.com/FlightControl-Master/MOOSE/issues/1076
-- Bug was initially only for engine shutdown event but after ED "fixed" it, it now happens on landing events.
SCHEDULER:New(nil, self.ReSpawn, {self, SpawnGroupIndex}, 3)
end end
end end
end end
@@ -2637,7 +2723,9 @@ function SPAWN:_OnEngineShutDown( EventData )
if Landed and self.RepeatOnEngineShutDown then if Landed and self.RepeatOnEngineShutDown then
local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup ) local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup )
self:T( { "EngineShutDown: ", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } ) self:T( { "EngineShutDown: ", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } )
self:ReSpawn( SpawnGroupIndex ) --self:ReSpawn( SpawnGroupIndex )
-- Delay respawn by three seconds due to DCS 2.5.4 OB bug https://github.com/FlightControl-Master/MOOSE/issues/1076
SCHEDULER:New(nil, self.ReSpawn, {self, SpawnGroupIndex}, 3)
end end
end end
end end

View File

@@ -195,6 +195,49 @@ function SPAWNSTATIC:SpawnFromPointVec2( PointVec2, Heading, NewName ) --R2.1
end end
--- Creates a new @{Static} from a COORDINATE.
-- @param #SPAWNSTATIC self
-- @param Core.Point#COORDINATE Coordinate The 3D coordinate where to spawn the static.
-- @param #number Heading (Optional) Heading The heading of the static, which is a number in degrees from 0 to 360. Default is 0 degrees.
-- @param #string NewName (Optional) The name of the new static.
-- @return #SPAWNSTATIC
function SPAWNSTATIC:SpawnFromCoordinate(Coordinate, Heading, NewName) --R2.4
self:F( { PointVec2, Heading, NewName } )
local StaticTemplate, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate( self.SpawnTemplatePrefix )
if StaticTemplate then
Heading=Heading or 0
local StaticUnitTemplate = StaticTemplate.units[1]
StaticUnitTemplate.x = Coordinate.x
StaticUnitTemplate.y = Coordinate.z
StaticUnitTemplate.alt = Coordinate.y
StaticTemplate.route = nil
StaticTemplate.groupId = nil
StaticTemplate.name = NewName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex )
StaticUnitTemplate.name = StaticTemplate.name
StaticUnitTemplate.heading = ( Heading / 180 ) * math.pi
_DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, CategoryID, CountryID)
self:F({StaticTemplate = StaticTemplate})
local Static = coalition.addStaticObject( self.CountryID or CountryID, StaticTemplate.units[1] )
self.SpawnIndex = self.SpawnIndex + 1
return _DATABASE:FindStatic(Static:getName())
end
return nil
end
--- Respawns the original @{Static}. --- Respawns the original @{Static}.
-- @param #SPAWNSTATIC self -- @param #SPAWNSTATIC self
-- @return #SPAWNSTATIC -- @return #SPAWNSTATIC

View File

@@ -70,7 +70,7 @@ do -- UserFlag
-- local BlueVictory = USERFLAG:New( "VictoryBlue" ) -- local BlueVictory = USERFLAG:New( "VictoryBlue" )
-- local BlueVictoryValue = BlueVictory:Get() -- Get the UserFlag VictoryBlue value. -- local BlueVictoryValue = BlueVictory:Get() -- Get the UserFlag VictoryBlue value.
-- --
function USERFLAG:Get( Number ) --R2.3 function USERFLAG:Get() --R2.3
return trigger.misc.getUserFlag( self.UserFlagName ) return trigger.misc.getUserFlag( self.UserFlagName )
end end

View File

@@ -118,15 +118,21 @@ do -- UserSound
--- Play the usersound to the given @{Wrapper.Group}. --- Play the usersound to the given @{Wrapper.Group}.
-- @param #USERSOUND self -- @param #USERSOUND self
-- @param Wrapper.Group#GROUP Group The @{Wrapper.Group} to play the usersound to. -- @param Wrapper.Group#GROUP Group The @{Wrapper.Group} to play the usersound to.
-- @param #number Delay (Optional) Delay in seconds, before the sound is played. Default 0.
-- @return #USERSOUND The usersound instance. -- @return #USERSOUND The usersound instance.
-- @usage -- @usage
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" ) -- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
-- local PlayerGroup = GROUP:FindByName( "PlayerGroup" ) -- Search for the active group named "PlayerGroup", that contains a human player. -- local PlayerGroup = GROUP:FindByName( "PlayerGroup" ) -- Search for the active group named "PlayerGroup", that contains a human player.
-- BlueVictory:ToGroup( PlayerGroup ) -- Play the sound that Blue has won to the player group. -- BlueVictory:ToGroup( PlayerGroup ) -- Play the sound that Blue has won to the player group.
-- --
function USERSOUND:ToGroup( Group ) --R2.3 function USERSOUND:ToGroup( Group, Delay ) --R2.3
trigger.action.outSoundForGroup( Group:GetID(), self.UserSoundFileName ) Delay=Delay or 0
if Delay>0 then
SCHEDULER:New(nil, USERSOUND.ToGroup,{self, Group}, Delay)
else
trigger.action.outSoundForGroup( Group:GetID(), self.UserSoundFileName )
end
return self return self
end end

View File

@@ -535,7 +535,7 @@ function ZONE_RADIUS:FlareZone( FlareColor, Points, Azimuth, AddHeight )
local Vec2 = self:GetVec2() local Vec2 = self:GetVec2()
AddHeight = AddHeight or 0 AddHeight = AddHeight or 0
Points = Points and Points or 360 Points = Points and Points or 360
local Angle local Angle
@@ -618,6 +618,9 @@ function ZONE_RADIUS:GetVec3( Height )
end end
--- Scan the zone for the presence of units of the given ObjectCategories. --- Scan the zone for the presence of units of the given ObjectCategories.
-- Note that after a zone has been scanned, the zone can be evaluated by: -- Note that after a zone has been scanned, the zone can be evaluated by:
-- --
@@ -629,11 +632,11 @@ end
-- @{#ZONE_RADIUS. -- @{#ZONE_RADIUS.
-- @param #ZONE_RADIUS self -- @param #ZONE_RADIUS self
-- @param ObjectCategories -- @param ObjectCategories
-- @param Coalition -- @param UnitCategories
-- @usage -- @usage
-- self.Zone:Scan() -- self.Zone:Scan()
-- local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition ) -- local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition )
function ZONE_RADIUS:Scan( ObjectCategories ) function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories )
self.ScanData = {} self.ScanData = {}
self.ScanData.Coalitions = {} self.ScanData.Coalitions = {}
@@ -660,9 +663,24 @@ function ZONE_RADIUS:Scan( ObjectCategories )
if ( ObjectCategory == Object.Category.UNIT and ZoneObject:isExist() and ZoneObject:isActive() ) or if ( ObjectCategory == Object.Category.UNIT and ZoneObject:isExist() and ZoneObject:isActive() ) or
(ObjectCategory == Object.Category.STATIC and ZoneObject:isExist()) then (ObjectCategory == Object.Category.STATIC and ZoneObject:isExist()) then
local CoalitionDCSUnit = ZoneObject:getCoalition() local CoalitionDCSUnit = ZoneObject:getCoalition()
self.ScanData.Coalitions[CoalitionDCSUnit] = true local Include = false
self.ScanData.Units[ZoneObject] = ZoneObject if not UnitCategories then
self:F2( { Name = ZoneObject:getName(), Coalition = CoalitionDCSUnit } ) Include = true
else
local CategoryDCSUnit = ZoneObject:getDesc().category
for UnitCategoryID, UnitCategory in pairs( UnitCategories ) do
if UnitCategory == CategoryDCSUnit then
Include = true
break
end
end
end
if Include then
local CoalitionDCSUnit = ZoneObject:getCoalition()
self.ScanData.Coalitions[CoalitionDCSUnit] = true
self.ScanData.Units[ZoneObject] = ZoneObject
self:F2( { Name = ZoneObject:getName(), Coalition = CoalitionDCSUnit } )
end
end end
if ObjectCategory == Object.Category.SCENERY then if ObjectCategory == Object.Category.SCENERY then
local SceneryType = ZoneObject:getTypeName() local SceneryType = ZoneObject:getTypeName()
@@ -1380,16 +1398,15 @@ end
--- Smokes the zone boundaries in a color. --- Smokes the zone boundaries in a color.
-- @param #ZONE_POLYGON_BASE self -- @param #ZONE_POLYGON_BASE self
-- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. -- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color.
-- @param #number Segments (Optional) Number of segments within boundary line. Default 10.
-- @return #ZONE_POLYGON_BASE self -- @return #ZONE_POLYGON_BASE self
function ZONE_POLYGON_BASE:SmokeZone( SmokeColor ) function ZONE_POLYGON_BASE:SmokeZone( SmokeColor, Segments )
self:F2( SmokeColor ) self:F2( SmokeColor )
local i Segments=Segments or 10
local j
local Segments = 10
i = 1 local i=1
j = #self._.Polygon local j=#self._.Polygon
while i <= #self._.Polygon do while i <= #self._.Polygon do
self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } )
@@ -1410,6 +1427,42 @@ function ZONE_POLYGON_BASE:SmokeZone( SmokeColor )
end end
--- Flare the zone boundaries in a color.
-- @param #ZONE_POLYGON_BASE self
-- @param Utilities.Utils#FLARECOLOR FlareColor The flare color.
-- @param #number Segments (Optional) Number of segments within boundary line. Default 10.
-- @param DCS#Azimuth Azimuth (optional) Azimuth The azimuth of the flare.
-- @param #number AddHeight (optional) The height to be added for the smoke.
-- @return #ZONE_POLYGON_BASE self
function ZONE_POLYGON_BASE:FlareZone( FlareColor, Segments, Azimuth, AddHeight )
self:F2(FlareColor)
Segments=Segments or 10
AddHeight = AddHeight or 0
local i=1
local j=#self._.Polygon
while i <= #self._.Polygon do
self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } )
local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x
local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y
for Segment = 0, Segments do -- We divide each line in 5 segments and smoke a point on the line.
local PointX = self._.Polygon[i].x + ( Segment * DeltaX / Segments )
local PointY = self._.Polygon[i].y + ( Segment * DeltaY / Segments )
POINT_VEC2:New( PointX, PointY, AddHeight ):Flare(FlareColor, Azimuth)
end
j = i
i = i + 1
end
return self
end
--- Returns if a location is within the zone. --- Returns if a location is within the zone.

View File

@@ -430,6 +430,8 @@ do -- Types
end -- end --
do -- Object do -- Object
--- [DCS Class Object](https://wiki.hoggitworld.com/view/DCS_Class_Object) --- [DCS Class Object](https://wiki.hoggitworld.com/view/DCS_Class_Object)
@@ -527,6 +529,126 @@ do -- CoalitionObject
end -- CoalitionObject end -- CoalitionObject
do -- Weapon
--- [DCS Class Weapon](https://wiki.hoggitworld.com/view/DCS_Class_Weapon)
-- @type Weapon
-- @extends #CoalitionObject
-- @field #Weapon.flag flag enum stores weapon flags. Some of them are combination of another flags.
-- @field #Weapon.Category Category enum that stores weapon categories.
-- @field #Weapon.GuidanceType GuidanceType enum that stores guidance methods. Available only for guided weapon (Weapon.Category.MISSILE and some Weapon.Category.BOMB).
-- @field #Weapon.MissileCategory MissileCategory enum that stores missile category. Available only for missiles (Weapon.Category.MISSILE).
-- @field #Weapon.WarheadType WarheadType enum that stores warhead types.
-- @field #Weapon.Desc Desc The descriptor of a weapon.
--- enum stores weapon flags. Some of them are combination of another flags.
-- @type Weapon.flag
-- @field LGB
-- @field TvGB
-- @field SNSGB
-- @field HEBomb
-- @field Penetrator
-- @field NapalmBomb
-- @field FAEBomb
-- @field ClusterBomb
-- @field Dispencer
-- @field CandleBomb
-- @field ParachuteBomb
-- @field GuidedBomb = LGB + TvGB + SNSGB
-- @field AnyUnguidedBomb = HEBomb + Penetrator + NapalmBomb + FAEBomb + ClusterBomb + Dispencer + CandleBomb + ParachuteBomb
-- @field AnyBomb = GuidedBomb + AnyUnguidedBomb
-- @field LightRocket
-- @field MarkerRocket
-- @field CandleRocket
-- @field HeavyRocket
-- @field AnyRocket = LightRocket + HeavyRocket + MarkerRocket + CandleRocket
-- @field AntiRadarMissile
-- @field AntiShipMissile
-- @field AntiTankMissile
-- @field FireAndForgetASM
-- @field LaserASM
-- @field TeleASM
-- @field CruiseMissile
-- @field GuidedASM = LaserASM + TeleASM
-- @field TacticASM = GuidedASM + FireAndForgetASM
-- @field AnyASM = AntiRadarMissile + AntiShipMissile + AntiTankMissile + FireAndForgetASM + GuidedASM + CruiseMissile
-- @field SRAAM
-- @field MRAAM
-- @field LRAAM
-- @field IR_AAM
-- @field SAR_AAM
-- @field AR_AAM
-- @field AnyAAM = IR_AAM + SAR_AAM + AR_AAM + SRAAM + MRAAM + LRAAM
-- @field AnyMissile = AnyASM + AnyAAM
-- @field AnyAutonomousMissile = IR_AAM + AntiRadarMissile + AntiShipMissile + FireAndForgetASM + CruiseMissile
-- @field GUN_POD
-- @field BuiltInCannon
-- @field Cannons = GUN_POD + BuiltInCannon
-- @field AnyAGWeapon = BuiltInCannon + GUN_POD + AnyBomb + AnyRocket + AnyASM
-- @field AnyAAWeapon = BuiltInCannon + GUN_POD + AnyAAM
-- @field UnguidedWeapon = Cannons + BuiltInCannon + GUN_POD + AnyUnguidedBomb + AnyRocket
-- @field GuidedWeapon = GuidedBomb + AnyASM + AnyAAM
-- @field AnyWeapon = AnyBomb + AnyRocket + AnyMissile + Cannons
-- @field MarkerWeapon = MarkerRocket + CandleRocket + CandleBomb
-- @field ArmWeapon = AnyWeapon - MarkerWeapon
--- Weapon.Category enum that stores weapon categories.
-- @type Weapon.Category
-- @field SHELL
-- @field MISSILE
-- @field ROCKET
-- @field BOMB
--- Weapon.GuidanceType enum that stores guidance methods. Available only for guided weapon (Weapon.Category.MISSILE and some Weapon.Category.BOMB).
-- @type Weapon.GuidanceType
-- @field INS
-- @field IR
-- @field RADAR_ACTIVE
-- @field RADAR_SEMI_ACTIVE
-- @field RADAR_PASSIVE
-- @field TV
-- @field LASER
-- @field TELE
--- Weapon.MissileCategory enum that stores missile category. Available only for missiles (Weapon.Category.MISSILE).
-- @type Weapon.MissileCategory
-- @field AAM
-- @field SAM
-- @field BM
-- @field ANTI_SHIP
-- @field CRUISE
-- @field OTHER
--- Weapon.WarheadType enum that stores warhead types.
-- @type Weapon.WarheadType
-- @field AP
-- @field HE
-- @field SHAPED_EXPLOSIVE
--- Returns the unit that launched the weapon.
-- @function [parent=#Weapon] getLauncher
-- @param #Weapon self
-- @return #Unit
--- returns target of the guided weapon. Unguided weapons and guided weapon that is targeted at the point on the ground will return nil.
-- @function [parent=#Weapon] getTarget
-- @param #Weapon self
-- @return #Object
--- returns weapon descriptor. Descriptor type depends on weapon category.
-- @function [parent=#Weapon] getDesc
-- @param #Weapon self
-- @return #Weapon.Desc
Weapon = {} --#Weapon
end -- Weapon
do -- Airbase do -- Airbase
--- [DCS Class Airbase](https://wiki.hoggitworld.com/view/DCS_Class_Airbase) --- [DCS Class Airbase](https://wiki.hoggitworld.com/view/DCS_Class_Airbase)
@@ -1196,7 +1318,4 @@ do -- AI
AI = {} --#AI AI = {} --#AI
end -- AI end -- AI

View File

@@ -216,7 +216,7 @@
-- One way to determin which types of ammo the unit carries, one can use the debug mode of the arty class via @{#ARTY.SetDebugON}(). -- One way to determin which types of ammo the unit carries, one can use the debug mode of the arty class via @{#ARTY.SetDebugON}().
-- In debug mode, the all ammo types of the group are printed to the monitor as message and can be found in the DCS.log file. -- In debug mode, the all ammo types of the group are printed to the monitor as message and can be found in the DCS.log file.
-- --
-- ## Empoying Selected Weapons -- ## Employing Selected Weapons
-- --
-- If an ARTY group carries multiple weapons, which can be used for artillery task, a certain weapon type can be selected to attack the target. -- If an ARTY group carries multiple weapons, which can be used for artillery task, a certain weapon type can be selected to attack the target.
-- This is done via the *weapontype* parameter of the @{#ARTY.AssignTargetCoord}(..., *weapontype*, ...) function. -- This is done via the *weapontype* parameter of the @{#ARTY.AssignTargetCoord}(..., *weapontype*, ...) function.
@@ -674,11 +674,13 @@ ARTY.id="ARTY | "
--- Arty script version. --- Arty script version.
-- @field #string version -- @field #string version
ARTY.version="1.0.6" ARTY.version="1.0.7"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list: -- TODO list:
-- TODO: Add hit event and make the arty group relocate.
-- TODO: Handle rearming for ships. How?
-- DONE: Delete targets from queue user function. -- DONE: Delete targets from queue user function.
-- DONE: Delete entire target queue user function. -- DONE: Delete entire target queue user function.
-- DONE: Add weapon types. Done but needs improvements. -- DONE: Add weapon types. Done but needs improvements.
@@ -697,11 +699,9 @@ ARTY.version="1.0.6"
-- DONE: Add command move to make arty group move. -- DONE: Add command move to make arty group move.
-- DONE: remove schedulers for status event. -- DONE: remove schedulers for status event.
-- DONE: Improve handling of special weapons. When winchester if using selected weapons? -- DONE: Improve handling of special weapons. When winchester if using selected weapons?
-- TODO: Handle rearming for ships. How?
-- DONE: Make coordinate after rearming general, i.e. also work after the group has moved to anonther location. -- DONE: Make coordinate after rearming general, i.e. also work after the group has moved to anonther location.
-- DONE: Add set commands via markers. E.g. set rearming place. -- DONE: Add set commands via markers. E.g. set rearming place.
-- DONE: Test stationary types like mortas ==> rearming etc. -- DONE: Test stationary types like mortas ==> rearming etc.
-- TODO: Add hit event and make the arty group relocate.
-- DONE: Add illumination and smoke. -- DONE: Add illumination and smoke.
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -2878,7 +2878,7 @@ function ARTY:onafterCeaseFire(Controllable, From, Event, To, target)
self.Controllable:ClearTasks() self.Controllable:ClearTasks()
else else
self:E(ARTY.id.."ERROR: No target in cease fire for group %s.", self.groupname) self:E(ARTY.id..string.format("ERROR: No target in cease fire for group %s.", self.groupname))
end end
-- Set number of shots to zero. -- Set number of shots to zero.
@@ -4253,101 +4253,116 @@ end
-- @param #ARTY self -- @param #ARTY self
function ARTY:_CheckTargetsInRange() function ARTY:_CheckTargetsInRange()
local targets2delete={}
for i=1,#self.targets do for i=1,#self.targets do
local _target=self.targets[i] local _target=self.targets[i]
self:T3(ARTY.id..string.format("Before: Target %s - in range = %s", _target.name, tostring(_target.inrange))) self:T3(ARTY.id..string.format("Before: Target %s - in range = %s", _target.name, tostring(_target.inrange)))
-- Check if target is in range. -- Check if target is in range.
local _inrange,_toofar,_tooclose=self:_TargetInRange(_target) local _inrange,_toofar,_tooclose,_remove=self:_TargetInRange(_target)
self:T3(ARTY.id..string.format("Inbetw: Target %s - in range = %s, toofar = %s, tooclose = %s", _target.name, tostring(_target.inrange), tostring(_toofar), tostring(_tooclose))) self:T3(ARTY.id..string.format("Inbetw: Target %s - in range = %s, toofar = %s, tooclose = %s", _target.name, tostring(_target.inrange), tostring(_toofar), tostring(_tooclose)))
-- Init default for assigning moves into range. if _remove then
local _movetowards=false
local _moveaway=false
if _target.inrange==nil then -- The ARTY group is immobile and not cargo but the target is not in range!
table.insert(targets2delete, _target.name)
-- First time the check is performed. We call the function again and send a message.
_target.inrange,_toofar,_tooclose=self:_TargetInRange(_target, self.report or self.Debug)
-- Send group towards/away from target. else
if _toofar then
_movetowards=true
elseif _tooclose then
_moveaway=true
end
elseif _target.inrange==true then -- Init default for assigning moves into range.
local _movetowards=false
-- Target was in range at previous check... local _moveaway=false
if _toofar then --...but is now too far away.
_movetowards=true
elseif _tooclose then --...but is now too close.
_moveaway=true
end
elseif _target.inrange==false then
-- Target was out of range at previous check.
if _inrange then if _target.inrange==nil then
-- Inform coalition that target is now in range.
local text=string.format("%s, target %s is now in range.", self.alias, _target.name)
self:T(ARTY.id..text)
MESSAGE:New(text,10):ToCoalitionIf(self.Controllable:GetCoalition(), self.report or self.Debug)
end
end
-- Assign a relocation command so that the unit will be in range of the requested target.
if self.autorelocate and (_movetowards or _moveaway) then
-- Get current position.
local _from=self.Controllable:GetCoordinate()
local _dist=_from:Get2DDistance(_target.coord)
if _dist<=self.autorelocatemaxdist then -- First time the check is performed. We call the function again and send a message.
_target.inrange,_toofar,_tooclose=self:_TargetInRange(_target, self.report or self.Debug)
local _tocoord --Core.Point#COORDINATE
local _name=""
local _safetymargin=500
if _movetowards then
-- Target was in range on previous check but now we are too far away. -- Send group towards/away from target.
local _waytogo=_dist-self.maxrange+_safetymargin if _toofar then
local _heading=self:_GetHeading(_from,_target.coord) _movetowards=true
_tocoord=_from:Translate(_waytogo, _heading) elseif _tooclose then
_name=string.format("%s, relocation to within max firing range of target %s", self.alias, _target.name) _moveaway=true
elseif _moveaway then
-- Target was in range on previous check but now we are too far away.
local _waytogo=_dist-self.minrange+_safetymargin
local _heading=self:_GetHeading(_target.coord,_from)
_tocoord=_from:Translate(_waytogo, _heading)
_name=string.format("%s, relocation to within min firing range of target %s", self.alias, _target.name)
end end
-- Send info message. elseif _target.inrange==true then
MESSAGE:New(_name.." assigned.", 10):ToCoalitionIf(self.Controllable:GetCoalition(), self.report or self.Debug)
-- Target was in range at previous check...
-- Assign relocation move.
self:AssignMoveCoord(_tocoord, nil, nil, self.autorelocateonroad, false, _name, true) if _toofar then --...but is now too far away.
_movetowards=true
elseif _tooclose then --...but is now too close.
_moveaway=true
end
elseif _target.inrange==false then
-- Target was out of range at previous check.
if _inrange then
-- Inform coalition that target is now in range.
local text=string.format("%s, target %s is now in range.", self.alias, _target.name)
self:T(ARTY.id..text)
MESSAGE:New(text,10):ToCoalitionIf(self.Controllable:GetCoalition(), self.report or self.Debug)
end
end end
-- Assign a relocation command so that the unit will be in range of the requested target.
if self.autorelocate and (_movetowards or _moveaway) then
-- Get current position.
local _from=self.Controllable:GetCoordinate()
local _dist=_from:Get2DDistance(_target.coord)
if _dist<=self.autorelocatemaxdist then
local _tocoord --Core.Point#COORDINATE
local _name=""
local _safetymargin=500
if _movetowards then
-- Target was in range on previous check but now we are too far away.
local _waytogo=_dist-self.maxrange+_safetymargin
local _heading=self:_GetHeading(_from,_target.coord)
_tocoord=_from:Translate(_waytogo, _heading)
_name=string.format("%s, relocation to within max firing range of target %s", self.alias, _target.name)
elseif _moveaway then
-- Target was in range on previous check but now we are too far away.
local _waytogo=_dist-self.minrange+_safetymargin
local _heading=self:_GetHeading(_target.coord,_from)
_tocoord=_from:Translate(_waytogo, _heading)
_name=string.format("%s, relocation to within min firing range of target %s", self.alias, _target.name)
end
-- Send info message.
MESSAGE:New(_name.." assigned.", 10):ToCoalitionIf(self.Controllable:GetCoalition(), self.report or self.Debug)
-- Assign relocation move.
self:AssignMoveCoord(_tocoord, nil, nil, self.autorelocateonroad, false, _name, true)
end
end
-- Update value.
_target.inrange=_inrange
self:T3(ARTY.id..string.format("After: Target %s - in range = %s", _target.name, tostring(_target.inrange)))
end end
-- Update value.
_target.inrange=_inrange
self:T3(ARTY.id..string.format("After: Target %s - in range = %s", _target.name, tostring(_target.inrange)))
end end
-- Remove targets not in range.
for _,targetname in pairs(targets2delete) do
self:RemoveTarget(targetname)
end
end end
--- Check all normal (untimed) targets and return the target with the highest priority which has been engaged the fewest times. --- Check all normal (untimed) targets and return the target with the highest priority which has been engaged the fewest times.
@@ -4728,6 +4743,7 @@ end
-- @return #boolean True if target is in range, false otherwise. -- @return #boolean True if target is in range, false otherwise.
-- @return #boolean True if ARTY group is too far away from the target, i.e. distance > max firing range. -- @return #boolean True if ARTY group is too far away from the target, i.e. distance > max firing range.
-- @return #boolean True if ARTY group is too close to the target, i.e. distance < min finring range. -- @return #boolean True if ARTY group is too close to the target, i.e. distance < min finring range.
-- @return #boolean True if target should be removed since ARTY group is immobile and not cargo.
function ARTY:_TargetInRange(target, message) function ARTY:_TargetInRange(target, message)
self:F3(target) self:F3(target)
@@ -4763,11 +4779,13 @@ function ARTY:_TargetInRange(target, message)
end end
-- Remove target if ARTY group cannot move, e.g. Mortas. No chance to be ever in range - unless they are cargo. -- Remove target if ARTY group cannot move, e.g. Mortas. No chance to be ever in range - unless they are cargo.
local _remove=false
if not (self.ismobile or self.iscargo) and _inrange==false then if not (self.ismobile or self.iscargo) and _inrange==false then
self:RemoveTarget(target.name) --self:RemoveTarget(target.name)
_remove=true
end end
return _inrange,_toofar,_tooclose return _inrange,_toofar,_tooclose,_remove
end end
--- Get the weapon type name, which should be used to attack the target. --- Get the weapon type name, which should be used to attack the target.

View File

@@ -1012,7 +1012,7 @@ do -- DETECTION_BASE
--- Set the parameters to calculate to optimal intercept point. --- Set the parameters to calculate to optimal intercept point.
-- @param #DETECTION_BASE self -- @param #DETECTION_BASE self
-- @param #boolean Intercept Intercept is true if an intercept point is calculated. Intercept is false if it is disabled. The default Intercept is false. -- @param #boolean Intercept Intercept is true if an intercept point is calculated. Intercept is false if it is disabled. The default Intercept is false.
-- @param #number IntereptDelay If Intercept is true, then InterceptDelay is the average time it takes to get airplanes airborne. -- @param #number InterceptDelay If Intercept is true, then InterceptDelay is the average time it takes to get airplanes airborne.
-- @return #DETECTION_BASE self -- @return #DETECTION_BASE self
function DETECTION_BASE:SetIntercept( Intercept, InterceptDelay ) function DETECTION_BASE:SetIntercept( Intercept, InterceptDelay )
self:F2() self:F2()
@@ -1233,7 +1233,7 @@ do -- DETECTION_BASE
-- @param DCS#Unit.Category Category The category of the unit. -- @param DCS#Unit.Category Category The category of the unit.
-- @return #boolean true if there are friendlies nearby -- @return #boolean true if there are friendlies nearby
function DETECTION_BASE:IsFriendliesNearBy( DetectedItem, Category ) function DETECTION_BASE:IsFriendliesNearBy( DetectedItem, Category )
--self:F( { "FriendliesNearBy Test", DetectedItem.FriendliesNearBy } ) -- self:F( { "FriendliesNearBy Test", DetectedItem.FriendliesNearBy } )
return ( DetectedItem.FriendliesNearBy and DetectedItem.FriendliesNearBy[Category] ~= nil ) or false return ( DetectedItem.FriendliesNearBy and DetectedItem.FriendliesNearBy[Category] ~= nil ) or false
end end

View File

@@ -546,7 +546,7 @@ RAT.id="RAT | "
--- RAT version. --- RAT version.
-- @list version -- @list version
RAT.version={ RAT.version={
version = "2.3.4", version = "2.3.5",
print = true, print = true,
} }
@@ -717,6 +717,11 @@ function RAT:Spawn(naircraft)
self.FLcruise=005*RAT.unit.FL2m self.FLcruise=005*RAT.unit.FL2m
end end
end end
-- Enable helos to go to destinations 100 meters away.
if self.category==RAT.cat.heli then
self.mindist=50
end
-- Run consistency checks. -- Run consistency checks.
self:_CheckConsistency() self:_CheckConsistency()
@@ -1812,14 +1817,14 @@ function RAT:ATC_Delay(time)
end end
--- Set minimum distance between departure and destination. Default is 5 km. --- Set minimum distance between departure and destination. Default is 5 km.
-- Minimum distance should not be smaller than maybe ~500 meters to ensure that departure and destination are different. -- Minimum distance should not be smaller than maybe ~100 meters to ensure that departure and destination are different.
-- @param #RAT self -- @param #RAT self
-- @param #number dist Distance in km. -- @param #number dist Distance in km.
-- @return #RAT RAT self object. -- @return #RAT RAT self object.
function RAT:SetMinDistance(dist) function RAT:SetMinDistance(dist)
self:F2(dist) self:F2(dist)
-- Distance in meters. Absolute minimum is 500 m. -- Distance in meters. Absolute minimum is 500 m.
self.mindist=math.max(500, dist*1000) self.mindist=math.max(100, dist*1000)
return self return self
end end
@@ -2446,7 +2451,7 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint)
local VxCruiseMax local VxCruiseMax
if self.Vcruisemax then if self.Vcruisemax then
-- User input. -- User input.
VxCruiseMax = min(self.Vcruisemax, self.aircraft.Vmax) VxCruiseMax = math.min(self.Vcruisemax, self.aircraft.Vmax)
else else
-- Max cruise speed 90% of Vmax or 900 km/h whichever is lower. -- Max cruise speed 90% of Vmax or 900 km/h whichever is lower.
VxCruiseMax = math.min(self.aircraft.Vmax*0.90, 250) VxCruiseMax = math.min(self.aircraft.Vmax*0.90, 250)
@@ -5435,7 +5440,7 @@ function RAT:_ATCInit(airports_map)
if not RAT.ATC.init then if not RAT.ATC.init then
local text local text
text="Starting RAT ATC.\nSimultanious = "..RAT.ATC.Nclearance.."\n".."Delay = "..RAT.ATC.delay text="Starting RAT ATC.\nSimultanious = "..RAT.ATC.Nclearance.."\n".."Delay = "..RAT.ATC.delay
self:T(RAT.id..text) BASE:T(RAT.id..text)
RAT.ATC.init=true RAT.ATC.init=true
for _,ap in pairs(airports_map) do for _,ap in pairs(airports_map) do
local name=ap:GetName() local name=ap:GetName()
@@ -5458,7 +5463,7 @@ end
-- @param #string name Group name of the flight. -- @param #string name Group name of the flight.
-- @param #string dest Name of the destination airport. -- @param #string dest Name of the destination airport.
function RAT:_ATCAddFlight(name, dest) function RAT:_ATCAddFlight(name, dest)
self:T(string.format("%sATC %s: Adding flight %s with destination %s.", RAT.id, dest, name, dest)) BASE:T(string.format("%sATC %s: Adding flight %s with destination %s.", RAT.id, dest, name, dest))
RAT.ATC.flight[name]={} RAT.ATC.flight[name]={}
RAT.ATC.flight[name].destination=dest RAT.ATC.flight[name].destination=dest
RAT.ATC.flight[name].Tarrive=-1 RAT.ATC.flight[name].Tarrive=-1
@@ -5483,7 +5488,7 @@ end
-- @param #string name Group name of the flight. -- @param #string name Group name of the flight.
-- @param #number time Time the fight first registered. -- @param #number time Time the fight first registered.
function RAT:_ATCRegisterFlight(name, time) function RAT:_ATCRegisterFlight(name, time)
self:T(RAT.id.."Flight ".. name.." registered at ATC for landing clearance.") BASE:T(RAT.id.."Flight ".. name.." registered at ATC for landing clearance.")
RAT.ATC.flight[name].Tarrive=time RAT.ATC.flight[name].Tarrive=time
RAT.ATC.flight[name].holding=0 RAT.ATC.flight[name].holding=0
end end
@@ -5514,7 +5519,7 @@ function RAT:_ATCStatus()
-- Aircraft is holding. -- Aircraft is holding.
local text=string.format("ATC %s: Flight %s is holding for %i:%02d. %s.", dest, name, hold/60, hold%60, busy) local text=string.format("ATC %s: Flight %s is holding for %i:%02d. %s.", dest, name, hold/60, hold%60, busy)
self:T(RAT.id..text) BASE:T(RAT.id..text)
elseif hold==RAT.ATC.onfinal then elseif hold==RAT.ATC.onfinal then
@@ -5522,7 +5527,7 @@ function RAT:_ATCStatus()
local Tfinal=Tnow-RAT.ATC.flight[name].Tonfinal local Tfinal=Tnow-RAT.ATC.flight[name].Tonfinal
local text=string.format("ATC %s: Flight %s is on final. Waiting %i:%02d for landing event.", dest, name, Tfinal/60, Tfinal%60) local text=string.format("ATC %s: Flight %s is on final. Waiting %i:%02d for landing event.", dest, name, Tfinal/60, Tfinal%60)
self:T(RAT.id..text) BASE:T(RAT.id..text)
elseif hold==RAT.ATC.unregistered then elseif hold==RAT.ATC.unregistered then
@@ -5530,7 +5535,7 @@ function RAT:_ATCStatus()
--self:T(string.format("ATC %s: Flight %s is not registered yet (hold %d).", dest, name, hold)) --self:T(string.format("ATC %s: Flight %s is not registered yet (hold %d).", dest, name, hold))
else else
self:E(RAT.id.."ERROR: Unknown holding time in RAT:_ATCStatus().") BASE:E(RAT.id.."ERROR: Unknown holding time in RAT:_ATCStatus().")
end end
end end
@@ -5572,12 +5577,12 @@ function RAT:_ATCCheck()
-- Debug message. -- Debug message.
local text=string.format("ATC %s: Flight %s runway is busy. You are #%d of %d in landing queue. Your holding time is %i:%02d.", name, flight,qID, nqueue, RAT.ATC.flight[flight].holding/60, RAT.ATC.flight[flight].holding%60) local text=string.format("ATC %s: Flight %s runway is busy. You are #%d of %d in landing queue. Your holding time is %i:%02d.", name, flight,qID, nqueue, RAT.ATC.flight[flight].holding/60, RAT.ATC.flight[flight].holding%60)
self:T(RAT.id..text) BASE:T(RAT.id..text)
else else
local text=string.format("ATC %s: Flight %s was cleared for landing. Your holding time was %i:%02d.", name, flight, RAT.ATC.flight[flight].holding/60, RAT.ATC.flight[flight].holding%60) local text=string.format("ATC %s: Flight %s was cleared for landing. Your holding time was %i:%02d.", name, flight, RAT.ATC.flight[flight].holding/60, RAT.ATC.flight[flight].holding%60)
self:T(RAT.id..text) BASE:T(RAT.id..text)
-- Clear flight for landing. -- Clear flight for landing.
RAT:_ATCClearForLanding(name, flight) RAT:_ATCClearForLanding(name, flight)
@@ -5705,12 +5710,7 @@ function RAT:_ATCQueue()
for k,v in ipairs(_queue) do for k,v in ipairs(_queue) do
table.insert(RAT.ATC.airport[airport].queue, v[1]) table.insert(RAT.ATC.airport[airport].queue, v[1])
end end
--fvh
--for k,v in ipairs(RAT.ATC.airport[airport].queue) do
--print(string.format("queue #%02i flight \"%s\" holding %d seconds",k, v, RAT.ATC.flight[v].holding))
--end
end end
end end

View File

@@ -11,7 +11,7 @@
-- --
-- ## Features: -- ## Features:
-- --
-- * Impact points of bombs, rockets and missils are recorded and distance to closest range target is measured and reported to the player. -- * Impact points of bombs, rockets and missiles are recorded and distance to closest range target is measured and reported to the player.
-- * Number of hits on strafing passes are counted and reported. Also the percentage of hits w.r.t fired shots is evaluated. -- * Number of hits on strafing passes are counted and reported. Also the percentage of hits w.r.t fired shots is evaluated.
-- * Results of all bombing and strafing runs are stored and top 10 results can be displayed. -- * Results of all bombing and strafing runs are stored and top 10 results can be displayed.
-- * Range targets can be marked by smoke. -- * Range targets can be marked by smoke.
@@ -56,9 +56,9 @@
-- @field #table strafeStatus Table containing the current strafing target a player as assigned to. -- @field #table strafeStatus Table containing the current strafing target a player as assigned to.
-- @field #table strafePlayerResults Table containing the strafing results of each player. -- @field #table strafePlayerResults Table containing the strafing results of each player.
-- @field #table bombPlayerResults Table containing the bombing results of each player. -- @field #table bombPlayerResults Table containing the bombing results of each player.
-- @field #table PlayerSettings Indiviual player settings. -- @field #table PlayerSettings Individual player settings.
-- @field #number dtBombtrack Time step [sec] used for tracking released bomb/rocket positions. Default 0.005 seconds. -- @field #number dtBombtrack Time step [sec] used for tracking released bomb/rocket positions. Default 0.005 seconds.
-- @field #number BombtrackThreshold Bombs/rockets/missiles are only tracked if player-range distance is smaller than this threashold [m]. Default 25000 m. -- @field #number BombtrackThreshold Bombs/rockets/missiles are only tracked if player-range distance is smaller than this threshold [m]. Default 25000 m.
-- @field #number Tmsg Time [sec] messages to players are displayed. Default 30 sec. -- @field #number Tmsg Time [sec] messages to players are displayed. Default 30 sec.
-- @field #string examinergroupname Name of the examiner group which should get all messages. -- @field #string examinergroupname Name of the examiner group which should get all messages.
-- @field #boolean examinerexclusive If true, only the examiner gets messages. If false, clients and examiner get messages. -- @field #boolean examinerexclusive If true, only the examiner gets messages. If false, clients and examiner get messages.
@@ -75,10 +75,11 @@
-- @field #boolean trackbombs If true (default), all bomb types are tracked and impact point to closest bombing target is evaluated. -- @field #boolean trackbombs If true (default), all bomb types are tracked and impact point to closest bombing target is evaluated.
-- @field #boolean trackrockets If true (default), all rocket types are tracked and impact point to closest bombing target is evaluated. -- @field #boolean trackrockets If true (default), all rocket types are tracked and impact point to closest bombing target is evaluated.
-- @field #boolean trackmissiles If true (default), all missile types are tracked and impact point to closest bombing target is evaluated. -- @field #boolean trackmissiles If true (default), all missile types are tracked and impact point to closest bombing target is evaluated.
-- @field #boolean defaultsmokebomb If true, initialize player settings to smoke bomb.
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
--- Enables a mission designer to easily set up practice ranges in DCS. A new RANGE object can be created with the @{#RANGE.New}(rangename) contructor. --- Enables a mission designer to easily set up practice ranges in DCS. A new RANGE object can be created with the @{#RANGE.New}(rangename) contructor.
-- The parameter "rangename" defindes the name of the range. It has to be unique since this is also the name displayed in the radio menu. -- The parameter "rangename" defines the name of the range. It has to be unique since this is also the name displayed in the radio menu.
-- --
-- Generally, a range consists of strafe pits and bombing targets. For strafe pits the number of hits for each pass is counted and tabulated. -- Generally, a range consists of strafe pits and bombing targets. For strafe pits the number of hits for each pass is counted and tabulated.
-- For bombing targets, the distance from the impact point of the bomb, rocket or missile to the closest range target is measured and tabulated. -- For bombing targets, the distance from the impact point of the bomb, rocket or missile to the closest range target is measured and tabulated.
@@ -89,12 +90,12 @@
-- **IMPORTANT** -- **IMPORTANT**
-- --
-- Due to a DCS bug, it is not possible to directly monitor when a player enters a plane. So in a mission with client slots, it is vital that -- Due to a DCS bug, it is not possible to directly monitor when a player enters a plane. So in a mission with client slots, it is vital that
-- a player first enters as spector and **after that** jumps into the slot of his aircraft! -- a player first enters as spectator or hits ESC twice and **after that** jumps into the slot of his aircraft!
-- If that is not done, the script is not started correctly. This can be checked by looking at the radio menues. If the mission was entered correctly, -- If that is not done, the script is not started correctly. This can be checked by looking at the radio menues. If the mission was entered correctly,
-- there should be an "On the Range" menu items in the "F10. Other..." menu. -- there should be an "On the Range" menu items in the "F10. Other..." menu.
-- --
-- ## Strafe Pits -- ## Strafe Pits
-- Each strafe pit can consist of multiple targets. Often one findes two or three strafe targets next to each other. -- Each strafe pit can consist of multiple targets. Often one finds two or three strafe targets next to each other.
-- --
-- A strafe pit can be added to the range by the @{#RANGE.AddStrafePit}(*targetnames, boxlength, boxwidth, heading, inverseheading, goodpass, foulline*) function. -- A strafe pit can be added to the range by the @{#RANGE.AddStrafePit}(*targetnames, boxlength, boxwidth, heading, inverseheading, goodpass, foulline*) function.
-- --
@@ -104,7 +105,7 @@
-- If the parameter *heading* is passed as **nil**, the heading is automatically taken from the heading of the first target unit as defined in the ME. -- If the parameter *heading* is passed as **nil**, the heading is automatically taken from the heading of the first target unit as defined in the ME.
-- The parameter *inverseheading* turns the heading around by 180 degrees. This is sometimes useful, since the default heading of strafe target units point in the -- The parameter *inverseheading* turns the heading around by 180 degrees. This is sometimes useful, since the default heading of strafe target units point in the
-- wrong/opposite direction. -- wrong/opposite direction.
-- * The parameter *goodpass* defines the number of hits a pilot has to achive during a run to be judged as a "good" pass. -- * The parameter *goodpass* defines the number of hits a pilot has to achieve during a run to be judged as a "good" pass.
-- * The last parameter *foulline* sets the distance from the pit targets to the foul line. Hit from closer than this line are not counted! -- * The last parameter *foulline* sets the distance from the pit targets to the foul line. Hit from closer than this line are not counted!
-- --
-- Another function to add a strafe pit is @{#RANGE.AddStrafePitGroup}(*group, boxlength, boxwidth, heading, inverseheading, goodpass, foulline*). Here, -- Another function to add a strafe pit is @{#RANGE.AddStrafePitGroup}(*group, boxlength, boxwidth, heading, inverseheading, goodpass, foulline*). Here,
@@ -151,7 +152,7 @@
-- * "F2. My Settings": Player specific settings. -- * "F2. My Settings": Player specific settings.
-- * "F3. Stats" Player: statistics and scores. -- * "F3. Stats" Player: statistics and scores.
-- * "Range Information": Information about the range, such as bearing and range. Also range and player specific settings are displayed. -- * "Range Information": Information about the range, such as bearing and range. Also range and player specific settings are displayed.
-- * "Weather Report": Temperatur, wind and QFE pressure information is provided. -- * "Weather Report": Temperature, wind and QFE pressure information is provided.
-- --
-- ## Examples -- ## Examples
-- --
@@ -243,6 +244,7 @@ RANGE={
trackbombs=true, trackbombs=true,
trackrockets=true, trackrockets=true,
trackmissiles=true, trackmissiles=true,
defaultsmokebomb=true,
} }
--- Default range parameters. --- Default range parameters.
@@ -266,19 +268,25 @@ RANGE.Defaults={
-- @field #table Names -- @field #table Names
RANGE.Names={} RANGE.Names={}
--- Main radio menu. --- Main radio menu on group level.
-- @field #table MenuF10 -- @field #table MenuF10 Root menu table on group level.
RANGE.MenuF10={} RANGE.MenuF10={}
--- Main radio menu on mission level.
-- @field #table MenuF10Root Root menu on mission level.
RANGE.MenuF10Root=nil
--- Some ID to identify who we are in output of the DCS.log file. --- Some ID to identify who we are in output of the DCS.log file.
-- @field #string id -- @field #string id
RANGE.id="RANGE | " RANGE.id="RANGE | "
--- Range script version. --- Range script version.
-- @field #string version -- @field #string version
RANGE.version="1.2.1" RANGE.version="1.2.4"
--TODO list: --TODO list:
--TODO: Verbosity level for messages.
--TODO: Add option for default settings such as smoke off.
--TODO: Add custom weapons, which can be specified by the user. --TODO: Add custom weapons, which can be specified by the user.
--TODO: Check if units are still alive. --TODO: Check if units are still alive.
--DONE: Add statics for strafe pits. --DONE: Add statics for strafe pits.
@@ -310,6 +318,9 @@ function RANGE:New(rangename)
local text=string.format("RANGE script version %s - creating new RANGE object of name: %s.", RANGE.version, self.rangename) local text=string.format("RANGE script version %s - creating new RANGE object of name: %s.", RANGE.version, self.rangename)
self:E(RANGE.id..text) self:E(RANGE.id..text)
MESSAGE:New(text, 10):ToAllIf(self.Debug) MESSAGE:New(text, 10):ToAllIf(self.Debug)
-- Defaults
self:SetDefaultPlayerSmokeBomb()
-- Return object. -- Return object.
return self return self
@@ -317,93 +328,102 @@ end
--- Initializes number of targets and location of the range. Starts the event handlers. --- Initializes number of targets and location of the range. Starts the event handlers.
-- @param #RANGE self -- @param #RANGE self
function RANGE:Start() -- @param #number delay Delay in seconds, before the RANGE is started. Default immediately.
-- @return self
function RANGE:Start(delay)
self:F() self:F()
-- Location/coordinate of range. if delay and delay>0 then
local _location=nil SCHEDULER:New(nil, self.Start, {self}, delay)
else
-- Count bomb targets. -- Location/coordinate of range.
local _count=0 local _location=nil
for _,_target in pairs(self.bombingTargets) do
_count=_count+1
-- Get range location. -- Count bomb targets.
if _location==nil then local _count=0
_location=_target.target:GetCoordinate() --Core.Point#COORDINATE for _,_target in pairs(self.bombingTargets) do
end _count=_count+1
end
self.nbombtargets=_count -- Get range location.
-- Count strafing targets.
_count=0
for _,_target in pairs(self.strafeTargets) do
_count=_count+1
for _,_unit in pairs(_target.targets) do
if _location==nil then if _location==nil then
_location=_unit:GetCoordinate() _location=_target.target:GetCoordinate() --Core.Point#COORDINATE
end end
end end
end self.nbombtargets=_count
self.nstrafetargets=_count
-- Location of the range. We simply take the first unit/target we find if it was not explicitly specified by the user.
if self.location==nil then
self.location=_location
end
if self.location==nil then
local text=string.format("ERROR! No range location found. Number of strafe targets = %d. Number of bomb targets = %d.", self.rangename, self.nstrafetargets, self.nbombtargets)
self:E(RANGE.id..text)
return
end
-- Define a MOOSE zone of the range.
if self.rangezone==nil then
self.rangezone=ZONE_RADIUS:New(self.rangename, {x=self.location.x, y=self.location.z}, self.rangeradius)
end
-- Starting range.
local text=string.format("Starting RANGE %s. Number of strafe targets = %d. Number of bomb targets = %d.", self.rangename, self.nstrafetargets, self.nbombtargets)
self:E(RANGE.id..text)
MESSAGE:New(text,10):ToAllIf(self.Debug)
-- Event handling.
if self.eventmoose then
-- Events are handled my MOOSE.
self:T(RANGE.id.."Events are handled by MOOSE.")
self:HandleEvent(EVENTS.Birth)
self:HandleEvent(EVENTS.Hit)
self:HandleEvent(EVENTS.Shot)
else
-- Events are handled directly by DCS.
self:T(RANGE.id.."Events are handled directly by DCS.")
world.addEventHandler(self)
end
-- Make bomb target move randomly within the range zone.
for _,_target in pairs(self.bombingTargets) do
-- Check if it is a static object.
local _static=self:_CheckStatic(_target.target:GetName())
if _target.move and _static==false and _target.speed>1 then -- Count strafing targets.
local unit=_target.target --Wrapper.Unit#UNIT _count=0
_target.target:PatrolZones({self.rangezone}, _target.speed*0.75, "Off road") for _,_target in pairs(self.strafeTargets) do
_count=_count+1
for _,_unit in pairs(_target.targets) do
if _location==nil then
_location=_unit:GetCoordinate()
end
end
end
self.nstrafetargets=_count
-- Location of the range. We simply take the first unit/target we find if it was not explicitly specified by the user.
if self.location==nil then
self.location=_location
end
if self.location==nil then
local text=string.format("ERROR! No range location found. Number of strafe targets = %d. Number of bomb targets = %d.", self.rangename, self.nstrafetargets, self.nbombtargets)
self:E(RANGE.id..text)
return
end
-- Define a MOOSE zone of the range.
if self.rangezone==nil then
self.rangezone=ZONE_RADIUS:New(self.rangename, {x=self.location.x, y=self.location.z}, self.rangeradius)
end
-- Starting range.
local text=string.format("Starting RANGE %s. Number of strafe targets = %d. Number of bomb targets = %d.", self.rangename, self.nstrafetargets, self.nbombtargets)
self:I(RANGE.id..text)
MESSAGE:New(text,10):ToAllIf(self.Debug)
-- Event handling.
if self.eventmoose then
-- Events are handled my MOOSE.
self:T(RANGE.id.."Events are handled by MOOSE.")
self:HandleEvent(EVENTS.Birth)
self:HandleEvent(EVENTS.Hit)
self:HandleEvent(EVENTS.Shot)
else
-- Events are handled directly by DCS.
self:T(RANGE.id.."Events are handled directly by DCS.")
world.addEventHandler(self)
end
-- Make bomb target move randomly within the range zone.
for _,_target in pairs(self.bombingTargets) do
-- Check if it is a static object.
local _static=self:_CheckStatic(_target.target:GetName())
if _target.move and _static==false and _target.speed>1 then
local unit=_target.target --Wrapper.Unit#UNIT
_target.target:PatrolZones({self.rangezone}, _target.speed*0.75, "Off road")
end
end
-- Debug mode: smoke all targets and range zone.
if self.Debug then
self:_MarkTargetsOnMap()
self:_SmokeBombTargets()
self:_SmokeStrafeTargets()
self:_SmokeStrafeTargetBoxes()
self.rangezone:SmokeZone(SMOKECOLOR.White)
end end
end end
-- Debug mode: smoke all targets and range zone. return self
if self.Debug then
self:_MarkTargetsOnMap()
self:_SmokeBombTargets()
self:_SmokeStrafeTargets()
self:_SmokeStrafeTargetBoxes()
self.rangezone:SmokeZone(SMOKECOLOR.White)
end
end end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -412,143 +432,199 @@ end
--- Set maximal strafing altitude. Player entering a strafe pit above that altitude are not registered for a valid pass. --- Set maximal strafing altitude. Player entering a strafe pit above that altitude are not registered for a valid pass.
-- @param #RANGE self -- @param #RANGE self
-- @param #number maxalt Maximum altitude AGL in meters. Default is 914 m= 3000 ft. -- @param #number maxalt Maximum altitude AGL in meters. Default is 914 m= 3000 ft.
-- @return #RANGE self
function RANGE:SetMaxStrafeAlt(maxalt) function RANGE:SetMaxStrafeAlt(maxalt)
self.strafemaxalt=maxalt or RANGE.Defaults.strafemaxalt self.strafemaxalt=maxalt or RANGE.Defaults.strafemaxalt
return self
end end
--- Set time interval for tracking bombs. A smaller time step increases accuracy but needs more CPU time. --- Set time interval for tracking bombs. A smaller time step increases accuracy but needs more CPU time.
-- @param #RANGE self -- @param #RANGE self
-- @param #number dt Time interval in seconds. Default is 0.005 s. -- @param #number dt Time interval in seconds. Default is 0.005 s.
-- @return #RANGE self
function RANGE:SetBombtrackTimestep(dt) function RANGE:SetBombtrackTimestep(dt)
self.dtBombtrack=dt or RANGE.Defaults.dtBombtrack self.dtBombtrack=dt or RANGE.Defaults.dtBombtrack
return self
end end
--- Set time how long (most) messages are displayed. --- Set time how long (most) messages are displayed.
-- @param #RANGE self -- @param #RANGE self
-- @param #number time Time in seconds. Default is 30 s. -- @param #number time Time in seconds. Default is 30 s.
-- @return #RANGE self
function RANGE:SetMessageTimeDuration(time) function RANGE:SetMessageTimeDuration(time)
self.Tmsg=time or RANGE.Defaults.Tmsg self.Tmsg=time or RANGE.Defaults.Tmsg
return self
end end
--- Set messages to examiner. The examiner will receive messages from all clients. --- Set messages to examiner. The examiner will receive messages from all clients.
-- @param #RANGE self -- @param #RANGE self
-- @param #string examinergroupname Name of the group of the examiner. -- @param #string examinergroupname Name of the group of the examiner.
-- @param #boolean exclusively If true, messages are send exclusively to the examiner, i.e. not to the clients. -- @param #boolean exclusively If true, messages are send exclusively to the examiner, i.e. not to the clients.
-- @return #RANGE self
function RANGE:SetMessageToExaminer(examinergroupname, exclusively) function RANGE:SetMessageToExaminer(examinergroupname, exclusively)
self.examinergroupname=examinergroupname self.examinergroupname=examinergroupname
self.examinerexclusive=exclusively self.examinerexclusive=exclusively
return self
end end
--- Set max number of player results that are displayed. --- Set max number of player results that are displayed.
-- @param #RANGE self -- @param #RANGE self
-- @param #number nmax Number of results. Default is 10. -- @param #number nmax Number of results. Default is 10.
-- @return #RANGE self
function RANGE:SetDisplayedMaxPlayerResults(nmax) function RANGE:SetDisplayedMaxPlayerResults(nmax)
self.ndisplayresult=nmax or RANGE.Defaults.ndisplayresult self.ndisplayresult=nmax or RANGE.Defaults.ndisplayresult
return self
end end
--- Set range radius. Defines the area in which e.g. bomb impacts are smoked. --- Set range radius. Defines the area in which e.g. bomb impacts are smoked.
-- @param #RANGE self -- @param #RANGE self
-- @param #number radius Radius in km. Default 5 km. -- @param #number radius Radius in km. Default 5 km.
-- @return #RANGE self
function RANGE:SetRangeRadius(radius) function RANGE:SetRangeRadius(radius)
self.rangeradius=radius*1000 or RANGE.Defaults.rangeradius self.rangeradius=radius*1000 or RANGE.Defaults.rangeradius
return self
end
--- Set player setting whether bomb impact points are smoked or not
-- @param #RANGE self
-- @param #boolean If true nor nil default is to smoke impact points of bombs.
-- @return #RANGE self
function RANGE:SetDefaultPlayerSmokeBomb(switch)
if switch==true or switch==nil then
self.defaultsmokebomb=true
else
self.defaultsmokebomb=false
end
return self
end end
--- Set bomb track threshold distance. Bombs/rockets/missiles are only tracked if player-range distance is less than this distance. Default 25 km. --- Set bomb track threshold distance. Bombs/rockets/missiles are only tracked if player-range distance is less than this distance. Default 25 km.
-- @param #RANGE self -- @param #RANGE self
-- @param #number distance Threshold distance in km. Default 25 km. -- @param #number distance Threshold distance in km. Default 25 km.
-- @return #RANGE self
function RANGE:SetBombtrackThreshold(distance) function RANGE:SetBombtrackThreshold(distance)
self.BombtrackThreshold=distance*1000 or 25*1000 self.BombtrackThreshold=distance*1000 or 25*1000
return self
end end
--- Set range location. If this is not done, one (random) unit position of the range is used to determine the center of the range. --- Set range location. If this is not done, one (random) unit position of the range is used to determine the location of the range.
-- The range location determines the position at which the weather data is evaluated.
-- @param #RANGE self -- @param #RANGE self
-- @param Core.Point#COORDINATE coordinate Coordinate of the center of the range. -- @param Core.Point#COORDINATE coordinate Coordinate of the range.
-- @return #RANGE self
function RANGE:SetRangeLocation(coordinate) function RANGE:SetRangeLocation(coordinate)
self.location=coordinate self.location=coordinate
return self
end end
--- Set range zone. For example, no bomb impact points are smoked if a bomb falls outside of this zone. --- Set range zone. For example, no bomb impact points are smoked if a bomb falls outside of this zone.
-- If a zone is not explicitly specified, the range zone is determined by its location and radius. -- If a zone is not explicitly specified, the range zone is determined by its location and radius.
-- @param #RANGE self -- @param #RANGE self
-- @param Core.Zone#ZONE zone MOOSE zone defining the range perimeters. -- @param Core.Zone#ZONE zone MOOSE zone defining the range perimeters.
function RANGE:SetRangeLocation(zone) -- @return #RANGE self
function RANGE:SetRangeZone(zone)
self.rangezone=zone self.rangezone=zone
return self
end end
--- Set smoke color for marking bomb targets. By default bomb targets are marked by red smoke. --- Set smoke color for marking bomb targets. By default bomb targets are marked by red smoke.
-- @param #RANGE self -- @param #RANGE self
-- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default SMOKECOLOR.Red. -- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default SMOKECOLOR.Red.
-- @return #RANGE self
function RANGE:SetBombTargetSmokeColor(colorid) function RANGE:SetBombTargetSmokeColor(colorid)
self.BombSmokeColor=colorid or SMOKECOLOR.Red self.BombSmokeColor=colorid or SMOKECOLOR.Red
return self
end end
--- Set smoke color for marking strafe targets. By default strafe targets are marked by green smoke. --- Set smoke color for marking strafe targets. By default strafe targets are marked by green smoke.
-- @param #RANGE self -- @param #RANGE self
-- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default SMOKECOLOR.Green. -- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default SMOKECOLOR.Green.
-- @return #RANGE self
function RANGE:SetStrafeTargetSmokeColor(colorid) function RANGE:SetStrafeTargetSmokeColor(colorid)
self.StrafeSmokeColor=colorid or SMOKECOLOR.Green self.StrafeSmokeColor=colorid or SMOKECOLOR.Green
return self
end end
--- Set smoke color for marking strafe pit approach boxes. By default strafe pit boxes are marked by white smoke. --- Set smoke color for marking strafe pit approach boxes. By default strafe pit boxes are marked by white smoke.
-- @param #RANGE self -- @param #RANGE self
-- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default SMOKECOLOR.White. -- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default SMOKECOLOR.White.
-- @return #RANGE self
function RANGE:SetStrafePitSmokeColor(colorid) function RANGE:SetStrafePitSmokeColor(colorid)
self.StrafePitSmokeColor=colorid or SMOKECOLOR.White self.StrafePitSmokeColor=colorid or SMOKECOLOR.White
return self
end end
--- Set time delay between bomb impact and starting to smoke the impact point. --- Set time delay between bomb impact and starting to smoke the impact point.
-- @param #RANGE self -- @param #RANGE self
-- @param #number delay Time delay in seconds. Default is 3 seconds. -- @param #number delay Time delay in seconds. Default is 3 seconds.
-- @return #RANGE self
function RANGE:SetSmokeTimeDelay(delay) function RANGE:SetSmokeTimeDelay(delay)
self.TdelaySmoke=delay or RANGE.Defaults.TdelaySmoke self.TdelaySmoke=delay or RANGE.Defaults.TdelaySmoke
return self
end end
--- Enable debug modus. --- Enable debug modus.
-- @param #RANGE self -- @param #RANGE self
-- @return #RANGE self
function RANGE:DebugON() function RANGE:DebugON()
self.Debug=true self.Debug=true
return self
end end
--- Disable debug modus. --- Disable debug modus.
-- @param #RANGE self -- @param #RANGE self
-- @return #RANGE self
function RANGE:DebugOFF() function RANGE:DebugOFF()
self.Debug=false self.Debug=false
return self
end end
--- Enables tracking of all bomb types. Note that this is the default setting. --- Enables tracking of all bomb types. Note that this is the default setting.
-- @param #RANGE self -- @param #RANGE self
-- @return #RANGE self
function RANGE:TrackBombsON() function RANGE:TrackBombsON()
self.trackbombs=true self.trackbombs=true
return self
end end
--- Disables tracking of all bomb types. --- Disables tracking of all bomb types.
-- @param #RANGE self -- @param #RANGE self
-- @return #RANGE self
function RANGE:TrackBombsOFF() function RANGE:TrackBombsOFF()
self.trackbombs=false self.trackbombs=false
return self
end end
--- Enables tracking of all rocket types. Note that this is the default setting. --- Enables tracking of all rocket types. Note that this is the default setting.
-- @param #RANGE self -- @param #RANGE self
-- @return #RANGE self
function RANGE:TrackRocketsON() function RANGE:TrackRocketsON()
self.trackrockets=true self.trackrockets=true
return self
end end
--- Disables tracking of all rocket types. --- Disables tracking of all rocket types.
-- @param #RANGE self -- @param #RANGE self
-- @return #RANGE self
function RANGE:TrackRocketsOFF() function RANGE:TrackRocketsOFF()
self.trackrockets=false self.trackrockets=false
return self
end end
--- Enables tracking of all missile types. Note that this is the default setting. --- Enables tracking of all missile types. Note that this is the default setting.
-- @param #RANGE self -- @param #RANGE self
-- @return #RANGE self
function RANGE:TrackMissilesON() function RANGE:TrackMissilesON()
self.trackmissiles=true self.trackmissiles=true
return self
end end
--- Disables tracking of all missile types. --- Disables tracking of all missile types.
-- @param #RANGE self -- @param #RANGE self
-- @return #RANGE self
function RANGE:TrackMissilesOFF() function RANGE:TrackMissilesOFF()
self.trackmissiles=false self.trackmissiles=false
return self
end end
@@ -563,6 +639,7 @@ end
-- @param #boolean inverseheading (Optional) Take inverse heading (heading --> heading - 180 Degrees). Default is false. -- @param #boolean inverseheading (Optional) Take inverse heading (heading --> heading - 180 Degrees). Default is false.
-- @param #number goodpass (Optional) Number of hits for a "good" strafing pass. Default is 20. -- @param #number goodpass (Optional) Number of hits for a "good" strafing pass. Default is 20.
-- @param #number foulline (Optional) Foul line distance. Hits from closer than this distance are not counted. Default 610 m = 2000 ft. Set to 0 for no foul line. -- @param #number foulline (Optional) Foul line distance. Hits from closer than this distance are not counted. Default 610 m = 2000 ft. Set to 0 for no foul line.
-- @return #RANGE self
function RANGE:AddStrafePit(targetnames, boxlength, boxwidth, heading, inverseheading, goodpass, foulline) function RANGE:AddStrafePit(targetnames, boxlength, boxwidth, heading, inverseheading, goodpass, foulline)
self:F({targetnames=targetnames, boxlength=boxlength, boxwidth=boxwidth, heading=heading, inverseheading=inverseheading, goodpass=goodpass, foulline=foulline}) self:F({targetnames=targetnames, boxlength=boxlength, boxwidth=boxwidth, heading=heading, inverseheading=inverseheading, goodpass=goodpass, foulline=foulline})
@@ -680,6 +757,8 @@ function RANGE:AddStrafePit(targetnames, boxlength, boxwidth, heading, inversehe
local text=string.format("Adding new strafe target %s with %d targets: heading = %03d, box_L = %.1f, box_W = %.1f, goodpass = %d, foul line = %.1f", _name, ntargets, heading, l, w, goodpass, foulline) local text=string.format("Adding new strafe target %s with %d targets: heading = %03d, box_L = %.1f, box_W = %.1f, goodpass = %d, foul line = %.1f", _name, ntargets, heading, l, w, goodpass, foulline)
self:T(RANGE.id..text) self:T(RANGE.id..text)
MESSAGE:New(text, 5):ToAllIf(self.Debug) MESSAGE:New(text, 5):ToAllIf(self.Debug)
return self
end end
@@ -695,6 +774,7 @@ end
-- @param #boolean inverseheading (Optional) Take inverse heading (heading --> heading - 180 Degrees). Default is false. -- @param #boolean inverseheading (Optional) Take inverse heading (heading --> heading - 180 Degrees). Default is false.
-- @param #number goodpass (Optional) Number of hits for a "good" strafing pass. Default is 20. -- @param #number goodpass (Optional) Number of hits for a "good" strafing pass. Default is 20.
-- @param #number foulline (Optional) Foul line distance. Hits from closer than this distance are not counted. Default 610 m = 2000 ft. Set to 0 for no foul line. -- @param #number foulline (Optional) Foul line distance. Hits from closer than this distance are not counted. Default 610 m = 2000 ft. Set to 0 for no foul line.
-- @return #RANGE self
function RANGE:AddStrafePitGroup(group, boxlength, boxwidth, heading, inverseheading, goodpass, foulline) function RANGE:AddStrafePitGroup(group, boxlength, boxwidth, heading, inverseheading, goodpass, foulline)
self:F({group=group, boxlength=boxlength, boxwidth=boxwidth, heading=heading, inverseheading=inverseheading, goodpass=goodpass, foulline=foulline}) self:F({group=group, boxlength=boxlength, boxwidth=boxwidth, heading=heading, inverseheading=inverseheading, goodpass=goodpass, foulline=foulline})
@@ -720,6 +800,7 @@ function RANGE:AddStrafePitGroup(group, boxlength, boxwidth, heading, inversehea
self:AddStrafePit(_names, boxlength, boxwidth, heading, inverseheading, goodpass, foulline) self:AddStrafePit(_names, boxlength, boxwidth, heading, inverseheading, goodpass, foulline)
end end
return self
end end
--- Add bombing target(s) to range. --- Add bombing target(s) to range.
@@ -727,6 +808,7 @@ end
-- @param #table targetnames Table containing names of unit or static objects serving as bomb targets. -- @param #table targetnames Table containing names of unit or static objects serving as bomb targets.
-- @param #number goodhitrange (Optional) Max distance from target unit (in meters) which is considered as a good hit. Default is 25 m. -- @param #number goodhitrange (Optional) Max distance from target unit (in meters) which is considered as a good hit. Default is 25 m.
-- @param #boolean randommove If true, unit will move randomly within the range. Default is false. -- @param #boolean randommove If true, unit will move randomly within the range. Default is false.
-- @return #RANGE self
function RANGE:AddBombingTargets(targetnames, goodhitrange, randommove) function RANGE:AddBombingTargets(targetnames, goodhitrange, randommove)
self:F({targetnames=targetnames, goodhitrange=goodhitrange, randommove=randommove}) self:F({targetnames=targetnames, goodhitrange=goodhitrange, randommove=randommove})
@@ -756,6 +838,8 @@ function RANGE:AddBombingTargets(targetnames, goodhitrange, randommove)
end end
end end
return self
end end
--- Add a unit or static object as bombing target. --- Add a unit or static object as bombing target.
@@ -763,6 +847,7 @@ end
-- @param Wrapper.Positionable#POSITIONABLE unit Positionable (unit or static) of the strafe target. -- @param Wrapper.Positionable#POSITIONABLE unit Positionable (unit or static) of the strafe target.
-- @param #number goodhitrange Max distance from unit which is considered as a good hit. -- @param #number goodhitrange Max distance from unit which is considered as a good hit.
-- @param #boolean randommove If true, unit will move randomly within the range. Default is false. -- @param #boolean randommove If true, unit will move randomly within the range. Default is false.
-- @return #RANGE self
function RANGE:AddBombingTargetUnit(unit, goodhitrange, randommove) function RANGE:AddBombingTargetUnit(unit, goodhitrange, randommove)
self:F({unit=unit, goodhitrange=goodhitrange, randommove=randommove}) self:F({unit=unit, goodhitrange=goodhitrange, randommove=randommove})
@@ -797,6 +882,8 @@ function RANGE:AddBombingTargetUnit(unit, goodhitrange, randommove)
-- Insert target to table. -- Insert target to table.
table.insert(self.bombingTargets, {name=name, target=unit, goodhitrange=goodhitrange, move=randommove, speed=speed}) table.insert(self.bombingTargets, {name=name, target=unit, goodhitrange=goodhitrange, move=randommove, speed=speed})
return self
end end
--- Add all units of a group as bombing targets. --- Add all units of a group as bombing targets.
@@ -804,6 +891,7 @@ end
-- @param Wrapper.Group#GROUP group Group of bombing targets. -- @param Wrapper.Group#GROUP group Group of bombing targets.
-- @param #number goodhitrange Max distance from unit which is considered as a good hit. -- @param #number goodhitrange Max distance from unit which is considered as a good hit.
-- @param #boolean randommove If true, unit will move randomly within the range. Default is false. -- @param #boolean randommove If true, unit will move randomly within the range. Default is false.
-- @return #RANGE self
function RANGE:AddBombingTargetGroup(group, goodhitrange, randommove) function RANGE:AddBombingTargetGroup(group, goodhitrange, randommove)
self:F({group=group, goodhitrange=goodhitrange, randommove=randommove}) self:F({group=group, goodhitrange=goodhitrange, randommove=randommove})
@@ -818,6 +906,7 @@ function RANGE:AddBombingTargetGroup(group, goodhitrange, randommove)
end end
end end
return self
end end
--- Measures the foule line distance between two unit or static objects. --- Measures the foule line distance between two unit or static objects.
@@ -970,11 +1059,12 @@ function RANGE:OnEventBirth(EventData)
-- By default, some bomb impact points and do not flare each hit on target. -- By default, some bomb impact points and do not flare each hit on target.
self.PlayerSettings[_playername]={} self.PlayerSettings[_playername]={}
self.PlayerSettings[_playername].smokebombimpact=true self.PlayerSettings[_playername].smokebombimpact=self.defaultsmokebomb
self.PlayerSettings[_playername].flaredirecthits=false self.PlayerSettings[_playername].flaredirecthits=false
self.PlayerSettings[_playername].smokecolor=SMOKECOLOR.Blue self.PlayerSettings[_playername].smokecolor=SMOKECOLOR.Blue
self.PlayerSettings[_playername].flarecolor=FLARECOLOR.Red self.PlayerSettings[_playername].flarecolor=FLARECOLOR.Red
self.PlayerSettings[_playername].delaysmoke=true self.PlayerSettings[_playername].delaysmoke=true
self.PlayerSettings[_playername].messages=true
-- Start check in zone timer. -- Start check in zone timer.
if self.planes[_uid] ~= true then if self.planes[_uid] ~= true then
@@ -1041,7 +1131,7 @@ function RANGE:OnEventHit(EventData)
if _currentTarget.pastfoulline==false and _unit and _playername then if _currentTarget.pastfoulline==false and _unit and _playername then
local _d=_currentTarget.zone.foulline local _d=_currentTarget.zone.foulline
local text=string.format("%s, Invalid hit!\nYou already passed foul line distance of %d m for target %s.", self:_myname(_unitName), _d, targetname) local text=string.format("%s, Invalid hit!\nYou already passed foul line distance of %d m for target %s.", self:_myname(_unitName), _d, targetname)
self:_DisplayMessageToGroup(_unit, text, 10) self:_DisplayMessageToGroup(_unit, text)
self:T2(RANGE.id..text) self:T2(RANGE.id..text)
_currentTarget.pastfoulline=true _currentTarget.pastfoulline=true
end end
@@ -1163,11 +1253,19 @@ function RANGE:OnEventShot(EventData)
-- Coordinate of impact point. -- Coordinate of impact point.
local impactcoord=COORDINATE:NewFromVec3(_lastBombPos) local impactcoord=COORDINATE:NewFromVec3(_lastBombPos)
-- Check if impact happend in range zone.
local insidezone=self.rangezone:IsCoordinateInZone(impactcoord)
-- Distance from range. We dont want to smoke targets outside of the range. -- Distance from range. We dont want to smoke targets outside of the range.
local impactdist=impactcoord:Get2DDistance(self.location) local impactdist=impactcoord:Get2DDistance(self.location)
-- Impact point of bomb.
if self.Debug then
impactcoord:MarkToAll("Bomb impact point")
end
-- Smoke impact point of bomb. -- Smoke impact point of bomb.
if self.PlayerSettings[_playername].smokebombimpact and impactdist<self.rangeradius then if self.PlayerSettings[_playername].smokebombimpact and insidezone then
if self.PlayerSettings[_playername].delaysmoke then if self.PlayerSettings[_playername].delaysmoke then
timer.scheduleFunction(self._DelayedSmoke, {coord=impactcoord, color=self.PlayerSettings[_playername].smokecolor}, timer.getTime() + self.TdelaySmoke) timer.scheduleFunction(self._DelayedSmoke, {coord=impactcoord, color=self.PlayerSettings[_playername].smokecolor}, timer.getTime() + self.TdelaySmoke)
else else
@@ -1184,6 +1282,8 @@ function RANGE:OnEventShot(EventData)
-- Distance between bomb and target. -- Distance between bomb and target.
local _temp = impactcoord:Get2DDistance(_target:GetCoordinate()) local _temp = impactcoord:Get2DDistance(_target:GetCoordinate())
--env.info(string.format("FF target = %s dist = %d m", _target:GetName(), _temp))
-- Find closest target to last known position of the bomb. -- Find closest target to last known position of the bomb.
if _distance == nil or _temp < _distance then if _distance == nil or _temp < _distance then
@@ -1203,7 +1303,7 @@ function RANGE:OnEventShot(EventData)
end end
end end
-- Count if bomb fell less than 1 km away from the target. -- Count if bomb fell less than ~1 km away from the target.
if _distance <= self.scorebombdistance then if _distance <= self.scorebombdistance then
-- Init bomb player results. -- Init bomb player results.
@@ -1222,10 +1322,10 @@ function RANGE:OnEventShot(EventData)
-- Send message. -- Send message.
self:_DisplayMessageToGroup(_unit, _message, nil, true) self:_DisplayMessageToGroup(_unit, _message, nil, true)
elseif _distance <= self.rangeradius then elseif insidezone then
-- Send message -- Send message
local _message=string.format("%s, weapon fell more than %.1f km away from nearest range target. No score!", _callsign, self.scorebombdistance/1000) local _message=string.format("%s, weapon fell more than %.1f km away from nearest range target. No score!", _callsign, self.scorebombdistance/1000)
self:_DisplayMessageToGroup(_unit, _message, nil, true) self:_DisplayMessageToGroup(_unit, _message, nil, false)
end end
--Terminate the timer --Terminate the timer
@@ -1309,7 +1409,7 @@ function RANGE:_DisplayMyStrafePitResults(_unitName)
end end
-- Send message to group. -- Send message to group.
self:_DisplayMessageToGroup(_unit, _message, nil, true) self:_DisplayMessageToGroup(_unit, _message, nil, true, true)
end end
end end
@@ -1365,7 +1465,7 @@ function RANGE:_DisplayStrafePitResults(_unitName)
end end
-- Send message. -- Send message.
self:_DisplayMessageToGroup(_unit, _message, nil, true) self:_DisplayMessageToGroup(_unit, _message, nil, true, true)
end end
end end
@@ -1422,7 +1522,7 @@ function RANGE:_DisplayMyBombingResults(_unitName)
end end
-- Send message. -- Send message.
self:_DisplayMessageToGroup(_unit, _message, nil, true) self:_DisplayMessageToGroup(_unit, _message, nil, true, true)
end end
end end
@@ -1478,7 +1578,7 @@ function RANGE:_DisplayBombingResults(_unitName)
end end
-- Send message. -- Send message.
self:_DisplayMessageToGroup(_unit, _message, nil, true) self:_DisplayMessageToGroup(_unit, _message, nil, true, true)
end end
end end
@@ -1555,7 +1655,7 @@ function RANGE:_DisplayRangeInfo(_unitname)
text=text..textdelay text=text..textdelay
-- Send message to player group. -- Send message to player group.
self:_DisplayMessageToGroup(unit, text, nil, true) self:_DisplayMessageToGroup(unit, text, nil, true, true)
-- Debug output. -- Debug output.
self:T2(RANGE.id..text) self:T2(RANGE.id..text)
@@ -1592,7 +1692,7 @@ function RANGE:_DisplayBombTargets(_unitname)
end end
end end
self:_DisplayMessageToGroup(_unit,_text, nil, true) self:_DisplayMessageToGroup(_unit,_text, nil, true, true)
end end
end end
@@ -1632,7 +1732,7 @@ function RANGE:_DisplayStrafePits(_unitname)
_text=_text..string.format("\n- %s: %s - heading %03d",_strafepit.name, mycoord, heading) _text=_text..string.format("\n- %s: %s - heading %03d",_strafepit.name, mycoord, heading)
end end
self:_DisplayMessageToGroup(_unit,_text, nil, true) self:_DisplayMessageToGroup(_unit,_text, nil, true, true)
end end
end end
@@ -1694,7 +1794,7 @@ function RANGE:_DisplayRangeWeather(_unitname)
end end
-- Send message to player group. -- Send message to player group.
self:_DisplayMessageToGroup(unit, text, nil, true) self:_DisplayMessageToGroup(unit, text, nil, true, true)
-- Debug output. -- Debug output.
self:T2(RANGE.id..text) self:T2(RANGE.id..text)
@@ -1738,7 +1838,7 @@ function RANGE:_CheckInZone(_unitName)
local unitinzone=_unit:IsInZone(zone) and unitalt <= self.strafemaxalt and towardspit local unitinzone=_unit:IsInZone(zone) and unitalt <= self.strafemaxalt and towardspit
-- Debug output -- Debug output
local text=string.format("Checking stil in zone. Unit = %s, player = %s in zone = %s. alt = %d, delta heading = %d", _unitName, _playername, tostring(unitinzone), unitalt, deltaheading) local text=string.format("Checking still in zone. Unit = %s, player = %s in zone = %s. alt = %d, delta heading = %d", _unitName, _playername, tostring(unitinzone), unitalt, deltaheading)
self:T2(RANGE.id..text) self:T2(RANGE.id..text)
-- Check if player is in strafe zone and below max alt. -- Check if player is in strafe zone and below max alt.
@@ -1883,12 +1983,33 @@ function RANGE:_AddF10Commands(_unitName)
-- Enable switch so we don't do this twice. -- Enable switch so we don't do this twice.
self.MenuAddedTo[_gid] = true self.MenuAddedTo[_gid] = true
-- Main F10 menu: F10/On the Range/<Range Name>/ -- Range root menu path.
if RANGE.MenuF10[_gid] == nil then local _rangePath=nil
RANGE.MenuF10[_gid]=missionCommands.addSubMenuForGroup(_gid, "On the Range")
end if RANGE.MenuF10Root then
local _rangePath = missionCommands.addSubMenuForGroup(_gid, self.rangename, RANGE.MenuF10[_gid])
-------------------
-- MISSION LEVEL --
-------------------
_rangePath = missionCommands.addSubMenuForGroup(_gid, self.rangename, RANGE.MenuF10Root)
else
-----------------
-- GROUP LEVEL --
-----------------
-- Main F10 menu: F10/On the Range/<Range Name>/
if RANGE.MenuF10[_gid] == nil then
RANGE.MenuF10[_gid]=missionCommands.addSubMenuForGroup(_gid, "On the Range")
end
_rangePath = missionCommands.addSubMenuForGroup(_gid, self.rangename, RANGE.MenuF10[_gid])
end
local _statsPath = missionCommands.addSubMenuForGroup(_gid, "Statistics", _rangePath) local _statsPath = missionCommands.addSubMenuForGroup(_gid, "Statistics", _rangePath)
local _markPath = missionCommands.addSubMenuForGroup(_gid, "Mark Targets", _rangePath) local _markPath = missionCommands.addSubMenuForGroup(_gid, "Mark Targets", _rangePath)
local _settingsPath = missionCommands.addSubMenuForGroup(_gid, "My Settings", _rangePath) local _settingsPath = missionCommands.addSubMenuForGroup(_gid, "My Settings", _rangePath)
@@ -1921,9 +2042,11 @@ function RANGE:_AddF10Commands(_unitName)
missionCommands.addCommandForGroup(_gid, "White Flares", _myflarePath, self._playerflarecolor, self, _unitName, FLARECOLOR.White) missionCommands.addCommandForGroup(_gid, "White Flares", _myflarePath, self._playerflarecolor, self, _unitName, FLARECOLOR.White)
missionCommands.addCommandForGroup(_gid, "Yellow Flares", _myflarePath, self._playerflarecolor, self, _unitName, FLARECOLOR.Yellow) missionCommands.addCommandForGroup(_gid, "Yellow Flares", _myflarePath, self._playerflarecolor, self, _unitName, FLARECOLOR.Yellow)
-- F10/On the Range/<Range Name>/My Settings/ -- F10/On the Range/<Range Name>/My Settings/
missionCommands.addCommandForGroup(_gid, "Smoke Delay On/Off", _settingsPath, self._SmokeBombDelayOnOff, self, _unitName) missionCommands.addCommandForGroup(_gid, "Smoke Delay On/Off", _settingsPath, self._SmokeBombDelayOnOff, self, _unitName)
missionCommands.addCommandForGroup(_gid, "Smoke Impact On/Off", _settingsPath, self._SmokeBombImpactOnOff, self, _unitName) missionCommands.addCommandForGroup(_gid, "Smoke Impact On/Off", _settingsPath, self._SmokeBombImpactOnOff, self, _unitName)
missionCommands.addCommandForGroup(_gid, "Flare Hits On/Off", _settingsPath, self._FlareDirectHitsOnOff, self, _unitName) missionCommands.addCommandForGroup(_gid, "Flare Hits On/Off", _settingsPath, self._FlareDirectHitsOnOff, self, _unitName)
missionCommands.addCommandForGroup(_gid, "All Messages On/Off", _settingsPath, self._MessagesToPlayerOnOff, self, _unitName)
-- F10/On the Range/<Range Name>/Range Information -- F10/On the Range/<Range Name>/Range Information
missionCommands.addCommandForGroup(_gid, "General Info", _infoPath, self._DisplayRangeInfo, self, _unitName) missionCommands.addCommandForGroup(_gid, "General Info", _infoPath, self._DisplayRangeInfo, self, _unitName)
missionCommands.addCommandForGroup(_gid, "Weather Report", _infoPath, self._DisplayRangeWeather, self, _unitName) missionCommands.addCommandForGroup(_gid, "Weather Report", _infoPath, self._DisplayRangeWeather, self, _unitName)
@@ -2103,7 +2226,7 @@ function RANGE:_ResetRangeStats(_unitName)
self.strafePlayerResults[_playername] = nil self.strafePlayerResults[_playername] = nil
self.bombPlayerResults[_playername] = nil self.bombPlayerResults[_playername] = nil
local text=string.format("%s, %s, your range stats were cleared.", self.rangename, _playername) local text=string.format("%s, %s, your range stats were cleared.", self.rangename, _playername)
self:DisplayMessageToGroup(_unit, text, 5) self:DisplayMessageToGroup(_unit, text, 5, false, true)
end end
end end
@@ -2113,33 +2236,35 @@ end
-- @param #string _text Message text. -- @param #string _text Message text.
-- @param #number _time Duration how long the message is displayed. -- @param #number _time Duration how long the message is displayed.
-- @param #boolean _clear Clear up old messages. -- @param #boolean _clear Clear up old messages.
function RANGE:_DisplayMessageToGroup(_unit, _text, _time, _clear) -- @param #boolean display If true, display message regardless of player setting "Messages Off".
function RANGE:_DisplayMessageToGroup(_unit, _text, _time, _clear, display)
self:F({unit=_unit, text=_text, time=_time, clear=_clear}) self:F({unit=_unit, text=_text, time=_time, clear=_clear})
-- Defaults
_time=_time or self.Tmsg _time=_time or self.Tmsg
if _clear==nil then if _clear==nil or _clear==false then
_clear=false _clear=false
else
_clear=true
end end
-- Group ID. -- Group ID.
local _gid=_unit:GetGroup():GetID() local _gid=_unit:GetGroup():GetID()
if _gid and not self.examinerexclusive then -- Get playername and player settings
if _clear == true then local _, playername=self:_GetPlayerUnitAndName(_unit:GetName())
trigger.action.outTextForGroup(_gid, _text, _time, _clear) local playermessage=self.PlayerSettings[playername].messages
else
trigger.action.outTextForGroup(_gid, _text, _time) -- Send message to player if messages enabled and not only for the examiner.
end if _gid and (playermessage==true or display) and (not self.examinerexclusive) then
trigger.action.outTextForGroup(_gid, _text, _time, _clear)
end end
-- Send message to examiner.
if self.examinergroupname~=nil then if self.examinergroupname~=nil then
local _examinerid=GROUP:FindByName(self.examinergroupname):GetID() local _examinerid=GROUP:FindByName(self.examinergroupname):GetID()
if _examinerid then if _examinerid then
if _clear == true then trigger.action.outTextForGroup(_examinerid, _text, _time, _clear)
trigger.action.outTextForGroup(_examinerid, _text, _time, _clear)
else
trigger.action.outTextForGroup(_examinerid, _text, _time)
end
end end
end end
@@ -2161,7 +2286,7 @@ function RANGE:_SmokeBombImpactOnOff(unitname)
self.PlayerSettigs[playername].smokebombimpact=true self.PlayerSettigs[playername].smokebombimpact=true
text=string.format("%s, %s, smoking impact points of bombs is now ON.", self.rangename, playername) text=string.format("%s, %s, smoking impact points of bombs is now ON.", self.rangename, playername)
end end
self:_DisplayMessageToGroup(unit, text, 5) self:_DisplayMessageToGroup(unit, text, 5, false, true)
end end
end end
@@ -2182,7 +2307,27 @@ function RANGE:_SmokeBombDelayOnOff(unitname)
self.PlayerSettigs[playername].delaysmoke=true self.PlayerSettigs[playername].delaysmoke=true
text=string.format("%s, %s, delayed smoke of bombs is now ON.", self.rangename, playername) text=string.format("%s, %s, delayed smoke of bombs is now ON.", self.rangename, playername)
end end
self:_DisplayMessageToGroup(unit, text, 5) self:_DisplayMessageToGroup(unit, text, 5, false, true)
end
end
--- Toggle display messages to player.
-- @param #RANGE self
-- @param #string unitname Name of the player unit.
function RANGE:_MessagesToPlayerOnOff(unitname)
self:F(unitname)
local unit, playername = self:_GetPlayerUnitAndName(unitname)
if unit and playername then
local text
if self.PlayerSettings[playername].messages==true then
text=string.format("%s, %s, display of ALL messages is now OFF.", self.rangename, playername)
else
text=string.format("%s, %s, display of ALL messages is now ON.", self.rangename, playername)
end
self:_DisplayMessageToGroup(unit, text, 5, false, true)
self.PlayerSettings[playername].messages=not self.PlayerSettings[playername].messages
end end
end end
@@ -2203,7 +2348,7 @@ function RANGE:_FlareDirectHitsOnOff(unitname)
self.PlayerSettings[playername].flaredirecthits=true self.PlayerSettings[playername].flaredirecthits=true
text=string.format("%s, %s, flaring direct hits is now ON.", self.rangename, playername) text=string.format("%s, %s, flaring direct hits is now ON.", self.rangename, playername)
end end
self:_DisplayMessageToGroup(unit, text, 5) self:_DisplayMessageToGroup(unit, text, 5, false, true)
end end
end end
@@ -2321,7 +2466,7 @@ function RANGE:_smokecolor2text(color)
elseif color==SMOKECOLOR.White then elseif color==SMOKECOLOR.White then
txt="white" txt="white"
else else
txt=string.format("unkown color (%s)", tostring(color)) txt=string.format("unknown color (%s)", tostring(color))
end end
return txt return txt
@@ -2344,7 +2489,7 @@ function RANGE:_flarecolor2text(color)
elseif color==FLARECOLOR.Yellow then elseif color==FLARECOLOR.Yellow then
txt="yellow" txt="yellow"
else else
txt=string.format("unkown color (%s)", tostring(color)) txt=string.format("unknown color (%s)", tostring(color))
end end
return txt return txt

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,115 @@
__Moose.Include( 'Scripts/Moose/Utilities/Routines.lua' )
__Moose.Include( 'Scripts/Moose/Utilities/Utils.lua' )
__Moose.Include( 'Scripts/Moose/Core/Base.lua' )
__Moose.Include( 'Scripts/Moose/Core/UserFlag.lua' )
__Moose.Include( 'Scripts/Moose/Core/UserSound.lua' )
__Moose.Include( 'Scripts/Moose/Core/Report.lua' )
__Moose.Include( 'Scripts/Moose/Core/Scheduler.lua' )
__Moose.Include( 'Scripts/Moose/Core/ScheduleDispatcher.lua' )
__Moose.Include( 'Scripts/Moose/Core/Event.lua' )
__Moose.Include( 'Scripts/Moose/Core/Settings.lua' )
__Moose.Include( 'Scripts/Moose/Core/Menu.lua' )
__Moose.Include( 'Scripts/Moose/Core/Zone.lua' )
__Moose.Include( 'Scripts/Moose/Core/Database.lua' )
__Moose.Include( 'Scripts/Moose/Core/Set.lua' )
__Moose.Include( 'Scripts/Moose/Core/Point.lua' )
__Moose.Include( 'Scripts/Moose/Core/Velocity.lua' )
__Moose.Include( 'Scripts/Moose/Core/Message.lua' )
__Moose.Include( 'Scripts/Moose/Core/Fsm.lua' )
__Moose.Include( 'Scripts/Moose/Core/Radio.lua' )
__Moose.Include( 'Scripts/Moose/Core/Spawn.lua' )
__Moose.Include( 'Scripts/Moose/Core/SpawnStatic.lua' )
__Moose.Include( 'Scripts/Moose/Core/Goal.lua' )
__Moose.Include( 'Scripts/Moose/Core/Spot.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Object.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Identifiable.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Positionable.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Controllable.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Group.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Unit.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Client.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Static.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Airbase.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Scenery.lua' )
__Moose.Include( 'Scripts/Moose/Cargo/Cargo.lua' )
__Moose.Include( 'Scripts/Moose/Cargo/CargoUnit.lua' )
__Moose.Include( 'Scripts/Moose/Cargo/CargoSlingload.lua' )
__Moose.Include( 'Scripts/Moose/Cargo/CargoCrate.lua' )
__Moose.Include( 'Scripts/Moose/Cargo/CargoGroup.lua' )
__Moose.Include( 'Scripts/Moose/Functional/Scoring.lua' )
__Moose.Include( 'Scripts/Moose/Functional/CleanUp.lua' )
__Moose.Include( 'Scripts/Moose/Functional/Movement.lua' )
__Moose.Include( 'Scripts/Moose/Functional/Sead.lua' )
__Moose.Include( 'Scripts/Moose/Functional/Escort.lua' )
__Moose.Include( 'Scripts/Moose/Functional/MissileTrainer.lua' )
__Moose.Include( 'Scripts/Moose/Functional/ATC_Ground.lua' )
__Moose.Include( 'Scripts/Moose/Functional/Detection.lua' )
__Moose.Include( 'Scripts/Moose/Functional/Designate.lua' )
__Moose.Include( 'Scripts/Moose/Functional/RAT.lua' )
__Moose.Include( 'Scripts/Moose/Functional/Range.lua' )
__Moose.Include( 'Scripts/Moose/Functional/ZoneGoal.lua' )
__Moose.Include( 'Scripts/Moose/Functional/ZoneGoalCoalition.lua' )
__Moose.Include( 'Scripts/Moose/Functional/ZoneCaptureCoalition.lua' )
__Moose.Include( 'Scripts/Moose/Functional/Artillery.lua' )
__Moose.Include( 'Scripts/Moose/Functional/Suppression.lua' )
__Moose.Include( 'Scripts/Moose/Functional/PseudoATC.lua' )
__Moose.Include( 'Scripts/Moose/Functional/Warehouse.lua' )
__Moose.Include( 'Scripts/Moose/Ops/Airboss.lua' )
__Moose.Include( 'Scripts/Moose/Ops/RecoveryTanker.lua' )
__Moose.Include( 'Scripts/Moose/Ops/RescueHelo.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_A2A.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_A2A_Patrol.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_A2A_Cap.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_A2A_Gci.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_A2A_Dispatcher.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_A2G.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_A2G_Engage.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_A2G_BAI.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_A2G_CAS.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_A2G_SEAD.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_A2G_Patrol.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_A2G_Dispatcher.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Patrol.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Cap.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Cas.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Bai.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Formation.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_APC.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Helicopter.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Airplane.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher_APC.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua' )
__Moose.Include( 'Scripts/Moose/Actions/Act_Assign.lua' )
__Moose.Include( 'Scripts/Moose/Actions/Act_Route.lua' )
__Moose.Include( 'Scripts/Moose/Actions/Act_Account.lua' )
__Moose.Include( 'Scripts/Moose/Actions/Act_Assist.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/CommandCenter.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/Mission.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/Task.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/TaskInfo.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/Task_Manager.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/DetectionManager.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/Task_A2G_Dispatcher.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/Task_A2G.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/Task_A2A_Dispatcher.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/Task_A2A.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/Task_Cargo.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/Task_Cargo_Transport.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/Task_Cargo_CSAR.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/Task_Cargo_Dispatcher.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/TaskZoneCapture.lua' )
__Moose.Include( 'Scripts/Moose/Globals.lua' )

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -46,6 +46,7 @@ do -- DETECTION MANAGER
--- @type DETECTION_MANAGER --- @type DETECTION_MANAGER
-- @field Core.Set#SET_GROUP SetGroup The groups to which the FAC will report to. -- @field Core.Set#SET_GROUP SetGroup The groups to which the FAC will report to.
-- @field Functional.Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. -- @field Functional.Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects.
-- @field Tasking.CommandCenter#COMMANDCENTER CC The command center that is used to communicate with the players.
-- @extends Core.Fsm#FSM -- @extends Core.Fsm#FSM
--- DETECTION_MANAGER class. --- DETECTION_MANAGER class.
@@ -218,6 +219,33 @@ do -- DETECTION MANAGER
return self._ReportDisplayTime return self._ReportDisplayTime
end end
--- Set a command center to communicate actions to the players reporting to the command center.
-- @param #DETECTION_MANAGER self
-- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter The command center.
-- @return #DETECTION_MANGER self
function DETECTION_MANAGER:SetCommandCenter( CommandCenter )
self.CC = CommandCenter
return self
end
--- Send an information message to the players reporting to the command center.
-- @param #DETECTION_MANAGER self
-- @param #string Message The message to be sent.
-- @return #DETECTION_MANGER self
function DETECTION_MANAGER:MessageToPlayers( Message )
if self.CC then
self.CC:MessageToAll( Message )
end
return self
end
--- Reports the detected items to the @{Core.Set#SET_GROUP}. --- Reports the detected items to the @{Core.Set#SET_GROUP}.
-- @param #DETECTION_MANAGER self -- @param #DETECTION_MANAGER self
-- @param Functional.Detection#DETECTION_BASE Detection -- @param Functional.Detection#DETECTION_BASE Detection

View File

@@ -43,6 +43,81 @@ BIGSMOKEPRESET = {
HugeSmoke=7, HugeSmoke=7,
} }
--- DCS map as returned by env.mission.theatre.
-- @type DCSMAP
-- @field #string Caucasus Caucasus map.
-- @field #string Normandy Normandy map.
-- @field #string NTTR Nevada Test and Training Range map.
-- @field #string PersianGulf Persian Gulf map.
DCSMAP = {
Caucasus="Caucasus",
NTTR="Nevada",
Normandy="Normandy",
PersianGulf="PersianGulf"
}
--- See [DCS_enum_callsigns](https://wiki.hoggitworld.com/view/DCS_enum_callsigns)
-- @type CALLSIGN
-- @field #table Aircraft Aircraft callsigns.
-- @field #table AWACS AWACS callsigns.
-- @field #table Tanker Tanker callsigns.
-- @field #table JTAC JTAC callsigns.
CALLSIGN={
-- Aircraft
Aircraft={
Enfield=1,
Springfield=2,
Uzi=3,
Cold=4,
Dodge=5,
Ford=6,
Chevy=7,
Pontiac=8,
-- A-10A or A-10C
Hawg=9,
Boar=10,
Pig=11,
Tusk=12,
},
-- AWACS
AWACS={
Overloard=1,
Magic=2,
Wizard=3,
Focus=4,
Darkstar=5,
},
-- Tanker
Tanker={
Texaco=1,
Arco=2,
Shell=3,
},
-- JTAC
JTAC={
Axeman=1,
Darknight=2,
Warrier=3,
Pointer=4,
Eyeball=5,
Moonbeam=6,
Whiplash=7,
Finger=8,
Pinpoint=9,
Ferret=10,
Shaba=11,
Playboy=12,
Hammer=13,
Jaguar=14,
Deathstar=15,
Anvil=16,
Firefly=17,
Mantis=18,
Badger=19,
},
} --#CALLSIGN
--- Utilities static class. --- Utilities static class.
-- @type UTILS -- @type UTILS
UTILS = { UTILS = {
@@ -250,7 +325,11 @@ UTILS.FeetToMeters = function(feet)
end end
UTILS.KnotsToKmph = function(knots) UTILS.KnotsToKmph = function(knots)
return knots* 1.852 return knots * 1.852
end
UTILS.KmphToKnots = function(knots)
return knots / 1.852
end end
UTILS.KmphToMps = function( kmph ) UTILS.KmphToMps = function( kmph )
@@ -281,7 +360,26 @@ UTILS.CelciusToFarenheit = function( Celcius )
return Celcius * 9/5 + 32 return Celcius * 9/5 + 32
end end
--- Convert pressure from hecto Pascal (hPa) to inches of mercury (inHg).
-- @param #number hPa Pressure in hPa.
-- @return #number Pressure in inHg.
UTILS.hPa2inHg = function( hPa )
return hPa * 0.0295299830714
end
--- Convert pressure from hecto Pascal (hPa) to millimeters of mercury (mmHg).
-- @param #number hPa Pressure in hPa.
-- @return #number Pressure in mmHg.
UTILS.hPa2mmHg = function( hPa )
return hPa * 0.7500615613030
end
--- Convert kilo gramms (kg) to pounds (lbs).
-- @param #number kg Mass in kg.
-- @return #number Mass in lbs.
UTILS.kg2lbs = function( kg )
return kg * 2.20462
end
--[[acc: --[[acc:
in DM: decimal point of minutes. in DM: decimal point of minutes.
@@ -526,7 +624,7 @@ function UTILS.SecondsToClock(seconds)
-- Seconds of this day. -- Seconds of this day.
local _seconds=seconds%(60*60*24) local _seconds=seconds%(60*60*24)
if seconds <= 0 then if seconds<0 then
return nil return nil
else else
local hours = string.format("%02.f", math.floor(_seconds/3600)) local hours = string.format("%02.f", math.floor(_seconds/3600))
@@ -539,7 +637,7 @@ end
--- Convert clock time from hours, minutes and seconds to seconds. --- Convert clock time from hours, minutes and seconds to seconds.
-- @param #string clock String of clock time. E.g., "06:12:35" or "5:1:30+1". Format is (H)H:(M)M:((S)S)(+D) H=Hours, M=Minutes, S=Seconds, D=Days. -- @param #string clock String of clock time. E.g., "06:12:35" or "5:1:30+1". Format is (H)H:(M)M:((S)S)(+D) H=Hours, M=Minutes, S=Seconds, D=Days.
-- @param #number Seconds. Corresponds to what you cet from timer.getAbsTime() function. -- @return #number Seconds. Corresponds to what you cet from timer.getAbsTime() function.
function UTILS.ClockToSeconds(clock) function UTILS.ClockToSeconds(clock)
-- Nil check. -- Nil check.
@@ -551,7 +649,7 @@ function UTILS.ClockToSeconds(clock)
local seconds=0 local seconds=0
-- Split additional days. -- Split additional days.
local dsplit=UTILS.split(clock, "+") local dsplit=UTILS.Split(clock, "+")
-- Convert days to seconds. -- Convert days to seconds.
if #dsplit>1 then if #dsplit>1 then
@@ -680,3 +778,131 @@ function UTILS.VecCross(a, b)
return {x=a.y*b.z - a.z*b.y, y=a.z*b.x - a.x*b.z, z=a.x*b.y - a.y*b.x} return {x=a.y*b.z - a.z*b.y, y=a.z*b.x - a.x*b.z, z=a.x*b.y - a.y*b.x}
end end
--- Calculate the difference between two 3D vectors by substracting the x,y,z components from each other.
-- @param DCS#Vec3 a Vector in 3D with x, y, z components.
-- @param DCS#Vec3 b Vector in 3D with x, y, z components.
-- @return DCS#Vec3 Vector c=a-b with c(i)=a(i)-b(i), i=x,y,z.
function UTILS.VecSubstract(a, b)
return {x=a.x-b.x, y=a.y-b.y, z=a.z-b.z}
end
--- Calculate the angle between two 3D vectors.
-- @param DCS#Vec3 a Vector in 3D with x, y, z components.
-- @param DCS#Vec3 b Vector in 3D with x, y, z components.
-- @return #number Angle alpha between and b in degrees. alpha=acos(a*b)/(|a||b|), (* denotes the dot product).
function UTILS.VecAngle(a, b)
local alpha=math.acos(UTILS.VecDot(a,b)/(UTILS.VecNorm(a)*UTILS.VecNorm(b)))
return math.deg(alpha)
end
--- Rotate 3D vector in the 2D (x,z) plane. y-component (usually altitude) unchanged.
-- @param DCS#Vec3 a Vector in 3D with x, y, z components.
-- @param #number angle Rotation angle in degrees.
-- @return DCS#Vec3 Vector rotated in the (x,z) plane.
function UTILS.Rotate2D(a, angle)
local phi=math.rad(angle)
local x=a.z
local y=a.x
local Z=x*math.cos(phi)-y*math.sin(phi)
local X=x*math.sin(phi)+y*math.cos(phi)
local Y=a.y
local A={x=X, y=Y, z=Z}
return A
end
--- Converts a TACAN Channel/Mode couple into a frequency in Hz.
-- @param #number TACANChannel The TACAN channel, i.e. the 10 in "10X".
-- @param #string TACANMode The TACAN mode, i.e. the "X" in "10X".
-- @return #number Frequency in Hz or #nil if parameters are invalid.
function UTILS.TACANToFrequency(TACANChannel, TACANMode)
if type(TACANChannel) ~= "number" then
return nil -- error in arguments
end
if TACANMode ~= "X" and TACANMode ~= "Y" then
return nil -- error in arguments
end
-- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137.
-- I have no idea what it does but it seems to work
local A = 1151 -- 'X', channel >= 64
local B = 64 -- channel >= 64
if TACANChannel < 64 then
B = 1
end
if TACANMode == 'Y' then
A = 1025
if TACANChannel < 64 then
A = 1088
end
else -- 'X'
if TACANChannel < 64 then
A = 962
end
end
return (A + TACANChannel - B) * 1000000
end
--- Returns the DCS map/theatre as optained by env.mission.theatre
-- @return #string DCS map name .
function UTILS.GetDCSMap()
return env.mission.theatre
end
--- Returns the magnetic declination of the map.
-- Returned values for the current maps are:
--
-- * Caucasus +6 (East), year ~ 2011
-- * NTTR +12 (East), year ~ 2011
-- * Normandy -10 (West), year ~ 1944
-- * Persian Gulf +2 (East), year ~ 2011
-- @param #string map (Optional) Map for which the declination is returned. Default is from env.mission.theatre
-- @return #number Declination in degrees.
function UTILS.GetMagneticDeclination(map)
-- Map.
map=map or UTILS.GetDCSMap()
local declination=0
if map==DCSMAP.Caucasus then
declination=6
elseif map==DCSMAP.NTTR then
declination=12
elseif map==DCSMAP.Normandy then
declination=-10
elseif map==DCSMAP.PersianGulf then
declination=2
else
declination=0
end
return declination
end
--- Checks if a file exists or not. This requires **io** to be desanitized.
-- @param #string file File that should be checked.
-- @return #boolean True if the file exists, false if the file does not exist or nil if the io module is not available and the check could not be performed.
function UTILS.FileExists(file)
if io then
local f=io.open(file, "r")
if f~=nil then
io.close(f)
return true
else
return false
end
else
return nil
end
end

View File

@@ -219,55 +219,54 @@ AIRBASE.Normandy = {
--- These are all airbases of the Persion Gulf Map: --- These are all airbases of the Persion Gulf Map:
-- --
-- * AIRBASE.PersianGulf.Fujairah_Intl
-- * AIRBASE.PersianGulf.Qeshm_Island
-- * AIRBASE.PersianGulf.Sir_Abu_Nuayr
-- * AIRBASE.PersianGulf.Abu_Musa_Island_Airport -- * AIRBASE.PersianGulf.Abu_Musa_Island_Airport
-- * AIRBASE.PersianGulf.Bandar_Abbas_Intl -- * AIRBASE.PersianGulf.Bandar_Abbas_Intl
-- * AIRBASE.PersianGulf.Bandar_Lengeh -- * AIRBASE.PersianGulf.Bandar_Lengeh
-- * AIRBASE.PersianGulf.Tunb_Island_AFB
-- * AIRBASE.PersianGulf.Havadarya
-- * AIRBASE.PersianGulf.Lar_Airbase
-- * AIRBASE.PersianGulf.Sirri_Island
-- * AIRBASE.PersianGulf.Tunb_Kochak
-- * AIRBASE.PersianGulf.Al_Dhafra_AB -- * AIRBASE.PersianGulf.Al_Dhafra_AB
-- * AIRBASE.PersianGulf.Dubai_Intl -- * AIRBASE.PersianGulf.Dubai_Intl
-- * AIRBASE.PersianGulf.Al_Maktoum_Intl -- * AIRBASE.PersianGulf.Al_Maktoum_Intl
-- * AIRBASE.PersianGulf.Fujairah_Intl
-- * AIRBASE.PersianGulf.Tunb_Island_AFB
-- * AIRBASE.PersianGulf.Havadarya
-- * AIRBASE.PersianGulf.Khasab -- * AIRBASE.PersianGulf.Khasab
-- * AIRBASE.PersianGulf.Lar_Airbase
-- * AIRBASE.PersianGulf.Al_Minhad_AB -- * AIRBASE.PersianGulf.Al_Minhad_AB
-- * AIRBASE.PersianGulf.Qeshm_Island
-- * AIRBASE.PersianGulf.Sharjah_Intl -- * AIRBASE.PersianGulf.Sharjah_Intl
-- * AIRBASE.PersianGulf.Shiraz_International_Airport -- * AIRBASE.PersianGulf.Sirri_Island
-- * AIRBASE.PersianGulf.Tunb_Kochak
-- * AIRBASE.PersianGulf.Sir_Abu_Nuayr
-- * AIRBASE.PersianGulf.Kerman_Airport -- * AIRBASE.PersianGulf.Kerman_Airport
-- * AIRBASE.PersianGulf.Shiraz_International_Airport
-- * AIRBASE.PersianGulf.Sas_Al_Nakheel_Airport -- * AIRBASE.PersianGulf.Sas_Al_Nakheel_Airport
-- * AIRBASE.PersianGulf.Bandar_e_Jask_airfield -- * AIRBASE.PersianGulf.Bandar-e-Jask_airfield
-- * AIRBASE.PersianGulf.Abu_Dhabi_International_Airport -- * AIRBASE.PersianGulf.Abu_Dhabi_International_Airport
-- * AIRBASE.PersianGulf.Al_Bateen_Airport -- * AIRBASE.PersianGulf.Al-Bateen_Airport
-- * AIRBASE.PersianGulf.Kish_International_Airport -- * AIRBASE.PersianGulf.Kish_International_Airport
-- * AIRBASE.PersianGulf.Al_Ain_International_Airport -- * AIRBASE.PersianGulf.Al_Ain_International_Airport
-- * AIRBASE.PersianGulf.Lavan_Island_Airport -- * AIRBASE.PersianGulf.Lavan_Island_Airport
-- * AIRBASE.PersianGulf.Jiroft_Airport -- * AIRBASE.PersianGulf.Jiroft_Airport
--
-- @field PersianGulf -- @field PersianGulf
AIRBASE.PersianGulf = { AIRBASE.PersianGulf = {
["Fujairah_Intl"] = "Fujairah Intl",
["Qeshm_Island"] = "Qeshm Island",
["Sir_Abu_Nuayr"] = "Sir Abu Nuayr",
["Abu_Musa_Island_Airport"] = "Abu Musa Island Airport", ["Abu_Musa_Island_Airport"] = "Abu Musa Island Airport",
["Bandar_Abbas_Intl"] = "Bandar Abbas Intl", ["Bandar_Abbas_Intl"] = "Bandar Abbas Intl",
["Bandar_Lengeh"] = "Bandar Lengeh", ["Bandar_Lengeh"] = "Bandar Lengeh",
["Al_Dhafra_AB"] = "Al Dhafra AB",
["Dubai_Intl"] = "Dubai Intl",
["Al_Maktoum_Intl"] = "Al Maktoum Intl",
["Fujairah_Intl"] = "Fujairah Intl",
["Tunb_Island_AFB"] = "Tunb Island AFB", ["Tunb_Island_AFB"] = "Tunb Island AFB",
["Havadarya"] = "Havadarya", ["Havadarya"] = "Havadarya",
["Khasab"] = "Khasab",
["Lar_Airbase"] = "Lar Airbase", ["Lar_Airbase"] = "Lar Airbase",
["Al_Minhad_AB"] = "Al Minhad AB",
["Qeshm_Island"] = "Qeshm Island",
["Sharjah_Intl"] = "Sharjah Intl",
["Sirri_Island"] = "Sirri Island", ["Sirri_Island"] = "Sirri Island",
["Tunb_Kochak"] = "Tunb Kochak", ["Tunb_Kochak"] = "Tunb Kochak",
["Al_Dhafra_AB"] = "Al Dhafra AB", ["Sir_Abu_Nuayr"] = "Sir Abu Nuayr",
["Dubai_Intl"] = "Dubai Intl",
["Al_Maktoum_Intl"] = "Al Maktoum Intl",
["Khasab"] = "Khasab",
["Al_Minhad_AB"] = "Al Minhad AB",
["Sharjah_Intl"] = "Sharjah Intl",
["Shiraz_International_Airport"] = "Shiraz International Airport",
["Kerman_Airport"] = "Kerman Airport", ["Kerman_Airport"] = "Kerman Airport",
["Shiraz_International_Airport"] = "Shiraz International Airport",
["Sas_Al_Nakheel_Airport"] = "Sas Al Nakheel Airport", ["Sas_Al_Nakheel_Airport"] = "Sas Al Nakheel Airport",
["Bandar_e_Jask_airfield"] = "Bandar-e-Jask airfield", ["Bandar_e_Jask_airfield"] = "Bandar-e-Jask airfield",
["Abu_Dhabi_International_Airport"] = "Abu Dhabi International Airport", ["Abu_Dhabi_International_Airport"] = "Abu Dhabi International Airport",
@@ -649,31 +648,20 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
verysafe=false verysafe=false
end end
-- Get the size of an object.
local function _GetObjectSize(unit,mooseobject)
if mooseobject then
unit=unit:GetDCSObject()
end
if unit and unit:isExist() then
local DCSdesc=unit:getDesc()
if DCSdesc.box then
local x=DCSdesc.box.max.x+math.abs(DCSdesc.box.min.x)
local y=DCSdesc.box.max.y+math.abs(DCSdesc.box.min.y) --height
local z=DCSdesc.box.max.z+math.abs(DCSdesc.box.min.z)
return math.max(x,z), x , y, z
end
end
return 0,0,0,0
end
-- Function calculating the overlap of two (square) objects. -- Function calculating the overlap of two (square) objects.
local function _overlap(object1, mooseobject1, object2, mooseobject2, dist) local function _overlap(object1, object2, dist)
local l1=_GetObjectSize(object1, mooseobject1) local pos1=object1 --Wrapper.Positionable#POSITIONABLE
local l2=_GetObjectSize(object2, mooseobject2) local pos2=object2 --Wrapper.Positionable#POSITIONABLE
local safedist=(l1/2+l2/2)*1.1 local r1=pos1:GetBoundingRadius()
local safe = (dist > safedist) local r2=pos2:GetBoundingRadius()
self:T3(string.format("l1=%.1f l2=%.1f s=%.1f d=%.1f ==> safe=%s", l1,l2,safedist,dist,tostring(safe))) if r1 and r2 then
return safe local safedist=(r1+r2)*1.1
local safe = (dist > safedist)
self:T2(string.format("r1=%.1f r2=%.1f s=%.1f d=%.1f ==> safe=%s", r1, r2, safedist, dist, tostring(safe)))
return safe
else
return true
end
end end
-- Get airport name. -- Get airport name.
@@ -688,7 +676,7 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
-- Get the aircraft size, i.e. it's longest side of x,z. -- Get the aircraft size, i.e. it's longest side of x,z.
local aircraft=group:GetUnit(1) local aircraft=group:GetUnit(1)
local _aircraftsize, ax,ay,az=_GetObjectSize(aircraft, true) local _aircraftsize, ax,ay,az=aircraft:GetObjectSize()
-- Number of spots we are looking for. Note that, e.g. grouping can require a number different from the group size! -- Number of spots we are looking for. Note that, e.g. grouping can require a number different from the group size!
local _nspots=nspots or group:GetSize() local _nspots=nspots or group:GetSize()
@@ -722,7 +710,7 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
if verysafe and (parkingspot.Free==false or parkingspot.TOAC==true) then if verysafe and (parkingspot.Free==false or parkingspot.TOAC==true) then
-- DCS getParking() routine returned that spot is not free. -- DCS getParking() routine returned that spot is not free.
self:E(string.format("%s: Parking spot id %d NOT free (or aircraft has not taken off yet). Free=%s, TOAC=%s.", airport, parkingspot.TerminalID, tostring(parkingspot.Free), tostring(parkingspot.TOAC))) self:T(string.format("%s: Parking spot id %d NOT free (or aircraft has not taken off yet). Free=%s, TOAC=%s.", airport, parkingspot.TerminalID, tostring(parkingspot.Free), tostring(parkingspot.TOAC)))
else else
@@ -734,16 +722,13 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
-- Check all units. -- Check all units.
for _,unit in pairs(_units) do for _,unit in pairs(_units) do
-- Unis are now returned as MOOSE units not DCS units!
--local _vec3=unit:getPoint()
--local _coord=COORDINATE:NewFromVec3(_vec3)
local _coord=unit:GetCoordinate() local _coord=unit:GetCoordinate()
local _dist=_coord:Get2DDistance(_spot) local _dist=_coord:Get2DDistance(_spot)
local _safe=_overlap(aircraft, true, unit, true,_dist) local _safe=_overlap(aircraft, unit, _dist)
if markobstacles then if markobstacles then
local l,x,y,z=_GetObjectSize(unit) local l,x,y,z=unit:GetObjectSize()
_coord:MarkToAll(string.format("Unit %s\nx=%.1f y=%.1f z=%.1f\nl=%.1f d=%.1f\nspot %d safe=%s", unit:getName(),x,y,z,l,_dist, _termid, tostring(_safe))) _coord:MarkToAll(string.format("Unit %s\nx=%.1f y=%.1f z=%.1f\nl=%.1f d=%.1f\nspot %d safe=%s", unit:GetName(),x,y,z,l,_dist, _termid, tostring(_safe)))
end end
if scanunits and not _safe then if scanunits and not _safe then
@@ -753,13 +738,14 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
-- Check all statics. -- Check all statics.
for _,static in pairs(_statics) do for _,static in pairs(_statics) do
local _static=STATIC:Find(static)
local _vec3=static:getPoint() local _vec3=static:getPoint()
local _coord=COORDINATE:NewFromVec3(_vec3) local _coord=COORDINATE:NewFromVec3(_vec3)
local _dist=_coord:Get2DDistance(_spot) local _dist=_coord:Get2DDistance(_spot)
local _safe=_overlap(aircraft, true, static, false,_dist) local _safe=_overlap(aircraft,_static,_dist)
if markobstacles then if markobstacles then
local l,x,y,z=_GetObjectSize(static) local l,x,y,z=_static:GetObjectSize()
_coord:MarkToAll(string.format("Static %s\nx=%.1f y=%.1f z=%.1f\nl=%.1f d=%.1f\nspot %d safe=%s", static:getName(),x,y,z,l,_dist, _termid, tostring(_safe))) _coord:MarkToAll(string.format("Static %s\nx=%.1f y=%.1f z=%.1f\nl=%.1f d=%.1f\nspot %d safe=%s", static:getName(),x,y,z,l,_dist, _termid, tostring(_safe)))
end end
@@ -770,13 +756,14 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
-- Check all scenery. -- Check all scenery.
for _,scenery in pairs(_sceneries) do for _,scenery in pairs(_sceneries) do
local _scenery=SCENERY:Register(scenery:getTypeName(), scenery)
local _vec3=scenery:getPoint() local _vec3=scenery:getPoint()
local _coord=COORDINATE:NewFromVec3(_vec3) local _coord=COORDINATE:NewFromVec3(_vec3)
local _dist=_coord:Get2DDistance(_spot) local _dist=_coord:Get2DDistance(_spot)
local _safe=_overlap(aircraft, true, scenery, false,_dist) local _safe=_overlap(aircraft,_scenery,_dist)
if markobstacles then if markobstacles then
local l,x,y,z=_GetObjectSize(scenery) local l,x,y,z=scenery:GetObjectSize(scenery)
_coord:MarkToAll(string.format("Scenery %s\nx=%.1f y=%.1f z=%.1f\nl=%.1f d=%.1f\nspot %d safe=%s", scenery:getTypeName(),x,y,z,l,_dist, _termid, tostring(_safe))) _coord:MarkToAll(string.format("Scenery %s\nx=%.1f y=%.1f z=%.1f\nl=%.1f d=%.1f\nspot %d safe=%s", scenery:getTypeName(),x,y,z,l,_dist, _termid, tostring(_safe)))
end end
@@ -788,7 +775,7 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
-- Now check the already given spots so that we do not put a large aircraft next to one we already assigned a nearby spot. -- Now check the already given spots so that we do not put a large aircraft next to one we already assigned a nearby spot.
for _,_takenspot in pairs(validspots) do for _,_takenspot in pairs(validspots) do
local _dist=_takenspot.Coordinate:Get2DDistance(_spot) local _dist=_takenspot.Coordinate:Get2DDistance(_spot)
local _safe=_overlap(aircraft, true, aircraft, true,_dist) local _safe=_overlap(aircraft, aircraft, _dist)
if not _safe then if not _safe then
occupied=true occupied=true
end end
@@ -798,11 +785,12 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
if occupied then if occupied then
self:T(string.format("%s: Parking spot id %d occupied.", airport, _termid)) self:T(string.format("%s: Parking spot id %d occupied.", airport, _termid))
else else
self:E(string.format("%s: Parking spot id %d free.", airport, _termid)) self:I(string.format("%s: Parking spot id %d free.", airport, _termid))
if nvalid<_nspots then if nvalid<_nspots then
table.insert(validspots, {Coordinate=_spot, TerminalID=_termid}) table.insert(validspots, {Coordinate=_spot, TerminalID=_termid})
end end
nvalid=nvalid+1 nvalid=nvalid+1
self:I(string.format("%s: Parking spot id %d free. Nfree=%d/%d.", airport, _termid, nvalid,_nspots))
end end
end -- loop over units end -- loop over units

View File

@@ -148,7 +148,7 @@
-- * @{#CONTROLLABLE.OptionROEReturnFirePossible} -- * @{#CONTROLLABLE.OptionROEReturnFirePossible}
-- * @{#CONTROLLABLE.OptionROEEvadeFirePossible} -- * @{#CONTROLLABLE.OptionROEEvadeFirePossible}
-- --
-- ## 5.2) Rule on thread: -- ## 5.2) Reaction On Thread:
-- --
-- * @{#CONTROLLABLE.OptionROTNoReaction} -- * @{#CONTROLLABLE.OptionROTNoReaction}
-- * @{#CONTROLLABLE.OptionROTPassiveDefense} -- * @{#CONTROLLABLE.OptionROTPassiveDefense}
@@ -168,6 +168,11 @@
-- * @{#CONTROLLABLE.OptionAlarmStateGreen} -- * @{#CONTROLLABLE.OptionAlarmStateGreen}
-- * @{#CONTROLLABLE.OptionAlarmStateRed} -- * @{#CONTROLLABLE.OptionAlarmStateRed}
-- --
-- ## 5.4) Jettison weapons:
--
-- * @{#CONTROLLABLE.OptionAllowJettisonWeaponsOnThreat}
-- * @{#CONTROLLABLE.OptionKeepWeaponsOnThreat}
--
-- @field #CONTROLLABLE -- @field #CONTROLLABLE
CONTROLLABLE = { CONTROLLABLE = {
ClassName = "CONTROLLABLE", ClassName = "CONTROLLABLE",
@@ -302,7 +307,7 @@ end
-- @param #CONTROLLABLE self -- @param #CONTROLLABLE self
-- @return #CONTROLLABLE -- @return #CONTROLLABLE
function CONTROLLABLE:ClearTasks() function CONTROLLABLE:ClearTasks()
self:F2() self:E( "ClearTasks" )
local DCSControllable = self:GetDCSObject() local DCSControllable = self:GetDCSObject()
@@ -342,16 +347,26 @@ function CONTROLLABLE:PushTask( DCSTask, WaitTime )
local DCSControllable = self:GetDCSObject() local DCSControllable = self:GetDCSObject()
if DCSControllable then if DCSControllable then
local Controller = self:_GetController()
local DCSControllableName = self:GetName()
-- When a controllable SPAWNs, it takes about a second to get the controllable in the simulator. Setting tasks to unspawned controllables provides unexpected results. -- When a controllable SPAWNs, it takes about a second to get the controllable in the simulator. Setting tasks to unspawned controllables provides unexpected results.
-- Therefore we schedule the functions to set the mission and options for the Controllable. -- Therefore we schedule the functions to set the mission and options for the Controllable.
-- Controller:pushTask( DCSTask ) -- Controller:pushTask( DCSTask )
local function PushTask( Controller, DCSTask )
if self and self:IsAlive() then
local Controller = self:_GetController()
Controller:pushTask( DCSTask )
else
BASE:E( { DCSControllableName .. " is not alive anymore.", DCSTask = DCSTask } )
end
end
if WaitTime then if not WaitTime or WaitTime == 0 then
self.TaskScheduler:Schedule( Controller, Controller.pushTask, { DCSTask }, WaitTime ) PushTask( self, DCSTask )
else else
Controller:pushTask( DCSTask ) self.TaskScheduler:Schedule( self, PushTask, { DCSTask }, WaitTime )
end end
return self return self
@@ -362,17 +377,19 @@ end
--- Clearing the Task Queue and Setting the Task on the queue from the controllable. --- Clearing the Task Queue and Setting the Task on the queue from the controllable.
-- @param #CONTROLLABLE self -- @param #CONTROLLABLE self
-- @param #DCS.Task DCSTask DCS Task array. -- @param DCS#Task DCSTask DCS Task array.
-- @param #number WaitTime Time in seconds, before the task is set. -- @param #number WaitTime Time in seconds, before the task is set.
-- @return Wrapper.Controllable#CONTROLLABLE self -- @return Wrapper.Controllable#CONTROLLABLE self
function CONTROLLABLE:SetTask( DCSTask, WaitTime ) function CONTROLLABLE:SetTask( DCSTask, WaitTime )
self:F2( { DCSTask = DCSTask } ) self:F( { "SetTask", WaitTime, DCSTask = DCSTask } )
local DCSControllable = self:GetDCSObject() local DCSControllable = self:GetDCSObject()
if DCSControllable then if DCSControllable then
local DCSControllableName = self:GetName() local DCSControllableName = self:GetName()
self:T2( "Controllable Name = " .. DCSControllableName )
-- When a controllable SPAWNs, it takes about a second to get the controllable in the simulator. Setting tasks to unspawned controllables provides unexpected results. -- When a controllable SPAWNs, it takes about a second to get the controllable in the simulator. Setting tasks to unspawned controllables provides unexpected results.
-- Therefore we schedule the functions to set the mission and options for the Controllable. -- Therefore we schedule the functions to set the mission and options for the Controllable.
@@ -383,7 +400,8 @@ function CONTROLLABLE:SetTask( DCSTask, WaitTime )
local Controller = self:_GetController() local Controller = self:_GetController()
--self:I( "Before SetTask" ) --self:I( "Before SetTask" )
Controller:setTask( DCSTask ) Controller:setTask( DCSTask )
--self:I( "After SetTask" ) -- AI_FORMATION class (used by RESCUEHELO) calls SetTask twice per second! hence spamming the DCS log file ==> setting this to trace.
self:T( { ControllableName = self:GetName(), DCSTask = DCSTask } )
else else
BASE:E( { DCSControllableName .. " is not alive anymore.", DCSTask = DCSTask } ) BASE:E( { DCSControllableName .. " is not alive anymore.", DCSTask = DCSTask } )
end end
@@ -391,6 +409,8 @@ function CONTROLLABLE:SetTask( DCSTask, WaitTime )
if not WaitTime or WaitTime == 0 then if not WaitTime or WaitTime == 0 then
SetTask( self, DCSTask ) SetTask( self, DCSTask )
-- See above.
self:T( { ControllableName = self:GetName(), DCSTask = DCSTask } )
else else
self.TaskScheduler:Schedule( self, SetTask, { DCSTask }, WaitTime ) self.TaskScheduler:Schedule( self, SetTask, { DCSTask }, WaitTime )
end end
@@ -540,9 +560,9 @@ end
--- Executes a command action --- Executes a command action for the CONTROLLABLE.
-- @param #CONTROLLABLE self -- @param #CONTROLLABLE self
-- @param DCS#Command DCSCommand -- @param DCS#Command DCSCommand The command to be executed.
-- @return #CONTROLLABLE self -- @return #CONTROLLABLE self
function CONTROLLABLE:SetCommand( DCSCommand ) function CONTROLLABLE:SetCommand( DCSCommand )
self:F2( DCSCommand ) self:F2( DCSCommand )
@@ -630,9 +650,143 @@ function CONTROLLABLE:StartUncontrolled(delay)
return self return self
end end
--- Give the CONTROLLABLE the command to activate a beacon. See [DCS_command_activateBeacon](https://wiki.hoggitworld.com/view/DCS_command_activateBeacon) on Hoggit.
-- For specific beacons like TACAN use the more convenient @{#BEACON} class.
-- Note that a controllable can only have one beacon activated at a time with the execption of ICLS.
-- @param #CONTROLLABLE self
-- @param Core.Radio#BEACON.Type Type Beacon type (VOR, DME, TACAN, RSBN, ILS etc).
-- @param Core.Radio#BEACON.System System Beacon system (VOR, DME, TACAN, RSBN, ILS etc).
-- @param #number Frequency Frequency in Hz the beacon is running on. Use @{#UTILS.TACANToFrequency} to generate a frequency for TACAN beacons.
-- @param #number UnitID The ID of the unit the beacon is attached to. Usefull if more units are in one group.
-- @param #number Channel Channel the beacon is using. For, e.g. TACAN beacons.
-- @param #string ModeChannel The TACAN mode of the beacon, i.e. "X" or "Y".
-- @param #boolean AA If true, create and Air-Air beacon. IF nil, automatically set if CONTROLLABLE depending on whether unit is and aircraft or not.
-- @param #string Callsign Morse code identification callsign.
-- @param #boolean Bearing If true, beacon provides bearing information - if supported by the unit the beacon is attached to.
-- @param #number Delay (Optional) Delay in seconds before the beacon is activated.
-- @return #CONTROLLABLE self
function CONTROLLABLE:CommandActivateBeacon(Type, System, Frequency, UnitID, Channel, ModeChannel, AA, Callsign, Bearing, Delay)
AA=AA or self:IsAir()
UnitID=UnitID or self:GetID()
-- Command
local CommandActivateBeacon= {
id = "ActivateBeacon",
params = {
["type"] = Type,
["system"] = System,
["frequency"] = Frequency,
["unitId"] = UnitID,
["channel"] = Channel,
["modeChannel"] = ModeChannel,
["AA"] = AA,
["callsign"] = Callsign,
["bearing"] = Bearing,
}
}
if Delay and Delay>0 then
SCHEDULER:New(nil, self.CommandActivateBeacon, {self, Type, System, Frequency, UnitID, Channel, ModeChannel, AA, Callsign, Bearing}, Delay)
else
self:SetCommand(CommandActivateBeacon)
end
return self
end
--- Activate ICLS system of the CONTROLLABLE. The controllable should be an aircraft carrier!
-- @param #CONTROLLABLE self
-- @param #number Channel ICLS channel.
-- @param #number UnitID The ID of the unit the ICLS system is attached to. Useful if more units are in one group.
-- @param #string Callsign Morse code identification callsign.
-- @param #number Delay (Optional) Delay in seconds before the ICLS is deactivated.
-- @return #CONTROLLABLE self
function CONTROLLABLE:CommandActivateICLS(Channel, UnitID, Callsign, Delay)
self:F()
-- Command to activate ICLS system.
local CommandActivateICLS= {
id = "ActivateICLS",
params= {
["type"] = BEACON.Type.ICLS,
["channel"] = Channel,
["unitId"] = UnitID,
["callsign"] = Callsign,
}
}
if Delay and Delay>0 then
SCHEDULER:New(nil, self.CommandActivateICLS, {self}, Delay)
else
self:SetCommand(CommandActivateICLS)
end
return self
end
--- Deactivate the active beacon of the CONTROLLABLE.
-- @param #CONTROLLABLE self
-- @param #number Delay (Optional) Delay in seconds before the beacon is deactivated.
-- @return #CONTROLLABLE self
function CONTROLLABLE:CommandDeactivateBeacon(Delay)
self:F()
-- Command to deactivate
local CommandDeactivateBeacon={id='DeactivateBeacon', params={}}
if Delay and Delay>0 then
SCHEDULER:New(nil, self.CommandActivateBeacon, {self}, Delay)
else
self:SetCommand(CommandDeactivateBeacon)
end
return self
end
--- Deactivate the ICLS of the CONTROLLABLE.
-- @param #CONTROLLABLE self
-- @param #number Delay (Optional) Delay in seconds before the ICLS is deactivated.
-- @return #CONTROLLABLE self
function CONTROLLABLE:CommandDeactivateICLS(Delay)
self:F()
-- Command to deactivate
local CommandDeactivateICLS={id='DeactivateICLS', params={}}
if Delay and Delay>0 then
SCHEDULER:New(nil, self.CommandDeactivateICLS, {self}, Delay)
else
self:SetCommand(CommandDeactivateICLS)
end
return self
end
--- Set callsign of the CONTROLLABLE. See [DCS_command_setCallsign](https://wiki.hoggitworld.com/view/DCS_command_setCallsign)
-- @param #CONTROLLABLE self
-- @param DCS#CALLSIGN CallName Number corresponding the the callsign identifier you wish this group to be called.
-- @param #number CallNumber The number value the group will be referred to as. Only valid numbers are 1-9. For example Uzi **5**-1. Default 1.
-- @param #number Delay (Optional) Delay in seconds before the callsign is set. Default is immediately.
-- @return #CONTROLLABLE self
function CONTROLLABLE:CommandSetCallsign(CallName, CallNumber, Delay)
self:F()
-- Command to set the callsign.
local CommandSetCallsign={id='SetCallsign', params={callname=CallName, callnumber=CallNumber or 1}}
if Delay and Delay>0 then
SCHEDULER:New(nil, self.CommandSetCallsign, {self, CallName, CallNumber}, Delay)
else
self:SetCommand(CommandSetCallsign)
end
return self
end
-- TASKS FOR AIR CONTROLLABLES -- TASKS FOR AIR CONTROLLABLES
--- (AIR) Attack a Controllable. --- (AIR) Attack a Controllable.
-- @param #CONTROLLABLE self -- @param #CONTROLLABLE self
-- @param Wrapper.Controllable#CONTROLLABLE AttackGroup The Controllable to be attacked. -- @param Wrapper.Controllable#CONTROLLABLE AttackGroup The Controllable to be attacked.
@@ -870,11 +1024,43 @@ function CONTROLLABLE:TaskOrbitCircleAtVec2( Point, Altitude, Speed )
return DCSTask return DCSTask
end end
--- (AIR) Orbit at a position with at a given altitude and speed. Optionally, a race track pattern can be specified.
-- @param #CONTROLLABLE self
-- @param Core.Point#COORDINATE Coord Coordinate at which the CONTROLLABLE orbits.
-- @param #number Altitude Altitude in meters of the orbit pattern.
-- @param #number Speed Speed [m/s] flying the orbit pattern
-- @param Core.Point#COORDINATE CoordRaceTrack (Optional) If this coordinate is specified, the CONTROLLABLE will fly a race-track pattern using this and the initial coordinate.
-- @return #CONTROLLABLE self
function CONTROLLABLE:TaskOrbit(Coord, Altitude, Speed, CoordRaceTrack)
local Pattern=AI.Task.OrbitPattern.CIRCLE
local P1=Coord:GetVec2()
local P2=nil
if CoordRaceTrack then
Pattern=AI.Task.OrbitPattern.RACE_TRACK
P2=CoordRaceTrack:GetVec2()
end
local Task = {
id = 'Orbit',
params = {
pattern = Pattern,
point = P1,
point2 = P2,
speed = Speed,
altitude = Altitude,
}
}
return Task
end
--- (AIR) Orbit at the current position of the first unit of the controllable at a specified alititude. --- (AIR) Orbit at the current position of the first unit of the controllable at a specified alititude.
-- @param #CONTROLLABLE self -- @param #CONTROLLABLE self
-- @param #number Altitude The altitude [m] to hold the position. -- @param #number Altitude The altitude [m] to hold the position.
-- @param #number Speed The speed [m/s] flying when holding the position. -- @param #number Speed The speed [m/s] flying when holding the position.
-- @param Core.Point#COORDINATE Coordinate The coordinate where to orbit. -- @param Core.Point#COORDINATE Coordinate (optional) The coordinate where to orbit. If the coordinate is not given, then the current position of the controllable is used.
-- @return #CONTROLLABLE self -- @return #CONTROLLABLE self
function CONTROLLABLE:TaskOrbitCircle( Altitude, Speed, Coordinate ) function CONTROLLABLE:TaskOrbitCircle( Altitude, Speed, Coordinate )
self:F2( { self.ControllableName, Altitude, Speed } ) self:F2( { self.ControllableName, Altitude, Speed } )
@@ -906,17 +1092,27 @@ end
--- (AIR) Delivering weapon on the runway. --- (AIR) Delivering weapon on the runway. See [hoggit](https://wiki.hoggitworld.com/view/DCS_task_bombingRunway)
--
-- Make sure the aircraft has the following role:
--
-- * CAS
-- * Ground Attack
-- * Runway Attack
-- * Anti-Ship Strike
-- * AFAC
-- * Pinpoint Strike
--
-- @param #CONTROLLABLE self -- @param #CONTROLLABLE self
-- @param Wrapper.Airbase#AIRBASE Airbase Airbase to attack. -- @param Wrapper.Airbase#AIRBASE Airbase Airbase to attack.
-- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. -- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. See [DCS enum weapon flag](https://wiki.hoggitworld.com/view/DCS_enum_weapon_flag). Default 2147485694 = AnyBomb (GuidedBomb + AnyUnguidedBomb).
-- @param DCS#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. -- @param DCS#AI.Task.WeaponExpend WeaponExpend Enum AI.Task.WeaponExpend that defines how much munitions the AI will expend per attack run. Default "ALL".
-- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. -- @param #number AttackQty Number of times the group will attack if the target. Default 1.
-- @param DCS#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. -- @param DCS#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction.
-- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. -- @param #boolean GroupAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a group and not to a single aircraft.
-- @return DCS#Task The DCS task structure. -- @return DCS#Task The DCS task structure.
function CONTROLLABLE:TaskBombingRunway( Airbase, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) function CONTROLLABLE:TaskBombingRunway(Airbase, WeaponType, WeaponExpend, AttackQty, Direction, GroupAttack)
self:F2( { self.ControllableName, Airbase, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) self:F2( { self.ControllableName, Airbase, WeaponType, WeaponExpend, AttackQty, Direction, GroupAttack } )
-- BombingRunway = { -- BombingRunway = {
-- id = 'BombingRunway', -- id = 'BombingRunway',
@@ -926,19 +1122,24 @@ function CONTROLLABLE:TaskBombingRunway( Airbase, WeaponType, WeaponExpend, Atta
-- expend = enum AI.Task.WeaponExpend, -- expend = enum AI.Task.WeaponExpend,
-- attackQty = number, -- attackQty = number,
-- direction = Azimuth, -- direction = Azimuth,
-- controllableAttack = boolean, -- groupAttack = boolean,
-- } -- }
-- } -- }
-- Defaults.
WeaponType=WeaponType or 2147485694
WeaponExpend=WeaponExpend or AI.Task.WeaponExpend.ALL
AttackQty=AttackQty or 1
local DCSTask local DCSTask
DCSTask = { id = 'BombingRunway', DCSTask = { id = 'BombingRunway',
params = { params = {
point = Airbase:GetID(), runwayId = Airbase:GetID(),
weaponType = WeaponType, weaponType = WeaponType,
expend = WeaponExpend, expend = WeaponExpend,
attackQty = AttackQty, attackQty = AttackQty,
direction = Direction, direction = Direction,
controllableAttack = ControllableAttack, groupAttack = GroupAttack,
}, },
}, },
@@ -958,11 +1159,7 @@ function CONTROLLABLE:TaskRefueling()
-- params = {} -- params = {}
-- } -- }
local DCSTask local DCSTask={id='Refueling', params={}}
DCSTask = { id = 'Refueling',
params = {
},
},
self:T3( { DCSTask } ) self:T3( { DCSTask } )
return DCSTask return DCSTask
@@ -1659,6 +1856,7 @@ function CONTROLLABLE:TaskFunction( FunctionString, ... )
local DCSScript = {} local DCSScript = {}
DCSScript[#DCSScript+1] = "local MissionControllable = GROUP:Find( ... ) " DCSScript[#DCSScript+1] = "local MissionControllable = GROUP:Find( ... ) "
DCSScript[#DCSScript+1] = "env.info( 'TaskFunction: ' .. ( MissionControllable and MissionControllable:GetName() ) or 'No Group' )"
if arg and arg.n > 0 then if arg and arg.n > 0 then
local ArgumentKey = '_' .. tostring( arg ):match("table: (.*)") local ArgumentKey = '_' .. tostring( arg ):match("table: (.*)")
@@ -2101,7 +2299,7 @@ do -- Route methods
FromCoordinate = FromCoordinate or self:GetCoordinate() FromCoordinate = FromCoordinate or self:GetCoordinate()
-- Get path and path length on road including the end points (From and To). -- Get path and path length on road including the end points (From and To).
local PathOnRoad, LengthOnRoad=FromCoordinate:GetPathOnRoad(ToCoordinate, true) local PathOnRoad, LengthOnRoad, GotPath =FromCoordinate:GetPathOnRoad(ToCoordinate, true)
-- Get the length only(!) on the road. -- Get the length only(!) on the road.
local _,LengthRoad=FromCoordinate:GetPathOnRoad(ToCoordinate, false) local _,LengthRoad=FromCoordinate:GetPathOnRoad(ToCoordinate, false)
@@ -2113,7 +2311,7 @@ do -- Route methods
-- Calculate the direct distance between the initial and final points. -- Calculate the direct distance between the initial and final points.
local LengthDirect=FromCoordinate:Get2DDistance(ToCoordinate) local LengthDirect=FromCoordinate:Get2DDistance(ToCoordinate)
if PathOnRoad then if GotPath then
-- Off road part of the rout: Total=OffRoad+OnRoad. -- Off road part of the rout: Total=OffRoad+OnRoad.
LengthOffRoad=LengthOnRoad-LengthRoad LengthOffRoad=LengthOnRoad-LengthRoad
@@ -2136,7 +2334,7 @@ do -- Route methods
local canroad=false local canroad=false
-- Check if a valid path on road could be found. -- Check if a valid path on road could be found.
if PathOnRoad and LengthDirect > 2000 then -- if the length of the movement is less than 1 km, drive directly. if GotPath and LengthDirect > 2000 then -- if the length of the movement is less than 1 km, drive directly.
-- Check whether the road is very long compared to direct path. -- Check whether the road is very long compared to direct path.
if LongRoad and Shortcut then if LongRoad and Shortcut then
@@ -2922,7 +3120,7 @@ function CONTROLLABLE:OptionRTBAmmo( WeaponsFlag )
local Controller = self:_GetController() local Controller = self:_GetController()
if self:IsAir() then if self:IsAir() then
Controller:setOption( AI.Option.GROUND.id.RTB_ON_OUT_OF_AMMO, WeaponsFlag ) Controller:setOption( AI.Option.Air.id.RTB_ON_OUT_OF_AMMO, WeaponsFlag )
end end
return self return self
@@ -2932,6 +3130,47 @@ function CONTROLLABLE:OptionRTBAmmo( WeaponsFlag )
end end
--- Allow to Jettison of weapons upon threat.
-- @param #CONTROLLABLE self
-- @return #CONTROLLABLE self
function CONTROLLABLE:OptionAllowJettisonWeaponsOnThreat()
self:F2( { self.ControllableName } )
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local Controller = self:_GetController()
if self:IsAir() then
Controller:setOption( AI.Option.Air.id.PROHIBIT_JETT, false )
end
return self
end
return nil
end
--- Keep weapons upon threat.
-- @param #CONTROLLABLE self
-- @return #CONTROLLABLE self
function CONTROLLABLE:OptionKeepWeaponsOnThreat()
self:F2( { self.ControllableName } )
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local Controller = self:_GetController()
if self:IsAir() then
Controller:setOption( AI.Option.Air.id.PROHIBIT_JETT, true )
end
return self
end
return nil
end
@@ -3024,6 +3263,3 @@ function CONTROLLABLE:IsAirPlane()
return nil return nil
end end
-- Message APIs

View File

@@ -325,7 +325,7 @@ end
-- So all event listeners will catch the destroy event of this group for each unit in the group. -- So all event listeners will catch the destroy event of this group for each unit in the group.
-- To raise these events, provide the `GenerateEvent` parameter. -- To raise these events, provide the `GenerateEvent` parameter.
-- @param #GROUP self -- @param #GROUP self
-- @param #boolean GenerateEvent true if you want to generate a crash or dead event for each unit. -- @param #boolean GenerateEvent If true, a crash or dead event for each unit is generated. If false, if no event is triggered. If nil, a RemoveUnit event is triggered.
-- @usage -- @usage
-- -- Air unit example: destroy the Helicopter and generate a S_EVENT_CRASH for each unit in the Helicopter group. -- -- Air unit example: destroy the Helicopter and generate a S_EVENT_CRASH for each unit in the Helicopter group.
-- Helicopter = GROUP:FindByName( "Helicopter" ) -- Helicopter = GROUP:FindByName( "Helicopter" )
@@ -934,7 +934,7 @@ end
-- @return #number The fuel state of the unit with the least amount of fuel -- @return #number The fuel state of the unit with the least amount of fuel
-- @return #Unit reference to #Unit object for further processing -- @return #Unit reference to #Unit object for further processing
function GROUP:GetFuelMin() function GROUP:GetFuelMin()
self:F(self.ControllableName) self:F3(self.ControllableName)
if not self:GetDCSObject() then if not self:GetDCSObject() then
BASE:E( { "Cannot GetFuel", Group = self, Alive = self:IsAlive() } ) BASE:E( { "Cannot GetFuel", Group = self, Alive = self:IsAlive() } )
@@ -1455,6 +1455,57 @@ function GROUP:InitRandomizePositionRadius( OuterRadius, InnerRadius )
return self return self
end end
--- Sets the radio comms on or off when the group is respawned. Same as checking/unchecking the COMM box in the mission editor.
-- @param #GROUP self
-- @param #boolean switch If true (or nil), enables the radio comms. If false, disables the radio for the spawned group.
-- @return #GROUP self
function GROUP:InitRadioCommsOnOff(switch)
self:F({switch=switch})
if switch==true or switch==nil then
self.InitRespawnRadio=true
else
self.InitRespawnRadio=false
end
return self
end
--- Sets the radio frequency of the group when it is respawned.
-- @param #GROUP self
-- @param #number frequency The frequency in MHz.
-- @return #GROUP self
function GROUP:InitRadioFrequency(frequency)
self:F({frequency=frequency})
self.InitRespawnFreq=frequency
return self
end
--- Set radio modulation when the group is respawned. Default is AM.
-- @param #GROUP self
-- @param #string modulation Either "FM" or "AM". If no value is given, modulation is set to AM.
-- @return #GROUP self
function GROUP:InitRadioModulation(modulation)
self:F({modulation=modulation})
if modulation and modulation:lower()=="fm" then
self.InitRespawnModu=radio.modulation.FM
else
self.InitRespawnModu=radio.modulation.AM
end
return self
end
--- Sets the modex (tail number) of the first unit of the group. If more units are in the group, the number is increased with every unit.
-- @param #GROUP self
-- @param #string modex Tail number of the first unit.
-- @return #GROUP self
function GROUP:InitModex(modex)
self:F({modex=modex})
if modex then
self.InitRespawnModex=tonumber(modex)
end
return self
end
--- Respawn the @{Wrapper.Group} at a @{Point}. --- Respawn the @{Wrapper.Group} at a @{Point}.
-- The method will setup the new group template according the Init(Respawn) settings provided for the group. -- The method will setup the new group template according the Init(Respawn) settings provided for the group.
@@ -1477,29 +1528,61 @@ end
-- --
-- @param Wrapper.Group#GROUP self -- @param Wrapper.Group#GROUP self
-- @param #table Template (optional) The template of the Group retrieved with GROUP:GetTemplate(). If the template is not provided, the template will be retrieved of the group itself. -- @param #table Template (optional) The template of the Group retrieved with GROUP:GetTemplate(). If the template is not provided, the template will be retrieved of the group itself.
-- @param #boolean Reset Reset positions if TRUE.
-- @return Wrapper.Group#GROUP self
function GROUP:Respawn( Template, Reset ) function GROUP:Respawn( Template, Reset )
if not Template then -- Given template or get old.
Template = self:GetTemplate() Template = Template or self:GetTemplate()
end
-- Get correct heading.
local function _Heading(course)
local h
if course<=180 then
h=math.rad(course)
else
h=-math.rad(360-course)
end
return h
end
-- First check if group is alive.
if self:IsAlive() then if self:IsAlive() then
-- Respawn zone.
local Zone = self.InitRespawnZone -- Core.Zone#ZONE local Zone = self.InitRespawnZone -- Core.Zone#ZONE
-- Zone position or current group position.
local Vec3 = Zone and Zone:GetVec3() or self:GetVec3() local Vec3 = Zone and Zone:GetVec3() or self:GetVec3()
-- From point of the template.
local From = { x = Template.x, y = Template.y } local From = { x = Template.x, y = Template.y }
-- X, Y
Template.x = Vec3.x Template.x = Vec3.x
Template.y = Vec3.z Template.y = Vec3.z
--Template.x = nil --Template.x = nil
--Template.y = nil --Template.y = nil
-- Debug number of units.
self:F( #Template.units ) self:F( #Template.units )
-- Reset position etc?
if Reset == true then if Reset == true then
-- Loop over units in group.
for UnitID, UnitData in pairs( self:GetUnits() ) do for UnitID, UnitData in pairs( self:GetUnits() ) do
local GroupUnit = UnitData -- Wrapper.Unit#UNIT local GroupUnit = UnitData -- Wrapper.Unit#UNIT
self:F( GroupUnit:GetName() ) self:F(GroupUnit:GetName())
if GroupUnit:IsAlive() then if GroupUnit:IsAlive() then
self:F( "Alive" ) self:F("Alive")
local GroupUnitVec3 = GroupUnit:GetVec3()
-- Get unit position vector.
local GroupUnitVec3 = GroupUnit:GetVec3()
-- Check if respawn zone is set.
if Zone then if Zone then
if self.InitRespawnRandomizePositionZone then if self.InitRespawnRandomizePositionZone then
GroupUnitVec3 = Zone:GetRandomVec3() GroupUnitVec3 = Zone:GetRandomVec3()
@@ -1512,17 +1595,38 @@ function GROUP:Respawn( Template, Reset )
end end
end end
-- Altitude
Template.units[UnitID].alt = self.InitRespawnHeight and self.InitRespawnHeight or GroupUnitVec3.y Template.units[UnitID].alt = self.InitRespawnHeight and self.InitRespawnHeight or GroupUnitVec3.y
Template.units[UnitID].x = ( Template.units[UnitID].x - From.x ) + GroupUnitVec3.x -- Keep the original x position of the template and translate to the new position.
Template.units[UnitID].y = ( Template.units[UnitID].y - From.y ) + GroupUnitVec3.z -- Keep the original z position of the template and translate to the new position. -- Unit position. Why not simply take the current positon?
Template.units[UnitID].heading = self.InitRespawnHeading and self.InitRespawnHeading or GroupUnit:GetHeading() if Zone then
Template.units[UnitID].x = ( Template.units[UnitID].x - From.x ) + GroupUnitVec3.x -- Keep the original x position of the template and translate to the new position.
Template.units[UnitID].y = ( Template.units[UnitID].y - From.y ) + GroupUnitVec3.z -- Keep the original z position of the template and translate to the new position.
else
Template.units[UnitID].x=GroupUnitVec3.x
Template.units[UnitID].y=GroupUnitVec3.z
end
-- Set heading.
Template.units[UnitID].heading = _Heading(self.InitRespawnHeading and self.InitRespawnHeading or GroupUnit:GetHeading())
Template.units[UnitID].psi = -Template.units[UnitID].heading
-- Debug.
self:F( { UnitID, Template.units[UnitID], Template.units[UnitID] } ) self:F( { UnitID, Template.units[UnitID], Template.units[UnitID] } )
end end
end end
else
else -- Reset=false or nil
-- Loop over template units.
for UnitID, TemplateUnitData in pairs( Template.units ) do for UnitID, TemplateUnitData in pairs( Template.units ) do
self:F( "Reset" ) self:F( "Reset" )
-- Position from template.
local GroupUnitVec3 = { x = TemplateUnitData.x, y = TemplateUnitData.alt, z = TemplateUnitData.y } local GroupUnitVec3 = { x = TemplateUnitData.x, y = TemplateUnitData.alt, z = TemplateUnitData.y }
-- Respawn zone position.
if Zone then if Zone then
if self.InitRespawnRandomizePositionZone then if self.InitRespawnRandomizePositionZone then
GroupUnitVec3 = Zone:GetRandomVec3() GroupUnitVec3 = Zone:GetRandomVec3()
@@ -1535,23 +1639,54 @@ function GROUP:Respawn( Template, Reset )
end end
end end
-- Set altitude.
Template.units[UnitID].alt = self.InitRespawnHeight and self.InitRespawnHeight or GroupUnitVec3.y Template.units[UnitID].alt = self.InitRespawnHeight and self.InitRespawnHeight or GroupUnitVec3.y
-- Unit position.
Template.units[UnitID].x = ( Template.units[UnitID].x - From.x ) + GroupUnitVec3.x -- Keep the original x position of the template and translate to the new position. Template.units[UnitID].x = ( Template.units[UnitID].x - From.x ) + GroupUnitVec3.x -- Keep the original x position of the template and translate to the new position.
Template.units[UnitID].y = ( Template.units[UnitID].y - From.y ) + GroupUnitVec3.z -- Keep the original z position of the template and translate to the new position. Template.units[UnitID].y = ( Template.units[UnitID].y - From.y ) + GroupUnitVec3.z -- Keep the original z position of the template and translate to the new position.
-- Heading
Template.units[UnitID].heading = self.InitRespawnHeading and self.InitRespawnHeading or TemplateUnitData.heading Template.units[UnitID].heading = self.InitRespawnHeading and self.InitRespawnHeading or TemplateUnitData.heading
-- Debug.
self:F( { UnitID, Template.units[UnitID], Template.units[UnitID] } ) self:F( { UnitID, Template.units[UnitID], Template.units[UnitID] } )
end end
end end
end end
self:Destroy() -- Set tail number.
_DATABASE:Spawn( Template ) if self.InitRespawnModex then
for UnitID=1,#Template.units do
Template.units[UnitID].onboard_num=string.format("%03d", self.InitRespawnModex+(UnitID-1))
end
end
-- Set radio frequency and modulation.
if self.InitRespawnRadio then
Template.communication=self.InitRespawnRadio
end
if self.InitRespawnFreq then
Template.frequency=self.InitRespawnFreq
end
if self.InitRespawnModu then
Template.modulation=self.InitRespawnModu
end
-- Destroy old group. Dont trigger any dead/crash events since this is a respawn.
self:Destroy(false)
self:T({Template=Template})
-- Spawn new group.
_DATABASE:Spawn(Template)
-- Reset events.
self:ResetEvents() self:ResetEvents()
return self return self
end end
@@ -1648,11 +1783,23 @@ function GROUP:RespawnAtCurrentAirbase(SpawnTemplate, Takeoff, Uncontrolled) --
-- Set uncontrolled state. -- Set uncontrolled state.
SpawnTemplate.uncontrolled=Uncontrolled SpawnTemplate.uncontrolled=Uncontrolled
-- Set radio frequency and modulation.
if self.InitRespawnRadio then
SpawnTemplate.communication=self.InitRespawnRadio
end
if self.InitRespawnFreq then
SpawnTemplate.frequency=self.InitRespawnFreq
end
if self.InitRespawnModu then
SpawnTemplate.modulation=self.InitRespawnModu
end
-- Destroy old group. -- Destroy old group.
self:Destroy(false) self:Destroy(false)
_DATABASE:Spawn( SpawnTemplate ) -- Spawn new group.
_DATABASE:Spawn(SpawnTemplate)
-- Reset events. -- Reset events.
self:ResetEvents() self:ResetEvents()
@@ -1800,8 +1947,8 @@ do -- Route methods
-- --
-- @param #GROUP self -- @param #GROUP self
-- @param Wrapper.Airbase#AIRBASE RTBAirbase (optional) The @{Wrapper.Airbase} to return to. If blank, the controllable will return to the nearest friendly airbase. -- @param Wrapper.Airbase#AIRBASE RTBAirbase (optional) The @{Wrapper.Airbase} to return to. If blank, the controllable will return to the nearest friendly airbase.
-- @param #number Speed (optional) The Speed, if no Speed is given, the maximum Speed of the first unit is selected. -- @param #number Speed (optional) The Speed, if no Speed is given, 80% of maximum Speed of the group is selected.
-- @return #GROUP -- @return #GROUP self
function GROUP:RouteRTB( RTBAirbase, Speed ) function GROUP:RouteRTB( RTBAirbase, Speed )
self:F( { RTBAirbase:GetName(), Speed } ) self:F( { RTBAirbase:GetName(), Speed } )
@@ -1811,42 +1958,42 @@ do -- Route methods
if RTBAirbase then if RTBAirbase then
local GroupPoint = self:GetVec2() -- If speed is not given take 80% of max speed.
local GroupVelocity = self:GetUnit(1):GetDesc().speedMax local Speed=Speed or self:GetSpeedMax()*0.8
local PointFrom = {}
PointFrom.x = GroupPoint.x
PointFrom.y = GroupPoint.y
PointFrom.type = "Turning Point"
PointFrom.action = "Turning Point"
PointFrom.speed = GroupVelocity
local PointTo = {}
local AirbasePointVec2 = RTBAirbase:GetPointVec2()
local AirbaseAirPoint = AirbasePointVec2:WaypointAir(
POINT_VEC3.RoutePointAltType.BARO,
"Land",
"Landing",
Speed or self:GetUnit(1):GetDesc().speedMax
)
AirbaseAirPoint["airdromeId"] = RTBAirbase:GetID() -- Curent (from) waypoint.
AirbaseAirPoint["speed_locked"] = true, local coord=self:GetCoordinate()
local PointFrom=coord:WaypointAirTurningPoint(nil, Speed)
-- Airbase coordinate.
--local PointAirbase=RTBAirbase:GetCoordinate():SetAltitude(coord.y):WaypointAirTurningPoint(nil ,Speed)
-- Landing waypoint. More general than prev version since it should also work with FAPRS and ships.
local PointLanding=RTBAirbase:GetCoordinate():WaypointAirLanding(Speed, RTBAirbase)
-- Waypoint table.
local Points={PointFrom, PointLanding}
--local Points={PointFrom, PointAirbase, PointLanding}
self:F(AirbaseAirPoint ) -- Debug info.
self:T3(Points)
local Points = { PointFrom, AirbaseAirPoint }
self:T3( Points )
local Template = self:GetTemplate() -- Get group template.
Template.route.points = Points local Template=self:GetTemplate()
self:Respawn( Template )
-- Set route points.
--self:Route( Points ) Template.route.points=Points
-- Respawn the group.
self:Respawn(Template, true)
-- Route the group or this will not work.
self:Route(Points)
else else
-- Clear all tasks.
self:ClearTasks() self:ClearTasks()
end end
end end

View File

@@ -4,7 +4,7 @@
-- --
-- ### Author: **FlightControl** -- ### Author: **FlightControl**
-- --
-- ### Contributions: -- ### Contributions: **Hardcard**, **funkyfranky**
-- --
-- === -- ===
-- --
@@ -310,6 +310,44 @@ function POSITIONABLE:GetCoordinate()
return nil return nil
end end
--- Returns a COORDINATE object, which is offset with respect to the orientation of the POSITIONABLE.
-- @param Wrapper.Positionable#POSITIONABLE self
-- @param #number x Offset in the direction "the nose" of the unit is pointing in meters. Default 0 m.
-- @param #number y Offset "above" the unit in meters. Default 0 m.
-- @param #number z Offset in the direction "the wing" of the unit is pointing in meters. z>0 starboard, z<0 port. Default 0 m.
-- @return Core.Point#COORDINATE The COORDINATE of the offset with respect to the orientation of the POSITIONABLE.
function POSITIONABLE:GetOffsetCoordinate(x,y,z)
-- Default if nil.
x=x or 0
y=y or 0
z=z or 0
-- Vectors making up the coordinate system.
local X=self:GetOrientationX()
local Y=self:GetOrientationY()
local Z=self:GetOrientationZ()
-- Offset vector: x meters ahead, z meters starboard, y meters above.
local A={x=x, y=y, z=z}
-- Scale components of orthonormal coordinate vectors.
local x={x=X.x*A.x, y=X.y*A.x, z=X.z*A.x}
local y={x=Y.x*A.y, y=Y.y*A.y, z=Y.z*A.y}
local z={x=Z.x*A.z, y=Z.y*A.z, z=Z.z*A.z}
-- Add up vectors in the unit coordinate system ==> this gives the offset vector relative the the origin of the map.
local a={x=x.x+y.x+z.x, y=x.y+y.y+z.y, z=x.z+y.z+z.z}
-- Vector from the origin of the map to the unit.
local u=self:GetVec3()
-- Translate offset vector from map origin to the unit: v=u+a.
local v={x=a.x+u.x, y=a.y+u.y, z=a.z+u.z}
-- Return the offset coordinate.
return COORDINATE:NewFromVec3(v)
end
--- Returns a random @{DCS#Vec3} vector within a range, indicating the point in 3D of the POSITIONABLE within the mission. --- Returns a random @{DCS#Vec3} vector within a range, indicating the point in 3D of the POSITIONABLE within the mission.
-- @param Wrapper.Positionable#POSITIONABLE self -- @param Wrapper.Positionable#POSITIONABLE self
@@ -390,6 +428,27 @@ function POSITIONABLE:GetBoundingBox() --R2.1
end end
--- Get the object size.
-- @param #POSITIONABLE self
-- @return DCS#Distance Max size of object in x, z or 0 if bounding box could not be obtained.
-- @return DCS#Distance Length x or 0 if bounding box could not be obtained.
-- @return DCS#Distance Height y or 0 if bounding box could not be obtained.
-- @return DCS#Distance Width z or 0 if bounding box could not be obtained.
function POSITIONABLE:GetObjectSize()
-- Get bounding box.
local box=self:GetBoundingBox()
if box then
local x=box.max.x+math.abs(box.min.x) --length
local y=box.max.y+math.abs(box.min.y) --height
local z=box.max.z+math.abs(box.min.z) --width
return math.max(x,z), x , y, z
end
return 0,0,0,0
end
--- Get the bounding radius of the underlying POSITIONABLE DCS Object. --- Get the bounding radius of the underlying POSITIONABLE DCS Object.
-- @param #POSITIONABLE self -- @param #POSITIONABLE self
-- @param #number mindist (Optional) If bounding box is smaller than this value, mindist is returned. -- @param #number mindist (Optional) If bounding box is smaller than this value, mindist is returned.
@@ -656,6 +715,14 @@ function POSITIONABLE:GetVelocityMPS()
return 0 return 0
end end
--- Returns the POSITIONABLE velocity in knots.
-- @param Wrapper.Positionable#POSITIONABLE self
-- @return #number The velocity in knots.
function POSITIONABLE:GetVelocityKNOTS()
self:F2( self.PositionableName )
return UTILS.MpsToKnots(self:GetVelocityMPS())
end
--- Returns the Angle of Attack of a positionable. --- Returns the Angle of Attack of a positionable.
-- @param Wrapper.Positionable#POSITIONABLE self -- @param Wrapper.Positionable#POSITIONABLE self
-- @return #number Angle of attack in degrees. -- @return #number Angle of attack in degrees.
@@ -706,8 +773,8 @@ end
--- Returns the unit's climb or descent angle. --- Returns the unit's climb or descent angle.
-- @param Wrapper.Positionable#POSITIONABLE self -- @param Wrapper.Positionable#POSITIONABLE self
-- @return #number Climb or descent angle in degrees. -- @return #number Climb or descent angle in degrees. Or 0 if velocity vector norm is zero (or nil). Or nil, if the position of the POSITIONABLE returns nil.
function POSITIONABLE:GetClimbAnge() function POSITIONABLE:GetClimbAngle()
-- Get position of the unit. -- Get position of the unit.
local unitpos = self:GetPosition() local unitpos = self:GetPosition()
@@ -719,10 +786,17 @@ function POSITIONABLE:GetClimbAnge()
if unitvel and UTILS.VecNorm(unitvel)~=0 then if unitvel and UTILS.VecNorm(unitvel)~=0 then
return math.asin(unitvel.y/UTILS.VecNorm(unitvel)) -- Calculate climb angle.
local angle=math.asin(unitvel.y/UTILS.VecNorm(unitvel))
-- Return angle in degrees.
return math.deg(angle)
else
return 0
end end
end end
return nil
end end
--- Returns the pitch angle of a unit. --- Returns the pitch angle of a unit.

View File

@@ -555,7 +555,7 @@ end
-- @return #number The relative amount of fuel (from 0.0 to 1.0). -- @return #number The relative amount of fuel (from 0.0 to 1.0).
-- @return #nil The DCS Unit is not existing or alive. -- @return #nil The DCS Unit is not existing or alive.
function UNIT:GetFuel() function UNIT:GetFuel()
self:F( self.UnitName ) self:F3( self.UnitName )
local DCSUnit = self:GetDCSObject() local DCSUnit = self:GetDCSObject()
@@ -571,7 +571,7 @@ end
-- @param #UNIT self -- @param #UNIT self
-- @return #list<Wrapper.Unit#UNIT> A list of one @{Wrapper.Unit}. -- @return #list<Wrapper.Unit#UNIT> A list of one @{Wrapper.Unit}.
function UNIT:GetUnits() function UNIT:GetUnits()
self:F2( { self.UnitName } ) self:F3( { self.UnitName } )
local DCSUnit = self:GetDCSObject() local DCSUnit = self:GetDCSObject()
local Units = {} local Units = {}
@@ -783,6 +783,27 @@ function UNIT:GetThreatLevel()
end end
--- Triggers an explosion at the coordinates of the unit.
-- @param #UNIT self
-- @param #number power Power of the explosion in kg TNT. Default 100 kg TNT.
-- @param #number delay (Optional) Delay of explosion in seconds.
-- @return #UNIT self
function UNIT:Explode(power, delay)
-- Default.
power=power or 100
-- Check if delay or not.
if delay and delay>0 then
-- Delayed call.
SCHEDULER:New(nil, self.Explode, {self, power}, delay)
else
-- Create an explotion at the coordinate of the unit.
self:GetCoordinate():Explosion(power)
end
return self
end
-- Is functions -- Is functions
@@ -902,29 +923,31 @@ end
function UNIT:InAir() function UNIT:InAir()
self:F2( self.UnitName ) self:F2( self.UnitName )
-- Get DCS unit object.
local DCSUnit = self:GetDCSObject() --DCS#Unit local DCSUnit = self:GetDCSObject() --DCS#Unit
if DCSUnit then if DCSUnit then
-- Implementation of workaround. The original code is below.
-- This to simulate the landing on buildings.
local UnitInAir = true
-- Get DCS result of whether unit is in air or not.
local UnitInAir = DCSUnit:inAir()
-- Get unit category.
local UnitCategory = DCSUnit:getDesc().category local UnitCategory = DCSUnit:getDesc().category
if UnitCategory == Unit.Category.HELICOPTER then
-- If DCS says that it is in air, check if this is really the case, since we might have landed on a building where inAir()=true but actually is not.
-- This is a workaround since DCS currently does not acknoledge that helos land on buildings.
-- Note however, that the velocity check will fail if the ground is moving, e.g. on an aircraft carrier!
if UnitInAir==true and UnitCategory == Unit.Category.HELICOPTER then
local VelocityVec3 = DCSUnit:getVelocity() local VelocityVec3 = DCSUnit:getVelocity()
local Velocity = ( VelocityVec3.x ^ 2 + VelocityVec3.y ^ 2 + VelocityVec3.z ^ 2 ) ^ 0.5 -- in meters / sec local Velocity = UTILS.VecNorm(VelocityVec3)
local Coordinate = DCSUnit:getPoint() local Coordinate = DCSUnit:getPoint()
local LandHeight = land.getHeight( { x = Coordinate.x, y = Coordinate.z } ) local LandHeight = land.getHeight( { x = Coordinate.x, y = Coordinate.z } )
local Height = Coordinate.y - LandHeight local Height = Coordinate.y - LandHeight
if Velocity < 1 and Height <= 60 then if Velocity < 1 and Height <= 60 then
UnitInAir = false UnitInAir = false
end end
else
UnitInAir = DCSUnit:inAir()
end end
self:T3( UnitInAir ) self:T3( UnitInAir )
return UnitInAir return UnitInAir
end end

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.ui.externaltools.ProgramLaunchConfigurationType">
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="C:\ProgramData\chocolatey\lib\lua51\tools\lua5.1.exe"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="&quot;Moose Setup\Moose_Create.lua&quot; &quot;D&quot; &quot;LOCAL&quot; &quot;${resource_loc:/MOOSE/Moose Development/Moose}&quot; &quot;${resource_loc:/MOOSE/Moose Setup}&quot;&#13;&#10;${resource_loc:/MOOSE_INCLUDE/Moose_Include_Dynamic}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/MOOSE}"/>
</launchConfiguration>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.ui.externaltools.ProgramLaunchConfigurationType">
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="C:\ProgramData\chocolatey\lib\lua51\tools\lua5.1.exe"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="&quot;Moose Setup\Moose_Create.lua&quot; &quot;S&quot; &quot;LOCAL&quot; &quot;${resource_loc:/MOOSE/Moose Development/Moose}&quot; &quot;${resource_loc:/MOOSE/Moose Setup}&quot;&#13;&#10;${resource_loc:/MOOSE_INCLUDE/Moose_Include_Static}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/MOOSE}"/>
</launchConfiguration>

View File

@@ -18,3 +18,5 @@ __Moose.Include = function( IncludeFile )
end end
__Moose.Includes = {} __Moose.Includes = {}
__Moose.Include( 'Scripts/Moose/Modules.lua' )

View File

@@ -59,12 +59,24 @@ Functional/Suppression.lua
Functional/PseudoATC.lua Functional/PseudoATC.lua
Functional/Warehouse.lua Functional/Warehouse.lua
Ops/Airboss.lua
Ops/RecoveryTanker.lua
Ops/RescueHelo.lua
AI/AI_Balancer.lua AI/AI_Balancer.lua
AI/AI_Air.lua
AI/AI_A2A.lua AI/AI_A2A.lua
AI/AI_A2A_Patrol.lua AI/AI_A2A_Patrol.lua
AI/AI_A2A_Cap.lua AI/AI_A2A_Cap.lua
AI/AI_A2A_Gci.lua AI/AI_A2A_Gci.lua
AI/AI_A2A_Dispatcher.lua AI/AI_A2A_Dispatcher.lua
AI/AI_A2G.lua
AI/AI_A2G_Engage.lua
AI/AI_A2G_BAI.lua
AI/AI_A2G_CAS.lua
AI/AI_A2G_SEAD.lua
AI/AI_A2G_Patrol.lua
AI/AI_A2G_Dispatcher.lua
AI/AI_Patrol.lua AI/AI_Patrol.lua
AI/AI_Cap.lua AI/AI_Cap.lua
AI/AI_Cas.lua AI/AI_Cas.lua
@@ -100,4 +112,4 @@ Tasking/Task_Cargo_CSAR.lua
Tasking/Task_Cargo_Dispatcher.lua Tasking/Task_Cargo_Dispatcher.lua
Tasking/TaskZoneCapture.lua Tasking/TaskZoneCapture.lua
Moose.lua Globals.lua

View File

@@ -12,15 +12,15 @@ print( "Moose development path : " .. MooseDevelopmentPath )
print( "Moose setup path : " .. MooseSetupPath ) print( "Moose setup path : " .. MooseSetupPath )
print( "Moose target path : " .. MooseTargetPath ) print( "Moose target path : " .. MooseTargetPath )
local MooseSourcesFilePath = MooseSetupPath .. "/Moose.files" local MooseModulesFilePath = MooseDevelopmentPath .. "/Modules.lua"
local MooseFilePath = MooseTargetPath.."/Moose.lua" local LoaderFilePath = MooseTargetPath .. "/Moose.lua"
print( "Reading Moose source list : " .. MooseSourcesFilePath ) print( "Reading Moose source list : " .. MooseModulesFilePath )
local MooseFile = io.open( MooseFilePath, "w" ) local LoaderFile = io.open( LoaderFilePath, "w" )
if MooseDynamicStatic == "S" then if MooseDynamicStatic == "S" then
MooseFile:write( "env.info( '*** MOOSE GITHUB Commit Hash ID: " .. MooseCommitHash .. " ***' )\n" ) LoaderFile:write( "env.info( '*** MOOSE GITHUB Commit Hash ID: " .. MooseCommitHash .. " ***' )\n" )
end end
local MooseLoaderPath local MooseLoaderPath
@@ -35,27 +35,26 @@ local MooseLoader = io.open( MooseLoaderPath, "r" )
local MooseLoaderText = MooseLoader:read( "*a" ) local MooseLoaderText = MooseLoader:read( "*a" )
MooseLoader:close() MooseLoader:close()
MooseFile:write( MooseLoaderText ) LoaderFile:write( MooseLoaderText )
local MooseSourcesFile = io.open( MooseModulesFilePath, "r" )
local MooseSourcesFile = io.open( MooseSourcesFilePath, "r" )
local MooseSource = MooseSourcesFile:read("*l") local MooseSource = MooseSourcesFile:read("*l")
while( MooseSource ) do while( MooseSource ) do
if MooseSource ~= "" then if MooseSource ~= "" then
MooseSource = string.match( MooseSource, "Scripts/Moose/(.+)'" )
local MooseFilePath = MooseDevelopmentPath .. "/" .. MooseSource local MooseFilePath = MooseDevelopmentPath .. "/" .. MooseSource
if MooseDynamicStatic == "D" then if MooseDynamicStatic == "D" then
print( "Load dynamic: " .. MooseSource ) print( "Load dynamic: " .. MooseFilePath )
MooseFile:write( "__Moose.Include( 'Scripts/Moose/" .. MooseSource .. "' )\n" )
end end
if MooseDynamicStatic == "S" then if MooseDynamicStatic == "S" then
print( "Load static: " .. MooseSource ) print( "Load static: " .. MooseFilePath )
local MooseSourceFile = io.open( MooseFilePath, "r" ) local MooseSourceFile = io.open( MooseFilePath, "r" )
local MooseSourceFileText = MooseSourceFile:read( "*a" ) local MooseSourceFileText = MooseSourceFile:read( "*a" )
MooseSourceFile:close() MooseSourceFile:close()
MooseFile:write( MooseSourceFileText ) LoaderFile:write( MooseSourceFileText )
end end
end end
@@ -63,13 +62,13 @@ while( MooseSource ) do
end end
if MooseDynamicStatic == "D" then if MooseDynamicStatic == "D" then
MooseFile:write( "BASE:TraceOnOff( true )\n" ) LoaderFile:write( "BASE:TraceOnOff( true )\n" )
end end
if MooseDynamicStatic == "S" then if MooseDynamicStatic == "S" then
MooseFile:write( "BASE:TraceOnOff( false )\n" ) LoaderFile:write( "BASE:TraceOnOff( false )\n" )
end end
MooseFile:write( "env.info( '*** MOOSE INCLUDE END *** ' )\n" ) LoaderFile:write( "env.info( '*** MOOSE INCLUDE END *** ' )\n" )
MooseSourcesFile:close() MooseSourcesFile:close()
MooseFile:close() LoaderFile:close()