TommyC81 0441acf101
Documentation fixes. (#1803)
Improve the consistency of the module intros to the most commonly used version (single dash).

Add missing module information (abbreviated where none existed previously).

Fix broken documentation links

Make module names correspond to filenames (and fix links).

Fix typos.
2022-10-19 12:20:39 +02:00

2858 lines
86 KiB
Lua

--- **Wrapper** - GROUP wraps the DCS Class Group objects.
--
-- ===
--
-- The @{#GROUP} class is a wrapper class to handle the DCS Group objects.
--
-- ## Features:
--
-- * Support all DCS Group APIs.
-- * Enhance with Group specific APIs not in the DCS Group API set.
-- * Handle local Group Controller.
-- * Manage the "state" of the DCS Group.
--
-- **IMPORTANT: ONE SHOULD NEVER SANITIZE these GROUP OBJECT REFERENCES! (make the GROUP object references nil).**
--
-- ===
--
-- For each DCS Group object alive within a running mission, a GROUP wrapper object (instance) will be created within the _@{DATABASE} object.
-- This is done at the beginning of the mission (when the mission starts), and dynamically when new DCS Group objects are spawned (using the @{SPAWN} class).
--
-- The GROUP class does not contain a :New() method, rather it provides :Find() methods to retrieve the object reference
-- using the DCS Group or the DCS GroupName.
--
-- The GROUP methods will reference the DCS Group object by name when it is needed during API execution.
-- If the DCS Group object does not exist or is nil, the GROUP methods will return nil and may log an exception in the DCS.log file.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ### Contributions:
--
-- * [**Entropy**](https://forums.eagle.ru/member.php?u=111471), **Afinegan**: Came up with the requirement for AIOnOff().
--
-- ===
--
-- @module Wrapper.Group
-- @image Wrapper_Group.JPG
--- @type GROUP
-- @extends Wrapper.Controllable#CONTROLLABLE
-- @field #string GroupName The name of the group.
--- Wrapper class of the DCS world Group object.
--
-- The GROUP class provides the following functions to retrieve quickly the relevant GROUP instance:
--
-- * @{#GROUP.Find}(): Find a GROUP instance from the _DATABASE object using a DCS Group object.
-- * @{#GROUP.FindByName}(): Find a GROUP instance from the _DATABASE object using a DCS Group name.
--
-- # 1. Tasking of groups
--
-- A GROUP is derived from the wrapper class CONTROLLABLE (@{Wrapper.Controllable#CONTROLLABLE}).
-- See the @{Wrapper.Controllable} task methods section for a description of the task methods.
--
-- But here is an example how a group can be assigned a task.
--
-- This test demonstrates the use(s) of the SwitchWayPoint method of the GROUP class.
--
-- First we look up the objects. We create a GROUP object `HeliGroup`, using the @{#GROUP:FindByName}() method, looking up the `"Helicopter"` group object.
-- Same for the `"AttackGroup"`.
--
-- local HeliGroup = GROUP:FindByName( "Helicopter" )
-- local AttackGroup = GROUP:FindByName( "AttackGroup" )
--
-- Now we retrieve the @{Wrapper.Unit#UNIT} objects of the `AttackGroup` object, using the method `:GetUnits()`.
--
-- local AttackUnits = AttackGroup:GetUnits()
--
-- Tasks are actually text strings that we build using methods of GROUP.
-- So first, we declare an list of `Tasks`.
--
-- local Tasks = {}
--
-- Now we loop over the `AttackUnits` using a for loop.
-- We retrieve the `AttackUnit` using the `AttackGroup:GetUnit()` method.
-- Each `AttackUnit` found, will be attacked by `HeliGroup`, using the method `HeliGroup:TaskAttackUnit()`.
-- This method returns a string containing a command line to execute the task to the `HeliGroup`.
-- The code will assign the task string command to the next element in the `Task` list, using `Tasks[#Tasks+1]`.
-- This little code will take the count of `Task` using `#` operator, and will add `1` to the count.
-- This result will be the index of the `Task` element.
--
-- for i = 1, #AttackUnits do
-- local AttackUnit = AttackGroup:GetUnit( i )
-- Tasks[#Tasks+1] = HeliGroup:TaskAttackUnit( AttackUnit )
-- end
--
-- Once these tasks have been executed, a function `_Resume` will be called ...
--
-- Tasks[#Tasks+1] = HeliGroup:TaskFunction( "_Resume", { "''" } )
--
-- --- @param Wrapper.Group#GROUP HeliGroup
-- function _Resume( HeliGroup )
-- env.info( '_Resume' )
--
-- HeliGroup:MessageToAll( "Resuming",10,"Info")
-- end
--
-- Now here is where the task gets assigned!
-- Using `HeliGroup:PushTask`, the task is pushed onto the task queue of the group `HeliGroup`.
-- Since `Tasks` is an array of tasks, we use the `HeliGroup:TaskCombo` method to execute the tasks.
-- The `HeliGroup:PushTask` method can receive a delay parameter in seconds.
-- In the example, `30` is given as a delay.
--
--
-- HeliGroup:PushTask(
-- HeliGroup:TaskCombo(
-- Tasks
-- ), 30
-- )
--
-- That's it!
-- But again, please refer to the @{Wrapper.Controllable} task methods section for a description of the different task methods that are available.
--
--
--
-- ### Obtain the mission from group templates
--
-- Group templates contain complete mission descriptions. Sometimes you want to copy a complete mission from a group and assign it to another:
--
-- * @{Wrapper.Controllable#CONTROLLABLE.TaskMission}: (AIR + GROUND) Return a mission task from a mission template.
--
-- ## GROUP Command methods
--
-- A GROUP is a @{Wrapper.Controllable}. See the @{Wrapper.Controllable} command methods section for a description of the command methods.
--
-- ## GROUP option methods
--
-- A GROUP is a @{Wrapper.Controllable}. See the @{Wrapper.Controllable} option methods section for a description of the option methods.
--
-- ## GROUP Zone validation methods
--
-- The group can be validated whether it is completely, partly or not within a @{Zone}.
-- Use the following Zone validation methods on the group:
--
-- * @{#GROUP.IsCompletelyInZone}: Returns true if all units of the group are within a @{Zone}.
-- * @{#GROUP.IsPartlyInZone}: Returns true if some units of the group are within a @{Zone}.
-- * @{#GROUP.IsNotInZone}: Returns true if none of the group units of the group are within a @{Zone}.
--
-- The zone can be of any @{Zone} class derived from @{Core.Zone#ZONE_BASE}. So, these methods are polymorphic to the zones tested on.
--
-- ## GROUP AI methods
--
-- A GROUP has AI methods to control the AI activation.
--
-- * @{#GROUP.SetAIOnOff}(): Turns the GROUP AI On or Off.
-- * @{#GROUP.SetAIOn}(): Turns the GROUP AI On.
-- * @{#GROUP.SetAIOff}(): Turns the GROUP AI Off.
--
-- @field #GROUP GROUP
GROUP = {
ClassName = "GROUP",
}
--- Enumerator for location at airbases
-- @type GROUP.Takeoff
GROUP.Takeoff = {
Air = 1,
Runway = 2,
Hot = 3,
Cold = 4,
}
GROUPTEMPLATE = {}
GROUPTEMPLATE.Takeoff = {
[GROUP.Takeoff.Air] = { "Turning Point", "Turning Point" },
[GROUP.Takeoff.Runway] = { "TakeOff", "From Runway" },
[GROUP.Takeoff.Hot] = { "TakeOffParkingHot", "From Parking Area Hot" },
[GROUP.Takeoff.Cold] = { "TakeOffParking", "From Parking Area" }
}
--- Generalized group attributes. See [DCS attributes](https://wiki.hoggitworld.com/view/DCS_enum_attributes) on hoggit.
-- @type GROUP.Attribute
-- @field #string AIR_TRANSPORTPLANE Airplane with transport capability. This can be used to transport other assets.
-- @field #string AIR_AWACS Airborne Early Warning and Control System.
-- @field #string AIR_FIGHTER Fighter, interceptor, ... airplane.
-- @field #string AIR_BOMBER Aircraft which can be used for strategic bombing.
-- @field #string AIR_TANKER Airplane which can refuel other aircraft.
-- @field #string AIR_TRANSPORTHELO Helicopter with transport capability. This can be used to transport other assets.
-- @field #string AIR_ATTACKHELO Attack helicopter.
-- @field #string AIR_UAV Unpiloted Aerial Vehicle, e.g. drones.
-- @field #string AIR_OTHER Any airborne unit that does not fall into any other airborne category.
-- @field #string GROUND_APC Infantry carriers, in particular Amoured Personell Carrier. This can be used to transport other assets.
-- @field #string GROUND_TRUCK Unarmed ground vehicles, which has the DCS "Truck" attribute.
-- @field #string GROUND_INFANTRY Ground infantry assets.
-- @field #string GROUND_IFV Ground Infantry Fighting Vehicle.
-- @field #string GROUND_ARTILLERY Artillery assets.
-- @field #string GROUND_TANK Tanks (modern or old).
-- @field #string GROUND_TRAIN Trains. Not that trains are **not** yet properly implemented in DCS and cannot be used currently.
-- @field #string GROUND_EWR Early Warning Radar.
-- @field #string GROUND_AAA Anti-Aircraft Artillery.
-- @field #string GROUND_SAM Surface-to-Air Missile system or components.
-- @field #string GROUND_OTHER Any ground unit that does not fall into any other ground category.
-- @field #string NAVAL_AIRCRAFTCARRIER Aircraft carrier.
-- @field #string NAVAL_WARSHIP War ship, i.e. cruisers, destroyers, firgates and corvettes.
-- @field #string NAVAL_ARMEDSHIP Any armed ship that is not an aircraft carrier, a cruiser, destroyer, firgatte or corvette.
-- @field #string NAVAL_UNARMEDSHIP Any unarmed naval vessel.
-- @field #string NAVAL_OTHER Any naval unit that does not fall into any other naval category.
-- @field #string OTHER_UNKNOWN Anything that does not fall into any other category.
GROUP.Attribute = {
AIR_TRANSPORTPLANE="Air_TransportPlane",
AIR_AWACS="Air_AWACS",
AIR_FIGHTER="Air_Fighter",
AIR_BOMBER="Air_Bomber",
AIR_TANKER="Air_Tanker",
AIR_TRANSPORTHELO="Air_TransportHelo",
AIR_ATTACKHELO="Air_AttackHelo",
AIR_UAV="Air_UAV",
AIR_OTHER="Air_OtherAir",
GROUND_APC="Ground_APC",
GROUND_TRUCK="Ground_Truck",
GROUND_INFANTRY="Ground_Infantry",
GROUND_IFV="Ground_IFV",
GROUND_ARTILLERY="Ground_Artillery",
GROUND_TANK="Ground_Tank",
GROUND_TRAIN="Ground_Train",
GROUND_EWR="Ground_EWR",
GROUND_AAA="Ground_AAA",
GROUND_SAM="Ground_SAM",
GROUND_OTHER="Ground_OtherGround",
NAVAL_AIRCRAFTCARRIER="Naval_AircraftCarrier",
NAVAL_WARSHIP="Naval_WarShip",
NAVAL_ARMEDSHIP="Naval_ArmedShip",
NAVAL_UNARMEDSHIP="Naval_UnarmedShip",
NAVAL_OTHER="Naval_OtherNaval",
OTHER_UNKNOWN="Other_Unknown",
}
--- Create a new GROUP from a given GroupTemplate as a parameter.
-- Note that the GroupTemplate is NOT spawned into the mission.
-- It is merely added to the @{Core.Database}.
-- @param #GROUP self
-- @param #table GroupTemplate The GroupTemplate Structure exactly as defined within the mission editor.
-- @param DCS#coalition.side CoalitionSide The coalition.side of the group.
-- @param DCS#Group.Category CategoryID The Group.Category of the group.
-- @param DCS#country.id CountryID the country.id of the group.
-- @return #GROUP self
function GROUP:NewTemplate( GroupTemplate, CoalitionSide, CategoryID, CountryID )
local GroupName = GroupTemplate.name
_DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, CategoryID, CountryID, GroupName )
local self = BASE:Inherit( self, CONTROLLABLE:New( GroupName ) )
self.GroupName = GroupName
if not _DATABASE.GROUPS[GroupName] then
_DATABASE.GROUPS[GroupName] = self
end
self:SetEventPriority( 4 )
return self
end
--- Create a new GROUP from an existing Group in the Mission.
-- @param #GROUP self
-- @param #string GroupName The Group name
-- @return #GROUP self
function GROUP:Register( GroupName )
local self = BASE:Inherit( self, CONTROLLABLE:New( GroupName ) ) -- #GROUP
self.GroupName = GroupName
self:SetEventPriority( 4 )
return self
end
-- Reference methods.
--- Find the GROUP wrapper class instance using the DCS Group.
-- @param #GROUP self
-- @param DCS#Group DCSGroup The DCS Group.
-- @return #GROUP The GROUP.
function GROUP:Find( DCSGroup )
local GroupName = DCSGroup:getName() -- Wrapper.Group#GROUP
local GroupFound = _DATABASE:FindGroup( GroupName )
return GroupFound
end
--- Find the created GROUP using the DCS Group Name.
-- @param #GROUP self
-- @param #string GroupName The DCS Group Name.
-- @return #GROUP The GROUP.
function GROUP:FindByName( GroupName )
local GroupFound = _DATABASE:FindGroup( GroupName )
return GroupFound
end
-- DCS Group methods support.
--- Returns the DCS Group.
-- @param #GROUP self
-- @return DCS#Group The DCS Group.
function GROUP:GetDCSObject()
local DCSGroup = Group.getByName( self.GroupName )
if DCSGroup then
return DCSGroup
end
return nil
end
--- Returns the @{DCS#Position3} position vectors indicating the point and direction vectors in 3D of the POSITIONABLE within the mission.
-- @param Wrapper.Positionable#POSITIONABLE self
-- @return DCS#Position The 3D position vectors of the POSITIONABLE or #nil if the groups not existing or alive.
function GROUP:GetPositionVec3() -- Overridden from POSITIONABLE:GetPositionVec3()
self:F2( self.PositionableName )
local DCSPositionable = self:GetDCSObject()
if DCSPositionable then
local unit = DCSPositionable:getUnits()[1]
if unit then
local PositionablePosition = unit:getPosition().p
self:T3( PositionablePosition )
return PositionablePosition
end
end
return nil
end
--- Returns if the group is alive.
-- The Group must:
--
-- * Exist at run-time.
-- * Has at least one unit.
--
-- When the first @{Wrapper.Unit} of the group is active, it will return true.
-- If the first @{Wrapper.Unit} of the group is inactive, it will return false.
--
-- @param #GROUP self
-- @return #boolean `true` if the group is alive *and* active, `false` if the group is alive but inactive or `#nil` if the group does not exist anymore.
function GROUP:IsAlive()
self:F2( self.GroupName )
local DCSGroup = self:GetDCSObject() -- DCS#Group
if DCSGroup then
if DCSGroup:isExist() then
local DCSUnit = DCSGroup:getUnit(1) -- DCS#Unit
if DCSUnit then
local GroupIsAlive = DCSUnit:isActive()
self:T3( GroupIsAlive )
return GroupIsAlive
end
end
end
return nil
end
--- Returns if the group is activated.
-- @param #GROUP self
-- @return #boolean `true` if group is activated or `#nil` The group is not existing or alive.
function GROUP:IsActive()
self:F2( self.GroupName )
local DCSGroup = self:GetDCSObject() -- DCS#Group
if DCSGroup then
local unit = DCSGroup:getUnit(1)
if unit then
local GroupIsActive = unit:isActive()
return GroupIsActive
end
end
return nil
end
--- Destroys the DCS Group and all of its DCS Units.
-- Note that this destroy method also can raise a destroy event at run-time.
-- 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.
-- @param #GROUP self
-- @param #boolean GenerateEvent If true, a crash [AIR] or dead [GROUND] event for each unit is generated. If false, if no event is triggered. If nil, a RemoveUnit event is triggered.
-- @param #number delay Delay in seconds before despawning the group.
-- @usage
-- -- Air unit example: destroy the Helicopter and generate a S_EVENT_CRASH for each unit in the Helicopter group.
-- Helicopter = GROUP:FindByName( "Helicopter" )
-- Helicopter:Destroy( true )
-- @usage
-- -- Ground unit example: destroy the Tanks and generate a S_EVENT_DEAD for each unit in the Tanks group.
-- Tanks = GROUP:FindByName( "Tanks" )
-- Tanks:Destroy( true )
-- @usage
-- -- Ship unit example: destroy the Ship silently.
-- Ship = GROUP:FindByName( "Ship" )
-- Ship:Destroy()
--
-- @usage
-- -- Destroy without event generation example.
-- Ship = GROUP:FindByName( "Boat" )
-- Ship:Destroy( false ) -- Don't generate an event upon destruction.
--
function GROUP:Destroy( GenerateEvent, delay )
self:F2( self.GroupName )
if delay and delay>0 then
self:ScheduleOnce(delay, GROUP.Destroy, self, GenerateEvent)
else
local DCSGroup = self:GetDCSObject()
if DCSGroup then
for Index, UnitData in pairs( DCSGroup:getUnits() ) do
if GenerateEvent and GenerateEvent == true then
if self:IsAir() then
self:CreateEventCrash( timer.getTime(), UnitData )
else
self:CreateEventDead( timer.getTime(), UnitData )
end
elseif GenerateEvent == false then
-- Do nothing!
else
self:CreateEventRemoveUnit( timer.getTime(), UnitData )
end
end
USERFLAG:New( self:GetName() ):Set( 100 )
DCSGroup:destroy()
DCSGroup = nil
end
end
return nil
end
--- Returns category of the DCS Group. Returns one of
--
-- * Group.Category.AIRPLANE
-- * Group.Category.HELICOPTER
-- * Group.Category.GROUND
-- * Group.Category.SHIP
-- * Group.Category.TRAIN
--
-- @param #GROUP self
-- @return DCS#Group.Category The category ID.
function GROUP:GetCategory()
self:F2( self.GroupName )
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local GroupCategory = DCSGroup:getCategory()
self:T3( GroupCategory )
return GroupCategory
end
return nil
end
--- Returns the category name of the #GROUP.
-- @param #GROUP self
-- @return #string Category name = Helicopter, Airplane, Ground Unit, Ship, Train.
function GROUP:GetCategoryName()
self:F2( self.GroupName )
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local CategoryNames = {
[Group.Category.AIRPLANE] = "Airplane",
[Group.Category.HELICOPTER] = "Helicopter",
[Group.Category.GROUND] = "Ground Unit",
[Group.Category.SHIP] = "Ship",
[Group.Category.TRAIN] = "Train",
}
local GroupCategory = DCSGroup:getCategory()
self:T3( GroupCategory )
return CategoryNames[GroupCategory]
end
return nil
end
--- Returns the coalition of the DCS Group.
-- @param #GROUP self
-- @return DCS#coalition.side The coalition side of the DCS Group.
function GROUP:GetCoalition()
self:F2( self.GroupName )
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local GroupCoalition = DCSGroup:getCoalition()
self:T3( GroupCoalition )
return GroupCoalition
end
return nil
end
--- Returns the country of the DCS Group.
-- @param #GROUP self
-- @return DCS#country.id The country identifier or nil if the DCS Group is not existing or alive.
function GROUP:GetCountry()
self:F2( self.GroupName )
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local GroupCountry = DCSGroup:getUnit(1):getCountry()
self:T3( GroupCountry )
return GroupCountry
end
return nil
end
--- Check if at least one (or all) unit(s) has (have) a certain attribute.
-- See [hoggit documentation](https://wiki.hoggitworld.com/view/DCS_func_hasAttribute).
-- @param #GROUP self
-- @param #string attribute The name of the attribute the group is supposed to have. Valid attributes can be found in the "db_attributes.lua" file which is located at in "C:\Program Files\Eagle Dynamics\DCS World\Scripts\Database".
-- @param #boolean all If true, all units of the group must have the attribute in order to return true. Default is only one unit of a heterogenious group needs to have the attribute.
-- @return #boolean Group has this attribute.
function GROUP:HasAttribute(attribute, all)
-- Get all units of the group.
local _units=self:GetUnits()
if _units then
local _allhave=true
local _onehas=false
for _,_unit in pairs(_units) do
local _unit=_unit --Wrapper.Unit#UNIT
if _unit then
local _hastit=_unit:HasAttribute(attribute)
if _hastit==true then
_onehas=true
else
_allhave=false
end
end
end
if all==true then
return _allhave
else
return _onehas
end
end
return nil
end
--- Returns the maximum speed of the group.
-- If the group is heterogenious and consists of different units, the max speed of the slowest unit is returned.
-- @param #GROUP self
-- @return #number Speed in km/h.
function GROUP:GetSpeedMax()
self:F2( self.GroupName )
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local Units=self:GetUnits()
local speedmax=nil
for _,unit in pairs(Units) do
local unit=unit --Wrapper.Unit#UNIT
local speed=unit:GetSpeedMax()
if speedmax==nil or speed<speedmax then
speedmax=speed
end
--env.info(string.format("FF unit %s: speed=%.1f, speedmax=%.1f", unit:GetName(), speed, speedmax))
end
return speedmax
end
return nil
end
--- Returns the maximum range of the group.
-- If the group is heterogenious and consists of different units, the smallest range of all units is returned.
-- @param #GROUP self
-- @return #number Range in meters.
function GROUP:GetRange()
self:F2( self.GroupName )
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local Units=self:GetUnits()
local Rangemin=nil
for _,unit in pairs(Units) do
local unit=unit --Wrapper.Unit#UNIT
local range=unit:GetRange()
if range then
if Rangemin==nil then
Rangemin=range
elseif range<Rangemin then
Rangemin=range
end
end
end
return Rangemin
end
return nil
end
--- Returns a list of @{Wrapper.Unit} objects of the @{Wrapper.Group}.
-- @param #GROUP self
-- @return #list<Wrapper.Unit#UNIT> The list of @{Wrapper.Unit} objects of the @{Wrapper.Group}.
function GROUP:GetUnits()
self:F2( { self.GroupName } )
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local DCSUnits = DCSGroup:getUnits() or {}
local Units = {}
for Index, UnitData in pairs( DCSUnits ) do
Units[#Units+1] = UNIT:Find( UnitData )
end
self:T3( Units )
return Units
end
return nil
end
--- Returns a list of @{Wrapper.Unit} objects of the @{Wrapper.Group} that are occupied by a player.
-- @param #GROUP self
-- @return #list<Wrapper.Unit#UNIT> The list of player occupied @{Wrapper.Unit} objects of the @{Wrapper.Group}.
function GROUP:GetPlayerUnits()
self:F2( { self.GroupName } )
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local DCSUnits = DCSGroup:getUnits()
local Units = {}
for Index, UnitData in pairs( DCSUnits ) do
local PlayerUnit = UNIT:Find( UnitData )
if PlayerUnit:GetPlayerName() then
Units[#Units+1] = PlayerUnit
end
end
self:T3( Units )
return Units
end
return nil
end
--- Check if an (air) group is a client or player slot. Information is retrieved from the group template.
-- @param #GROUP self
-- @return #boolean If true, group is associated with a client or player slot.
function GROUP:IsPlayer()
return self:GetUnit(1):IsPlayer()
end
--- Returns the UNIT wrapper class with number UnitNumber.
-- If the underlying DCS Unit does not exist, the method will return nil. .
-- @param #GROUP self
-- @param #number UnitNumber The number of the UNIT wrapper class to be returned.
-- @return Wrapper.Unit#UNIT The UNIT wrapper class.
function GROUP:GetUnit( UnitNumber )
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local UnitFound = nil
-- 2.7.1 dead event bug, return the first alive unit instead
local units = DCSGroup:getUnits() or {}
for _,_unit in pairs(units) do
local UnitFound = UNIT:Find(_unit)
if UnitFound then
return UnitFound
end
end
end
return nil
end
--- Returns the DCS Unit with number UnitNumber.
-- If the underlying DCS Unit does not exist, the method will return nil. .
-- @param #GROUP self
-- @param #number UnitNumber The number of the DCS Unit to be returned.
-- @return DCS#Unit The DCS Unit.
function GROUP:GetDCSUnit( UnitNumber )
local DCSGroup = self:GetDCSObject()
if DCSGroup then
if DCSGroup.getUnit and DCSGroup:getUnit( UnitNumber ) then
return DCSGroup:getUnit( UnitNumber )
else
local UnitFound = nil
-- 2.7.1 dead event bug, return the first alive unit instead
local units = DCSGroup:getUnits() or {}
for _,_unit in pairs(units) do
if _unit and _unit:isExist() then
return _unit
end
end
end
end
return nil
end
--- Returns current size of the DCS Group.
-- If some of the DCS Units of the DCS Group are destroyed the size of the DCS Group is changed.
-- @param #GROUP self
-- @return #number The DCS Group size.
function GROUP:GetSize()
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local GroupSize = DCSGroup:getSize()
if GroupSize then
return GroupSize
else
return 0
end
end
return nil
end
--- Count number of alive units in the group.
-- @param #GROUP self
-- @return #number Number of alive units. If DCS group is nil, 0 is returned.
function GROUP:CountAliveUnits()
self:F3( { self.GroupName } )
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local units=self:GetUnits()
local n=0
for _,_unit in pairs(units) do
local unit=_unit --Wrapper.Unit#UNIT
if unit and unit:IsAlive() then
n=n+1
end
end
return n
end
return 0
end
--- Get the first unit of the group which is alive.
-- @param #GROUP self
-- @return Wrapper.Unit#UNIT First unit alive.
function GROUP:GetFirstUnitAlive()
self:F3({self.GroupName})
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local units=self:GetUnits()
for _,_unit in pairs(units) do
local unit=_unit --Wrapper.Unit#UNIT
if unit and unit:IsAlive() then
return unit
end
end
end
return nil
end
--- Returns the average velocity Vec3 vector.
-- @param Wrapper.Group#GROUP self
-- @return DCS#Vec3 The velocity Vec3 vector or `#nil` if the GROUP is not existing or alive.
function GROUP:GetVelocityVec3()
self:F2( self.GroupName )
local DCSGroup = self:GetDCSObject()
if DCSGroup and DCSGroup:isExist() then
local GroupUnits = DCSGroup:getUnits()
local GroupCount = #GroupUnits
local VelocityVec3 = { x = 0, y = 0, z = 0 }
for _, DCSUnit in pairs( GroupUnits ) do
local UnitVelocityVec3 = DCSUnit:getVelocity()
VelocityVec3.x = VelocityVec3.x + UnitVelocityVec3.x
VelocityVec3.y = VelocityVec3.y + UnitVelocityVec3.y
VelocityVec3.z = VelocityVec3.z + UnitVelocityVec3.z
end
VelocityVec3.x = VelocityVec3.x / GroupCount
VelocityVec3.y = VelocityVec3.y / GroupCount
VelocityVec3.z = VelocityVec3.z / GroupCount
return VelocityVec3
end
BASE:E( { "Cannot GetVelocityVec3", Group = self, Alive = self:IsAlive() } )
return nil
end
--- Returns the average group altitude in meters.
-- @param Wrapper.Group#GROUP self
-- @param #boolean FromGround Measure from the ground or from sea level (ASL). Provide **true** for measuring from the ground (AGL). **false** or **nil** if you measure from sea level.
-- @return #number The altitude of the group or nil if is not existing or alive.
function GROUP:GetAltitude(FromGround)
self:F2( self.GroupName )
return self:GetHeight(FromGround)
end
--- Returns the average group height in meters.
-- @param Wrapper.Group#GROUP self
-- @param #boolean FromGround Measure from the ground or from sea level (ASL). Provide **true** for measuring from the ground (AGL). **false** or **nil** if you measure from sea level.
-- @return #number The height of the group or nil if is not existing or alive.
function GROUP:GetHeight( FromGround )
self:F2( self.GroupName )
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local GroupUnits = DCSGroup:getUnits()
local GroupCount = #GroupUnits
local GroupHeight = 0
for _, DCSUnit in pairs( GroupUnits ) do
local GroupPosition = DCSUnit:getPosition()
if FromGround == true then
local LandHeight = land.getHeight( { x = GroupPosition.p.x, y = GroupPosition.p.z } )
GroupHeight = GroupHeight + ( GroupPosition.p.y - LandHeight )
else
GroupHeight = GroupHeight + GroupPosition.p.y
end
end
return GroupHeight / GroupCount
end
return nil
end
---
--- Returns the initial size of the DCS Group.
-- If some of the DCS Units of the DCS Group are destroyed, the initial size of the DCS Group is unchanged.
-- @param #GROUP self
-- @return #number The DCS Group initial size.
function GROUP:GetInitialSize()
self:F3( { self.GroupName } )
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local GroupInitialSize = DCSGroup:getInitialSize()
self:T3( GroupInitialSize )
return GroupInitialSize
end
return nil
end
--- Returns the DCS Units of the DCS Group.
-- @param #GROUP self
-- @return #table The DCS Units.
function GROUP:GetDCSUnits()
self:F2( { self.GroupName } )
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local DCSUnits = DCSGroup:getUnits()
self:T3( DCSUnits )
return DCSUnits
end
return nil
end
--- Activates a late activated GROUP.
-- @param #GROUP self
-- @param #number delay Delay in seconds, before the group is activated.
-- @return #GROUP self
function GROUP:Activate(delay)
self:F2( { self.GroupName } )
if delay and delay>0 then
self:ScheduleOnce(delay, GROUP.Activate, self)
else
trigger.action.activateGroup( self:GetDCSObject() )
end
return self
end
--- Gets the type name of the group.
-- @param #GROUP self
-- @return #string The type name of the group.
function GROUP:GetTypeName()
self:F2( self.GroupName )
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local GroupTypeName = DCSGroup:getUnit(1):getTypeName()
self:T3( GroupTypeName )
return( GroupTypeName )
end
return nil
end
--- [AIRPLANE] Get the NATO reporting name (platform, e.g. "Flanker") of a GROUP (note - first unit the group). "Bogey" if not found. Currently airplanes only!
--@param #GROUP self
--@return #string NatoReportingName or "Bogey" if unknown.
function GROUP:GetNatoReportingName()
self:F2( self.GroupName )
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local GroupTypeName = DCSGroup:getUnit(1):getTypeName()
self:T3( GroupTypeName )
return UTILS.GetReportingName(GroupTypeName)
end
return "Bogey"
end
--- Gets the player name of the group.
-- @param #GROUP self
-- @return #string The player name of the group.
function GROUP:GetPlayerName()
self:F2( self.GroupName )
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local PlayerName = DCSGroup:getUnit(1):getPlayerName()
self:T3( PlayerName )
return( PlayerName )
end
return nil
end
--- Gets the CallSign of the first DCS Unit of the DCS Group.
-- @param #GROUP self
-- @return #string The CallSign of the first DCS Unit of the DCS Group.
function GROUP:GetCallsign()
self:F2( self.GroupName )
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local GroupCallSign = DCSGroup:getUnit(1):getCallsign()
self:T3( GroupCallSign )
return GroupCallSign
end
BASE:E( { "Cannot GetCallsign", Positionable = self, Alive = self:IsAlive() } )
return nil
end
--- Returns the current point (Vec2 vector) of the first DCS Unit in the DCS Group.
-- @param #GROUP self
-- @return DCS#Vec2 Current Vec2 point of the first DCS Unit of the DCS Group.
function GROUP:GetVec2()
local Unit=self:GetUnit(1)
if Unit then
local vec2=Unit:GetVec2()
return vec2
end
end
--- Returns the current Vec3 vector of the first Unit in the GROUP.
-- @param #GROUP self
-- @return DCS#Vec3 Current Vec3 of the first Unit of the GROUP or nil if cannot be found.
function GROUP:GetVec3()
-- Get first unit.
local unit=self:GetUnit(1)
if unit then
local vec3=unit:GetVec3()
return vec3
end
self:E("ERROR: Cannot get Vec3 of group "..tostring(self.GroupName))
return nil
end
--- Returns the average Vec3 vector of the Units in the GROUP.
-- @param #GROUP self
-- @return DCS#Vec3 Current Vec3 of the GROUP or nil if cannot be found.
function GROUP:GetAverageVec3()
local units = self:GetUnits() or {}
-- Init.
local x=0 ; local y=0 ; local z=0 ; local n=0
-- Loop over all units.
for _,unit in pairs(units) do
local vec3=nil --DCS#Vec3
if unit and unit:IsAlive() then
vec3 = unit:GetVec3()
end
if vec3 then
-- Sum up posits.
x=x+vec3.x
y=y+vec3.y
z=z+vec3.z
-- Increase counter.
n=n+1
end
end
if n>0 then
-- Average.
local Vec3={x=x/n, y=y/n, z=z/n} --DCS#Vec3
return Vec3
end
return nil
end
--- Returns a POINT_VEC2 object indicating the point in 2D of the first UNIT of the GROUP within the mission.
-- @param #GROUP self
-- @return Core.Point#POINT_VEC2 The 2D point vector of the first DCS Unit of the GROUP.
-- @return #nil The first UNIT is not existing or alive.
function GROUP:GetPointVec2()
self:F2(self.GroupName)
local FirstUnit = self:GetUnit(1)
if FirstUnit then
local FirstUnitPointVec2 = FirstUnit:GetPointVec2()
self:T3(FirstUnitPointVec2)
return FirstUnitPointVec2
end
BASE:E( { "Cannot GetPointVec2", Group = self, Alive = self:IsAlive() } )
return nil
end
--- Returns a COORDINATE object indicating the average position of the GROUP within the mission.
-- @param Wrapper.Group#GROUP self
-- @return Core.Point#COORDINATE The COORDINATE of the GROUP.
function GROUP:GetAverageCoordinate()
local vec3 = self:GetAverageVec3()
if vec3 then
local coord = COORDINATE:NewFromVec3(vec3)
local Heading = self:GetHeading()
coord.Heading = Heading
else
BASE:E( { "Cannot GetAverageCoordinate", Group = self, Alive = self:IsAlive() } )
return nil
end
end
--- Returns a COORDINATE object indicating the point of the first UNIT of the GROUP within the mission.
-- @param Wrapper.Group#GROUP self
-- @return Core.Point#COORDINATE The COORDINATE of the GROUP.
function GROUP:GetCoordinate()
local Units = self:GetUnits() or {}
for _,_unit in pairs(Units) do
local FirstUnit = _unit -- Wrapper.Unit#UNIT
if FirstUnit then
local FirstUnitCoordinate = FirstUnit:GetCoordinate()
if FirstUnitCoordinate then
local Heading = self:GetHeading()
FirstUnitCoordinate.Heading = Heading
return FirstUnitCoordinate
end
end
end
BASE:E( { "Cannot GetCoordinate", Group = self, Alive = self:IsAlive() } )
end
--- Returns a random @{DCS#Vec3} vector (point in 3D of the UNIT within the mission) within a range around the first UNIT of the GROUP.
-- @param #GROUP self
-- @param #number Radius Radius in meters.
-- @return DCS#Vec3 The random 3D point vector around the first UNIT of the GROUP or #nil The GROUP is invalid or empty.
-- @usage
-- -- If Radius is ignored, returns the DCS#Vec3 of first UNIT of the GROUP
function GROUP:GetRandomVec3(Radius)
self:F2(self.GroupName)
local FirstUnit = self:GetUnit(1)
if FirstUnit then
local FirstUnitRandomPointVec3 = FirstUnit:GetRandomVec3(Radius)
self:T3(FirstUnitRandomPointVec3)
return FirstUnitRandomPointVec3
end
BASE:E( { "Cannot GetRandomVec3", Group = self, Alive = self:IsAlive() } )
return nil
end
--- Returns the mean heading of every UNIT in the GROUP in degrees
-- @param #GROUP self
-- @return #number Mean heading of the GROUP in degrees or #nil The first UNIT is not existing or alive.
function GROUP:GetHeading()
self:F2(self.GroupName)
self:F2(self.GroupName)
local GroupSize = self:GetSize()
local HeadingAccumulator = 0
local n=0
local Units = self:GetUnits()
if GroupSize then
for _,unit in pairs(Units) do
if unit and unit:IsAlive() then
HeadingAccumulator = HeadingAccumulator + unit:GetHeading()
n=n+1
end
end
return math.floor(HeadingAccumulator / n)
end
BASE:E( { "Cannot GetHeading", Group = self, Alive = self:IsAlive() } )
return nil
end
--- Return the fuel state and unit reference for the unit with the least
-- amount of fuel in the group.
-- @param #GROUP self
-- @return #number The fuel state of the unit with the least amount of fuel.
-- @return Wrapper.Unit#UNIT reference to #Unit object for further processing.
function GROUP:GetFuelMin()
self:F3(self.ControllableName)
if not self:GetDCSObject() then
BASE:E( { "Cannot GetFuel", Group = self, Alive = self:IsAlive() } )
return 0
end
local min = 65535 -- some sufficiently large number to init with
local unit = nil
local tmp = nil
for UnitID, UnitData in pairs( self:GetUnits() ) do
if UnitData and UnitData:IsAlive() then
tmp = UnitData:GetFuel()
if tmp < min then
min = tmp
unit = UnitData
end
end
end
return min, unit
end
--- Returns relative amount of fuel (from 0.0 to 1.0) the group has in its
-- internal tanks. If there are additional fuel tanks the value may be
-- greater than 1.0.
-- @param #GROUP self
-- @return #number The relative amount of fuel (from 0.0 to 1.0).
-- @return #nil The GROUP is not existing or alive.
function GROUP:GetFuelAvg()
self:F( self.ControllableName )
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local GroupSize = self:GetSize()
local TotalFuel = 0
for UnitID, UnitData in pairs( self:GetUnits() ) do
local Unit = UnitData -- Wrapper.Unit#UNIT
local UnitFuel = Unit:GetFuel() or 0
self:F( { Fuel = UnitFuel } )
TotalFuel = TotalFuel + UnitFuel
end
local GroupFuel = TotalFuel / GroupSize
return GroupFuel
end
BASE:E( { "Cannot GetFuel", Group = self, Alive = self:IsAlive() } )
return 0
end
--- Returns relative amount of fuel (from 0.0 to 1.0) the group has in its internal tanks. If there are additional fuel tanks the value may be greater than 1.0.
-- @param #GROUP self
-- @return #number The relative amount of fuel (from 0.0 to 1.0).
-- @return #nil The GROUP is not existing or alive.
function GROUP:GetFuel()
return self:GetFuelAvg()
end
--- Get the number of shells, rockets, bombs and missiles the whole group currently has.
-- @param #GROUP self
-- @return #number Total amount of ammo the group has left. This is the sum of shells, rockets, bombs and missiles of all units.
-- @return #number Number of shells left.
-- @return #number Number of rockets left.
-- @return #number Number of bombs left.
-- @return #number Number of missiles left.
-- @return #number Number of artillery shells left (with explosive mass, included in shells; shells can also be machine gun ammo)
function GROUP:GetAmmunition()
self:F( self.ControllableName )
local DCSControllable = self:GetDCSObject()
local Ntot=0
local Nshells=0
local Nrockets=0
local Nmissiles=0
local Nbombs=0
local Narti=0
if DCSControllable then
-- Loop over units.
for UnitID, UnitData in pairs( self:GetUnits() ) do
local Unit = UnitData -- Wrapper.Unit#UNIT
-- Get ammo of the unit
local ntot, nshells, nrockets, nbombs, nmissiles, narti = Unit:GetAmmunition()
Ntot=Ntot+ntot
Nshells=Nshells+nshells
Nrockets=Nrockets+nrockets
Nmissiles=Nmissiles+nmissiles
Nbombs=Nbombs+nbombs
Narti=Narti+narti
end
end
return Ntot, Nshells, Nrockets, Nbombs, Nmissiles, Narti
end
do -- Is Zone methods
--- Check if any unit of a group is inside a @{Zone}.
-- @param #GROUP self
-- @param Core.Zone#ZONE_BASE Zone The zone to test.
-- @return #boolean Returns `true` if *at least one unit* is inside the zone or `false` if *no* unit is inside.
function GROUP:IsInZone( Zone )
if self:IsAlive() then
for UnitID, UnitData in pairs(self:GetUnits()) do
local Unit = UnitData -- Wrapper.Unit#UNIT
local vec2 = nil
if Unit then
-- Get 2D vector. That's all we need for the zone check.
vec2=Unit:GetVec2()
end
if vec2 and Zone:IsVec2InZone(vec2) then
return true -- At least one unit is in the zone. That is enough.
end
end
return false
end
return nil
end
--- Returns true if all units of the group are within a @{Zone}.
-- @param #GROUP self
-- @param Core.Zone#ZONE_BASE Zone The zone to test.
-- @return #boolean Returns true if the Group is completely within the @{Core.Zone#ZONE_BASE}
function GROUP:IsCompletelyInZone( Zone )
self:F2( { self.GroupName, Zone } )
if not self:IsAlive() then return false end
for UnitID, UnitData in pairs( self:GetUnits() ) do
local Unit = UnitData -- Wrapper.Unit#UNIT
if Zone:IsVec3InZone( Unit:GetVec3() ) then
else
return false
end
end
return true
end
--- Returns true if some but NOT ALL units of the group are within a @{Zone}.
-- @param #GROUP self
-- @param Core.Zone#ZONE_BASE Zone The zone to test.
-- @return #boolean Returns true if the Group is partially within the @{Core.Zone#ZONE_BASE}
function GROUP:IsPartlyInZone( Zone )
self:F2( { self.GroupName, Zone } )
local IsOneUnitInZone = false
local IsOneUnitOutsideZone = false
if not self:IsAlive() then return false end
for UnitID, UnitData in pairs( self:GetUnits() ) do
local Unit = UnitData -- Wrapper.Unit#UNIT
if Zone:IsVec3InZone( Unit:GetVec3() ) then
IsOneUnitInZone = true
else
IsOneUnitOutsideZone = true
end
end
if IsOneUnitInZone and IsOneUnitOutsideZone then
return true
else
return false
end
end
--- Returns true if part or all units of the group are within a @{Zone}.
-- @param #GROUP self
-- @param Core.Zone#ZONE_BASE Zone The zone to test.
-- @return #boolean Returns true if the Group is partially or completely within the @{Core.Zone#ZONE_BASE}.
function GROUP:IsPartlyOrCompletelyInZone( Zone )
return self:IsPartlyInZone(Zone) or self:IsCompletelyInZone(Zone)
end
--- Returns true if none of the group units of the group are within a @{Zone}.
-- @param #GROUP self
-- @param Core.Zone#ZONE_BASE Zone The zone to test.
-- @return #boolean Returns true if the Group is not within the @{Core.Zone#ZONE_BASE}
function GROUP:IsNotInZone( Zone )
self:F2( { self.GroupName, Zone } )
if not self:IsAlive() then return true end
for UnitID, UnitData in pairs( self:GetUnits() ) do
local Unit = UnitData -- Wrapper.Unit#UNIT
if Zone:IsVec3InZone( Unit:GetVec3() ) then
return false
end
end
return true
end
--- Returns true if any units of the group are within a @{Core.Zone}.
-- @param #GROUP self
-- @param Core.Zone#ZONE_BASE Zone The zone to test.
-- @return #boolean Returns true if any unit of the Group is within the @{Core.Zone#ZONE_BASE}
function GROUP:IsAnyInZone( Zone )
if not self:IsAlive() then return false end
for UnitID, UnitData in pairs( self:GetUnits() ) do
local Unit = UnitData -- Wrapper.Unit#UNIT
if Zone:IsVec3InZone( Unit:GetVec3() ) then
return true
end
end
return false
end
--- Returns the number of UNITs that are in the @{Zone}
-- @param #GROUP self
-- @param Core.Zone#ZONE_BASE Zone The zone to test.
-- @return #number The number of UNITs that are in the @{Zone}
function GROUP:CountInZone( Zone )
self:F2( {self.GroupName, Zone} )
local Count = 0
if not self:IsAlive() then return Count end
for UnitID, UnitData in pairs( self:GetUnits() ) do
local Unit = UnitData -- Wrapper.Unit#UNIT
if Zone:IsVec3InZone( Unit:GetVec3() ) then
Count = Count + 1
end
end
return Count
end
--- Returns if the group is of an air category.
-- If the group is a helicopter or a plane, then this method will return true, otherwise false.
-- @param #GROUP self
-- @return #boolean Air category evaluation result.
function GROUP:IsAir()
self:F2( self.GroupName )
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local IsAirResult = DCSGroup:getCategory() == Group.Category.AIRPLANE or DCSGroup:getCategory() == Group.Category.HELICOPTER
self:T3( IsAirResult )
return IsAirResult
end
return nil
end
--- Returns if the DCS Group contains Helicopters.
-- @param #GROUP self
-- @return #boolean true if DCS Group contains Helicopters.
function GROUP:IsHelicopter()
self:F2( self.GroupName )
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local GroupCategory = DCSGroup:getCategory()
self:T2( GroupCategory )
return GroupCategory == Group.Category.HELICOPTER
end
return nil
end
--- Returns if the DCS Group contains AirPlanes.
-- @param #GROUP self
-- @return #boolean true if DCS Group contains AirPlanes.
function GROUP:IsAirPlane()
self:F2()
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local GroupCategory = DCSGroup:getCategory()
self:T2( GroupCategory )
return GroupCategory == Group.Category.AIRPLANE
end
return nil
end
--- Returns if the DCS Group contains Ground troops.
-- @param #GROUP self
-- @return #boolean true if DCS Group contains Ground troops.
function GROUP:IsGround()
self:F2()
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local GroupCategory = DCSGroup:getCategory()
self:T2( GroupCategory )
return GroupCategory == Group.Category.GROUND
end
return nil
end
--- Returns if the DCS Group contains Ships.
-- @param #GROUP self
-- @return #boolean true if DCS Group contains Ships.
function GROUP:IsShip()
self:F2()
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local GroupCategory = DCSGroup:getCategory()
self:T2( GroupCategory )
return GroupCategory == Group.Category.SHIP
end
return nil
end
--- Returns if all units of the group are on the ground or landed.
-- If all units of this group are on the ground, this function will return true, otherwise false.
-- @param #GROUP self
-- @return #boolean All units on the ground result.
function GROUP:AllOnGround()
self:F2()
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local AllOnGroundResult = true
for Index, UnitData in pairs( DCSGroup:getUnits() ) do
if UnitData:inAir() then
AllOnGroundResult = false
end
end
self:T3( AllOnGroundResult )
return AllOnGroundResult
end
return nil
end
end
do -- AI methods
--- Turns the AI On or Off for the GROUP.
-- @param #GROUP self
-- @param #boolean AIOnOff The value true turns the AI On, the value false turns the AI Off.
-- @return #GROUP The GROUP.
function GROUP:SetAIOnOff( AIOnOff )
local DCSGroup = self:GetDCSObject() -- DCS#Group
if DCSGroup then
local DCSController = DCSGroup:getController() -- DCS#Controller
if DCSController then
DCSController:setOnOff( AIOnOff )
return self
end
end
return nil
end
--- Turns the AI On for the GROUP.
-- @param #GROUP self
-- @return #GROUP The GROUP.
function GROUP:SetAIOn()
return self:SetAIOnOff( true )
end
--- Turns the AI Off for the GROUP.
-- @param #GROUP self
-- @return #GROUP The GROUP.
function GROUP:SetAIOff()
return self:SetAIOnOff( false )
end
end
--- Returns the current maximum velocity of the group.
-- Each unit within the group gets evaluated, and the maximum velocity (= the unit which is going the fastest) is returned.
-- @param #GROUP self
-- @return #number Maximum velocity found.
function GROUP:GetMaxVelocity()
self:F2()
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local GroupVelocityMax = 0
for Index, UnitData in pairs( DCSGroup:getUnits() ) do
local UnitVelocityVec3 = UnitData:getVelocity()
local UnitVelocity = math.abs( UnitVelocityVec3.x ) + math.abs( UnitVelocityVec3.y ) + math.abs( UnitVelocityVec3.z )
if UnitVelocity > GroupVelocityMax then
GroupVelocityMax = UnitVelocity
end
end
return GroupVelocityMax
end
return nil
end
--- Returns the current minimum height of the group.
-- Each unit within the group gets evaluated, and the minimum height (= the unit which is the lowest elevated) is returned.
-- @param #GROUP self
-- @return #number Minimum height found.
function GROUP:GetMinHeight()
self:F2()
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local GroupHeightMin = 999999999
for Index, UnitData in pairs( DCSGroup:getUnits() ) do
local UnitData = UnitData -- DCS#Unit
local UnitHeight = UnitData:getPoint()
if UnitHeight < GroupHeightMin then
GroupHeightMin = UnitHeight
end
end
return GroupHeightMin
end
return nil
end
--- Returns the current maximum height of the group.
-- Each unit within the group gets evaluated, and the maximum height (= the unit which is the highest elevated) is returned.
-- @param #GROUP self
-- @return #number Maximum height found.
function GROUP:GetMaxHeight()
self:F2()
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local GroupHeightMax = -999999999
for Index, UnitData in pairs( DCSGroup:getUnits() ) do
local UnitData = UnitData -- DCS#Unit
local UnitHeight = UnitData:getPoint()
if UnitHeight > GroupHeightMax then
GroupHeightMax = UnitHeight
end
end
return GroupHeightMax
end
return nil
end
-- RESPAWNING
--- Returns the group template from the @{DATABASE} (_DATABASE object).
-- @param #GROUP self
-- @return #table
function GROUP:GetTemplate()
local GroupName = self:GetName()
return UTILS.DeepCopy( _DATABASE:GetGroupTemplate( GroupName ) )
end
--- Returns the group template route.points[] (the waypoints) from the @{DATABASE} (_DATABASE object).
-- @param #GROUP self
-- @return #table
function GROUP:GetTemplateRoutePoints()
local GroupName = self:GetName()
return UTILS.DeepCopy( _DATABASE:GetGroupTemplate( GroupName ).route.points )
end
--- Sets the controlled status in a Template.
-- @param #GROUP self
-- @param #boolean Controlled true is controlled, false is uncontrolled.
-- @return #table
function GROUP:SetTemplateControlled( Template, Controlled )
Template.uncontrolled = not Controlled
return Template
end
--- Sets the CountryID of the group in a Template.
-- @param #GROUP self
-- @param DCS#country.id CountryID The country ID.
-- @return #table
function GROUP:SetTemplateCountry( Template, CountryID )
Template.CountryID = CountryID
return Template
end
--- Sets the CoalitionID of the group in a Template.
-- @param #GROUP self
-- @param DCS#coalition.side CoalitionID The coalition ID.
-- @return #table
function GROUP:SetTemplateCoalition( Template, CoalitionID )
Template.CoalitionID = CoalitionID
return Template
end
--- Set the heading for the units in degrees within the respawned group.
-- @param #GROUP self
-- @param #number Heading The heading in meters.
-- @return #GROUP self
function GROUP:InitHeading( Heading )
self.InitRespawnHeading = Heading
return self
end
--- Set the height for the units in meters for the respawned group. (This is applicable for air units).
-- @param #GROUP self
-- @param #number Height The height in meters.
-- @return #GROUP self
function GROUP:InitHeight( Height )
self.InitRespawnHeight = Height
return self
end
--- Set the respawn @{Zone} for the respawned group.
-- @param #GROUP self
-- @param Core.Zone#ZONE Zone The zone in meters.
-- @return #GROUP self
function GROUP:InitZone( Zone )
self.InitRespawnZone = Zone
return self
end
--- Randomize the positions of the units of the respawned group within the @{Zone}.
-- When a Respawn happens, the units of the group will be placed at random positions within the Zone (selected).
-- @param #GROUP self
-- @param #boolean PositionZone true will randomize the positions within the Zone.
-- @return #GROUP self
function GROUP:InitRandomizePositionZone( PositionZone )
self.InitRespawnRandomizePositionZone = PositionZone
self.InitRespawnRandomizePositionInner = nil
self.InitRespawnRandomizePositionOuter = nil
return self
end
--- Randomize the positions of the units of the respawned group in a circle band.
-- When a Respawn happens, the units of the group will be positioned at random places within the Outer and Inner radius.
-- Thus, a band is created around the respawn location where the units will be placed at random positions.
-- @param #GROUP self
-- @param #boolean OuterRadius Outer band in meters from the center.
-- @param #boolean InnerRadius Inner band in meters from the center.
-- @return #GROUP self
function GROUP:InitRandomizePositionRadius( OuterRadius, InnerRadius )
self.InitRespawnRandomizePositionZone = nil
self.InitRespawnRandomizePositionOuter = OuterRadius
self.InitRespawnRandomizePositionInner = InnerRadius
return self
end
--- Set respawn coordinate.
-- @param #GROUP self
-- @param Core.Point#COORDINATE coordinate Coordinate where the group should be respawned.
-- @return #GROUP self
function GROUP:InitCoordinate(coordinate)
self:F({coordinate=coordinate})
self.InitCoord=coordinate
return self
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}.
-- The method will setup the new group template according the Init(Respawn) settings provided for the group.
-- These settings can be provided by calling the relevant Init...() methods of the Group.
--
-- - @{#GROUP.InitHeading}: Set the heading for the units in degrees within the respawned group.
-- - @{#GROUP.InitHeight}: Set the height for the units in meters for the respawned group. (This is applicable for air units).
-- - @{#GROUP.InitRandomizeHeading}: Randomize the headings for the units within the respawned group.
-- - @{#GROUP.InitZone}: Set the respawn @{Zone} for the respawned group.
-- - @{#GROUP.InitRandomizeZones}: Randomize the respawn @{Zone} between one of the @{Zone}s given for the respawned group.
-- - @{#GROUP.InitRandomizePositionZone}: Randomize the positions of the units of the respawned group within the @{Zone}.
-- - @{#GROUP.InitRandomizePositionRadius}: Randomize the positions of the units of the respawned group in a circle band.
-- - @{#GROUP.InitRandomizeTemplates}: Randomize the Template for the respawned group.
--
--
-- Notes:
--
-- - When InitZone or InitRandomizeZones is not used, the position of the respawned group will be its current position.
-- - The current alive group will always be destroyed and respawned using the template definition.
--
-- @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 #boolean Reset Reset positions if TRUE.
-- @return Wrapper.Group#GROUP self
function GROUP:Respawn( Template, Reset )
-- Given template or get old.
Template = Template or self:GetTemplate()
-- 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
-- Respawn zone.
local Zone = self.InitRespawnZone -- Core.Zone#ZONE
-- Zone position or current group position.
local Vec3 = Zone and Zone:GetVec3() or self:GetVec3()
-- From point of the template.
local From = { x = Template.x, y = Template.y }
-- X, Y
Template.x = Vec3.x
Template.y = Vec3.z
--Template.x = nil
--Template.y = nil
-- Debug number of units.
self:F( #Template.units )
-- Reset position etc?
if Reset == true then
-- Loop over units in group.
for UnitID, UnitData in pairs( self:GetUnits() ) do
local GroupUnit = UnitData -- Wrapper.Unit#UNIT
self:F(GroupUnit:GetName())
if GroupUnit:IsAlive() then
self:I("FF Alive")
-- Get unit position vector.
local GroupUnitVec3 = GroupUnit:GetVec3()
-- Check if respawn zone is set.
if Zone then
if self.InitRespawnRandomizePositionZone then
GroupUnitVec3 = Zone:GetRandomVec3()
else
if self.InitRespawnRandomizePositionInner and self.InitRespawnRandomizePositionOuter then
GroupUnitVec3 = POINT_VEC3:NewFromVec2( From ):GetRandomPointVec3InRadius( self.InitRespawnRandomizePositionsOuter, self.InitRespawnRandomizePositionsInner )
else
GroupUnitVec3 = Zone:GetVec3()
end
end
end
-- Coordinate where the group should be respawned.
if self.InitCoord then
GroupUnitVec3=self.InitCoord:GetVec3()
end
-- Altitude
Template.units[UnitID].alt = self.InitRespawnHeight and self.InitRespawnHeight or GroupUnitVec3.y
-- Unit position. Why not simply take the current positon?
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] } )
end
end
elseif Reset==false then -- Reset=false or nil
-- Loop over template units.
for UnitID, TemplateUnitData in pairs( Template.units ) do
self:F( "Reset" )
-- Position from template.
local GroupUnitVec3 = { x = TemplateUnitData.x, y = TemplateUnitData.alt, z = TemplateUnitData.y }
-- Respawn zone position.
if Zone then
if self.InitRespawnRandomizePositionZone then
GroupUnitVec3 = Zone:GetRandomVec3()
else
if self.InitRespawnRandomizePositionInner and self.InitRespawnRandomizePositionOuter then
GroupUnitVec3 = POINT_VEC3:NewFromVec2( From ):GetRandomPointVec3InRadius( self.InitRespawnRandomizePositionsOuter, self.InitRespawnRandomizePositionsInner )
else
GroupUnitVec3 = Zone:GetVec3()
end
end
end
-- Coordinate where the group should be respawned.
if self.InitCoord then
GroupUnitVec3=self.InitCoord:GetVec3()
end
-- Set altitude.
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].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
-- Debug.
self:F( { UnitID, Template.units[UnitID], Template.units[UnitID] } )
end
else
local units=self:GetUnits()
-- Loop over template units.
for UnitID, Unit in pairs(Template.units) do
for _,_unit in pairs(units) do
local unit=_unit --Wrapper.Unit#UNIT
if unit:GetName()==Unit.name then
local coord=unit:GetCoordinate()
local heading=unit:GetHeading()
Unit.x=coord.x
Unit.y=coord.z
Unit.alt=coord.y
Unit.heading=math.rad(heading)
Unit.psi=-Unit.heading
end
end
end
end
end
-- Set tail number.
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()
return self
end
--- Respawn a group at an airbase.
-- Note that the group has to be on parking spots at the airbase already in order for this to work.
-- So each unit of the group is respawned at exactly the same parking spot as it currently occupies.
-- @param Wrapper.Group#GROUP self
-- @param #table SpawnTemplate (Optional) The spawn template for the group. If no template is given it is exacted from the group.
-- @param Core.Spawn#SPAWN.Takeoff Takeoff (Optional) Takeoff type. Sould be either SPAWN.Takeoff.Cold or SPAWN.Takeoff.Hot. Default is SPAWN.Takeoff.Hot.
-- @param #boolean Uncontrolled (Optional) If true, spawn in uncontrolled state.
-- @return Wrapper.Group#GROUP Group spawned at airbase or nil if group could not be spawned.
function GROUP:RespawnAtCurrentAirbase(SpawnTemplate, Takeoff, Uncontrolled) -- R2.4
self:F2( { SpawnTemplate, Takeoff, Uncontrolled} )
if self and self:IsAlive() then
-- Get closest airbase. Should be the one we are currently on.
local airbase=self:GetCoordinate():GetClosestAirbase()
if airbase then
self:F2("Closest airbase = "..airbase:GetName())
else
self:E("ERROR: could not find closest airbase!")
return nil
end
-- Takeoff type. Default hot.
Takeoff = Takeoff or SPAWN.Takeoff.Hot
-- Coordinate of the airbase.
local AirbaseCoord=airbase:GetCoordinate()
-- Spawn template.
SpawnTemplate = SpawnTemplate or self:GetTemplate()
if SpawnTemplate then
local SpawnPoint = SpawnTemplate.route.points[1]
-- These are only for ships.
SpawnPoint.linkUnit = nil
SpawnPoint.helipadId = nil
SpawnPoint.airdromeId = nil
-- Aibase id and category.
local AirbaseID = airbase:GetID()
local AirbaseCategory = airbase:GetAirbaseCategory()
if AirbaseCategory == Airbase.Category.SHIP or AirbaseCategory == Airbase.Category.HELIPAD then
SpawnPoint.linkUnit = AirbaseID
SpawnPoint.helipadId = AirbaseID
elseif AirbaseCategory == Airbase.Category.AIRDROME then
SpawnPoint.airdromeId = AirbaseID
end
SpawnPoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type
SpawnPoint.action = GROUPTEMPLATE.Takeoff[Takeoff][2] -- action
-- Get the units of the group.
local units=self:GetUnits()
local x
local y
for UnitID=1,#units do
local unit=units[UnitID] --Wrapper.Unit#UNIT
-- Get closest parking spot of current unit. Note that we look for occupied spots since the unit is currently sitting on it!
local Parkingspot, TermialID, Distance=unit:GetCoordinate():GetClosestParkingSpot(airbase)
--Parkingspot:MarkToAll("parking spot")
self:T2(string.format("Closest parking spot distance = %s, terminal ID=%s", tostring(Distance), tostring(TermialID)))
-- Get unit coordinates for respawning position.
local uc=unit:GetCoordinate()
--uc:MarkToAll(string.format("re-spawnplace %s terminal %d", unit:GetName(), TermialID))
SpawnTemplate.units[UnitID].x = uc.x --Parkingspot.x
SpawnTemplate.units[UnitID].y = uc.z --Parkingspot.z
SpawnTemplate.units[UnitID].alt = uc.y --Parkingspot.y
SpawnTemplate.units[UnitID].parking = TermialID
SpawnTemplate.units[UnitID].parking_id = nil
--SpawnTemplate.units[UnitID].unitId=nil
end
--SpawnTemplate.groupId=nil
SpawnPoint.x = SpawnTemplate.units[1].x --x --AirbaseCoord.x
SpawnPoint.y = SpawnTemplate.units[1].y --y --AirbaseCoord.z
SpawnPoint.alt = SpawnTemplate.units[1].alt --AirbaseCoord:GetLandHeight()
SpawnTemplate.x = SpawnTemplate.units[1].x --x --AirbaseCoord.x
SpawnTemplate.y = SpawnTemplate.units[1].y --y --AirbaseCoord.z
-- Set uncontrolled state.
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.
self:Destroy(false)
-- Spawn new group.
_DATABASE:Spawn(SpawnTemplate)
-- Reset events.
self:ResetEvents()
return self
end
else
self:E("WARNING: GROUP is not alive!")
end
return nil
end
--- Return the mission template of the group.
-- @param #GROUP self
-- @return #table The MissionTemplate
function GROUP:GetTaskMission()
self:F2( self.GroupName )
return routines.utils.deepCopy( _DATABASE.Templates.Groups[self.GroupName].Template )
end
--- Return the mission route of the group.
-- @param #GROUP self
-- @return #table The mission route defined by points.
function GROUP:GetTaskRoute()
self:F2( self.GroupName )
return routines.utils.deepCopy( _DATABASE.Templates.Groups[self.GroupName].Template.route.points )
end
--- Return the route of a group by using the @{Core.Database#DATABASE} class.
-- @param #GROUP self
-- @param #number Begin The route point from where the copy will start. The base route point is 0.
-- @param #number End The route point where the copy will end. The End point is the last point - the End point. The last point has base 0.
-- @param #boolean Randomize Randomization of the route, when true.
-- @param #number Radius When randomization is on, the randomization is within the radius.
function GROUP:CopyRoute( Begin, End, Randomize, Radius )
self:F2( { Begin, End } )
local Points = {}
-- Could be a Spawned Group
local GroupName = string.match( self:GetName(), ".*#" )
if GroupName then
GroupName = GroupName:sub( 1, -2 )
else
GroupName = self:GetName()
end
self:T3( { GroupName } )
local Template = _DATABASE.Templates.Groups[GroupName].Template
if Template then
if not Begin then
Begin = 0
end
if not End then
End = 0
end
for TPointID = Begin + 1, #Template.route.points - End do
if Template.route.points[TPointID] then
Points[#Points+1] = routines.utils.deepCopy( Template.route.points[TPointID] )
if Randomize then
if not Radius then
Radius = 500
end
Points[#Points].x = Points[#Points].x + math.random( Radius * -1, Radius )
Points[#Points].y = Points[#Points].y + math.random( Radius * -1, Radius )
end
end
end
return Points
else
error( "Template not found for Group : " .. GroupName )
end
return nil
end
--- Calculate the maxium A2G threat level of the Group.
-- @param #GROUP self
-- @return #number Number between 0 and 10.
function GROUP:CalculateThreatLevelA2G()
local MaxThreatLevelA2G = 0
for UnitName, UnitData in pairs( self:GetUnits() ) do
local ThreatUnit = UnitData -- Wrapper.Unit#UNIT
local ThreatLevelA2G = ThreatUnit:GetThreatLevel()
if ThreatLevelA2G > MaxThreatLevelA2G then
MaxThreatLevelA2G = ThreatLevelA2G
end
end
self:T3( MaxThreatLevelA2G )
return MaxThreatLevelA2G
end
--- Get threat level of the group.
-- @param #GROUP self
-- @return #number Max threat level (a number between 0 and 10).
function GROUP:GetThreatLevel()
local threatlevelMax = 0
for UnitName, UnitData in pairs(self:GetUnits()) do
local ThreatUnit = UnitData -- Wrapper.Unit#UNIT
local threatlevel = ThreatUnit:GetThreatLevel()
if threatlevel > threatlevelMax then
threatlevelMax=threatlevel
end
end
return threatlevelMax
end
--- Returns true if the first unit of the GROUP is in the air.
-- @param Wrapper.Group#GROUP self
-- @return #boolean true if in the first unit of the group is in the air or #nil if the GROUP is not existing or not alive.
function GROUP:InAir()
self:F2( self.GroupName )
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local DCSUnit = DCSGroup:getUnit(1)
if DCSUnit then
local GroupInAir = DCSGroup:getUnit(1):inAir()
self:T3( GroupInAir )
return GroupInAir
end
end
return nil
end
--- Checks whether any unit (or optionally) all units of a group is(are) airbore or not.
-- @param Wrapper.Group#GROUP self
-- @param #boolean AllUnits (Optional) If true, check whether all units of the group are airborne.
-- @return #boolean True if at least one (optionally all) unit(s) is(are) airborne or false otherwise. Nil if no unit exists or is alive.
function GROUP:IsAirborne(AllUnits)
self:F2( self.GroupName )
-- Get all units of the group.
local units=self:GetUnits()
if units then
if AllUnits then
--- We want to know if ALL units are airborne.
for _,_unit in pairs(units) do
local unit=_unit --Wrapper.Unit#UNIT
if unit then
-- Unit in air or not.
local inair=unit:InAir()
-- At least one unit is not in air.
if not inair then
return false
end
end
end
-- All units are in air.
return true
else
--- We want to know if ANY unit is airborne.
for _,_unit in pairs(units) do
local unit=_unit --Wrapper.Unit#UNIT
if unit then
-- Unit in air or not.
local inair=unit:InAir()
if inair then
-- At least one unit is in air.
return true
end
end
-- No unit is in air.
return false
end
end
end
return nil
end
--- Returns the DCS descriptor table of the nth unit of the group.
-- @param #GROUP self
-- @param #number n (Optional) The number of the unit for which the dscriptor is returned.
-- @return DCS#Object.Desc The descriptor of the first unit of the group or #nil if the group does not exist any more.
function GROUP:GetDCSDesc(n)
-- Default.
n=n or 1
local unit=self:GetUnit(n)
if unit and unit:IsAlive()~=nil then
local desc=unit:GetDesc()
return desc
end
return nil
end
--- Get the generalized attribute of a self.
-- Note that for a heterogenious self, the attribute is determined from the attribute of the first unit!
-- @param #GROUP self
-- @return #string Generalized attribute of the self.
function GROUP:GetAttribute()
-- Default
local attribute=GROUP.Attribute.OTHER_UNKNOWN --#GROUP.Attribute
if self then
-----------
--- Air ---
-----------
-- Planes
local transportplane=self:HasAttribute("Transports") and self:HasAttribute("Planes")
local awacs=self:HasAttribute("AWACS")
local fighter=self:HasAttribute("Fighters") or self:HasAttribute("Interceptors") or self:HasAttribute("Multirole fighters") or (self:HasAttribute("Bombers") and not self:HasAttribute("Strategic bombers"))
local bomber=self:HasAttribute("Strategic bombers")
local tanker=self:HasAttribute("Tankers")
local uav=self:HasAttribute("UAVs")
-- Helicopters
local transporthelo=self:HasAttribute("Transport helicopters")
local attackhelicopter=self:HasAttribute("Attack helicopters")
--------------
--- Ground ---
--------------
-- Ground
local apc=self:HasAttribute("APC")
local truck=self:HasAttribute("Trucks") and self:GetCategory()==Group.Category.GROUND
local infantry=self:HasAttribute("Infantry")
local artillery=self:HasAttribute("Artillery")
local tank=self:HasAttribute("Old Tanks") or self:HasAttribute("Modern Tanks")
local aaa=self:HasAttribute("AAA") and (not self:HasAttribute("SAM elements"))
local ewr=self:HasAttribute("EWR")
local ifv=self:HasAttribute("IFV")
local sam=self:HasAttribute("SAM elements") or self:HasAttribute("Optical Tracker")
-- Train
local train=self:GetCategory()==Group.Category.TRAIN
-------------
--- Naval ---
-------------
-- Ships
local aircraftcarrier=self:HasAttribute("Aircraft Carriers")
local warship=self:HasAttribute("Heavy armed ships")
local armedship=self:HasAttribute("Armed ships")
local unarmedship=self:HasAttribute("Unarmed ships")
-- Define attribute. Order of attack is important.
if fighter then
attribute=GROUP.Attribute.AIR_FIGHTER
elseif bomber then
attribute=GROUP.Attribute.AIR_BOMBER
elseif awacs then
attribute=GROUP.Attribute.AIR_AWACS
elseif transportplane then
attribute=GROUP.Attribute.AIR_TRANSPORTPLANE
elseif tanker then
attribute=GROUP.Attribute.AIR_TANKER
-- helos
elseif attackhelicopter then
attribute=GROUP.Attribute.AIR_ATTACKHELO
elseif transporthelo then
attribute=GROUP.Attribute.AIR_TRANSPORTHELO
elseif uav then
attribute=GROUP.Attribute.AIR_UAV
-- ground - order of attack
elseif ewr then
attribute=GROUP.Attribute.GROUND_EWR
elseif sam then
attribute=GROUP.Attribute.GROUND_SAM
elseif aaa then
attribute=GROUP.Attribute.GROUND_AAA
elseif artillery then
attribute=GROUP.Attribute.GROUND_ARTILLERY
elseif tank then
attribute=GROUP.Attribute.GROUND_TANK
elseif ifv then
attribute=GROUP.Attribute.GROUND_IFV
elseif apc then
attribute=GROUP.Attribute.GROUND_APC
elseif infantry then
attribute=GROUP.Attribute.GROUND_INFANTRY
elseif truck then
attribute=GROUP.Attribute.GROUND_TRUCK
elseif train then
attribute=GROUP.Attribute.GROUND_TRAIN
-- ships
elseif aircraftcarrier then
attribute=GROUP.Attribute.NAVAL_AIRCRAFTCARRIER
elseif warship then
attribute=GROUP.Attribute.NAVAL_WARSHIP
elseif armedship then
attribute=GROUP.Attribute.NAVAL_ARMEDSHIP
elseif unarmedship then
attribute=GROUP.Attribute.NAVAL_UNARMEDSHIP
else
if self:IsGround() then
attribute=GROUP.Attribute.GROUND_OTHER
elseif self:IsShip() then
attribute=GROUP.Attribute.NAVAL_OTHER
elseif self:IsAir() then
attribute=GROUP.Attribute.AIR_OTHER
else
attribute=GROUP.Attribute.OTHER_UNKNOWN
end
end
end
return attribute
end
do -- Route methods
--- (AIR) Return the Group to an @{Wrapper.Airbase#AIRBASE}.
-- The following things are to be taken into account:
--
-- * The group is respawned to achieve the RTB, there may be side artefacts as a result of this. (Like weapons suddenly come back).
-- * A group consisting out of more than one unit, may rejoin formation when respawned.
-- * A speed can be given in km/h. If no speed is specified, the maximum speed of the first unit will be taken to return to base.
-- * When there is no @{Wrapper.Airbase} object specified, the group will return to the home base if the route of the group is pinned at take-off or at landing to a base.
-- * When there is no @{Wrapper.Airbase} object specified and the group route is not pinned to any airbase, it will return to the nearest airbase.
--
-- @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 #number Speed (optional) The Speed, if no Speed is given, 80% of maximum Speed of the group is selected.
-- @return #GROUP self
function GROUP:RouteRTB( RTBAirbase, Speed )
self:F( { RTBAirbase:GetName(), Speed } )
local DCSGroup = self:GetDCSObject()
if DCSGroup then
if RTBAirbase then
-- If speed is not given take 80% of max speed.
local Speed=Speed or self:GetSpeedMax()*0.8
-- Curent (from) waypoint.
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}
-- Debug info.
self:T3(Points)
-- Get group template.
local Template=self:GetTemplate()
-- Set 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
-- Clear all tasks.
self:ClearTasks()
end
end
return self
end
end
function GROUP:OnReSpawn( ReSpawnFunction )
self.ReSpawnFunction = ReSpawnFunction
end
do -- Event Handling
--- Subscribe to a DCS Event.
-- @param #GROUP self
-- @param Core.Event#EVENTS Event
-- @param #function EventFunction (optional) The function to be called when the event occurs for the GROUP.
-- @return #GROUP
function GROUP:HandleEvent( Event, EventFunction, ... )
self:EventDispatcher():OnEventForGroup( self:GetName(), EventFunction, self, Event, ... )
return self
end
--- UnSubscribe to a DCS event.
-- @param #GROUP self
-- @param Core.Event#EVENTS Event
-- @return #GROUP
function GROUP:UnHandleEvent( Event )
self:EventDispatcher():RemoveEvent( self, Event )
return self
end
--- Reset the subscriptions.
-- @param #GROUP self
-- @return #GROUP
function GROUP:ResetEvents()
self:EventDispatcher():Reset( self )
for UnitID, UnitData in pairs( self:GetUnits() ) do
UnitData:ResetEvents()
end
return self
end
end
do -- Players
--- Get player names
-- @param #GROUP self
-- @return #table The group has players, an array of player names is returned.
-- @return #nil The group has no players
function GROUP:GetPlayerNames()
local HasPlayers = false
local PlayerNames = {}
local Units = self:GetUnits()
for UnitID, UnitData in pairs( Units ) do
local Unit = UnitData -- Wrapper.Unit#UNIT
local PlayerName = Unit:GetPlayerName()
if PlayerName and PlayerName ~= "" then
PlayerNames = PlayerNames or {}
table.insert( PlayerNames, PlayerName )
HasPlayers = true
end
end
if HasPlayers == true then
self:F2( PlayerNames )
return PlayerNames
end
return nil
end
--- Get the active player count in the group.
-- @param #GROUP self
-- @return #number The amount of players.
function GROUP:GetPlayerCount()
local PlayerCount = 0
local Units = self:GetUnits()
for UnitID, UnitData in pairs( Units or {} ) do
local Unit = UnitData -- Wrapper.Unit#UNIT
local PlayerName = Unit:GetPlayerName()
if PlayerName and PlayerName ~= "" then
PlayerCount = PlayerCount + 1
end
end
return PlayerCount
end
end
--- GROUND - Switch on/off radar emissions for the group.
-- @param #GROUP self
-- @param #boolean switch If true, emission is enabled. If false, emission is disabled.
-- @return #GROUP self
function GROUP:EnableEmission(switch)
self:F2( self.GroupName )
local switch = switch or false
local DCSUnit = self:GetDCSObject()
if DCSUnit then
DCSUnit:enableEmission(switch)
end
return self
end
--- Switch on/off invisible flag for the group.
-- @param #GROUP self
-- @param #boolean switch If true, emission is enabled. If false, emission is disabled.
-- @return #GROUP self
function GROUP:SetCommandInvisible(switch)
self:F2( self.GroupName )
if switch==nil then
switch=false
end
local SetInvisible = {id = 'SetInvisible', params = {value = switch}}
self:SetCommand(SetInvisible)
return self
end
--- Switch on/off immortal flag for the group.
-- @param #GROUP self
-- @param #boolean switch If true, emission is enabled. If false, emission is disabled.
-- @return #GROUP self
function GROUP:SetCommandImmortal(switch)
self:F2( self.GroupName )
if switch==nil then
switch=false
end
local SetImmortal = {id = 'SetImmortal', params = {value = switch}}
self:SetCommand(SetImmortal)
return self
end
--- Get skill from Group. Effectively gets the skill from Unit 1 as the group holds no skill value.
-- @param #GROUP self
-- @return #string Skill String of skill name.
function GROUP:GetSkill()
self:F2( self.GroupName )
local unit = self:GetUnit(1)
local name = unit:GetName()
local skill = _DATABASE.Templates.Units[name].Template.skill or "Random"
return skill
end
--- Get the unit in the group with the highest threat level, which is still alive.
-- @param #GROUP self
-- @return Wrapper.Unit#UNIT The most dangerous unit in the group.
-- @return #number Threat level of the unit.
function GROUP:GetHighestThreat()
-- Get units of the group.
local units=self:GetUnits()
if units then
local threat=nil ; local maxtl=0
for _,_unit in pairs(units or {}) do
local unit=_unit --Wrapper.Unit#UNIT
if unit and unit:IsAlive() then
-- Threat level of group.
local tl=unit:GetThreatLevel()
-- Check if greater the current threat.
if tl>maxtl then
maxtl=tl
threat=unit
end
end
end
return threat, maxtl
end
return nil, nil
end
--- Get TTS friendly, optionally customized callsign mainly for **player groups**. A customized callsign is taken from the #GROUP name, after an optional '#' sign, e.g. "Aerial 1-1#Ghostrider" resulting in "Ghostrider 9", or,
-- if that isn't available, from the playername, as set in the mission editor main screen under Logbook, after an optional '|' sign (actually, more of a personal call sign), e.g. "Apple|Moose" results in "Moose 9 1". Options see below.
-- @param #GROUP self
-- @param #boolean ShortCallsign Return a shortened customized callsign, i.e. "Ghostrider 9" and not "Ghostrider 9 1"
-- @param #boolean Keepnumber (Player only) Return customized callsign, incl optional numbers at the end, e.g. "Aerial 1-1#Ghostrider 109" results in "Ghostrider 109", if you want to e.g. use historical US Navy Callsigns
-- @param #table CallsignTranslations Table to translate between DCS standard callsigns and bespoke ones. Does not apply if using customized
-- callsigns from playername or group name.
-- @return #string Callsign
-- @usage
-- -- Set Custom CAP Flight Callsigns for use with TTS
-- mygroup:GetCustomCallSign(true,false,{
-- Devil = 'Bengal',
-- Snake = 'Winder',
-- Colt = 'Camelot',
-- Enfield = 'Victory',
-- Uzi = 'Evil Eye'
-- })
--
-- results in this outcome if the group has Callsign "Enfield 9 1" on the 1st #UNIT of the group:
--
-- 'Victory 9'
--
--
function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations)
--self:I("GetCustomCallSign")
local callsign = "Ghost 1"
if self:IsAlive() then
local IsPlayer = self:IsPlayer()
local shortcallsign = self:GetCallsign() or "unknown91" -- e.g.Uzi91, but we want Uzi 9 1
local callsignroot = string.match(shortcallsign, '(%a+)') -- Uzi
--self:I("CallSign = " .. callsignroot)
local groupname = self:GetName()
local callnumber = string.match(shortcallsign, "(%d+)$" ) or "91" -- 91
local callnumbermajor = string.char(string.byte(callnumber,1)) -- 9
local callnumberminor = string.char(string.byte(callnumber,2)) -- 1
local personalized = false
if IsPlayer and string.find(groupname,"#") then
-- personalized flight name in group naming
if Keepnumber then
shortcallsign = string.match(groupname,"#(.+)") or "Ghost 111" -- Ghostrider 219
else
shortcallsign = string.match(groupname,"#%s*([%a]+)") or "Ghost" -- Ghostrider
end
personalized = true
elseif IsPlayer and string.find(self:GetPlayerName(),"|") then
-- personalized flight name in group naming
shortcallsign = string.match(self:GetPlayerName(),"|%s*([%a]+)") or string.match(self:GetPlayerName(),"|%s*([%d]+)") or "Ghost" -- Ghostrider
personalized = true
end
if (not personalized) and CallsignTranslations and CallsignTranslations[callsignroot] then
callsignroot = CallsignTranslations[callsignroot]
end
if personalized then
-- player personalized callsign
-- remove trailing/leading spaces
shortcallsign=string.gsub(shortcallsign,"^%s*","")
shortcallsign=string.gsub(shortcallsign,"%s*$","")
if Keepnumber then
return shortcallsign -- Ghostrider 219
elseif ShortCallsign then
callsign = shortcallsign.." "..callnumbermajor -- Ghostrider 9
else
callsign = shortcallsign.." "..callnumbermajor.." "..callnumberminor -- Ghostrider 9 1
end
return callsign
end
-- AI or not personalized
if ShortCallsign then
callsign = callsignroot.." "..callnumbermajor -- Uzi/Victory 9
else
callsign = callsignroot.." "..callnumbermajor.." "..callnumberminor -- Uzi/Victory 9 1
end
--self:I("Generated Callsign = " .. callsign)
end
return callsign
end