This commit is contained in:
Frank
2022-12-20 23:51:18 +01:00
parent 0adca414ce
commit 30e6542887
9 changed files with 1311 additions and 342 deletions

View File

@@ -88,6 +88,7 @@ DATABASE = {
WAREHOUSES = {}, WAREHOUSES = {},
FLIGHTGROUPS = {}, FLIGHTGROUPS = {},
FLIGHTCONTROLS = {}, FLIGHTCONTROLS = {},
OPSZONES = {},
} }
local _DATABASECoalition = local _DATABASECoalition =
@@ -410,6 +411,46 @@ do -- Zone_Goal
end end
end -- Zone_Goal end -- Zone_Goal
do -- OpsZone
--- Finds a @{Ops.OpsZone#OPSZONE} based on the zone name.
-- @param #DATABASE self
-- @param #string ZoneName The name of the zone.
-- @return Ops.OpsZone#OPSZONE The found OPSZONE.
function DATABASE:FindOpsZone( ZoneName )
local ZoneFound = self.OPSZONES[ZoneName]
return ZoneFound
end
--- Adds a @{Ops.OpsZone#OPSZONE} based on the zone name in the DATABASE.
-- @param #DATABASE self
-- @param Ops.OpsZone#OPSZONE OpsZone The zone.
function DATABASE:AddOpsZone( OpsZone )
if OpsZone then
local ZoneName=OpsZone:GetName()
if not self.OPSZONES[ZoneName] then
self.OPSZONES[ZoneName] = OpsZone
end
end
end
--- Deletes a @{Ops.OpsZone#OPSZONE} from the DATABASE based on the zone name.
-- @param #DATABASE self
-- @param #string ZoneName The name of the zone.
function DATABASE:DeleteOpsZone( ZoneName )
self.OPSZONES[ZoneName] = nil
end
end -- OpsZone
do -- cargo do -- cargo
--- Adds a Cargo based on the Cargo Name in the DATABASE. --- Adds a Cargo based on the Cargo Name in the DATABASE.

View File

@@ -53,6 +53,7 @@ do -- SET_BASE
-- @field #table Index Table of indices. -- @field #table Index Table of indices.
-- @field #table List Unused table. -- @field #table List Unused table.
-- @field Core.Scheduler#SCHEDULER CallScheduler -- @field Core.Scheduler#SCHEDULER CallScheduler
-- @field #SET_BASE.Filters Filter Filters
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
--- The @{Core.Set#SET_BASE} class defines the core functions that define a collection of objects. --- The @{Core.Set#SET_BASE} class defines the core functions that define a collection of objects.
@@ -83,6 +84,11 @@ do -- SET_BASE
YieldInterval = nil, YieldInterval = nil,
} }
--- Filters
-- @type SET_BASE.Filters
-- @field #table Coalition Coalitions
-- @field #table Prefix Prefixes.
--- Creates a new SET_BASE object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. --- Creates a new SET_BASE object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names.
-- @param #SET_BASE self -- @param #SET_BASE self
-- @return #SET_BASE -- @return #SET_BASE
@@ -135,11 +141,12 @@ do -- SET_BASE
--- Clear the Objects in the Set. --- Clear the Objects in the Set.
-- @param #SET_BASE self -- @param #SET_BASE self
-- @param #boolean TriggerEvent If `true`, an event remove is triggered for each group that is removed from the set.
-- @return #SET_BASE self -- @return #SET_BASE self
function SET_BASE:Clear() function SET_BASE:Clear(TriggerEvent)
for Name, Object in pairs( self.Set ) do for Name, Object in pairs( self.Set ) do
self:Remove( Name ) self:Remove( Name, not TriggerEvent )
end end
return self return self
@@ -166,7 +173,7 @@ do -- SET_BASE
--- Gets a list of the Names of the Objects in the Set. --- Gets a list of the Names of the Objects in the Set.
-- @param #SET_BASE self -- @param #SET_BASE self
-- @return #SET_BASE self -- @return #table Table of names.
function SET_BASE:GetSetNames() -- R2.3 function SET_BASE:GetSetNames() -- R2.3
self:F2() self:F2()
@@ -181,7 +188,7 @@ do -- SET_BASE
--- Return a table of the Objects in the Set. --- Return a table of the Objects in the Set.
-- @param #SET_BASE self -- @param #SET_BASE self
-- @return #SET_BASE self -- @return #table Table of objects.
function SET_BASE:GetSetObjects() -- R2.3 function SET_BASE:GetSetObjects() -- R2.3
self:F2() self:F2()
@@ -197,16 +204,21 @@ do -- SET_BASE
--- Removes a @{Core.Base#BASE} object from the @{Core.Set#SET_BASE} and derived classes, based on the Object Name. --- Removes a @{Core.Base#BASE} object from the @{Core.Set#SET_BASE} and derived classes, based on the Object Name.
-- @param #SET_BASE self -- @param #SET_BASE self
-- @param #string ObjectName -- @param #string ObjectName
-- @param NoTriggerEvent (Optional) When `true`, the :Remove() method will not trigger a **Removed** event. -- @param #boolean NoTriggerEvent (Optional) When `true`, the :Remove() method will not trigger a **Removed** event.
function SET_BASE:Remove( ObjectName, NoTriggerEvent ) function SET_BASE:Remove( ObjectName, NoTriggerEvent )
self:F2( { ObjectName = ObjectName } ) self:F2( { ObjectName = ObjectName } )
local TriggerEvent = true local TriggerEvent = true
if NoTriggerEvent then TriggerEvent = false end if NoTriggerEvent then
TriggerEvent = false
else
TriggerEvent = true
end
local Object = self.Set[ObjectName] local Object = self.Set[ObjectName]
if Object then if Object then
for Index, Key in ipairs( self.Index ) do for Index, Key in ipairs( self.Index ) do
if Key == ObjectName then if Key == ObjectName then
table.remove( self.Index, Index ) table.remove( self.Index, Index )
@@ -214,6 +226,7 @@ do -- SET_BASE
break break
end end
end end
-- When NoTriggerEvent is true, then no Removed event will be triggered. -- When NoTriggerEvent is true, then no Removed event will be triggered.
if TriggerEvent then if TriggerEvent then
self:Removed( ObjectName, Object ) self:Removed( ObjectName, Object )
@@ -311,7 +324,6 @@ do -- SET_BASE
-- @param #SET_BASE self -- @param #SET_BASE self
-- @param Core.Set#SET_BASE SetB Set other set, called *B*. -- @param Core.Set#SET_BASE SetB Set other set, called *B*.
-- @return Core.Set#SET_BASE A set of objects that is included in set *A* **and** in set *B*. -- @return Core.Set#SET_BASE A set of objects that is included in set *A* **and** in set *B*.
function SET_BASE:GetSetIntersection(SetB) function SET_BASE:GetSetIntersection(SetB)
local intersection=SET_BASE:New() local intersection=SET_BASE:New()
@@ -463,16 +475,32 @@ do -- SET_BASE
-- @param #SET_BASE self -- @param #SET_BASE self
-- @return #SET_BASE self -- @return #SET_BASE self
function SET_BASE:FilterOnce() function SET_BASE:FilterOnce()
--self:Clear()
for ObjectName, Object in pairs( self.Database ) do for ObjectName, Object in pairs( self.Database ) do
if self:IsIncludeObject( Object ) then if self:IsIncludeObject( Object ) then
self:Add( ObjectName, Object ) self:Add( ObjectName, Object )
else
self:Remove(ObjectName, true)
end end
end end
return self return self
end end
--- Clear all filters. You still need to apply :FilterOnce()
-- @param #SET_BASE self
-- @return #SET_BASE self
function SET_BASE:FilterClear()
for key,value in pairs(self.Filter) do
self.Filter[key]={}
end
return self
end
--- Starts the filtering for the defined collection. --- Starts the filtering for the defined collection.
-- @param #SET_BASE self -- @param #SET_BASE self
@@ -819,7 +847,7 @@ do -- SET_BASE
--- Decides whether an object is in the SET --- Decides whether an object is in the SET
-- @param #SET_BASE self -- @param #SET_BASE self
-- @param #table Object -- @param #table Object
-- @return #SET_BASE self -- @return #boolean `true` if object is in set and `false` otherwise.
function SET_BASE:IsInSet( Object ) function SET_BASE:IsInSet( Object )
self:F3( Object ) self:F3( Object )
local outcome = false local outcome = false
@@ -1023,9 +1051,9 @@ do -- SET_GROUP
return self return self
end end
--- Gets the Set. --- Get a *new* set that only contains alive groups.
-- @param #SET_GROUP self -- @param #SET_GROUP self
-- @return #table Table of objects -- @return #SET_GROUP Set of alive groups.
function SET_GROUP:GetAliveSet() function SET_GROUP:GetAliveSet()
self:F2() self:F2()
@@ -1171,11 +1199,14 @@ do -- SET_GROUP
--- Builds a set of groups in zones. --- Builds a set of groups in zones.
-- @param #SET_GROUP self -- @param #SET_GROUP self
-- @param #table Zones Table of Core.Zone#ZONE Zone objects, or a Core.Set#SET_ZONE -- @param #table Zones Table of Core.Zone#ZONE Zone objects, or a Core.Set#SET_ZONE
-- @param #boolean Clear If `true`, clear any previously defined filters.
-- @return #SET_GROUP self -- @return #SET_GROUP self
function SET_GROUP:FilterZones( Zones ) function SET_GROUP:FilterZones( Zones, Clear )
if not self.Filter.Zones then
if Clear or not self.Filter.Zones then
self.Filter.Zones = {} self.Filter.Zones = {}
end end
local zones = {} local zones = {}
if Zones.ClassName and Zones.ClassName == "SET_ZONE" then if Zones.ClassName and Zones.ClassName == "SET_ZONE" then
zones = Zones.Set zones = Zones.Set
@@ -1185,34 +1216,12 @@ do -- SET_GROUP
else else
zones = Zones zones = Zones
end end
for _, Zone in pairs( zones ) do for _, Zone in pairs( zones ) do
local zonename = Zone:GetName() local zonename = Zone:GetName()
self.Filter.Zones[zonename] = Zone self.Filter.Zones[zonename] = Zone
end end
return self
end
--- Builds a set of groups in zones.
-- @param #SET_GROUP self
-- @param #table Zones Table of Core.Zone#ZONE Zone objects, or a Core.Set#SET_ZONE
-- @return #SET_GROUP self
function SET_GROUP:FilterZones( Zones )
if not self.Filter.Zones then
self.Filter.Zones = {}
end
local zones = {}
if Zones.ClassName and Zones.ClassName == "SET_ZONE" then
zones = Zones.Set
elseif type( Zones ) ~= "table" or (type( Zones ) == "table" and Zones.ClassName ) then
self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!")
return self
else
zones = Zones
end
for _,Zone in pairs( zones ) do
local zonename = Zone:GetName()
self.Filter.Zones[zonename] = Zone
end
return self return self
end end
@@ -1220,17 +1229,21 @@ do -- SET_GROUP
-- Possible current coalitions are red, blue and neutral. -- Possible current coalitions are red, blue and neutral.
-- @param #SET_GROUP self -- @param #SET_GROUP self
-- @param #string Coalitions Can take the following values: "red", "blue", "neutral". -- @param #string Coalitions Can take the following values: "red", "blue", "neutral".
-- @param #boolean Clear If `true`, clear any previously defined filters.
-- @return #SET_GROUP self -- @return #SET_GROUP self
function SET_GROUP:FilterCoalitions( Coalitions ) function SET_GROUP:FilterCoalitions( Coalitions, Clear )
if not self.Filter.Coalitions then
if Clear or (not self.Filter.Coalitions) then
self.Filter.Coalitions = {} self.Filter.Coalitions = {}
end end
if type( Coalitions ) ~= "table" then
Coalitions = { Coalitions } -- Ensure table.
end Coalitions = UTILS.EnsureTable(Coalitions, false)
for CoalitionID, Coalition in pairs( Coalitions ) do for CoalitionID, Coalition in pairs( Coalitions ) do
self.Filter.Coalitions[Coalition] = Coalition self.Filter.Coalitions[Coalition] = Coalition
end end
return self return self
end end
@@ -1238,17 +1251,22 @@ do -- SET_GROUP
-- Possible current categories are plane, helicopter, ground, ship. -- Possible current categories are plane, helicopter, ground, ship.
-- @param #SET_GROUP self -- @param #SET_GROUP self
-- @param #string Categories Can take the following values: "plane", "helicopter", "ground", "ship". -- @param #string Categories Can take the following values: "plane", "helicopter", "ground", "ship".
-- @param #boolean Clear If `true`, clear any previously defined filters.
-- @return #SET_GROUP self -- @return #SET_GROUP self
function SET_GROUP:FilterCategories( Categories ) function SET_GROUP:FilterCategories( Categories, Clear )
if not self.Filter.Categories then
if Clear or not self.Filter.Categories then
self.Filter.Categories = {} self.Filter.Categories = {}
end end
if type( Categories ) ~= "table" then if type( Categories ) ~= "table" then
Categories = { Categories } Categories = { Categories }
end end
for CategoryID, Category in pairs( Categories ) do for CategoryID, Category in pairs( Categories ) do
self.Filter.Categories[Category] = Category self.Filter.Categories[Category] = Category
end end
return self return self
end end
@@ -2127,9 +2145,11 @@ do -- SET_UNIT
if type( Coalitions ) ~= "table" then if type( Coalitions ) ~= "table" then
Coalitions = { Coalitions } Coalitions = { Coalitions }
end end
for CoalitionID, Coalition in pairs( Coalitions ) do for CoalitionID, Coalition in pairs( Coalitions ) do
self.Filter.Coalitions[Coalition] = Coalition self.Filter.Coalitions[Coalition] = Coalition
end end
return self return self
end end
@@ -4001,7 +4021,7 @@ do -- SET_CLIENT
--- Builds a set of clients in zones. --- Builds a set of clients in zones.
-- @param #SET_CLIENT self -- @param #SET_CLIENT self
-- @param #table Zones Table of Core.Zone#ZONE Zone objects, or a Core.Set#SET_ZONE -- @param #table Zones Table of Core.Zone#ZONE Zone objects, or a Core.Set#SET_ZONE
-- @return #SET_TABLE self -- @return #SET_CLIENT self
function SET_CLIENT:FilterZones( Zones ) function SET_CLIENT:FilterZones( Zones )
if not self.Filter.Zones then if not self.Filter.Zones then
self.Filter.Zones = {} self.Filter.Zones = {}
@@ -5784,8 +5804,7 @@ do -- SET_ZONE
-- If zones overlap, the first zone that validates the test is returned. -- If zones overlap, the first zone that validates the test is returned.
-- @param #SET_ZONE self -- @param #SET_ZONE self
-- @param Core.Point#COORDINATE Coordinate The coordinate to be searched. -- @param Core.Point#COORDINATE Coordinate The coordinate to be searched.
-- @return Core.Zone#ZONE_BASE The zone that validates the coordinate location. -- @return Core.Zone#ZONE_BASE The zone (if any) that validates the coordinate location.
-- @return #nil No zone has been found.
function SET_ZONE:IsCoordinateInZone( Coordinate ) function SET_ZONE:IsCoordinateInZone( Coordinate )
for _, Zone in pairs( self:GetSet() ) do for _, Zone in pairs( self:GetSet() ) do
@@ -5797,6 +5816,27 @@ do -- SET_ZONE
return nil return nil
end end
--- Get the closest zone to a given coordinate.
-- @param #SET_ZONE self
-- @param Core.Point#COORDINATE Coordinate The reference coordinate from which the closest zone is determined.
-- @return Core.Zone#ZONE_BASE The closest zone (if any).
-- @return #number Distance to ref coordinate in meters.
function SET_ZONE:GetClosestZone( Coordinate )
local dmin=math.huge
local zmin=nil
for _, Zone in pairs( self:GetSet() ) do
local Zone = Zone -- Core.Zone#ZONE_BASE
local d=Zone:Get2DDistance(Coordinate)
if d<dmin then
dmin=d
zmin=Zone
end
end
return zmin, dmin
end
end end
@@ -6111,6 +6151,458 @@ do -- SET_ZONE_GOAL
end end
do -- SET_OPSZONE
--- @type SET_OPSZONE
-- @extends Core.Set#SET_BASE
--- Mission designers can use the @{Core.Set#SET_OPSZONE} class to build sets of zones of various types.
--
-- ## SET_OPSZONE constructor
--
-- Create a new SET_OPSZONE object with the @{#SET_OPSZONE.New} method:
--
-- * @{#SET_OPSZONE.New}: Creates a new SET_OPSZONE object.
--
-- ## Add or Remove ZONEs from SET_OPSZONE
--
-- ZONEs can be added and removed using the @{Core.Set#SET_OPSZONE.AddZonesByName} and @{Core.Set#SET_OPSZONE.RemoveZonesByName} respectively.
-- These methods take a single ZONE name or an array of ZONE names to be added or removed from SET_OPSZONE.
--
-- ## SET_OPSZONE filter criteria
--
-- You can set filter criteria to build the collection of zones in SET_OPSZONE.
-- Filter criteria are defined by:
--
-- * @{#SET_OPSZONE.FilterPrefixes}: Builds the SET_OPSZONE with the zones having a certain text pattern in their name. **ATTENTION!** Bad naming convention as this *does not* only filter *prefixes*.
--
-- Once the filter criteria have been set for the SET_OPSZONE, you can start filtering using:
--
-- * @{#SET_OPSZONE.FilterStart}: Starts the filtering of the zones within the SET_OPSZONE.
--
-- ## SET_OPSZONE iterators
--
-- Once the filters have been defined and the SET_OPSZONE has been built, you can iterate the SET_OPSZONE with the available iterator methods.
-- The iterator methods will walk the SET_OPSZONE set, and call for each airbase within the set a function that you provide.
-- The following iterator methods are currently available within the SET_OPSZONE:
--
-- * @{#SET_OPSZONE.ForEachZone}: Calls a function for each zone it finds within the SET_OPSZONE.
--
-- ===
-- @field #SET_OPSZONE SET_OPSZONE
SET_OPSZONE = {
ClassName = "SET_OPSZONE",
Zones = {},
Filter = {
Prefixes = nil,
Coalitions = nil,
},
FilterMeta = {
Coalitions = {
red = coalition.side.RED,
blue = coalition.side.BLUE,
neutral = coalition.side.NEUTRAL,
},
}, --FilterMeta
}
--- Creates a new SET_OPSZONE object, building a set of zones.
-- @param #SET_OPSZONE self
-- @return #SET_OPSZONE self
function SET_OPSZONE:New()
-- Inherits from BASE
local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.OPSZONES ) )
return self
end
--- Add an OPSZONE to set.
-- @param Core.Set#SET_OPSZONE self
-- @param Ops.OpsZone#OPSZONE Zone The OPSZONE object.
-- @return self
function SET_OPSZONE:AddZone( Zone )
self:Add( Zone:GetName(), Zone )
return self
end
--- Remove ZONEs from SET_OPSZONE.
-- @param Core.Set#SET_OPSZONE self
-- @param #table RemoveZoneNames A single name or an array of OPSZONE names.
-- @return self
function SET_OPSZONE:RemoveZonesByName( RemoveZoneNames )
local RemoveZoneNamesArray = (type( RemoveZoneNames ) == "table") and RemoveZoneNames or { RemoveZoneNames }
--UTILS.EnsureTable(Object,ReturnNil)
for RemoveZoneID, RemoveZoneName in pairs( RemoveZoneNamesArray ) do
self:Remove( RemoveZoneName )
end
return self
end
--- Finds a Zone based on its name.
-- @param #SET_OPSZONE self
-- @param #string ZoneName
-- @return Ops.OpsZone#OPSZONE The found Zone.
function SET_OPSZONE:FindZone( ZoneName )
local ZoneFound = self.Set[ZoneName]
return ZoneFound
end
--- Get a random zone from the set.
-- @param #SET_OPSZONE self
-- @return Ops.OpsZone#OPSZONE The random Zone.
function SET_OPSZONE:GetRandomZone()
if self:Count() ~= 0 then
local Index = self.Index
local ZoneFound = nil -- Core.Zone#ZONE_BASE
-- Loop until a zone has been found.
-- The :GetZoneMaybe() call will evaluate the probability for the zone to be selected.
-- If the zone is not selected, then nil is returned by :GetZoneMaybe() and the loop continues!
while not ZoneFound do
local ZoneRandom = math.random( 1, #Index )
ZoneFound = self.Set[Index[ZoneRandom]]:GetZoneMaybe()
end
return ZoneFound
end
return nil
end
--- Set a zone probability.
-- @param #SET_OPSZONE self
-- @param #string ZoneName The name of the zone.
-- @param #number Probability The probability in percent.
function SET_OPSZONE:SetZoneProbability( ZoneName, Probability )
local Zone = self:FindZone( ZoneName )
Zone:SetZoneProbability( Probability )
return self
end
--- Builds a set of OPSZONEs that contain the given string in their name.
-- **ATTENTION!** Bad naming convention as this **does not** filter only **prefixes** but all zones that **contain** the string.
-- @param #SET_OPSZONE self
-- @param #string Prefixes The string pattern(s) that needs to be contained in the zone name. Can also be passed as a `#table` of strings.
-- @return #SET_OPSZONE self
function SET_OPSZONE:FilterPrefixes( Prefixes )
if not self.Filter.Prefixes then
self.Filter.Prefixes = {}
end
Prefixes=UTILS.EnsureTable(Prefixes, false)
for PrefixID, Prefix in pairs( Prefixes ) do
self.Filter.Prefixes[Prefix] = Prefix
end
return self
end
--- Builds a set of groups of coalitions. Possible current coalitions are red, blue and neutral.
-- @param #SET_OPSZONE self
-- @param #string Coalitions Can take the following values: "red", "blue", "neutral" or combinations as a table, for example `{"red", "neutral"}`.
-- @return #SET_OPSZONE self
function SET_OPSZONE:FilterCoalitions(Coalitions)
-- Create an empty set.
if not self.Filter.Coalitions then
self.Filter.Coalitions={}
end
-- Ensure we got a table.
Coalitions=UTILS.EnsureTable(Coalitions, false)
-- Set filter.
for CoalitionID, Coalition in pairs( Coalitions ) do
self.Filter.Coalitions[Coalition] = Coalition
end
return self
end
--- Filters for the defined collection.
-- @param #SET_OPSZONE self
-- @return #SET_OPSZONE self
function SET_OPSZONE:FilterOnce()
for ObjectName, Object in pairs( self.Database ) do
-- First remove the object (without creating an event).
self:Remove(ObjectName, true)
if self:IsIncludeObject( Object ) then
self:Add( ObjectName, Object )
end
end
return self
end
--- Clear all filters. You still need to apply `FilterOnce()` to have an effect on the set.
-- @param #SET_OPSZONE self
-- @return #SET_OPSZONE self
function SET_OPSZONE:FilterClear()
local parent=self:GetParent(self, SET_OPSZONE) --#SET_BASE
parent:FilterClear()
return self
end
--- Starts the filtering.
-- @param #SET_OPSZONE self
-- @return #SET_OPSZONE self
function SET_OPSZONE:FilterStart()
if _DATABASE then
-- We initialize the first set.
for ObjectName, Object in pairs( self.Database ) do
if self:IsIncludeObject( Object ) then
self:Add( ObjectName, Object )
else
self:RemoveZonesByName( ObjectName )
end
end
end
self:HandleEvent( EVENTS.NewZoneGoal )
self:HandleEvent( EVENTS.DeleteZoneGoal )
return self
end
--- Stops the filtering for the defined collection.
-- @param #SET_OPSZONE self
-- @return #SET_OPSZONE self
function SET_OPSZONE:FilterStop()
self:UnHandleEvent( EVENTS.NewZoneGoal )
self:UnHandleEvent( EVENTS.DeleteZoneGoal )
return self
end
--- Handles the Database to check on an event (birth) that the Object was added in the Database.
-- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event!
-- @param #SET_OPSZONE self
-- @param Core.Event#EVENTDATA Event
-- @return #string The name of the AIRBASE
-- @return #table The AIRBASE
function SET_OPSZONE:AddInDatabase( Event )
self:F3( { Event } )
return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName]
end
--- Handles the Database to check on any event that Object exists in the Database.
-- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa!
-- @param #SET_OPSZONE self
-- @param Core.Event#EVENTDATA Event
-- @return #string The name of the AIRBASE
-- @return #table The AIRBASE
function SET_OPSZONE:FindInDatabase( Event )
self:F3( { Event } )
return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName]
end
--- Iterate the SET_OPSZONE and call an iterator function for each ZONE, providing the ZONE and optional parameters.
-- @param #SET_OPSZONE self
-- @param #function IteratorFunction The function that will be called when there is an alive ZONE in the SET_OPSZONE. The function needs to accept a AIRBASE parameter.
-- @return #SET_OPSZONE self
function SET_OPSZONE:ForEachZone( IteratorFunction, ... )
self:F2( arg )
self:ForEach( IteratorFunction, arg, self:GetSet() )
return self
end
--- Private function that checks if an object is contained in the set or filtered.
-- @param #SET_OPSZONE self
-- @param Ops.OpsZone#OPSZONE MZone The OPSZONE object.
-- @return #SET_OPSZONE self
function SET_OPSZONE:IsIncludeObject( MZone )
self:F2( MZone )
local MZoneInclude = true
if MZone then
local MZoneName = MZone:GetName()
if self.Filter.Prefixes then
local MZonePrefix = false
-- Loop over prefixes.
for ZonePrefixId, ZonePrefix in pairs( self.Filter.Prefixes ) do
-- Prifix
self:T3( { "Prefix:", string.find( MZoneName, ZonePrefix, 1 ), ZonePrefix } )
if string.find(MZoneName, ZonePrefix, 1) then
MZonePrefix = true
break --Break the loop as we found the prefix.
end
end
self:T( { "Evaluated Prefix", MZonePrefix } )
MZoneInclude = MZoneInclude and MZonePrefix
end
-- Filter coalitions.
if self.Filter.Coalitions then
local MGroupCoalition = false
local coalition=MZone:GetOwner()
for _, CoalitionName in pairs( self.Filter.Coalitions ) do
if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName]==coalition then
MGroupCoalition = true
break -- Break the loop the coalition is contains.
end
end
MZoneInclude = MZoneInclude and MGroupCoalition
end
end
self:T2( MZoneInclude )
return MZoneInclude
end
--- Handles the OnEventNewZone event for the Set.
-- @param #SET_OPSZONE self
-- @param Core.Event#EVENTDATA EventData
function SET_OPSZONE:OnEventNewZoneGoal( EventData )
-- Debug info.
self:T( { "New Zone Capture Coalition", EventData } )
self:T( { "Zone Capture Coalition", EventData.ZoneGoal } )
if EventData.ZoneGoal then
if EventData.ZoneGoal and self:IsIncludeObject( EventData.ZoneGoal ) then
self:T( { "Adding Zone Capture Coalition", EventData.ZoneGoal.ZoneName, EventData.ZoneGoal } )
self:Add( EventData.ZoneGoal.ZoneName, EventData.ZoneGoal )
end
end
end
--- Handles the OnDead or OnCrash event for alive units set.
-- @param #SET_OPSZONE self
-- @param Core.Event#EVENTDATA EventData
function SET_OPSZONE:OnEventDeleteZoneGoal( EventData ) -- R2.1
self:F3( { EventData } )
if EventData.ZoneGoal then
local Zone = _DATABASE:FindZone( EventData.ZoneGoal.ZoneName )
if Zone and Zone.ZoneName then
-- When cargo was deleted, it may probably be because of an S_EVENT_DEAD.
-- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call.
-- And this is a problem because it will remove all entries from the SET_OPSZONEs.
-- To prevent this from happening, the Zone object has a flag NoDestroy.
-- When true, the SET_OPSZONE won't Remove the Zone object from the set.
-- This flag is switched off after the event handlers have been called in the EVENT class.
self:F( { ZoneNoDestroy = Zone.NoDestroy } )
if Zone.NoDestroy then
else
self:Remove( Zone.ZoneName )
end
end
end
end
--- Validate if a coordinate is in one of the zones in the set.
-- Returns the ZONE object where the coordiante is located.
-- If zones overlap, the first zone that validates the test is returned.
-- @param #SET_OPSZONE self
-- @param Core.Point#COORDINATE Coordinate The coordinate to be searched.
-- @return Core.Zone#ZONE_BASE The zone that validates the coordinate location.
-- @return #nil No zone has been found.
function SET_OPSZONE:IsCoordinateInZone( Coordinate )
for _,_Zone in pairs( self:GetSet() ) do
local Zone = _Zone --Ops.OpsZone#OPSZONE
if Zone:GetZone():IsCoordinateInZone( Coordinate ) then
return Zone
end
end
return nil
end
--- Get the closest OPSZONE from a given reference coordinate.
-- @param #SET_OPSZONE self
-- @param Core.Point#COORDINATE Coordinate The reference coordinate from which the closest zone is determined.
-- @param #table Coalitions Only consider the given coalition(s), *e.g.* `{coaliton.side.RED}` to find the closest red zone.
-- @return Ops.OpsZone#OPSZONE The closest OPSZONE (if any).
-- @return #number Distance to ref coordinate in meters.
function SET_OPSZONE:GetClosestZone( Coordinate, Coalitions )
env.info("FF 100",showMessageBox)
Coalitions=UTILS.EnsureTable(Coalitions, true)
local dmin=math.huge --#number
local zmin=nil --Ops.OpsZone#OPSZONE
for _,_opszone in pairs(self:GetSet()) do
local opszone=_opszone --Ops.OpsZone#OPSZONE
local coal=opszone:GetOwner()
env.info("FF 200",showMessageBox)
env.info("In table="..tostring(UTILS.IsInTable(Coalitions, coal)))
BASE:I(Coalitions)
BASE:I(coal)
if Coalitions==nil or (Coalitions and UTILS.IsInTable(Coalitions, coal)) then
env.info("FF 300",showMessageBox)
-- Get 2D distance.
local d=opszone:GetZone():Get2DDistance(Coordinate)
if d<dmin then
env.info("FF 400",showMessageBox)
dmin=d
zmin=opszone
end
end
end
return zmin, dmin
end
end
do -- SET_OPSGROUP do -- SET_OPSGROUP
@@ -6233,7 +6725,7 @@ do -- SET_OPSGROUP
return self return self
end end
--- Gets the Set. --- Gets a **new** set that only contains alive groups.
-- @param #SET_OPSGROUP self -- @param #SET_OPSGROUP self
-- @return #SET_OPSGROUP self -- @return #SET_OPSGROUP self
function SET_OPSGROUP:GetAliveSet() function SET_OPSGROUP:GetAliveSet()
@@ -6398,11 +6890,12 @@ do -- SET_OPSGROUP
-- Possible current coalitions are red, blue and neutral. -- Possible current coalitions are red, blue and neutral.
-- @param #SET_OPSGROUP self -- @param #SET_OPSGROUP self
-- @param #string Coalitions Can take the following values: "red", "blue", "neutral" or combinations as a table, for example `{"red", "neutral"}`. -- @param #string Coalitions Can take the following values: "red", "blue", "neutral" or combinations as a table, for example `{"red", "neutral"}`.
-- @param #boolean Clear If `true`, clear any previously defined filters.
-- @return #SET_OPSGROUP self -- @return #SET_OPSGROUP self
function SET_OPSGROUP:FilterCoalitions(Coalitions) function SET_OPSGROUP:FilterCoalitions(Coalitions, Clear)
-- Create an empty set. -- Create an empty set.
if not self.Filter.Coalitions then if Clear or not self.Filter.Coalitions then
self.Filter.Coalitions={} self.Filter.Coalitions={}
end end
@@ -6431,10 +6924,11 @@ do -- SET_OPSGROUP
-- --
-- @param #SET_OPSGROUP self -- @param #SET_OPSGROUP self
-- @param #string Categories Can take the following values: "plane", "helicopter", "ground", "ship" or combinations as a table, for example `{"plane", "helicopter"}`. -- @param #string Categories Can take the following values: "plane", "helicopter", "ground", "ship" or combinations as a table, for example `{"plane", "helicopter"}`.
-- @param #boolean Clear If `true`, clear any previously defined filters.
-- @return #SET_OPSGROUP self -- @return #SET_OPSGROUP self
function SET_OPSGROUP:FilterCategories( Categories ) function SET_OPSGROUP:FilterCategories( Categories, Clear )
if not self.Filter.Categories then if Clear or not self.Filter.Categories then
self.Filter.Categories={} self.Filter.Categories={}
end end
@@ -6492,11 +6986,12 @@ do -- SET_OPSGROUP
--- Builds a set of groups of defined countries. --- Builds a set of groups of defined countries.
-- @param #SET_OPSGROUP self -- @param #SET_OPSGROUP self
-- @param #string Countries Can take those country strings known within DCS world. -- @param #string Countries Can take those country strings known within DCS world.
-- @param #boolean Clear If `true`, clear any previously defined filters.
-- @return #SET_OPSGROUP self -- @return #SET_OPSGROUP self
function SET_OPSGROUP:FilterCountries(Countries) function SET_OPSGROUP:FilterCountries(Countries, Clear)
-- Create empty table if necessary. -- Create empty table if necessary.
if not self.Filter.Countries then if Clear or not self.Filter.Countries then
self.Filter.Countries = {} self.Filter.Countries = {}
end end
@@ -6518,11 +7013,12 @@ do -- SET_OPSGROUP
-- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all groups that **contain** the string. -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all groups that **contain** the string.
-- @param #SET_OPSGROUP self -- @param #SET_OPSGROUP self
-- @param #string Prefixes The string pattern(s) that needs to be contained in the group name. Can also be passed as a `#table` of strings. -- @param #string Prefixes The string pattern(s) that needs to be contained in the group name. Can also be passed as a `#table` of strings.
-- @param #boolean Clear If `true`, clear any previously defined filters.
-- @return #SET_OPSGROUP self -- @return #SET_OPSGROUP self
function SET_OPSGROUP:FilterPrefixes(Prefixes) function SET_OPSGROUP:FilterPrefixes(Prefixes, Clear)
-- Create emtpy table if necessary. -- Create emtpy table if necessary.
if not self.Filter.GroupPrefixes then if Clear or not self.Filter.GroupPrefixes then
self.Filter.GroupPrefixes={} self.Filter.GroupPrefixes={}
end end

View File

@@ -2607,6 +2607,15 @@ function AUFTRAG:SetRepeatOnSuccess(Nrepeat)
return self return self
end end
--- **[LEGION, COMMANDER, CHIEF]** Set that mission assets get reinforced if their number drops below Nmin.
-- @param #AUFTRAG self
-- @param #number Nreinforce Number of max asset groups used to reinforce.
-- @return #AUFTRAG self
function AUFTRAG:SetReinforce(Nreinforce)
self.reinforce=Nreinforce
return self
end
--- **[LEGION, COMMANDER, CHIEF]** Define how many assets are required to do the job. Only used if the mission is handled by a **LEGION** (AIRWING, BRIGADE, ...) or higher level. --- **[LEGION, COMMANDER, CHIEF]** Define how many assets are required to do the job. Only used if the mission is handled by a **LEGION** (AIRWING, BRIGADE, ...) or higher level.
-- @param #AUFTRAG self -- @param #AUFTRAG self
-- @param #number NassetsMin Minimum number of asset groups. Default 1. -- @param #number NassetsMin Minimum number of asset groups. Default 1.
@@ -2628,24 +2637,32 @@ end
--- **[LEGION, COMMANDER, CHIEF]** Get number of required assets. --- **[LEGION, COMMANDER, CHIEF]** Get number of required assets.
-- @param #AUFTRAG self -- @param #AUFTRAG self
-- @param Ops.Legion#Legion Legion (Optional) Only get the required assets for a specific legion. If required assets for this legion are not defined, the total number is returned.
-- @return #number Min. number of required assets. -- @return #number Min. number of required assets.
-- @return #number Max. number of required assets. -- @return #number Max. number of required assets.
function AUFTRAG:GetRequiredAssets(Legion) function AUFTRAG:GetRequiredAssets()
--local N=self.nassets
--if Legion and self.Nassets[Legion.alias] then
-- N=self.Nassets[Legion.alias]
--end
local Nmin=self.NassetsMin local Nmin=self.NassetsMin
local Nmax=self.NassetsMax local Nmax=self.NassetsMax
if self.type==AUFTRAG.Type.RELOCATECOHORT then if self.type==AUFTRAG.Type.RELOCATECOHORT then
-- Relocation gets all the assets.
local cohort=self.DCStask.params.cohort --Ops.Cohort#COHORT local cohort=self.DCStask.params.cohort --Ops.Cohort#COHORT
Nmin=#cohort.assets Nmin=#cohort.assets
Nmax=Nmin Nmax=Nmin
else
-- Check if this is an reinforcement.
if self:IsExecuting() and self.reinforce and self.reinforce>0 then
local N=self:CountOpsGroups()
if N<Nmin then
Nmin=math.min(Nmin-N, self.reinforce)
Nmax=Nmin
self:T(self.lid..string.format("FF Executing Nmin=%d, N=%d, Nreinfoce=%d ==> Nmin=%d", self.NassetsMin, N, self.reinforce, Nmin))
end
end
end end
return Nmin, Nmax return Nmin, Nmax
@@ -2905,6 +2922,7 @@ function AUFTRAG:AddTransportCarriers(Carriers)
end end
return self
end end
--- **[LEGION, COMMANDER, CHIEF]** Set required attribute(s) the assets must have. --- **[LEGION, COMMANDER, CHIEF]** Set required attribute(s) the assets must have.
@@ -2912,10 +2930,8 @@ end
-- @param #table Attributes Generalized attribute(s). -- @param #table Attributes Generalized attribute(s).
-- @return #AUFTRAG self -- @return #AUFTRAG self
function AUFTRAG:SetRequiredAttribute(Attributes) function AUFTRAG:SetRequiredAttribute(Attributes)
if Attributes and type(Attributes)~="table" then self.attributes=UTILS.EnsureTable(Attributes, true)
Attributes={Attributes} return self
end
self.attributes=Attributes
end end
--- **[LEGION, COMMANDER, CHIEF]** Set required property or properties the assets must have. --- **[LEGION, COMMANDER, CHIEF]** Set required property or properties the assets must have.
@@ -2924,10 +2940,8 @@ end
-- @param #table Properties Property or table of properties. -- @param #table Properties Property or table of properties.
-- @return #AUFTRAG self -- @return #AUFTRAG self
function AUFTRAG:SetRequiredProperty(Properties) function AUFTRAG:SetRequiredProperty(Properties)
if Properties and type(Properties)~="table" then self.properties=UTILS.EnsureTable(Properties, true)
Properties={Properties} return self
end
self.properties=Properties
end end
--- **[LEGION, COMMANDER, CHIEF]** Set number of required carrier groups if an OPSTRANSPORT assignment is required. --- **[LEGION, COMMANDER, CHIEF]** Set number of required carrier groups if an OPSTRANSPORT assignment is required.
@@ -3798,7 +3812,7 @@ function AUFTRAG:onafterStatus(From, Event, To)
self:T(self.lid.."No targets left cancelling mission!") self:T(self.lid.."No targets left cancelling mission!")
self:Cancel() self:Cancel()
elseif self:IsExecuting() then elseif self:IsExecuting() and ((not self.reinforce) or self.reinforce==0) then
-- Had the case that mission was in state Executing but all assigned groups were dead. -- Had the case that mission was in state Executing but all assigned groups were dead.
-- TODO: might need to loop over all assigned groups -- TODO: might need to loop over all assigned groups
@@ -4327,6 +4341,12 @@ function AUFTRAG:CheckGroupsDone()
self:T2(self.lid..string.format("CheckGroupsDone: Mission is still in state %s [FSM=%s] (PLANNED or QUEUED or REQUESTED). Mission NOT DONE!", self.status, self:GetState())) self:T2(self.lid..string.format("CheckGroupsDone: Mission is still in state %s [FSM=%s] (PLANNED or QUEUED or REQUESTED). Mission NOT DONE!", self.status, self:GetState()))
return false return false
end end
-- Check if there is still reinforcement to be expected.
if self:IsExecuting() and self.reinforce and self.reinforce>0 then
self:T2(self.lid..string.format("CheckGroupsDone: Mission is still in state %s [FSM=%s] and reinfoce=%d. Mission NOT DONE!", self.status, self:GetState(), self.reinforce))
return false
end
-- It could be that all flights were destroyed on the way to the mission execution waypoint. -- It could be that all flights were destroyed on the way to the mission execution waypoint.
-- TODO: would be better to check if everybody is dead by now. -- TODO: would be better to check if everybody is dead by now.
@@ -4486,7 +4506,7 @@ function AUFTRAG:onafterAssetDead(From, Event, To, Asset)
self:T(self.lid..string.format("Asset %s dead! Number of ops groups remaining %d", tostring(Asset.spawngroupname), N)) self:T(self.lid..string.format("Asset %s dead! Number of ops groups remaining %d", tostring(Asset.spawngroupname), N))
-- All assets dead? -- All assets dead?
if N==0 then if N==0 and (self.reinforce==nil or self.reinforce==0) then
if self:IsNotOver() then if self:IsNotOver() then

View File

@@ -176,7 +176,11 @@ function COHORT:New(TemplateGroupName, Ngroups, CohortName)
for i,_unit in pairs(units) do for i,_unit in pairs(units) do
local unit=_unit --Wrapper.Unit#UNIT local unit=_unit --Wrapper.Unit#UNIT
local desc=unit:GetDesc() local desc=unit:GetDesc()
self.weightAsset=self.weightAsset + (desc.massMax or 666) local mass=666
if desc then
mass=desc.massMax or desc.massEmpty
end
self.weightAsset=self.weightAsset + (mass or 666)
if i==1 then if i==1 then
self.cargobayLimit=unit:GetCargoBayFreeWeight() self.cargobayLimit=unit:GetCargoBayFreeWeight()
end end

View File

@@ -1671,25 +1671,51 @@ function COMMANDER:RecruitAssetsForMission(Mission)
-- Debug info. -- Debug info.
self:T2(self.lid..string.format("Recruiting assets for mission \"%s\" [%s]", Mission:GetName(), Mission:GetType())) self:T2(self.lid..string.format("Recruiting assets for mission \"%s\" [%s]", Mission:GetName(), Mission:GetType()))
-- Cohorts.
local Cohorts=self:_GetCohorts(Mission.specialLegions, Mission.specialCohorts, Mission.operation)
-- Debug info.
self:T(self.lid..string.format("Found %d cohort candidates for mission", #Cohorts))
-- Number of required assets. -- Number of required assets.
local NreqMin, NreqMax=Mission:GetRequiredAssets() local NreqMin, NreqMax=Mission:GetRequiredAssets()
-- Target position. -- Target position.
local TargetVec2=Mission:GetTargetVec2() local TargetVec2=Mission:GetTargetVec2()
-- Special payloads. -- Special payloads.
local Payloads=Mission.payloads local Payloads=Mission.payloads
-- Largest cargo bay available of available carrier assets if mission assets need to be transported.
local MaxWeight=nil
if Mission.NcarriersMin then
-- Get transport cohorts.
local Cohorts=LEGION._GetCohorts(Mission.transportLegions or self.legions, Mission.transportCohorts)
-- Filter cohorts that can actually perform transport missions.
local transportcohorts={}
for _,_cohort in pairs(Cohorts) do
local cohort=_cohort --Ops.Cohort#COHORT
-- Check if cohort can perform transport to target.
--TODO: Option to filter transport carrier asset categories, attributes and/or properties.
local can=LEGION._CohortCan(cohort, AUFTRAG.Type.OPSTRANSPORT, Categories, Attributes, Properties, nil, TargetVec2)
-- MaxWeight of cargo assets is limited by the largets available cargo bay. We don't want to select, e.g., tanks that cannot be transported by APCs or helos.
if can and (MaxWeight==nil or cohort.cargobayLimit>MaxWeight) then
MaxWeight=cohort.cargobayLimit
end
end
self:T(self.lid..string.format("Largest cargo bay available=%.1f", MaxWeight))
end
-- Get cohorts.
local Cohorts=LEGION._GetCohorts(Mission.specialLegions or self.legions, Mission.specialCohorts, Mission.operation, self.opsqueue)
-- Debug info.
self:T(self.lid..string.format("Found %d cohort candidates for mission", #Cohorts))
-- Recruite assets. -- Recruite assets.
local recruited, assets, legions=LEGION.RecruitCohortAssets(Cohorts, Mission.type, Mission.alert5MissionType, NreqMin, NreqMax, TargetVec2, Payloads, local recruited, assets, legions=LEGION.RecruitCohortAssets(Cohorts, Mission.type, Mission.alert5MissionType, NreqMin, NreqMax, TargetVec2, Payloads,
Mission.engageRange, Mission.refuelSystem, nil, nil, nil, Mission.attributes, Mission.properties, {Mission.engageWeaponType}) Mission.engageRange, Mission.refuelSystem, nil, nil, MaxWeight, nil, Mission.attributes, Mission.properties, {Mission.engageWeaponType})
return recruited, assets, legions return recruited, assets, legions
end end

View File

@@ -665,9 +665,24 @@ function LEGION:CheckMissionQueue()
-- Look for first task that is not accomplished. -- Look for first task that is not accomplished.
for _,_mission in pairs(self.missionqueue) do for _,_mission in pairs(self.missionqueue) do
local mission=_mission --Ops.Auftrag#AUFTRAG local mission=_mission --Ops.Auftrag#AUFTRAG
-- Check if reinforcement is necessary.
local reinforce=false
if mission:IsExecuting() and mission.reinforce and mission.reinforce>0 then
local N=mission:CountOpsGroups()
local Nmin, Nmax=mission:GetRequiredAssets()
if N<Nmin then
reinforce=true
end
end
mission:CountOpsGroups()
-- Firstly, check if mission is due? -- Firstly, check if mission is due?
if mission:IsQueued(self) and mission:IsReadyToGo() and (mission.importance==nil or mission.importance<=vip) then if (mission:IsQueued(self) or reinforce) and mission:IsReadyToGo() and (mission.importance==nil or mission.importance<=vip) then
-- Recruit best assets for the job. -- Recruit best assets for the job.
local recruited, assets, legions=self:RecruitAssetsForMission(mission) local recruited, assets, legions=self:RecruitAssetsForMission(mission)
@@ -693,8 +708,11 @@ function LEGION:CheckMissionQueue()
-- Recruit carrier assets for transport. -- Recruit carrier assets for transport.
local Transport=nil local Transport=nil
if mission.NcarriersMin then if mission.NcarriersMin then
-- Transport legions.
local Legions=mission.transportLegions or {self} local Legions=mission.transportLegions or {self}
-- Assign carrier assets for transport.
TransportAvail, Transport=self:AssignAssetsForTransport(Legions, assets, mission.NcarriersMin, mission.NcarriersMax, mission.transportDeployZone, mission.transportDisembarkZone) TransportAvail, Transport=self:AssignAssetsForTransport(Legions, assets, mission.NcarriersMin, mission.NcarriersMax, mission.transportDeployZone, mission.transportDisembarkZone)
end end
@@ -706,8 +724,10 @@ function LEGION:CheckMissionQueue()
end end
if EscortAvail and TransportAvail then if EscortAvail and TransportAvail then
-- Got a mission. -- Got a mission.
self:MissionRequest(mission) self:MissionRequest(mission)
return true return true
else else
-- Recruited assets but no requested escort available. Unrecruit assets! -- Recruited assets but no requested escort available. Unrecruit assets!
@@ -2133,29 +2153,39 @@ function LEGION:RecruitAssetsForMission(Mission)
-- Payloads. -- Payloads.
local Payloads=Mission.payloads local Payloads=Mission.payloads
-- Get special escort legions and/or cohorts. -- Largest cargo bay available of available carrier assets if mission assets need to be transported.
local Cohorts={} local MaxWeight=nil
for _,_legion in pairs(Mission.specialLegions or {}) do
local legion=_legion --Ops.Legion#LEGION if Mission.NcarriersMin then
for _,_cohort in pairs(legion.cohorts) do
local cohort=_cohort --Ops.Cohort#COHORT -- Get transport cohorts.
table.insert(Cohorts, cohort) local Cohorts=LEGION._GetCohorts(Mission.transportLegions or {self}, Mission.transportCohorts or self.cohorts)
end
end
for _,_cohort in pairs(Mission.specialCohorts or {}) do
local cohort=_cohort --Ops.Cohort#COHORT
table.insert(Cohorts, cohort)
end
-- No escort cohorts/legions given ==> take own cohorts.
if #Cohorts==0 then
Cohorts=self.cohorts
end
-- Filter cohorts that can actually perform transport missions.
local transportcohorts={}
for _,_cohort in pairs(Cohorts) do
local cohort=_cohort --Ops.Cohort#COHORT
-- Check if cohort can perform transport to target.
--TODO: Option to filter transport carrier asset categories, attributes and/or properties.
local can=LEGION._CohortCan(cohort, AUFTRAG.Type.OPSTRANSPORT, Categories, Attributes, Properties, nil, TargetVec2)
-- MaxWeight of cargo assets is limited by the largets available cargo bay. We don't want to select, e.g., tanks that cannot be transported by APCs or helos.
if can and (MaxWeight==nil or cohort.cargobayLimit>MaxWeight) then
MaxWeight=cohort.cargobayLimit
end
end
self:T(self.lid..string.format("Largest cargo bay available=%.1f", MaxWeight))
end
-- Get cohorts.
local Cohorts=LEGION._GetCohorts(Mission.specialLegions or {self}, Mission.specialCohorts or self.cohorts, Operation, OpsQueue)
-- Recuit assets. -- Recuit assets.
local recruited, assets, legions=LEGION.RecruitCohortAssets(Cohorts, Mission.type, Mission.alert5MissionType, NreqMin, NreqMax, TargetVec2, Payloads, local recruited, assets, legions=LEGION.RecruitCohortAssets(Cohorts, Mission.type, Mission.alert5MissionType, NreqMin, NreqMax, TargetVec2, Payloads,
Mission.engageRange, Mission.refuelSystem, nil, nil, nil, Mission.attributes, Mission.properties, {Mission.engageWeaponType}) Mission.engageRange, Mission.refuelSystem, nil, nil, MaxWeight, nil, Mission.attributes, Mission.properties, {Mission.engageWeaponType})
return recruited, assets, legions return recruited, assets, legions
end end
@@ -2249,42 +2279,118 @@ function LEGION:RecruitAssetsForEscort(Mission, Assets)
return true return true
end end
--- Get cohorts.
-- @param #table Legions Special legions.
-- @param #table Cohorts Special cohorts.
-- @param Ops.Operation#OPERATION Operation Operation.
-- @param #table OpsQueue Queue of operations.
-- @return #table Cohorts.
function LEGION._GetCohorts(Legions, Cohorts, Operation, OpsQueue)
OpsQueue=OpsQueue or {}
--- Function that check if a legion or cohort is part of an operation.
local function CheckOperation(LegionOrCohort)
-- No operations ==> no problem!
if #OpsQueue==0 then
return true
end
-- Cohort is not dedicated to a running(!) operation. We assume so.
local isAvail=true
-- Only available...
if Operation then
isAvail=false
end
for _,_operation in pairs(OpsQueue) do
local operation=_operation --Ops.Operation#OPERATION
-- Legion is assigned to this operation.
local isOps=operation:IsAssignedCohortOrLegion(LegionOrCohort)
if isOps and operation:IsRunning() then
-- Is dedicated.
isAvail=false
if Operation==nil then
-- No Operation given and this is dedicated to at least one operation.
return false
else
if Operation.uid==operation.uid then
-- Operation given and is part of it.
return true
end
end
end
end
return isAvail
end
-- Chosen cohorts.
local cohorts={}
-- Check if there are any special legions and/or cohorts.
if (Legions and #Legions>0) or (Cohorts and #Cohorts>0) then
-- Add cohorts of special legions.
for _,_legion in pairs(Legions or {}) do
local legion=_legion --Ops.Legion#LEGION
-- Check that runway is operational.
local Runway=legion:IsAirwing() and legion:IsRunwayOperational() or true
-- Legion has to be running.
if legion:IsRunning() and Runway then
-- Add cohorts of legion.
for _,_cohort in pairs(legion.cohorts) do
local cohort=_cohort --Ops.Cohort#COHORT
if (CheckOperation(cohort.legion) or CheckOperation(cohort)) and not UTILS.IsInTable(cohorts, cohort, "name") then
table.insert(cohorts, cohort)
end
end
end
end
-- Add special cohorts.
for _,_cohort in pairs(Cohorts or {}) do
local cohort=_cohort --Ops.Cohort#COHORT
if CheckOperation(cohort) and not UTILS.IsInTable(cohorts, cohort, "name") then
table.insert(cohorts, cohort)
end
end
end
return cohorts
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Recruiting and Optimization Functions -- Recruiting and Optimization Functions
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Recruit assets from Cohorts for the given parameters. **NOTE** that we set the `asset.isReserved=true` flag so it cant be recruited by anyone else. --- Recruit assets from Cohorts for the given parameters. **NOTE** that we set the `asset.isReserved=true` flag so it cant be recruited by anyone else.
-- @param #table Cohorts Cohorts included. -- @param Ops.Cohort#COHORT Cohort The Cohort.
-- @param #string MissionTypeRecruit Mission type for recruiting the cohort assets. -- @param #string MissionType Misson type(s).
-- @param #string MissionTypeOpt Mission type for which the assets are optimized. Default is the same as `MissionTypeRecruit`. -- @param #table Categories Group categories.
-- @param #number NreqMin Minimum number of required assets.
-- @param #number NreqMax Maximum number of required assets.
-- @param DCS#Vec2 TargetVec2 Target position as 2D vector.
-- @param #table Payloads Special payloads.
-- @param #number RangeMax Max range in meters.
-- @param #number RefuelSystem Refuelsystem.
-- @param #number CargoWeight Cargo weight for recruiting transport carriers.
-- @param #number TotalWeight Total cargo weight in kg.
-- @param #table Categories Group categories.
-- @param #table Attributes Group attributes. See `GROUP.Attribute.` -- @param #table Attributes Group attributes. See `GROUP.Attribute.`
-- @param #table Properties DCS attributes. -- @param #table Properties DCS attributes.
-- @param #table WeaponTypes Bit of weapon types. -- @param #table WeaponTypes Bit of weapon types.
-- @return #boolean If `true` enough assets could be recruited. -- @param DCS#Vec2 TargetVec2 Target position.
-- @return #table Recruited assets. **NOTE** that we set the `asset.isReserved=true` flag so it cant be recruited by anyone else. -- @param RangeMax Max range in meters.
-- @return #table Legions of recruited assets. -- @param #number RefuelSystem Refueling system (boom or probe).
function LEGION.RecruitCohortAssets(Cohorts, MissionTypeRecruit, MissionTypeOpt, NreqMin, NreqMax, TargetVec2, Payloads, RangeMax, RefuelSystem, CargoWeight, TotalWeight, Categories, Attributes, Properties, WeaponTypes) -- @param #number CargoWeight Cargo weight [kg]. This checks the cargo bay of the cohort assets and ensures that it is large enough to carry the given cargo weight.
-- @param #number MaxWeight Max weight [kg]. This checks whether the cohort asset group is not too heavy.
-- @return #boolean Returns `true` if given cohort can meet all requirements.
function LEGION._CohortCan(Cohort, MissionType, Categories, Attributes, Properties, WeaponTypes, TargetVec2, RangeMax, RefuelSystem, CargoWeight, MaxWeight)
-- The recruited assets.
local Assets={}
-- Legions of recruited assets.
local Legions={}
-- Set MissionTypeOpt to Recruit if nil.
if MissionTypeOpt==nil then
MissionTypeOpt=MissionTypeRecruit
end
--- Function to check category. --- Function to check category.
local function CheckCategory(_cohort) local function CheckCategory(_cohort)
local cohort=_cohort --Ops.Cohort#COHORT local cohort=_cohort --Ops.Cohort#COHORT
@@ -2350,9 +2456,9 @@ function LEGION.RecruitCohortAssets(Cohorts, MissionTypeRecruit, MissionTypeOpt,
return true return true
end end
end end
-- Loops over cohorts. --- Function to check range.
for _,_cohort in pairs(Cohorts) do local function CheckRange(_cohort)
local cohort=_cohort --Ops.Cohort#COHORT local cohort=_cohort --Ops.Cohort#COHORT
-- Distance to target. -- Distance to target.
@@ -2362,50 +2468,175 @@ function LEGION.RecruitCohortAssets(Cohorts, MissionTypeRecruit, MissionTypeOpt,
local Rmax=cohort:GetMissionRange(WeaponTypes) local Rmax=cohort:GetMissionRange(WeaponTypes)
local InRange=(RangeMax and math.max(RangeMax, Rmax) or Rmax) >= TargetDistance local InRange=(RangeMax and math.max(RangeMax, Rmax) or Rmax) >= TargetDistance
return InRange
end
--- Function to check weapon type.
local function CheckRefueling(_cohort)
local cohort=_cohort --Ops.Cohort#COHORT
-- Has the requested refuelsystem? -- Has the requested refuelsystem?
local Refuel=RefuelSystem~=nil and (RefuelSystem==cohort.tankerSystem) or true --local Refuel=RefuelSystem~=nil and (RefuelSystem==cohort.tankerSystem) or true
-- STRANGE: Why did the above line did not give the same result?! Above Refuel is always true! -- STRANGE: Why did the above line did not give the same result?! Above Refuel is always true!
local Refuel=true
if RefuelSystem then if RefuelSystem then
if cohort.tankerSystem then if cohort.tankerSystem then
Refuel=RefuelSystem==cohort.tankerSystem return RefuelSystem==cohort.tankerSystem
else else
Refuel=false return false
end end
else
return true
end
end
--- Function to check cargo weight.
local function CheckCargoWeight(_cohort)
local cohort=_cohort --Ops.Cohort#COHORT
if CargoWeight~=nil then
return cohort.cargobayLimit>=CargoWeight
else
return true
end
end
--- Function to check cargo weight.
local function CheckMaxWeight(_cohort)
local cohort=_cohort --Ops.Cohort#COHORT
if MaxWeight~=nil then
cohort:I(string.format("Cohort weight=%.1f | max weight=%.1f", cohort.weightAsset, MaxWeight))
return cohort.weightAsset<=MaxWeight
else
return true
end
end
-- Is capable of the mission type?
local can=AUFTRAG.CheckMissionCapability(MissionType, Cohort.missiontypes)
if can then
can=CheckCategory(Cohort)
else
env.info(string.format("Cohort %s cannot because of mission types", Cohort.name))
return false
end
if can then
if MissionType==AUFTRAG.Type.RELOCATECOHORT then
can=Cohort:IsRelocating()
else
can=Cohort:IsOnDuty()
end end
else
-- Is capable of the mission type? env.info(string.format("Cohort %s cannot because of category", Cohort.name))
local Capable=AUFTRAG.CheckMissionCapability({MissionTypeRecruit}, cohort.missiontypes) BASE:I(Categories)
BASE:I(Cohort.category)
return false
end
if can then
can=CheckAttribute(Cohort)
else
env.info(string.format("Cohort %s cannot because of readyiness", Cohort.name))
return false
end
if can then
can=CheckProperty(Cohort)
else
env.info(string.format("Cohort %s cannot because of attribute", Cohort.name))
return false
end
if can then
can=CheckWeapon(Cohort)
else
env.info(string.format("Cohort %s cannot because of property", Cohort.name))
return false
end
if can then
can=CheckRange(Cohort)
else
env.info(string.format("Cohort %s cannot because of weapon type", Cohort.name))
return false
end
if can then
can=CheckRefueling(Cohort)
else
env.info(string.format("Cohort %s cannot because of range", Cohort.name))
return false
end
if can then
can=CheckCargoWeight(Cohort)
else
env.info(string.format("Cohort %s cannot because of refueling system", Cohort.name))
return false
end
if can then
can=CheckMaxWeight(Cohort)
else
env.info(string.format("Cohort %s cannot because of cargo weight", Cohort.name))
return false
end
if can then
return true
else
env.info(string.format("Cohort %s cannot because of max weight", Cohort.name))
return false
end
return nil
end
--- Recruit assets from Cohorts for the given parameters. **NOTE** that we set the `asset.isReserved=true` flag so it cant be recruited by anyone else.
-- @param #table Cohorts Cohorts included.
-- @param #string MissionTypeRecruit Mission type for recruiting the cohort assets.
-- @param #string MissionTypeOpt Mission type for which the assets are optimized. Default is the same as `MissionTypeRecruit`.
-- @param #number NreqMin Minimum number of required assets.
-- @param #number NreqMax Maximum number of required assets.
-- @param DCS#Vec2 TargetVec2 Target position as 2D vector.
-- @param #table Payloads Special payloads.
-- @param #number RangeMax Max range in meters.
-- @param #number RefuelSystem Refuelsystem.
-- @param #number CargoWeight Cargo weight for recruiting transport carriers.
-- @param #number TotalWeight Total cargo weight in kg.
-- @param #number MaxWeight Max weight [kg] of the asset group.
-- @param #table Categories Group categories.
-- @param #table Attributes Group attributes. See `GROUP.Attribute.`
-- @param #table Properties DCS attributes.
-- @param #table WeaponTypes Bit of weapon types.
-- @return #boolean If `true` enough assets could be recruited.
-- @return #table Recruited assets. **NOTE** that we set the `asset.isReserved=true` flag so it cant be recruited by anyone else.
-- @return #table Legions of recruited assets.
function LEGION.RecruitCohortAssets(Cohorts, MissionTypeRecruit, MissionTypeOpt, NreqMin, NreqMax, TargetVec2, Payloads, RangeMax, RefuelSystem, CargoWeight, TotalWeight, MaxWeight, Categories, Attributes, Properties, WeaponTypes)
-- The recruited assets.
local Assets={}
-- Legions of recruited assets.
local Legions={}
-- Set MissionTypeOpt to Recruit if nil.
if MissionTypeOpt==nil then
MissionTypeOpt=MissionTypeRecruit
end
-- Can carry the cargo? -- Loops over cohorts.
local CanCarry=CargoWeight and cohort.cargobayLimit>=CargoWeight or true for _,_cohort in pairs(Cohorts) do
local cohort=_cohort --Ops.Cohort#COHORT
-- Right category. -- Check if cohort can do the mission.
local RightCategory=CheckCategory(cohort) local can=LEGION._CohortCan(cohort, MissionTypeRecruit, Categories, Attributes, Properties, WeaponTypes, TargetVec2, RangeMax, RefuelSystem, CargoWeight, MaxWeight)
-- Right attribute.
local RightAttribute=CheckAttribute(cohort)
-- Right property (DCS attribute).
local RightProperty=CheckProperty(cohort)
-- Right weapon type.
local RightWeapon=CheckWeapon(cohort)
-- Cohort ready to execute mission.
local Ready=cohort:IsOnDuty()
if MissionTypeRecruit==AUFTRAG.Type.RELOCATECOHORT then
Ready=cohort:IsRelocating()
Capable=true
end
-- Debug info.
cohort:T(cohort.lid..string.format("State=%s: Capable=%s, InRange=%s, Refuel=%s, CanCarry=%s, Category=%s, Attribute=%s, Property=%s, Weapon=%s",
cohort:GetState(), tostring(Capable), tostring(InRange), tostring(Refuel), tostring(CanCarry), tostring(RightCategory), tostring(RightAttribute), tostring(RightProperty), tostring(RightWeapon)))
-- Check OnDuty, capable, in range and refueling type (if TANKER). -- Check OnDuty, capable, in range and refueling type (if TANKER).
if Ready and Capable and InRange and Refuel and CanCarry and RightCategory and RightAttribute and RightProperty and RightWeapon then if can then
-- Recruit assets from cohort. -- Recruit assets from cohort.
local assets, npayloads=cohort:RecruitAssets(MissionTypeRecruit, 999) local assets, npayloads=cohort:RecruitAssets(MissionTypeRecruit, 999)
@@ -2456,23 +2687,30 @@ function LEGION.RecruitCohortAssets(Cohorts, MissionTypeRecruit, MissionTypeOpt,
-- Found enough assets -- Found enough assets
--- ---
-- Add assets to mission. -- Total cargo bay of all carrier assets.
local cargobay=0 local cargobay=0
-- Add assets to mission.
for i=1,Nassets do for i=1,Nassets do
local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem
-- Asset is reserved and will not be picked for other missions.
asset.isReserved=true asset.isReserved=true
-- Add legion.
Legions[asset.legion.alias]=asset.legion Legions[asset.legion.alias]=asset.legion
-- Check if total cargo weight was given.
if TotalWeight then if TotalWeight then
-- Number of -- Number of
local N=math.floor(asset.cargobaytot/asset.nunits / CargoWeight)*asset.nunits local N=math.floor(asset.cargobaytot/asset.nunits / CargoWeight)*asset.nunits
--env.info(string.format("cargobaytot=%d, cargoweight=%d ==> N=%d", asset.cargobaytot, CargoWeight, N)) --env.info(string.format("cargobaytot=%d, cargoweight=%d ==> N=%d", asset.cargobaytot, CargoWeight, N))
-- Sum up total cargo bay of all carrier assets.
cargobay=cargobay + N*CargoWeight cargobay=cargobay + N*CargoWeight
-- Check if enough carrier assets were found to transport all cargo.
if cargobay>=TotalWeight then if cargobay>=TotalWeight then
--env.info(string.format("FF found enough assets to transport all cargo! N=%d [%d], cargobay=%.1f >= %.1f kg total weight", i, Nassets, cargobay, TotalWeight)) --env.info(string.format("FF found enough assets to transport all cargo! N=%d [%d], cargobay=%.1f >= %.1f kg total weight", i, Nassets, cargobay, TotalWeight))
Nassets=i Nassets=i
@@ -2580,7 +2818,7 @@ function LEGION:AssignAssetsForEscort(Cohorts, Assets, NescortMin, NescortMax, M
TargetTypes=TargetTypes or targetTypes TargetTypes=TargetTypes or targetTypes
-- Recruit escort asset for the mission asset. -- Recruit escort asset for the mission asset.
local Erecruited, eassets, elegions=LEGION.RecruitCohortAssets(Cohorts, AUFTRAG.Type.ESCORT, MissionType, NescortMin, NescortMax, TargetVec2, nil, nil, nil, nil, nil, Categories) local Erecruited, eassets, elegions=LEGION.RecruitCohortAssets(Cohorts, AUFTRAG.Type.ESCORT, MissionType, NescortMin, NescortMax, TargetVec2, nil, nil, nil, nil, nil, nil, Categories)
if Erecruited then if Erecruited then
Escorts[asset.spawngroupname]={EscortLegions=elegions, EscortAssets=eassets, ecategory=asset.category} Escorts[asset.spawngroupname]={EscortLegions=elegions, EscortAssets=eassets, ecategory=asset.category}
@@ -2685,24 +2923,8 @@ function LEGION:AssignAssetsForTransport(Legions, CargoAssets, NcarriersMin, Nca
-- Is an escort requested in the first place? -- Is an escort requested in the first place?
if NcarriersMin and NcarriersMax and (NcarriersMin>0 or NcarriersMax>0) then if NcarriersMin and NcarriersMax and (NcarriersMin>0 or NcarriersMax>0) then
-- Cohorts. -- Get cohorts.
local Cohorts={} local Cohorts=LEGION._GetCohorts(Legions)
for _,_legion in pairs(Legions) do
local legion=_legion --Ops.Legion#LEGION
-- Check that runway is operational.
local Runway=legion:IsAirwing() and legion:IsRunwayOperational() or true
if legion:IsRunning() and Runway then
-- Loops over cohorts.
for _,_cohort in pairs(legion.cohorts) do
local cohort=_cohort --Ops.Cohort#COHORT
table.insert(Cohorts, cohort)
end
end
end
-- Get all legions and heaviest cargo group weight -- Get all legions and heaviest cargo group weight
local CargoLegions={} ; local CargoWeight=nil ; local TotalWeight=0 local CargoLegions={} ; local CargoWeight=nil ; local TotalWeight=0
@@ -2714,13 +2936,17 @@ function LEGION:AssignAssetsForTransport(Legions, CargoAssets, NcarriersMin, Nca
end end
TotalWeight=TotalWeight+asset.weight TotalWeight=TotalWeight+asset.weight
end end
-- Debug info.
self:T(self.lid..string.format("Cargo weight=%.1f", CargoWeight))
self:T(self.lid..string.format("Total weight=%.1f", TotalWeight))
-- Target is the deploy zone. -- Target is the deploy zone.
local TargetVec2=DeployZone:GetVec2() local TargetVec2=DeployZone:GetVec2()
-- Recruit assets and legions. -- Recruit assets and legions.
local TransportAvail, CarrierAssets, CarrierLegions= local TransportAvail, CarrierAssets, CarrierLegions=
LEGION.RecruitCohortAssets(Cohorts, AUFTRAG.Type.OPSTRANSPORT, nil, NcarriersMin, NcarriersMax, TargetVec2, nil, nil, nil, CargoWeight, TotalWeight, Categories, Attributes, Properties) LEGION.RecruitCohortAssets(Cohorts, AUFTRAG.Type.OPSTRANSPORT, nil, NcarriersMin, NcarriersMax, TargetVec2, nil, nil, nil, CargoWeight, TotalWeight, nil, Categories, Attributes, Properties)
if TransportAvail then if TransportAvail then
@@ -2922,7 +3148,7 @@ function LEGION._OptimizeAssetSelection(assets, MissionType, TargetVec2, Include
local text=string.format("Optimized %d assets for %s mission/transport (payload=%s):", #assets, MissionType, tostring(IncludePayload)) local text=string.format("Optimized %d assets for %s mission/transport (payload=%s):", #assets, MissionType, tostring(IncludePayload))
for i,Asset in pairs(assets) do for i,Asset in pairs(assets) do
local asset=Asset --Functional.Warehouse#WAREHOUSE.Assetitem local asset=Asset --Functional.Warehouse#WAREHOUSE.Assetitem
text=text..string.format("\n%s %s: score=%d", asset.squadname, asset.spawngroupname, asset.score) text=text..string.format("\n%s %s: score=%d", asset.squadname, asset.spawngroupname, asset.score or -1)
asset.score=nil asset.score=nil
end end
env.info(text) env.info(text)

View File

@@ -885,10 +885,10 @@ function OPSGROUP:GetCoalition()
return self.group:GetCoalition() return self.group:GetCoalition()
end end
--- Returns the absolute (average) life points of the group. --- Returns the absolute total life points of the group.
-- @param #OPSGROUP self -- @param #OPSGROUP self
-- @param #OPSGROUP.Element Element (Optional) Only get life points of this element. -- @param #OPSGROUP.Element Element (Optional) Only get life points of this element.
-- @return #number Life points. If group contains more than one element, the average is given. -- @return #number Life points, *i.e.* the sum of life points over all units in the group (unless a specific element was passed).
-- @return #number Initial life points. -- @return #number Initial life points.
function OPSGROUP:GetLifePoints(Element) function OPSGROUP:GetLifePoints(Element)
@@ -3315,7 +3315,13 @@ function OPSGROUP:RemoveWaypoint(wpindex)
else else
self.currentwp=self.currentwp-1 self.currentwp=self.currentwp-1
end end
-- Could be that the waypoint we are currently moving to was the LAST waypoint. Then we now passed the final waypoint.
if (self.adinfinitum or istemp) then
self:_PassedFinalWaypoint(false, "Removed PASSED temporary waypoint ")
end
end end
end end
@@ -5689,6 +5695,7 @@ function OPSGROUP:RouteToMission(mission, delay)
end end
waypoint=ARMYGROUP.AddWaypoint(self, waypointcoord, SpeedToMission, uid, formation, false) waypoint=ARMYGROUP.AddWaypoint(self, waypointcoord, SpeedToMission, uid, formation, false)
elseif self:IsNavygroup() then elseif self:IsNavygroup() then
waypoint=NAVYGROUP.AddWaypoint(self, waypointcoord, SpeedToMission, uid, UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise), false) waypoint=NAVYGROUP.AddWaypoint(self, waypointcoord, SpeedToMission, uid, UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise), false)
@@ -5725,6 +5732,8 @@ function OPSGROUP:RouteToMission(mission, delay)
if targetzone and self:IsInZone(targetzone) then if targetzone and self:IsInZone(targetzone) then
self:T(self.lid.."Already in mission zone ==> TaskExecute()") self:T(self.lid.."Already in mission zone ==> TaskExecute()")
self:TaskExecute(waypointtask) self:TaskExecute(waypointtask)
-- TODO: Calling PassingWaypoint here is probably better as it marks the mission waypoint as passed!
--self:PassingWaypoint(waypoint)
return return
elseif d<25 then elseif d<25 then
self:T(self.lid.."Already within 25 meters of mission waypoint ==> TaskExecute()") self:T(self.lid.."Already within 25 meters of mission waypoint ==> TaskExecute()")
@@ -6923,7 +6932,7 @@ function OPSGROUP:onafterElementDamaged(From, Event, To, Element)
local lifepoints=0 local lifepoints=0
if Element.DCSunit and Element.DCSunit:isExist() then if Element.DCSunit then --and Element.DCSunit:isExist() then
-- Get life of unit -- Get life of unit
lifepoints=Element.DCSunit:getLife() lifepoints=Element.DCSunit:getLife()
@@ -10145,28 +10154,32 @@ end
-- @return #OPSGROUP self -- @return #OPSGROUP self
function OPSGROUP:_CheckDamage() function OPSGROUP:_CheckDamage()
self:T(self.lid..string.format("Checking damage..."))
self.life=0 self.life=0
local damaged=false local damaged=false
for _,_element in pairs(self.elements) do for _,_element in pairs(self.elements) do
local element=_element --Ops.OpsGroup#OPSGROUP.Element local element=_element --Ops.OpsGroup#OPSGROUP.Element
if element.status~=OPSGROUP.ElementStatus.DEAD and element.status~=OPSGROUP.ElementStatus.INUTERO then if element.status~=OPSGROUP.ElementStatus.DEAD and element.status~=OPSGROUP.ElementStatus.INUTERO then
-- Current life points.
local life=element.unit:GetLife()
self.life=self.life+life
if life<element.life then
element.life=life
self:ElementDamaged(element)
damaged=true
end
end -- Current life points.
local life=element.unit:GetLife()
self.life=self.life+life
if life<element.life then
element.life=life
self:ElementDamaged(element)
damaged=true
end
end
end end
-- If anyone in the group was damaged, trigger event.
if damaged then if damaged then
self:Damaged() self:Damaged()
end end

View File

@@ -30,6 +30,9 @@
-- @field #number Nred Number of red units in the zone. -- @field #number Nred Number of red units in the zone.
-- @field #number Nblu Number of blue units in the zone. -- @field #number Nblu Number of blue units in the zone.
-- @field #number Nnut Number of neutral units in the zone. -- @field #number Nnut Number of neutral units in the zone.
-- @field #number Tred Threat level of red units in the zone.
-- @field #number Tblu Threat level of blue units in the zone.
-- @field #number Tnut Threat level of neutral units in the zone.
-- @field #number TminCaptured Time interval in seconds how long an attacker must have troops inside the zone to capture. -- @field #number TminCaptured Time interval in seconds how long an attacker must have troops inside the zone to capture.
-- @field #number Tcaptured Time stamp (abs.) when the attacker destroyed all owning troops. -- @field #number Tcaptured Time stamp (abs.) when the attacker destroyed all owning troops.
-- @field #table ObjectCategories Object categories for the scan. -- @field #table ObjectCategories Object categories for the scan.
@@ -43,6 +46,10 @@
-- @field #string markerText Text shown in the maker. -- @field #string markerText Text shown in the maker.
-- @field #table chiefs Chiefs that monitor this zone. -- @field #table chiefs Chiefs that monitor this zone.
-- @field #table Missions Missions that are attached to this OpsZone. -- @field #table Missions Missions that are attached to this OpsZone.
-- @field #number nunitsCapture Number of units necessary to capture a zone.
-- @field #number threatlevelCapture Threat level necessary to capture a zone.
-- @field Core.Set#SET_UNIT ScanUnitSet Set of scanned units.
-- @field Core.Set#SET_GROUP ScanGroupSet Set of scanned groups.
-- @extends Core.Fsm#FSM -- @extends Core.Fsm#FSM
--- *Gentlemen, when the enemy is committed to a mistake we must not interrupt him too soon.* --- Horation Nelson --- *Gentlemen, when the enemy is committed to a mistake we must not interrupt him too soon.* --- Horation Nelson
@@ -64,6 +71,9 @@ OPSZONE = {
Nred = 0, Nred = 0,
Nblu = 0, Nblu = 0,
Nnut = 0, Nnut = 0,
Tred = 0,
Tblu = 0,
Tnut = 0,
chiefs = {}, chiefs = {},
Missions = {}, Missions = {},
} }
@@ -76,7 +86,7 @@ OPSZONE = {
--- OPSZONE class version. --- OPSZONE class version.
-- @field #string version -- @field #string version
OPSZONE.version="0.3.1" OPSZONE.version="0.3.2"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list -- ToDo list
@@ -100,10 +110,10 @@ OPSZONE.version="0.3.1"
-- @param #number CoalitionOwner Initial owner of the coaliton. Default `coalition.side.NEUTRAL`. -- @param #number CoalitionOwner Initial owner of the coaliton. Default `coalition.side.NEUTRAL`.
-- @return #OPSZONE self -- @return #OPSZONE self
-- @usage -- @usage
-- myopszone = OPSZONE:New(ZONE:FindByName("OpsZoneOne"),coalition.side.RED) -- base zone from the mission editor -- myopszone = OPSZONE:New(ZONE:FindByName("OpsZoneOne"), coalition.side.RED) -- base zone from the mission editor
-- myopszone = OPSZONE:New(ZONE_RADIUS:New("OpsZoneTwo",mycoordinate:GetVec2(),5000),coalition.side.BLUE) -- radius zone of 5km at a coordinate -- myopszone = OPSZONE:New(ZONE_RADIUS:New("OpsZoneTwo", mycoordinate:GetVec2(),5000),coalition.side.BLUE) -- radius zone of 5km at a coordinate
-- myopszone = OPSZONE:New(ZONE_RADIUS:New("Batumi")) -- airbase zone from Batumi Airbase, ca 2500m radius -- myopszone = OPSZONE:New(ZONE_RADIUS:New("Batumi")) -- airbase zone from Batumi Airbase, ca 2500m radius
-- myopszone = OPSZONE:New(ZONE_AIRBASE:New("Batumi",6000),coalition.side.BLUE) -- airbase zone from Batumi Airbase, but with a specific radius of 6km -- myopszone = OPSZONE:New(ZONE_AIRBASE:New("Batumi",6000),coalition.side.BLUE) -- airbase zone from Batumi Airbase, but with a specific radius of 6km
-- --
function OPSZONE:New(Zone, CoalitionOwner) function OPSZONE:New(Zone, CoalitionOwner)
@@ -151,6 +161,11 @@ function OPSZONE:New(Zone, CoalitionOwner)
self.zoneName=Zone:GetName() self.zoneName=Zone:GetName()
self.zoneRadius=Zone:GetRadius() self.zoneRadius=Zone:GetRadius()
self.Missions = {} self.Missions = {}
self.ScanUnitSet=SET_UNIT:New():FilterZones({Zone})
self.ScanGroupSet=SET_GROUP:New():FilterZones({Zone})
-- Add to database.
_DATABASE:AddOpsZone(self)
-- Current and previous owners. -- Current and previous owners.
self.ownerCurrent=CoalitionOwner or coalition.side.NEUTRAL self.ownerCurrent=CoalitionOwner or coalition.side.NEUTRAL
@@ -165,9 +180,6 @@ function OPSZONE:New(Zone, CoalitionOwner)
self.ownerPrevious=self.airbase:GetCoalition() self.ownerPrevious=self.airbase:GetCoalition()
end end
-- Set time to capture.
self:SetTimeCapture()
-- Set object categories. -- Set object categories.
self:SetObjectCategories() self:SetObjectCategories()
self:SetUnitCategories() self:SetUnitCategories()
@@ -176,6 +188,11 @@ function OPSZONE:New(Zone, CoalitionOwner)
self:SetDrawZone() self:SetDrawZone()
self:SetMarkZone(true) self:SetMarkZone(true)
-- Default capture parameters.
self:SetCaptureTime()
self:SetCaptureNunits()
self:SetCaptureThreatlevel()
-- Status timer. -- Status timer.
self.timerStatus=TIMER:New(OPSZONE.Status, self) self.timerStatus=TIMER:New(OPSZONE.Status, self)
@@ -186,6 +203,8 @@ function OPSZONE:New(Zone, CoalitionOwner)
-- From State --> Event --> To State -- From State --> Event --> To State
self:AddTransition("Stopped", "Start", "Empty") -- Start FSM. self:AddTransition("Stopped", "Start", "Empty") -- Start FSM.
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
self:AddTransition("*", "Evaluated", "*") -- Evaluation done.
self:AddTransition("*", "Captured", "Guarded") -- Zone was captured. self:AddTransition("*", "Captured", "Guarded") -- Zone was captured.
@@ -220,6 +239,23 @@ function OPSZONE:New(Zone, CoalitionOwner)
-- @param #number delay Delay in seconds. -- @param #number delay Delay in seconds.
--- Triggers the FSM event "Evaluated".
-- @function [parent=#OPSZONE] Evaluated
-- @param #OPSZONE self
--- Triggers the FSM event "Evaluated" after a delay.
-- @function [parent=#OPSZONE] __Evaluated
-- @param #OPSZONE self
-- @param #number delay Delay in seconds.
--- On after "Evaluated" event.
-- @function [parent=#OPSZONE] OnAfterEvaluated
-- @param #OPSZONE self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
--- Triggers the FSM event "Captured". --- Triggers the FSM event "Captured".
-- @function [parent=#OPSZONE] Captured -- @function [parent=#OPSZONE] Captured
-- @param #OPSZONE self -- @param #OPSZONE self
@@ -369,27 +405,27 @@ function OPSZONE:SetUnitCategories(Categories)
return self return self
end end
--- Set threat level threshold that the defending units must have to hold a zone. --- Set threat level threshold that the offending units must have to capture a zone.
-- The reason why you might want to set this is that unarmed units (*e.g.* fuel trucks) should not be able to hold a zone as they do not pose a threat. -- The reason why you might want to set this is that unarmed units (*e.g.* fuel trucks) should not be able to capture a zone as they do not pose a threat.
-- @param #OPSZONE self -- @param #OPSZONE self
-- @param #number Threatlevel Threat level threshod. Default 0. -- @param #number Threatlevel Threat level threshold. Default 0.
-- @return #OPSZONE self -- @return #OPSZONE self
function OPSZONE:SetThreatlevelDefinding(Threatlevel) function OPSZONE:SetCaptureThreatlevel(Threatlevel)
self.threatlevelDefending=Threatlevel or 0 self.threatlevelCapture=Threatlevel or 0
return self return self
end end
--- Set how many units must be present in a zone to capture it. By default, one unit is enough.
--- Set threat level threshold that the offending units must have to capture a zone.
-- The reason why you might want to set this is that unarmed units (*e.g.* fuel trucks) should not be able to capture a zone as they do not pose a threat.
-- @param #OPSZONE self -- @param #OPSZONE self
-- @param #number Threatlevel Threat level threshod. Default 0. -- @param #number Nunits Number of units. Default 1.
-- @return #OPSZONE self -- @return #OPSZONE self
function OPSZONE:SetThreatlevelOffending(Threatlevel) function OPSZONE:SetCaptureNunits(Nunits)
self.threatlevelOffending=Threatlevel or 0 Nunits=Nunits or 1
self.nunitsCapture=Nunits
return self return self
end end
@@ -399,7 +435,7 @@ end
-- @param #OPSZONE self -- @param #OPSZONE self
-- @param #number Tcapture Time in seconds. Default 0. -- @param #number Tcapture Time in seconds. Default 0.
-- @return #OPSZONE self -- @return #OPSZONE self
function OPSZONE:SetTimeCapture(Tcapture) function OPSZONE:SetCaptureTime(Tcapture)
self.TminCaptured=Tcapture or 0 self.TminCaptured=Tcapture or 0
@@ -479,6 +515,21 @@ function OPSZONE:GetCoordinate()
return coordinate return coordinate
end end
--- Get scanned units inside the zone.
-- @param #OPSZONE self
-- @return Core.Set#SET_UNIT Set of units inside the zone.
function OPSZONE:GetScannedUnitSet()
return self.ScanUnitSet
end
--- Get scanned groups inside the zone.
-- @param #OPSZONE self
-- @return Core.Set#SET_GROUP Set of groups inside the zone.
function OPSZONE:GetScannedGroupSet()
return self.ScanGroupSet
end
--- Returns a random coordinate in the zone. --- Returns a random coordinate in the zone.
-- @param #OPSZONE self -- @param #OPSZONE self
-- @param #number inner (Optional) Minimal distance from the center of the zone in meters. Default is 0 m. -- @param #number inner (Optional) Minimal distance from the center of the zone in meters. Default is 0 m.
@@ -753,11 +804,7 @@ function OPSZONE:onafterEmpty(From, Event, To)
-- Debug info. -- Debug info.
self:T(self.lid..string.format("Zone is empty EVENT")) self:T(self.lid..string.format("Zone is empty EVENT"))
-- Inform chief.
for _,_chief in pairs(self.chiefs) do
local chief=_chief --Ops.Chief#CHIEF
chief:ZoneEmpty(self)
end
end end
@@ -771,17 +818,7 @@ function OPSZONE:onafterAttacked(From, Event, To, AttackerCoalition)
-- Debug info. -- Debug info.
self:T(self.lid..string.format("Zone is being attacked by coalition=%s!", tostring(AttackerCoalition))) self:T(self.lid..string.format("Zone is being attacked by coalition=%s!", tostring(AttackerCoalition)))
-- Inform chief.
if AttackerCoalition then
for _,_chief in pairs(self.chiefs) do
local chief=_chief --Ops.Chief#CHIEF
if chief.coalition~=AttackerCoalition then
chief:ZoneAttacked(self)
end
end
end
end end
--- On after "Defeated" event. --- On after "Defeated" event.
@@ -806,19 +843,24 @@ end
-- @param #string Event Event. -- @param #string Event Event.
-- @param #string To To state. -- @param #string To To state.
function OPSZONE:onenterGuarded(From, Event, To) function OPSZONE:onenterGuarded(From, Event, To)
if From~=To then
-- Debug info. -- Debug info.
self:T(self.lid..string.format("Zone is guarded")) self:T(self.lid..string.format("Zone is guarded"))
-- Not attacked any more. -- Not attacked any more.
self.Tattacked=nil self.Tattacked=nil
if self.drawZone then if self.drawZone then
self.zone:UndrawZone()
local color=self:_GetZoneColor() self.zone:UndrawZone()
local color=self:_GetZoneColor()
self.zone:DrawZone(nil, color, 1.0, color, 0.5)
end
self.zone:DrawZone(nil, color, 1.0, color, 0.5)
end end
end end
@@ -828,26 +870,43 @@ end
-- @param #string From From state. -- @param #string From From state.
-- @param #string Event Event. -- @param #string Event Event.
-- @param #string To To state. -- @param #string To To state.
function OPSZONE:onenterAttacked(From, Event, To) -- @param #number AttackerCoalition Coalition of the attacking ground troops.
function OPSZONE:onenterAttacked(From, Event, To, AttackerCoalition)
-- Debug info.
self:T(self.lid..string.format("Zone is Attacked"))
-- Time stamp when the attack started. -- Time stamp when the attack started.
self.Tattacked=timer.getAbsTime() if From~="Attacked" then
-- Draw zone? -- Debug info.
if self.drawZone then self:T(self.lid..string.format("Zone is Attacked"))
self.zone:UndrawZone()
-- Color.
local color={1, 204/255, 204/255}
-- Draw zone.
self.zone:DrawZone(nil, color, 1.0, color, 0.5)
end
self:_CleanMissionTable() -- Set time stamp.
self.Tattacked=timer.getAbsTime()
-- Inform chief.
if AttackerCoalition then
for _,_chief in pairs(self.chiefs) do
local chief=_chief --Ops.Chief#CHIEF
if chief.coalition~=AttackerCoalition then
chief:ZoneAttacked(self)
end
end
end
-- Draw zone?
if self.drawZone then
self.zone:UndrawZone()
-- Color.
local color={1, 204/255, 204/255}
-- Draw zone.
self.zone:DrawZone(nil, color, 1.0, color, 0.5)
end
self:_CleanMissionTable()
end
end end
--- On enter "Empty" event. --- On enter "Empty" event.
@@ -857,17 +916,27 @@ end
-- @param #string To To state. -- @param #string To To state.
function OPSZONE:onenterEmpty(From, Event, To) function OPSZONE:onenterEmpty(From, Event, To)
-- Debug info. if From~=To then
self:T(self.lid..string.format("Zone is empty now"))
if self.drawZone then -- Debug info.
self.zone:UndrawZone() self:T(self.lid..string.format("Zone is empty now"))
local color=self:_GetZoneColor()
self.zone:DrawZone(nil, color, 1.0, color, 0.2)
end
-- Inform chief.
for _,_chief in pairs(self.chiefs) do
local chief=_chief --Ops.Chief#CHIEF
chief:ZoneEmpty(self)
end
if self.drawZone then
self.zone:UndrawZone()
local color=self:_GetZoneColor()
self.zone:DrawZone(nil, color, 1.0, color, 0.2)
end
end
end end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -892,6 +961,13 @@ function OPSZONE:Scan()
local Nred=0 local Nred=0
local Nblu=0 local Nblu=0
local Nnut=0 local Nnut=0
local Tred=0
local Tblu=0
local Tnut=0
self.ScanGroupSet:Clear(false)
self.ScanUnitSet:Clear(false)
--- Function to evaluate the world search --- Function to evaluate the world search
local function EvaluateZone(_ZoneObject) local function EvaluateZone(_ZoneObject)
@@ -939,13 +1015,35 @@ function OPSZONE:Scan()
-- Get Coalition. -- Get Coalition.
local Coalition=DCSUnit:getCoalition() local Coalition=DCSUnit:getCoalition()
local tl=0
local unit=UNIT:Find(DCSUnit)
if unit then
-- Threat level of unit.
tl=unit:GetThreatLevel()
-- Add unit to set.
self.ScanUnitSet:AddUnit(unit)
-- Get group of unit.
local group=unit:GetGroup()
if group then
self.ScanGroupSet:AddGroup(group, true)
end
end
-- Increase counter. -- Increase counter.
if Coalition==coalition.side.RED then if Coalition==coalition.side.RED then
Nred=Nred+1 Nred=Nred+1
Tred=Tred+tl
elseif Coalition==coalition.side.BLUE then elseif Coalition==coalition.side.BLUE then
Nblu=Nblu+1 Nblu=Nblu+1
Tblu=Tblu+tl
elseif Coalition==coalition.side.NEUTRAL then elseif Coalition==coalition.side.NEUTRAL then
Nnut=Nnut+1 Nnut=Nnut+1
Tnut=Tnut+tl
end end
-- Debug info. -- Debug info.
@@ -1014,6 +1112,10 @@ function OPSZONE:Scan()
self.Nred=Nred self.Nred=Nred
self.Nblu=Nblu self.Nblu=Nblu
self.Nnut=Nnut self.Nnut=Nnut
self.Tblu=Tblu
self.Tred=Tred
self.Tnut=Tnut
return self return self
end end
@@ -1028,6 +1130,30 @@ function OPSZONE:EvaluateZone()
local Nblu=self.Nblu local Nblu=self.Nblu
local Nnut=self.Nnut local Nnut=self.Nnut
local Tnow=timer.getAbsTime()
--- Capture
-- @param #number coal Coaltion capturing.
local function captured(coal)
-- Blue captured red zone.
if not self.airbase then
-- Set time stamp if it does not exist.
if not self.Tcaptured then
self.Tcaptured=Tnow
end
-- Check if enough time elapsed.
if Tnow-self.Tcaptured>=self.TminCaptured then
self:Captured(coal)
self.Tcaptured=nil
end
end
end
if self:IsRed() then if self:IsRed() then
--- ---
@@ -1038,43 +1164,16 @@ function OPSZONE:EvaluateZone()
-- No red units in red zone any more. -- No red units in red zone any more.
if Nblu>0 then if Nblu>=self.nunitsCapture and self.Tblu>=self.threatlevelCapture then
-- Blue captured red zone.
if not self.airbase then -- Blue captued red zone.
local Tnow=timer.getAbsTime() captured(coalition.side.BLUE)
-- Set time stamp if it does not exist. elseif Nnut>=self.nunitsCapture and self.Tnut>=self.threatlevelCapture and self.neutralCanCapture then
if not self.Tcaptured then
self.Tcaptured=Tnow
end
-- Check if enough time elapsed.
if Tnow-self.Tcaptured>=self.TminCaptured then
self:Captured(coalition.side.BLUE)
self.Tcaptured=nil
end
end
elseif Nnut>0 and self.neutralCanCapture then
-- Neutral captured red zone. -- Neutral captured red zone.
if not self.airbase then captured(coalition.side.NEUTRAL)
local Tnow=timer.getAbsTime()
-- Set time stamp if it does not exist.
if not self.Tcaptured then
self.Tcaptured=Tnow
end
-- Check if enough time elapsed.
if Tnow-self.Tcaptured>=self.TminCaptured then
self:Captured(coalition.side.NEUTRAL)
self.Tcaptured=nil
end
end
else
-- Red zone is now empty (but will remain red).
if not self:IsEmpty() then
self:Empty()
end
end end
else else
@@ -1117,21 +1216,16 @@ function OPSZONE:EvaluateZone()
-- No blue units in blue zone any more. -- No blue units in blue zone any more.
if Nred>0 then if Nred>=self.nunitsCapture and self.Tred>=self.threatlevelCapture then
-- Red captured blue zone. -- Red captured blue zone.
if not self.airbase then captured(coalition.side.RED)
self:Captured(coalition.side.RED)
end elseif Nnut>=self.nunitsCapture and self.Tnut>=self.threatlevelCapture and self.neutralCanCapture then
elseif Nnut>0 and self.neutralCanCapture then
-- Neutral captured blue zone. -- Neutral captured blue zone.
if not self.airbase then captured(coalition.side.NEUTRAL)
self:Captured(coalition.side.NEUTRAL)
end
else
-- Blue zone is empty now.
if not self:IsEmpty() then
self:Empty()
end
end end
else else
@@ -1152,7 +1246,7 @@ function OPSZONE:EvaluateZone()
self:Defeated(coalition.side.RED) self:Defeated(coalition.side.RED)
elseif self:IsEmpty() then elseif self:IsEmpty() then
-- Blue units left zone and returned (or from initial Empty state). -- Blue units left zone and returned (or from initial Empty state).
self:Guarded() self:Guarded()
end end
end end
@@ -1183,21 +1277,12 @@ function OPSZONE:EvaluateZone()
self:Attacked() self:Attacked()
end end
self.isContested=true self.isContested=true
elseif Nred>0 then elseif Nred>=self.nunitsCapture and self.Tred>=self.threatlevelCapture then
-- Red captured neutral zone. -- Red captured neutral zone.
if not self.airbase then captured(coalition.side.RED)
self:Captured(coalition.side.RED) elseif Nblu>=self.nunitsCapture and self.Tblu>=self.threatlevelCapture then
end
elseif Nblu>0 then
-- Blue captured neutral zone. -- Blue captured neutral zone.
if not self.airbase then captured(coalition.side.BLUE)
self:Captured(coalition.side.BLUE)
end
else
-- Neutral zone is empty now.
if not self:IsEmpty() then
self:Empty()
end
end end
--end --end
@@ -1206,6 +1291,11 @@ function OPSZONE:EvaluateZone()
self:E(self.lid.."ERROR: Unknown coaliton!") self:E(self.lid.."ERROR: Unknown coaliton!")
end end
-- No units of any coalition in zone any more ==> Empty!
if Nblu==0 and Nred==0 and Nnut==0 and (not self:IsEmpty()) then
self:Empty()
end
-- Finally, check airbase coalition -- Finally, check airbase coalition
if self.airbase then if self.airbase then
@@ -1219,6 +1309,9 @@ function OPSZONE:EvaluateZone()
end end
end end
-- Trigger event.
self:Evaluated()
end end
@@ -1328,7 +1421,7 @@ function OPSZONE:_UpdateMarker()
end end
--- Get marker text --- Get marker text.
-- @param #OPSZONE self -- @param #OPSZONE self
-- @return #string Marker text. -- @return #string Marker text.
function OPSZONE:_GetMarkerText() function OPSZONE:_GetMarkerText()
@@ -1337,8 +1430,10 @@ function OPSZONE:_GetMarkerText()
local prevowner=UTILS.GetCoalitionName(self.ownerPrevious) local prevowner=UTILS.GetCoalitionName(self.ownerPrevious)
-- Get marker text. -- Get marker text.
local text=string.format("%s: Owner=%s [%s]\nState=%s [Contested=%s]\nBlue=%d, Red=%d, Neutral=%d", local text=string.format("%s [N=%d, TL=%d T=%d]:\nOwner=%s [%s]\nState=%s [Contested=%s]\nBlue=%d [TL=%d]\nRed=%d [TL=%d]\nNeutral=%d [TL=%d]",
self.zoneName, owner, prevowner, self:GetState(), tostring(self:IsContested()), self.Nblu, self.Nred, self.Nnut) self.zoneName, self.nunitsCapture or 0, self.threatlevelCapture or 0, self.TminCaptured or 0,
owner, prevowner, self:GetState(), tostring(self:IsContested()),
self.Nblu, self.Tblu, self.Nred, self.Tred, self.Nnut, self.Tnut)
return text return text
end end

View File

@@ -2511,3 +2511,51 @@ function UTILS.ToStringBRAANATO(FromGrp,ToGrp)
end end
return BRAANATO return BRAANATO
end end
--- Check if an object is contained in a table.
-- @param #table Table The table.
-- @param #table Object The object to check.
-- @param #string Key (Optional) Key to check. By default, the object itself is checked.
-- @return #booolen Returns `true` if object is in table.
function UTILS.IsInTable(Table, Object, Key)
for key, object in pairs(Table) do
if Key then
if Object[Key]==object[Key] then
return true
end
else
if object==Object then
return true
end
end
end
return false
end
--- Check if any object of multiple given objects is contained in a table.
-- @param #table Table The table.
-- @param #table Objects The objects to check.
-- @param #string Key (Optional) Key to check.
-- @return #booolen Returns `true` if object is in table.
function UTILS.IsAnyInTable(Table, Objects, Key)
for _,Object in pairs(UTILS.EnsureTable(Objects)) do
for key, object in pairs(Table) do
if Key then
if Object[Key]==object[Key] then
return true
end
else
if object==Object then
return true
end
end
end
end
return false
end