- Improved asset selection
This commit is contained in:
Frank 2021-09-16 11:02:49 +02:00
parent 35b50e1a9d
commit 589ebd5bca
9 changed files with 557 additions and 150 deletions

View File

@ -34,8 +34,6 @@
-- --
-- === -- ===
-- --
-- ![Banner Image](..\Presentations\Timer\TIMER_Main.jpg)
--
-- # The TIMER Concept -- # The TIMER Concept
-- --
-- The TIMER class is the little sister of the @{Core.Scheduler#SCHEDULER} class. It does the same thing but is a bit easier to use and has less overhead. It should be sufficient in many cases. -- The TIMER class is the little sister of the @{Core.Scheduler#SCHEDULER} class. It does the same thing but is a bit easier to use and has less overhead. It should be sufficient in many cases.
@ -107,9 +105,6 @@ TIMER = {
--- Timer ID. --- Timer ID.
_TIMERID=0 _TIMERID=0
--- Timer data base.
--_TIMERDB={}
--- TIMER class version. --- TIMER class version.
-- @field #string version -- @field #string version
TIMER.version="0.1.1" TIMER.version="0.1.1"
@ -118,7 +113,7 @@ TIMER.version="0.1.1"
-- TODO list -- TODO list
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: A lot. -- TODO: Pause/unpause.
-- TODO: Write docs. -- TODO: Write docs.
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -156,9 +151,6 @@ function TIMER:New(Function, ...)
-- Log id. -- Log id.
self.lid=string.format("TIMER UID=%d | ", self.uid) self.lid=string.format("TIMER UID=%d | ", self.uid)
-- Add to DB.
--_TIMERDB[self.uid]=self
return self return self
end end
@ -220,9 +212,6 @@ function TIMER:Stop(Delay)
-- Not running any more. -- Not running any more.
self.isrunning=false self.isrunning=false
-- Remove DB entry.
--_TIMERDB[self.uid]=nil
end end
end end

View File

@ -2169,7 +2169,6 @@ function AUFTRAG:_AssignCohort(Cohort)
self:T3(self.lid..string.format("Assigning cohort %s", tostring(Cohort.name))) self:T3(self.lid..string.format("Assigning cohort %s", tostring(Cohort.name)))
table.insert(self.squadrons, Cohort) table.insert(self.squadrons, Cohort)
return self return self
end end

View File

@ -22,6 +22,7 @@
-- @field Core.Set#SET_ZONE yellowzoneset Set of zones defining the extended border. Defcon is set to YELLOW if enemy activity is detected. -- @field Core.Set#SET_ZONE yellowzoneset Set of zones defining the extended border. Defcon is set to YELLOW if enemy activity is detected.
-- @field Core.Set#SET_ZONE engagezoneset Set of zones where enemies are actively engaged. -- @field Core.Set#SET_ZONE engagezoneset Set of zones where enemies are actively engaged.
-- @field #string Defcon Defence condition. -- @field #string Defcon Defence condition.
-- @field #string strategy Strategy of the CHIEF.
-- @field Ops.Commander#COMMANDER commander Commander of assigned legions. -- @field Ops.Commander#COMMANDER commander Commander of assigned legions.
-- @extends Ops.Intelligence#INTEL -- @extends Ops.Intelligence#INTEL
@ -70,18 +71,11 @@ CHIEF.Strategy = {
TOTALWAR="Total War" TOTALWAR="Total War"
} }
--- Strategy. --- Mission performance.
-- @type CHIEF.MissionTypePerformance -- @type CHIEF.MissionPerformance
-- @field #string MissionType Mission Type. -- @field #string MissionType Mission Type.
-- @field #number Performance Performance: a number between 0 and 100, where 100 is best performance. -- @field #number Performance Performance: a number between 0 and 100, where 100 is best performance.
--- Strategy.
-- @type CHIEF.MissionTypeMapping
-- @field #string Attribute Generalized attibute
-- @field #table MissionTypes
--- CHIEF class version. --- CHIEF class version.
-- @field #string version -- @field #string version
CHIEF.version="0.0.1" CHIEF.version="0.0.1"
@ -93,8 +87,8 @@ CHIEF.version="0.0.1"
-- TODO: Create a good mission, which can be passed on to the COMMANDER. -- TODO: Create a good mission, which can be passed on to the COMMANDER.
-- TODO: Capture OPSZONEs. -- TODO: Capture OPSZONEs.
-- TODO: Get list of own assets and capabilities. -- TODO: Get list of own assets and capabilities.
-- TODO: Get list/overview of enemy assets etc. -- DONE: Get list/overview of enemy assets etc.
-- TODO: Put all contacts into target list. Then make missions from them. -- DONE: Put all contacts into target list. Then make missions from them.
-- TODO: Set of interesting zones. -- TODO: Set of interesting zones.
-- TODO: Define A2A and A2G parameters. -- TODO: Define A2A and A2G parameters.
-- DONE: Add/remove spawned flightgroups to detection set. -- DONE: Add/remove spawned flightgroups to detection set.
@ -119,18 +113,18 @@ function CHIEF:New(AgentSet, Coalition, Alias)
-- Inherit everything from INTEL class. -- Inherit everything from INTEL class.
local self=BASE:Inherit(self, INTEL:New(AgentSet, Coalition, Alias)) --#CHIEF local self=BASE:Inherit(self, INTEL:New(AgentSet, Coalition, Alias)) --#CHIEF
-- Define zones. -- Defaults.
self:SetBorderZones() self:SetBorderZones()
self:SetYellowZones() self:SetYellowZones()
self:SetThreatLevelRange() self:SetThreatLevelRange()
-- Init stuff.
self.Defcon=CHIEF.DEFCON.GREEN
self.strategy=CHIEF.Strategy.DEFENSIVE
-- Create a new COMMANDER. -- Create a new COMMANDER.
self.commander=COMMANDER:New() self.commander=COMMANDER:New()
-- Init DEFCON.
self.Defcon=CHIEF.DEFCON.GREEN
-- Add FSM transitions. -- Add FSM transitions.
-- From State --> Event --> To State -- From State --> Event --> To State
self:AddTransition("*", "MissionAssignToAny", "*") -- Assign mission to a COMMANDER. self:AddTransition("*", "MissionAssignToAny", "*") -- Assign mission to a COMMANDER.
@ -142,11 +136,11 @@ function CHIEF:New(AgentSet, Coalition, Alias)
self:AddTransition("*", "MissionCancel", "*") -- Cancel mission. self:AddTransition("*", "MissionCancel", "*") -- Cancel mission.
self:AddTransition("*", "TransportCancel", "*") -- Cancel transport. self:AddTransition("*", "TransportCancel", "*") -- Cancel transport.
self:AddTransition("*", "Defcon", "*") -- Change defence condition. self:AddTransition("*", "DefconChange", "*") -- Change defence condition.
self:AddTransition("*", "Stategy", "*") -- Change strategy condition. self:AddTransition("*", "StategyChange", "*") -- Change strategy condition.
self:AddTransition("*", "DeclareWar", "*") -- Declare War. self:AddTransition("*", "DeclareWar", "*") -- Declare War. Not implemented.
------------------------ ------------------------
--- Pseudo Functions --- --- Pseudo Functions ---
@ -181,6 +175,46 @@ function CHIEF:New(AgentSet, Coalition, Alias)
-- @param #number delay Delay in seconds. -- @param #number delay Delay in seconds.
--- Triggers the FSM event "DefconChange".
-- @function [parent=#CHIEF] DefconChange
-- @param #CHIEF self
-- @param #string Defcon New Defence Condition.
--- Triggers the FSM event "DefconChange" after a delay.
-- @function [parent=#CHIEF] __DefconChange
-- @param #CHIEF self
-- @param #number delay Delay in seconds.
-- @param #string Defcon New Defence Condition.
--- On after "DefconChange" event.
-- @function [parent=#CHIEF] OnAfterDefconChange
-- @param #CHIEF self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string Defcon New Defence Condition.
--- Triggers the FSM event "StrategyChange".
-- @function [parent=#CHIEF] StrategyChange
-- @param #CHIEF self
-- @param #string Strategy New strategy.
--- Triggers the FSM event "StrategyChange" after a delay.
-- @function [parent=#CHIEF] __StrategyChange
-- @param #CHIEF self
-- @param #number delay Delay in seconds.
-- @param #string Strategy New strategy.
--- On after "StrategyChange" event.
-- @function [parent=#CHIEF] OnAfterStrategyChange
-- @param #CHIEF self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string Strategy New stragegy.
--- Triggers the FSM event "MissionAssignToAny". --- Triggers the FSM event "MissionAssignToAny".
-- @function [parent=#CHIEF] MissionAssignToAny -- @function [parent=#CHIEF] MissionAssignToAny
-- @param #CHIEF self -- @param #CHIEF self
@ -317,12 +351,60 @@ end
-- @return #CHIEF self -- @return #CHIEF self
function CHIEF:SetDefcon(Defcon) function CHIEF:SetDefcon(Defcon)
-- Check if valid string was passed.
local gotit=false
for _,defcon in pairs(CHIEF.DEFCON) do
if defcon==Defcon then
gotit=true
end
end
if not gotit then
self:E(self.lid..string.format("ERROR: Unknown DEFCON specified! Dont know defcon=%s", tostring(Defcon)))
return self
end
-- Trigger event if defcon changed.
if Defcon~=self.Defcon then
self:DefconChange(Defcon)
end
-- Set new DEFCON.
self.Defcon=Defcon self.Defcon=Defcon
--self:Defcon(Defcon)
return self return self
end end
--- Get defence condition.
-- @param #CHIEF self
-- @param #string Current Defence condition. See @{#CHIEF.DEFCON}, e.g. `CHIEF.DEFCON.RED`.
function CHIEF:GetDefcon(Defcon)
return self.Defcon
end
--- Set stragegy.
-- @param #CHIEF self
-- @param #string Strategy Strategy. See @{#CHIEF.Stragegy}, e.g. `CHIEF.Strategy.DEFENSIVE` (default).
-- @return #CHIEF self
function CHIEF:SetStragety(Strategy)
-- Trigger event if Strategy changed.
if Strategy~=self.strategy then
self:StrategyChange(Strategy)
end
-- Set new Strategy.
self.strategy=Strategy
return self
end
--- Get defence condition.
-- @param #CHIEF self
-- @param #string Current Defence condition. See @{#CHIEF.DEFCON}, e.g. `CHIEF.DEFCON.RED`.
function CHIEF:GetDefcon(Defcon)
return self.Defcon
end
--- Get the commander. --- Get the commander.
-- @param #CHIEF self -- @param #CHIEF self
@ -389,14 +471,31 @@ end
-- @return #CHIEF self -- @return #CHIEF self
function CHIEF:AddTarget(Target) function CHIEF:AddTarget(Target)
Target:SetPriority()
Target:SetImportance()
table.insert(self.targetqueue, Target) table.insert(self.targetqueue, Target)
return self return self
end end
--- Remove target from queue.
-- @param #CHIEF self
-- @param Ops.Target#TARGET Target The target.
-- @return #CHIEF self
function CHIEF:RemoveTarget(Target)
for i,_target in pairs(self.targetqueue) do
local target=_target --Ops.Target#TARGET
if target.uid==Target.uid then
self:I(self.lid..string.format("Removing target %s from queue", Target.name))
table.remove(self.targetqueue, i)
break
end
end
return self
end
--- Add strategically important zone. --- Add strategically important zone.
-- @param #CHIEF self -- @param #CHIEF self
-- @param Core.Zone#ZONE_RADIUS Zone Strategic zone. -- @param Core.Zone#ZONE_RADIUS Zone Strategic zone.
@ -519,7 +618,8 @@ function CHIEF:onafterStatus(From, Event, To)
-- Cancel this mission. -- Cancel this mission.
contact.mission:Cancel() contact.mission:Cancel()
-- TODO: contact.target -- Remove a target from the queue.
self:RemoveTarget(contact.target)
end end
@ -535,19 +635,39 @@ function CHIEF:onafterStatus(From, Event, To)
local contact=_contact --Ops.Intelligence#INTEL.Contact local contact=_contact --Ops.Intelligence#INTEL.Contact
local group=contact.group --Wrapper.Group#GROUP local group=contact.group --Wrapper.Group#GROUP
-- Check if contact inside of out border.
local inred=self:CheckGroupInBorder(group) local inred=self:CheckGroupInBorder(group)
if inred then if inred then
Nred=Nred+1 Nred=Nred+1
end end
-- Check if contact is in the yellow zones.
local inyellow=self:CheckGroupInYellow(group) local inyellow=self:CheckGroupInYellow(group)
if inyellow then if inyellow then
Nyellow=Nyellow+1 Nyellow=Nyellow+1
end end
-- Check if this is not already a target.
if not contact.target then
-- Create a new TARGET of the contact group.
local Target=TARGET:New(contact.group)
-- Set to contact.
contact.target=Target
-- Set contact to target. Might be handy.
Target.contact=contact
-- Add target to queue.
self:AddTarget(Target)
end
-- Is this a threat? -- Is this a threat?
local threat=contact.threatlevel>=self.threatLevelMin and contact.threatlevel<=self.threatLevelMax local threat=contact.threatlevel>=self.threatLevelMin and contact.threatlevel<=self.threatLevelMax
--[[
local redalert=true local redalert=true
if self.borderzoneset:Count()>0 then if self.borderzoneset:Count()>0 then
redalert=inred redalert=inred
@ -555,11 +675,17 @@ function CHIEF:onafterStatus(From, Event, To)
if redalert and threat and not contact.target then if redalert and threat and not contact.target then
-- Create a new TARGET of the contact group.
local Target=TARGET:New(contact.group) local Target=TARGET:New(contact.group)
-- Set to contact.
contact.target=Target
-- Add target to queue.
self:AddTarget(Target) self:AddTarget(Target)
end end
]]
end end
@ -594,7 +720,7 @@ function CHIEF:onafterStatus(From, Event, To)
local Ntargets=#self.targetqueue local Ntargets=#self.targetqueue
-- Info message -- Info message
local text=string.format("Defcon=%s Assets=%d, Contacts: Total=%d Yellow=%d Red=%d, Targets=%d, Missions=%d", self.Defcon, Nassets, Ncontacts, Nyellow, Nred, Ntargets, Nmissions) local text=string.format("Defcon=%s: Assets=%d, Contacts=%d [Yellow=%d Red=%d], Targets=%d, Missions=%d", self.Defcon, Nassets, Ncontacts, Nyellow, Nred, Ntargets, Nmissions)
self:I(self.lid..text) self:I(self.lid..text)
end end
@ -788,48 +914,27 @@ function CHIEF:onafterTransportCancel(From, Event, To, Transport)
end end
--- On before "Defcon" event. --- On after "DefconChange" event.
-- @param #CHIEF self -- @param #CHIEF self
-- @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.
-- @param #string Defcon New defence condition. -- @param #string Defcon New defence condition.
function CHIEF:onbeforeDefcon(From, Event, To, Defcon) function CHIEF:onafterDefconChange(From, Event, To, Defcon)
local gotit=false
for _,defcon in pairs(CHIEF.DEFCON) do
if defcon==Defcon then
gotit=true
end
end
if not gotit then
self:E(self.lid..string.format("ERROR: Unknown DEFCON specified! Dont know defcon=%s", tostring(Defcon)))
return false
end
-- Defcon did not change.
if Defcon==self.Defcon then
self:I(self.lid..string.format("Defcon %s unchanged. Not processing transition!", tostring(Defcon)))
return false
end
return true
end
--- On after "Defcon" event.
-- @param #CHIEF self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string Defcon New defence condition.
function CHIEF:onafterDefcon(From, Event, To, Defcon)
self:I(self.lid..string.format("Changing Defcon from %s --> %s", self.Defcon, Defcon)) self:I(self.lid..string.format("Changing Defcon from %s --> %s", self.Defcon, Defcon))
-- Set new defcon.
self.Defcon=Defcon
end end
--- On after "StrategyChange" event.
-- @param #CHIEF self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string Strategy
function CHIEF:onafterDefconChange(From, Event, To, Strategy)
self:I(self.lid..string.format("Changing Strategy from %s --> %s", self.strategy, Strategy))
end
--- On after "DeclareWar" event. --- On after "DeclareWar" event.
-- @param #CHIEF self -- @param #CHIEF self
-- @param #string From From state. -- @param #string From From state.
@ -852,71 +957,134 @@ end
-- @param #CHIEF self -- @param #CHIEF self
function CHIEF:CheckTargetQueue() function CHIEF:CheckTargetQueue()
-- TODO: Sort mission queue. wrt what? Threat level? -- Number of missions.
local Ntargets=#self.targetqueue
-- Treat special cases.
if Ntargets==0 then
return nil
end
-- Sort results table wrt prio and threatlevel.
local function _sort(a, b)
local taskA=a --Ops.Target#TARGET
local taskB=b --Ops.Target#TARGET
return (taskA.prio<taskB.prio) or (taskA.prio==taskB.prio and taskA.threatlevel0>taskB.threatlevel0)
end
table.sort(self.targetqueue, _sort)
-- Get the lowest importance value (lower means more important).
-- If a target with importance 1 exists, targets with importance 2 will not be assigned. Targets with no importance (nil) can still be selected.
local vip=math.huge
for _,_target in pairs(self.targetqueue) do
local target=_target --Ops.Target#TARGET
if target.importance and target.importance<vip then
vip=target.importance
end
end
-- Loop over targets.
for _,_target in pairs(self.targetqueue) do for _,_target in pairs(self.targetqueue) do
local target=_target --Ops.Target#TARGET local target=_target --Ops.Target#TARGET
if target:IsAlive() and not target.mission then -- Check that target is alive and not already a mission has been assigned.
if target:IsAlive() and (target.importance==nil or target.importance<=vip) and not target.mission then
-- TODO: stategry
self.strategy=CHIEF.Strategy.TOTALWAR
-- Check if this target is "valid", i.e. fits with the current strategy.
local valid=false local valid=false
if self.strategy==CHIEF.Strategy.DEFENSIVE then if self.strategy==CHIEF.Strategy.DEFENSIVE then
---
-- DEFENSIVE: Attack inside borders only.
---
if self:CheckTargetInZones(target, self.borderzoneset) then if self:CheckTargetInZones(target, self.borderzoneset) then
valid=true valid=true
end end
elseif self.strategy==CHIEF.Strategy.OFFENSIVE then elseif self.strategy==CHIEF.Strategy.OFFENSIVE then
---
-- OFFENSIVE: Attack inside borders and in yellow zones.
---
if self:CheckTargetInZones(target, self.borderzoneset) or self:CheckTargetInZones(target, self.yellowzoneset) then if self:CheckTargetInZones(target, self.borderzoneset) or self:CheckTargetInZones(target, self.yellowzoneset) then
valid=true valid=true
end end
elseif self.strategy==CHIEF.Strategy.AGGRESSIVE then elseif self.strategy==CHIEF.Strategy.AGGRESSIVE then
---
-- AGGRESSIVE: Attack in all zone sets.
---
if self:CheckTargetInZones(target, self.borderzoneset) or self:CheckTargetInZones(target, self.yellowzoneset) or self:CheckTargetInZones(target, self.engagezoneset) then if self:CheckTargetInZones(target, self.borderzoneset) or self:CheckTargetInZones(target, self.yellowzoneset) or self:CheckTargetInZones(target, self.engagezoneset) then
valid=true valid=true
end end
elseif self.strategy==CHIEF.Strategy.TOTALWAR then elseif self.strategy==CHIEF.Strategy.TOTALWAR then
---
-- TOTAL WAR: We attack anything we find.
---
valid=true valid=true
end end
-- Valid target? -- Valid target?
if valid then if valid then
env.info("FF got valid target "..target:GetName()) -- Debug info.
self:I(self.lid..string.format("Got valid target %s: category=%s, threatlevel=%d", target:GetName(), target.category, target.threatlevel0))
-- Get mission performances for the given target. -- Get mission performances for the given target.
local MissionPerformances=self:_GetMissionPerformanceFromTarget(target) local MissionPerformances=self:_GetMissionPerformanceFromTarget(target)
-- Mission. -- Mission.
local mission=nil --Ops.Auftrag#AUFTRAG local mission=nil --Ops.Auftrag#AUFTRAG
local Legions=nil
if #MissionPerformances>0 then if #MissionPerformances>0 then
env.info(string.format("FF found mission performance N=%d", #MissionPerformances)) --TODO: Number of required assets. How many do we want? Should depend on:
-- * number of enemy units
-- Mission Type. -- * target threatlevel
local MissionType=nil -- * how many assets are still in stock
-- * is it inside of our border
local NassetsMin=1
local NassetsMax=3
for _,_mp in pairs(MissionPerformances) do for _,_mp in pairs(MissionPerformances) do
local mp=_mp --#CHIEF.MissionTypePerformance local mp=_mp --#CHIEF.MissionPerformance
local n=self.commander:CountAssets(true, {mp.MissionType})
env.info(string.format("FF Found N=%d assets in stock for mission type %s [Performance=%d]", n, mp.MissionType, mp.Performance)) -- Debug info.
if n>0 then self:I(self.lid..string.format("Recruiting assets for mission type %s [performance=%d] of target %s", mp.MissionType, mp.Performance, target:GetName()))
-- Recruit assets.
local recruited, legions, assets=self:RecruitAssetsForTarget(target, mp.MissionType, NassetsMin, NassetsMax)
if recruited then
-- Create a mission.
mission=AUFTRAG:NewFromTarget(target, mp.MissionType) mission=AUFTRAG:NewFromTarget(target, mp.MissionType)
-- Add asset to mission.
if mission then
for _,asset in pairs(assets) do
mission:AddAsset(asset)
end
Legions=legions
-- We got what we wanted ==> leave loop.
break break
end end
end
end end
end
end
if mission then -- Check if mission could be defined.
if mission and Legions then
-- Set target mission entry. -- Set target mission entry.
target.mission=mission target.mission=mission
@ -925,8 +1093,10 @@ function CHIEF:CheckTargetQueue()
mission.prio=target.prio mission.prio=target.prio
mission.importance=target.importance mission.importance=target.importance
-- Add mission to COMMANDER queue. -- Assign mission to legions.
self:AddMission(mission) for _,Legion in pairs(Legions) do
self.commander:MissionAssign(Legion, mission)
end
-- Only ONE target is assigned per check. -- Only ONE target is assigned per check.
return return
@ -941,7 +1111,7 @@ end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Resources -- Zone Check Functions
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Check if group is inside our border. --- Check if group is inside our border.
@ -1007,32 +1177,13 @@ end
-- Resources -- Resources
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Check resources.
-- @param #CHIEF self
-- @param Ops.Target#TARGET Target Target.
-- @return #table
function CHIEF:CheckResources(Target)
local objective=Target:GetObjective()
local missionperformances={}
for _,missionperformance in pairs(missionperformances) do
local mp=missionperformance --#CHIEF.MissionTypePerformance
end
end
--- Create a mission performance table. --- Create a mission performance table.
-- @param #CHIEF self -- @param #CHIEF self
-- @param #string MissionType Mission type. -- @param #string MissionType Mission type.
-- @param #number Performance Performance. -- @param #number Performance Performance.
-- @return #CHIEF.MissionPerformance Mission performance. -- @return #CHIEF.MissionPerformance Mission performance.
function CHIEF:_CreateMissionPerformance(MissionType, Performance) function CHIEF:_CreateMissionPerformance(MissionType, Performance)
local mp={} --#CHIEF.MissionTypePerformance local mp={} --#CHIEF.MissionPerformance
mp.MissionType=MissionType mp.MissionType=MissionType
mp.Performance=Performance mp.Performance=Performance
return mp return mp
@ -1064,7 +1215,7 @@ function CHIEF:_GetMissionPerformanceFromTarget(Target)
local TargetCategory=Target:GetCategory() local TargetCategory=Target:GetCategory()
local missionperf={} --#CHIEF.MissionTypePerformance local missionperf={} --#CHIEF.MissionPerformance
if group then if group then
@ -1148,63 +1299,63 @@ function CHIEF:_GetMissionTypeForGroupAttribute(Attribute)
if Attribute==GROUP.Attribute.AIR_ATTACKHELO then if Attribute==GROUP.Attribute.AIR_ATTACKHELO then
local mt={} --#CHIEF.MissionTypePerformance local mt={} --#CHIEF.MissionPerformance
mt.MissionType=AUFTRAG.Type.INTERCEPT mt.MissionType=AUFTRAG.Type.INTERCEPT
mt.Performance=100 mt.Performance=100
table.insert(missiontypes, mt) table.insert(missiontypes, mt)
elseif Attribute==GROUP.Attribute.GROUND_AAA then elseif Attribute==GROUP.Attribute.GROUND_AAA then
local mt={} --#CHIEF.MissionTypePerformance local mt={} --#CHIEF.MissionPerformance
mt.MissionType=AUFTRAG.Type.BAI mt.MissionType=AUFTRAG.Type.BAI
mt.Performance=100 mt.Performance=100
table.insert(missiontypes, mt) table.insert(missiontypes, mt)
local mt={} --#CHIEF.MissionTypePerformance local mt={} --#CHIEF.MissionPerformance
mt.MissionType=AUFTRAG.Type.BOMBING mt.MissionType=AUFTRAG.Type.BOMBING
mt.Performance=70 mt.Performance=70
table.insert(missiontypes, mt) table.insert(missiontypes, mt)
local mt={} --#CHIEF.MissionTypePerformance local mt={} --#CHIEF.MissionPerformance
mt.MissionType=AUFTRAG.Type.BOMBCARPET mt.MissionType=AUFTRAG.Type.BOMBCARPET
mt.Performance=70 mt.Performance=70
table.insert(missiontypes, mt) table.insert(missiontypes, mt)
local mt={} --#CHIEF.MissionTypePerformance local mt={} --#CHIEF.MissionPerformance
mt.MissionType=AUFTRAG.Type.ARTY mt.MissionType=AUFTRAG.Type.ARTY
mt.Performance=30 mt.Performance=30
table.insert(missiontypes, mt) table.insert(missiontypes, mt)
elseif Attribute==GROUP.Attribute.GROUND_SAM then elseif Attribute==GROUP.Attribute.GROUND_SAM then
local mt={} --#CHIEF.MissionTypePerformance local mt={} --#CHIEF.MissionPerformance
mt.MissionType=AUFTRAG.Type.SEAD mt.MissionType=AUFTRAG.Type.SEAD
mt.Performance=100 mt.Performance=100
table.insert(missiontypes, mt) table.insert(missiontypes, mt)
local mt={} --#CHIEF.MissionTypePerformance local mt={} --#CHIEF.MissionPerformance
mt.MissionType=AUFTRAG.Type.BAI mt.MissionType=AUFTRAG.Type.BAI
mt.Performance=100 mt.Performance=100
table.insert(missiontypes, mt) table.insert(missiontypes, mt)
local mt={} --#CHIEF.MissionTypePerformance local mt={} --#CHIEF.MissionPerformance
mt.MissionType=AUFTRAG.Type.ARTY mt.MissionType=AUFTRAG.Type.ARTY
mt.Performance=50 mt.Performance=50
table.insert(missiontypes, mt) table.insert(missiontypes, mt)
elseif Attribute==GROUP.Attribute.GROUND_EWR then elseif Attribute==GROUP.Attribute.GROUND_EWR then
local mt={} --#CHIEF.MissionTypePerformance local mt={} --#CHIEF.MissionPerformance
mt.MissionType=AUFTRAG.Type.SEAD mt.MissionType=AUFTRAG.Type.SEAD
mt.Performance=100 mt.Performance=100
table.insert(missiontypes, mt) table.insert(missiontypes, mt)
local mt={} --#CHIEF.MissionTypePerformance local mt={} --#CHIEF.MissionPerformance
mt.MissionType=AUFTRAG.Type.BAI mt.MissionType=AUFTRAG.Type.BAI
mt.Performance=100 mt.Performance=100
table.insert(missiontypes, mt) table.insert(missiontypes, mt)
local mt={} --#CHIEF.MissionTypePerformance local mt={} --#CHIEF.MissionPerformance
mt.MissionType=AUFTRAG.Type.ARTY mt.MissionType=AUFTRAG.Type.ARTY
mt.Performance=50 mt.Performance=50
table.insert(missiontypes, mt) table.insert(missiontypes, mt)
@ -1214,6 +1365,183 @@ function CHIEF:_GetMissionTypeForGroupAttribute(Attribute)
end end
--- Recruit assets for a given mission.
-- @param #CHIEF self
-- @param Ops.Target#TARGET Target The target.
-- @param #string MissionType Mission Type.
-- @return #boolean If `true` enough assets could be recruited.
-- @return #table Legions that have recruited assets.
-- @return #table Assets that have been recruited from all legions.
function CHIEF:RecruitAssetsForTarget(Target, MissionType, NassetsMin, NassetsMax)
-- The recruited assets.
local Assets={}
-- Legions which have the best assets for the Mission.
local Legions={}
-- Target vector.
local TargetVec2=Target:GetVec2()
for _,_legion in pairs(self.commander.legions) do
local legion=_legion --Ops.Legion#LEGION
-- Distance to target.
local TargetDistance=Target:GetCoordinate():Get2DDistance(legion:GetCoordinate())
-- Number of payloads in stock per aircraft type.
local Npayloads={}
-- First get payloads for aircraft types of squadrons.
for _,_cohort in pairs(legion.cohorts) do
local cohort=_cohort --Ops.Cohort#COHORT
if Npayloads[cohort.aircrafttype]==nil then
Npayloads[cohort.aircrafttype]=legion:IsAirwing() and legion:CountPayloadsInStock(MissionType, cohort.aircrafttype) or 999
self:T2(self.lid..string.format("Got N=%d payloads for mission type %s [%s]", Npayloads[cohort.aircrafttype], MissionType, cohort.aircrafttype))
end
end
-- Loops over cohorts.
for _,_cohort in pairs(legion.cohorts) do
local cohort=_cohort --Ops.Cohort#COHORT
local npayloads=Npayloads[cohort.aircrafttype]
if cohort:IsOnDuty() and cohort:CheckMissionCapability({MissionType}) and cohort.engageRange>=TargetDistance and npayloads>0 then
-- Recruit assets from squadron.
local assets, npayloads=cohort:RecruitAssets(MissionType, npayloads)
Npayloads[cohort.aircrafttype]=npayloads
for _,asset in pairs(assets) do
table.insert(Assets, asset)
end
end
end
end
-- Now we have a long list with assets.
self:_OptimizeAssetSelection(Assets, MissionType, TargetVec2, false)
for _,_asset in pairs(Assets) do
local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem
if asset.legion:IsAirwing() then
-- Only assets that have no payload. Should be only spawned assets!
if not asset.payload then
-- Fetch payload for asset. This can be nil!
asset.payload=asset.legion:FetchPayloadFromStock(asset.unittype, MissionType)
end
end
end
-- Remove assets that dont have a payload.
for i=#Assets,1,-1 do
local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem
if asset.legion:IsAirwing() and not asset.payload then
self:T3(self.lid..string.format("Remove asset %s with no payload", tostring(asset.spawngroupname)))
table.remove(Assets, i)
end
end
-- Now find the best asset for the given payloads.
self:_OptimizeAssetSelection(Assets, MissionType, TargetVec2, true)
-- Number of assets. At most NreqMax.
local Nassets=math.min(#Assets, NassetsMax)
if #Assets>=Nassets then
---
-- Found enough assets
---
-- Get Legions of assets and put into table.
for i=1,Nassets do
local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem
Legions[asset.legion.alias]=asset.legion
end
-- Return payloads and remove not needed assets.
for i=#Assets,Nassets+1,-1 do
local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem
if asset.legion:IsAirwing() and not asset.spawned then
self:T(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname))
asset.legion:ReturnPayloadFromAsset(asset)
end
table.remove(Assets, i)
end
-- Found enough assets.
return true, Legions, Assets
else
---
-- NOT enough assets
---
-- Return payloads of assets.
for i=1,#Assets do
local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem
if asset.legion:IsAirwing() and not asset.spawned then
self:T2(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname))
asset.legion:ReturnPayloadFromAsset(asset)
end
end
-- Not enough assets found.
return false, {}, {}
end
return nil, {}, {}
end
--- Optimize chosen assets for the mission at hand.
-- @param #CHIEF self
-- @param #table assets Table of (unoptimized) assets.
-- @param #string MissionType MissionType for which the best assets are desired.
-- @param DCS#Vec2 TargetVec2 Target 2D vector.
-- @param #boolean includePayload If true, include the payload in the calulation if the asset has one attached.
function CHIEF:_OptimizeAssetSelection(assets, MissionType, TargetVec2, includePayload)
-- Calculate the mission score of all assets.
for _,_asset in pairs(assets) do
local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem
asset.score=asset.legion:CalculateAssetMissionScore(asset, MissionType, TargetVec2, includePayload)
end
--- Sort assets wrt to their mission score. Higher is better.
local function optimize(a, b)
local assetA=a --Functional.Warehouse#WAREHOUSE.Assetitem
local assetB=b --Functional.Warehouse#WAREHOUSE.Assetitem
-- Higher score wins. If equal score ==> closer wins.
return (assetA.score>assetB.score)
end
table.sort(assets, optimize)
-- Remove distance parameter.
local text=string.format("Optimized %d assets for %s mission (payload=%s):", #assets, MissionType, tostring(includePayload))
for i,Asset in pairs(assets) do
local asset=Asset --Functional.Warehouse#WAREHOUSE.Assetitem
text=text..string.format("\n%s %s: score=%d", asset.squadname, asset.spawngroupname, asset.score)
asset.score=nil
end
self:T2(self.lid..text)
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@ -417,9 +417,31 @@ function COMMANDER:onafterStatus(From, Event, To)
for _,_asset in pairs(cohort.assets) do for _,_asset in pairs(cohort.assets) do
local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem
local state="In Stock"
if asset.flightgroup then
state=asset.flightgroup:GetState()
local mission=legion:GetAssetCurrentMission(asset)
if mission then
state=state..string.format("Mission %s [%s]", mission:GetName(), mission:GetType())
end
else
if asset.spawned then
env.info("FF ERROR: asset has opsgroup but is NOT spawned!")
end
if asset.requested and asset.isReserved then
env.info("FF ERROR: asset is requested and reserved. Should not be both!")
state="Reserved+Requested!"
elseif asset.isReserved then
state="Reserved"
elseif asset.requested then
state="Requested"
end
end
-- Text. -- Text.
text=text..string.format("\n- %s [UID=%d] Legion=%s, Cohort=%s: Spawned=%s, Requested=%s [RID=%s], Reserved=%s", text=text..string.format("\n[UID=%03d] %s Legion=%s [%s]: State=%s [RID=%s]",
asset.spawngroupname, asset.uid, legion.alias, cohort.name, tostring(asset.spawned), tostring(asset.requested), tostring(asset.rid), tostring(asset.isReserved)) asset.uid, asset.spawngroupname, legion.alias, cohort.name, state, tostring(asset.rid))
if asset.spawned then if asset.spawned then
Nspawned=Nspawned+1 Nspawned=Nspawned+1
@ -852,10 +874,13 @@ end
-- @param #boolean includePayload If true, include the payload in the calulation if the asset has one attached. -- @param #boolean includePayload If true, include the payload in the calulation if the asset has one attached.
function COMMANDER:_OptimizeAssetSelection(assets, Mission, includePayload) function COMMANDER:_OptimizeAssetSelection(assets, Mission, includePayload)
-- Target position.
local TargetVec2=Mission.type~=AUFTRAG.Type.ALERT5 and Mission:GetTargetVec2() or nil
-- Calculate the mission score of all assets. -- Calculate the mission score of all assets.
for _,_asset in pairs(assets) do for _,_asset in pairs(assets) do
local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem
asset.score=asset.legion:CalculateAssetMissionScore(asset, Mission, includePayload) asset.score=asset.legion:CalculateAssetMissionScore(asset, Mission, TargetVec2, includePayload)
end end
--- Sort assets wrt to their mission score. Higher is better. --- Sort assets wrt to their mission score. Higher is better.

View File

@ -2550,10 +2550,11 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand)
-- Waypoints from current position to holding point. -- Waypoints from current position to holding point.
local wp={} local wp={}
wp[#wp+1]=c0:WaypointAir(nil, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, UTILS.KnotsToKmph(SpeedTo), true , nil, {}, "Current Pos") -- NOTE: Currently, this first waypoint confuses the AI. It makes them go in circles. Looks like they cannot find the waypoint and are flying around it.
wp[#wp+1]=c1:WaypointAir(nil, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, UTILS.KnotsToKmph(SpeedTo), true , nil, {}, "Climb") --wp[#wp+1]=c0:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, UTILS.KnotsToKmph(SpeedTo), true , nil, {}, "Current Pos")
wp[#wp+1]=c2:WaypointAir(nil, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, UTILS.KnotsToKmph(SpeedTo), true , nil, {}, "Descent") wp[#wp+1]=c1:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, UTILS.KnotsToKmph(SpeedTo), true , nil, {}, "Climb")
wp[#wp+1]=p0:WaypointAir(nil, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, UTILS.KnotsToKmph(SpeedTo), true , nil, {TaskArrived, TaskHold, TaskKlar}, "Holding Point") wp[#wp+1]=c2:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, UTILS.KnotsToKmph(SpeedTo), true , nil, {}, "Descent")
wp[#wp+1]=p0:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, UTILS.KnotsToKmph(SpeedTo), true , nil, {TaskArrived, TaskHold, TaskKlar}, "Holding Point")
-- Approach point: 10 NN in direction of runway. -- Approach point: 10 NN in direction of runway.
if airbase:IsAirdrome() then if airbase:IsAirdrome() then
@ -2563,7 +2564,7 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand)
--- ---
local papp=airbase:GetCoordinate():Translate(x1, runway.heading-180):SetAltitude(h1) local papp=airbase:GetCoordinate():Translate(x1, runway.heading-180):SetAltitude(h1)
wp[#wp+1]=papp:WaypointAirTurningPoint(nil, UTILS.KnotsToKmph(SpeedLand), {}, "Final Approach") wp[#wp+1]=papp:WaypointAirTurningPoint("BARO", UTILS.KnotsToKmph(SpeedLand), {}, "Final Approach")
-- Okay, it looks like it's best to specify the coordinates not at the airbase but a bit away. This causes a more direct landing approach. -- Okay, it looks like it's best to specify the coordinates not at the airbase but a bit away. This causes a more direct landing approach.
local pland=airbase:GetCoordinate():Translate(x2, runway.heading-180):SetAltitude(h2) local pland=airbase:GetCoordinate():Translate(x2, runway.heading-180):SetAltitude(h2)

View File

@ -196,12 +196,12 @@ function INTEL:New(DetectionSet, Coalition, Alias)
if Alias then if Alias then
self.alias=tostring(Alias) self.alias=tostring(Alias)
else else
self.alias="SPECTRE" self.alias="INTEL SPECTRE"
if self.coalition then if self.coalition then
if self.coalition==coalition.side.RED then if self.coalition==coalition.side.RED then
self.alias="KGB" self.alias="INTEL KGB"
elseif self.coalition==coalition.side.BLUE then elseif self.coalition==coalition.side.BLUE then
self.alias="CIA" self.alias="INTEL CIA"
end end
end end
end end
@ -214,7 +214,7 @@ function INTEL:New(DetectionSet, Coalition, Alias)
self.DetectDLINK = true self.DetectDLINK = true
-- Set some string id for output to DCS.log file. -- Set some string id for output to DCS.log file.
self.lid=string.format("INTEL %s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown") self.lid=string.format("%s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown")
-- Start State. -- Start State.
self:SetStartState("Stopped") self:SetStartState("Stopped")

View File

@ -1775,9 +1775,10 @@ end
-- @param #LEGION self -- @param #LEGION self
-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset Asset -- @param Functional.Warehouse#WAREHOUSE.Assetitem asset Asset
-- @param Ops.Auftrag#AUFTRAG Mission Mission for which the best assets are desired. -- @param Ops.Auftrag#AUFTRAG Mission Mission for which the best assets are desired.
-- @param DCS#Vec2 TargetVec2 Target 2D vector.
-- @param #boolean includePayload If true, include the payload in the calulation if the asset has one attached. -- @param #boolean includePayload If true, include the payload in the calulation if the asset has one attached.
-- @return #number Mission score. -- @return #number Mission score.
function LEGION:CalculateAssetMissionScore(asset, Mission, includePayload) function LEGION:CalculateAssetMissionScore(asset, Mission, TargetVec2, includePayload)
-- Mission score. -- Mission score.
local score=0 local score=0
@ -1801,9 +1802,6 @@ function LEGION:CalculateAssetMissionScore(asset, Mission, includePayload)
score=score+self:GetPayloadPeformance(asset.payload, Mission.type) score=score+self:GetPayloadPeformance(asset.payload, Mission.type)
end end
-- Target position.
local TargetVec2=Mission.type~=AUFTRAG.Type.ALERT5 and Mission:GetTargetVec2() or nil --Mission:GetTargetVec2()
-- Origin: We take the flightgroups position or the one of the legion. -- Origin: We take the flightgroups position or the one of the legion.
local OrigVec2=asset.flightgroup and asset.flightgroup:GetVec2() or self:GetVec2() local OrigVec2=asset.flightgroup and asset.flightgroup:GetVec2() or self:GetVec2()
@ -1843,10 +1841,13 @@ end
-- @param #boolean includePayload If true, include the payload in the calulation if the asset has one attached. -- @param #boolean includePayload If true, include the payload in the calulation if the asset has one attached.
function LEGION:_OptimizeAssetSelection(assets, Mission, includePayload) function LEGION:_OptimizeAssetSelection(assets, Mission, includePayload)
-- Target position.
local TargetVec2=Mission.type~=AUFTRAG.Type.ALERT5 and Mission:GetTargetVec2() or nil
-- Calculate the mission score of all assets. -- Calculate the mission score of all assets.
for _,_asset in pairs(assets) do for _,_asset in pairs(assets) do
local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem
asset.score=self:CalculateAssetMissionScore(asset, Mission, includePayload) asset.score=self:CalculateAssetMissionScore(asset, Mission, TargetVec2, includePayload)
end end
--- Sort assets wrt to their mission score. Higher is better. --- Sort assets wrt to their mission score. Higher is better.

View File

@ -30,6 +30,7 @@
-- @field #number Tattacked Abs. mission time stamp when an attack was started. -- @field #number Tattacked Abs. mission time stamp when an attack was started.
-- @field #number dTCapture Time interval in seconds until a zone is captured. -- @field #number dTCapture Time interval in seconds until a zone is captured.
-- @field #boolean neutralCanCapture Neutral units can capture. Default `false`. -- @field #boolean neutralCanCapture Neutral units can capture. Default `false`.
-- @field #drawZone If `true`, draw the zone on the F10 map.
-- @extends Core.Fsm#FSM -- @extends Core.Fsm#FSM
--- Be surprised! --- Be surprised!

View File

@ -17,6 +17,7 @@
-- @type TARGET -- @type TARGET
-- @field #string ClassName Name of the class. -- @field #string ClassName Name of the class.
-- @field #number verbose Verbosity level. -- @field #number verbose Verbosity level.
-- @field #number uid Unique ID of the target.
-- @field #string lid Class id string for output to DCS log file. -- @field #string lid Class id string for output to DCS log file.
-- @field #table targets Table of target objects. -- @field #table targets Table of target objects.
-- @field #number targetcounter Running number to generate target object IDs. -- @field #number targetcounter Running number to generate target object IDs.
@ -153,6 +154,9 @@ function TARGET:New(TargetObject)
-- Increase counter. -- Increase counter.
_TARGETID=_TARGETID+1 _TARGETID=_TARGETID+1
-- Set UID.
self.uid=_TARGETID
-- Add object. -- Add object.
self:AddObject(TargetObject) self:AddObject(TargetObject)
@ -164,6 +168,10 @@ function TARGET:New(TargetObject)
return nil return nil
end end
-- Defaults.
self:SetPriority()
self:SetImportance()
-- Target Name. -- Target Name.
self.name=self:GetTargetName(Target) self.name=self:GetTargetName(Target)
@ -862,10 +870,25 @@ function TARGET:GetLife()
return N return N
end end
--- Get target 2D position vector.
-- @param #TARGET self
-- @param #TARGET.Object Target Target object.
-- @return DCS#Vec2 Vector with x,y components.
function TARGET:GetTargetVec2(Target)
local vec3=self:GetTargetVec3(Target)
if vec3 then
return {x=vec3.x, y=vec3.z}
end
return nil
end
--- Get target 3D position vector. --- Get target 3D position vector.
-- @param #TARGET self -- @param #TARGET self
-- @param #TARGET.Object Target Target object. -- @param #TARGET.Object Target Target object.
-- @return DCS#Vec3 Vector with x,y,z components -- @return DCS#Vec3 Vector with x,y,z components.
function TARGET:GetTargetVec3(Target) function TARGET:GetTargetVec3(Target)
if Target.Type==TARGET.ObjectType.GROUP then if Target.Type==TARGET.ObjectType.GROUP then
@ -1031,6 +1054,46 @@ function TARGET:GetName()
return self.name return self.name
end end
--- Get 2D vector.
-- @param #TARGET self
-- @return DCS#Vec2 2D vector of the target.
function TARGET:GetVec2()
for _,_target in pairs(self.targets) do
local Target=_target --#TARGET.Object
local coordinate=self:GetTargetVec2(Target)
if coordinate then
return coordinate
end
end
self:E(self.lid..string.format("ERROR: Cannot get Vec2 of target %s", self.name))
return nil
end
--- Get 3D vector.
-- @param #TARGET self
-- @return DCS#Vec3 3D vector of the target.
function TARGET:GetVec3()
for _,_target in pairs(self.targets) do
local Target=_target --#TARGET.Object
local coordinate=self:GetTargetVec3(Target)
if coordinate then
return coordinate
end
end
self:E(self.lid..string.format("ERROR: Cannot get Vec3 of target %s", self.name))
return nil
end
--- Get coordinate. --- Get coordinate.
-- @param #TARGET self -- @param #TARGET self
-- @return Core.Point#COORDINATE Coordinate of the target. -- @return Core.Point#COORDINATE Coordinate of the target.