OPSZONE and CHIEF

This commit is contained in:
Frank 2021-09-14 23:51:03 +02:00
parent f038564b1b
commit 35b50e1a9d
6 changed files with 688 additions and 137 deletions

View File

@ -1390,22 +1390,35 @@ function AUFTRAG:NewALERT5(MissionType)
end
--- Create a mission to attack a group. Mission type is automatically chosen from the group category.
--- Create a mission to attack a TARGET object.
-- @param #AUFTRAG self
-- @param Ops.Target#TARGET Target The target.
-- @param #string MissionType The mission type.
-- @return #AUFTRAG self
function AUFTRAG:NewTargetAir(Target)
function AUFTRAG:NewFromTarget(Target, MissionType)
local mission=nil --#AUFTRAG
self.engageTarget=Target
local target=self.engageTarget:GetObject()
local mission=self:NewAUTO(target)
if mission then
mission:SetPriority(10, true)
if MissionType==AUFTRAG.Type.ANTISHIP then
mission=self:NewANTISHIP(Target, Altitude)
elseif MissionType==AUFTRAG.Type.ARTY then
mission=self:NewARTY(Target, Nshots, Radius)
elseif MissionType==AUFTRAG.Type.BAI then
mission=self:NewBAI(Target, Altitude)
elseif MissionType==AUFTRAG.Type.BOMBCARPET then
mission=self:NewBOMBCARPET(Target, Altitude, CarpetLength)
elseif MissionType==AUFTRAG.Type.BOMBING then
mission=self:NewBOMBING(Target, Altitude)
elseif MissionType==AUFTRAG.Type.BOMBRUNWAY then
mission=self:NewBOMBRUNWAY(Target, Altitude)
elseif MissionType==AUFTRAG.Type.INTERCEPT then
mission=self:NewINTERCEPT(Target)
elseif MissionType==AUFTRAG.Type.SEAD then
mission=self:NewSEAD(Target, Altitude)
elseif MissionType==AUFTRAG.Type.STRIKE then
mission=self:NewSTRIKE(Target, Altitude)
else
return nil
end
return mission

View File

@ -17,6 +17,7 @@
-- @field #number verbose Verbosity level.
-- @field #string lid Class id string for output to DCS log file.
-- @field #table targetqueue Target queue.
-- @field #table zonequeue Strategic zone queue.
-- @field Core.Set#SET_ZONE borderzoneset Set of zones defining the border of our territory.
-- @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.
@ -30,9 +31,7 @@
--
-- # The CHIEF Concept
--
-- The Chief of staff gathers intel and assigns missions (AUFTRAG) the airforce (WINGCOMMANDER), army (GENERAL) or navy (ADMIRAL).
--
-- **Note** that currently only assignments to airborne forces (WINGCOMMANDER) are implemented.
-- The Chief of staff gathers INTEL and assigns missions (AUFTRAG) the airforce, army and/or navy.
--
--
-- @field #CHIEF
@ -41,6 +40,7 @@ CHIEF = {
verbose = 0,
lid = nil,
targetqueue = {},
zonequeue = {},
borderzoneset = nil,
yellowzoneset = nil,
engagezoneset = nil,
@ -70,6 +70,18 @@ CHIEF.Strategy = {
TOTALWAR="Total War"
}
--- Strategy.
-- @type CHIEF.MissionTypePerformance
-- @field #string MissionType Mission Type.
-- @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.
-- @field #string version
CHIEF.version="0.0.1"
@ -97,15 +109,17 @@ CHIEF.version="0.0.1"
-- @param #CHIEF self
-- @param Core.Set#SET_GROUP AgentSet Set of agents (groups) providing intel. Default is an empty set.
-- @param #number Coalition Coalition side, e.g. `coaliton.side.BLUE`. Can also be passed as a string "red", "blue" or "neutral".
-- @param #string Alias An *optional* alias how this object is called in the logs etc.
-- @return #CHIEF self
function CHIEF:New(AgentSet, Coalition)
function CHIEF:New(AgentSet, Coalition, Alias)
-- Set alias.
Alias=Alias or "CHIEF"
-- Inherit everything from INTEL class.
local self=BASE:Inherit(self, INTEL:New(AgentSet, Coalition)) --#CHIEF
-- Set some string id for output to DCS.log file.
--self.lid=string.format("CHIEF | ")
local self=BASE:Inherit(self, INTEL:New(AgentSet, Coalition, Alias)) --#CHIEF
-- Define zones.
self:SetBorderZones()
self:SetYellowZones()
@ -114,15 +128,19 @@ function CHIEF:New(AgentSet, Coalition)
-- Create a new COMMANDER.
self.commander=COMMANDER:New()
-- Init DEFCON.
self.Defcon=CHIEF.DEFCON.GREEN
-- Add FSM transitions.
-- From State --> Event --> To State
self:AddTransition("*", "AssignMissionAirforce", "*") -- Assign mission to a COMMANDER but request only AIR assets.
self:AddTransition("*", "AssignMissionNavy", "*") -- Assign mission to a COMMANDER but request only NAVAL assets.
self:AddTransition("*", "AssignMissionArmy", "*") -- Assign mission to a COMMANDER but request only GROUND assets.
self:AddTransition("*", "MissionAssignToAny", "*") -- Assign mission to a COMMANDER.
self:AddTransition("*", "MissionAssignToAirfore", "*") -- Assign mission to a COMMANDER but request only AIR assets.
self:AddTransition("*", "MissionAssignToNavy", "*") -- Assign mission to a COMMANDER but request only NAVAL assets.
self:AddTransition("*", "MissionAssignToArmy", "*") -- Assign mission to a COMMANDER but request only GROUND assets.
self:AddTransition("*", "MissionCancel", "*") -- Cancel mission.
self:AddTransition("*", "TransportCancel", "*") -- Cancel transport.
self:AddTransition("*", "Defcon", "*") -- Change defence condition.
@ -163,13 +181,33 @@ function CHIEF:New(AgentSet, Coalition)
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "MissionAssignToAny".
-- @function [parent=#CHIEF] MissionAssignToAny
-- @param #CHIEF self
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
--- Triggers the FSM event "MissionAssignToAny" after a delay.
-- @function [parent=#CHIEF] __MissionAssignToAny
-- @param #CHIEF self
-- @param #number delay Delay in seconds.
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
--- On after "MissionAssignToAny" event.
-- @function [parent=#CHIEF] OnAfterMissionAssignToAny
-- @param #CHIEF self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
--- Triggers the FSM event "MissionCancel".
-- @function [parent=#CHIEF] MissionCancel
-- @param #CHIEF self
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
--- Triggers the FSM event "MissionCancel" after a delay.
-- @function [parent=#CHIEF] MissionCancel
-- @function [parent=#CHIEF] __MissionCancel
-- @param #CHIEF self
-- @param #number delay Delay in seconds.
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
@ -189,7 +227,7 @@ function CHIEF:New(AgentSet, Coalition)
-- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport.
--- Triggers the FSM event "TransportCancel" after a delay.
-- @function [parent=#CHIEF] TransportCancel
-- @function [parent=#CHIEF] __TransportCancel
-- @param #CHIEF self
-- @param #number delay Delay in seconds.
-- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport.
@ -359,6 +397,19 @@ function CHIEF:AddTarget(Target)
return self
end
--- Add strategically important zone.
-- @param #CHIEF self
-- @param Core.Zone#ZONE_RADIUS Zone Strategic zone.
-- @return #CHIEF self
function CHIEF:AddStrateticZone(Zone)
local opszone=OPSZONE:New(Zone, CoalitionOwner)
table.insert(self.zonequeue, opszone)
return self
end
--- Set border zone set.
-- @param #CHIEF self
@ -538,7 +589,7 @@ function CHIEF:onafterStatus(From, Event, To)
if self.verbose>=1 then
local Nassets=self.commander:CountAssets()
local Ncontacts=#self.contacts
local Ncontacts=#self.Contacts
local Nmissions=#self.commander.missionqueue
local Ntargets=#self.targetqueue
@ -589,7 +640,7 @@ function CHIEF:onafterStatus(From, Event, To)
-- Mission queue.
if self.verbose>=4 and #self.commander.missionqueue>0 then
local text="Mission queue:"
for i,_mission in pairs(self.missionqueue) do
for i,_mission in pairs(self.commander.missionqueue) do
local mission=_mission --Ops.Auftrag#AUFTRAG
local target=mission:GetTargetName() or "unknown"
@ -629,13 +680,13 @@ end
-- FSM Events
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- On after "AssignMissionAssignAirforce" event.
--- On after "MissionAssignToAny" event.
-- @param #CHIEF self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
function CHIEF:onafterAssignMissionAirforce(From, Event, To, Mission)
function CHIEF:onafterMissionAssignToAny(From, Event, To, Mission)
if self.commander then
self:I(self.lid..string.format("Assigning mission %s (%s) to COMMANDER", Mission.name, Mission.type))
@ -647,13 +698,31 @@ function CHIEF:onafterAssignMissionAirforce(From, Event, To, Mission)
end
--- On after "AssignMissionAssignArmy" event.
--- On after "MissionAssignToAirforce" event.
-- @param #CHIEF self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
function CHIEF:onafterAssignMissionArmy(From, Event, To, Mission)
function CHIEF:onafterMissionAssignToAirforce(From, Event, To, Mission)
if self.commander then
self:I(self.lid..string.format("Assigning mission %s (%s) to COMMANDER", Mission.name, Mission.type))
--TODO: Request only air assets.
self.commander:AddMission(Mission)
else
self:E(self.lid..string.format("Mission cannot be assigned as no COMMANDER is defined!"))
end
end
--- On after "MissionAssignToArmy" event.
-- @param #CHIEF self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
function CHIEF:onafterMissionAssignToArmy(From, Event, To, Mission)
if self.commander then
self:I(self.lid..string.format("Assigning mission %s (%s) to COMMANDER", Mission.name, Mission.type))
@ -819,10 +888,33 @@ function CHIEF:CheckTargetQueue()
-- Valid target?
if valid then
--TODO: Create a good mission, which can be passed on to the COMMANDER.
env.info("FF got valid target "..target:GetName())
-- Create mission.
local mission=AUFTRAG:NewTargetAir(target)
-- Get mission performances for the given target.
local MissionPerformances=self:_GetMissionPerformanceFromTarget(target)
-- Mission.
local mission=nil --Ops.Auftrag#AUFTRAG
if #MissionPerformances>0 then
env.info(string.format("FF found mission performance N=%d", #MissionPerformances))
-- Mission Type.
local MissionType=nil
for _,_mp in pairs(MissionPerformances) do
local mp=_mp --#CHIEF.MissionTypePerformance
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))
if n>0 then
mission=AUFTRAG:NewFromTarget(target, mp.MissionType)
break
end
end
end
if mission then
@ -847,6 +939,7 @@ function CHIEF:CheckTargetQueue()
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Resources
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -916,29 +1009,209 @@ end
--- Check resources.
-- @param #CHIEF self
-- @param Ops.Target#TARGET Target Target.
-- @return #table
function CHIEF:CheckResources()
function CHIEF:CheckResources(Target)
-- TODO: look at lower classes to do this! it's all there...
local objective=Target:GetObjective()
local capabilities={}
local missionperformances={}
for _,MissionType in pairs(AUFTRAG.Type) do
capabilities[MissionType]=0
for _,missionperformance in pairs(missionperformances) do
local mp=missionperformance --#CHIEF.MissionTypePerformance
end
end
--- Create a mission performance table.
-- @param #CHIEF self
-- @param #string MissionType Mission type.
-- @param #number Performance Performance.
-- @return #CHIEF.MissionPerformance Mission performance.
function CHIEF:_CreateMissionPerformance(MissionType, Performance)
local mp={} --#CHIEF.MissionTypePerformance
mp.MissionType=MissionType
mp.Performance=Performance
return mp
end
--- Create a mission to attack a group. Mission type is automatically chosen from the group category.
-- @param #CHIEF self
-- @param Ops.Target#TARGET Target
-- @return #table Mission performances of type #CHIEF.MissionPerformance
function CHIEF:_GetMissionPerformanceFromTarget(Target)
local group=nil --Wrapper.Group#GROUP
local airbase=nil --Wrapper.Airbase#AIRBASE
local scenery=nil --Wrapper.Scenery#SCENERY
local coordinate=nil --Core.Point#COORDINATE
-- Get target objective.
local target=Target:GetObject()
if target:IsInstanceOf("GROUP") then
group=target --Target is already a group.
elseif target:IsInstanceOf("UNIT") then
group=target:GetGroup()
elseif target:IsInstanceOf("AIRBASE") then
airbase=target
elseif target:IsInstanceOf("SCENERY") then
scenery=target
end
local TargetCategory=Target:GetCategory()
local missionperf={} --#CHIEF.MissionTypePerformance
if group then
local category=group:GetCategory()
local attribute=group:GetAttribute()
if category==Group.Category.AIRPLANE or category==Group.Category.HELICOPTER then
---
-- A2A: Intercept
---
table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.INTERCEPT, 100))
elseif category==Group.Category.GROUND or category==Group.Category.TRAIN then
---
-- GROUND
---
if attribute==GROUP.Attribute.GROUND_SAM then
-- SEAD/DEAD
table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.SEAD, 100))
elseif attribute==GROUP.Attribute.GROUND_AAA then
table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BAI, 100))
elseif attribute==GROUP.Attribute.GROUND_ARTILLERY then
table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BAI, 100))
table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BOMBING, 70))
table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.ARTY, 30))
elseif attribute==GROUP.Attribute.GROUND_INFANTRY then
table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BAI, 100))
else
table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BAI, 100))
end
elseif category==Group.Category.SHIP then
---
-- NAVAL
---
table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.ANTISHIP, 100))
else
self:E(self.lid.."ERROR: Unknown Group category!")
end
elseif airbase then
table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BOMBRUNWAY, 100))
elseif scenery then
table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.STRIKE, 100))
table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BOMBING, 70))
table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.ARTY, 30))
elseif coordinate then
table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BOMBING, 100))
table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.ARTY, 30))
end
return missionperf
end
--- Check if group is inside our border.
-- @param #CHIEF self
-- @param #string Attribute Group attibute.
-- @return #table Mission types
function CHIEF:_GetMissionTypeForGroupAttribute(Attribute)
local missiontypes={}
if Attribute==GROUP.Attribute.AIR_ATTACKHELO then
local mt={} --#CHIEF.MissionTypePerformance
mt.MissionType=AUFTRAG.Type.INTERCEPT
mt.Performance=100
table.insert(missiontypes, mt)
elseif Attribute==GROUP.Attribute.GROUND_AAA then
local mt={} --#CHIEF.MissionTypePerformance
mt.MissionType=AUFTRAG.Type.BAI
mt.Performance=100
table.insert(missiontypes, mt)
local mt={} --#CHIEF.MissionTypePerformance
mt.MissionType=AUFTRAG.Type.BOMBING
mt.Performance=70
table.insert(missiontypes, mt)
local mt={} --#CHIEF.MissionTypePerformance
mt.MissionType=AUFTRAG.Type.BOMBCARPET
mt.Performance=70
table.insert(missiontypes, mt)
local mt={} --#CHIEF.MissionTypePerformance
mt.MissionType=AUFTRAG.Type.ARTY
mt.Performance=30
table.insert(missiontypes, mt)
elseif Attribute==GROUP.Attribute.GROUND_SAM then
local mt={} --#CHIEF.MissionTypePerformance
mt.MissionType=AUFTRAG.Type.SEAD
mt.Performance=100
table.insert(missiontypes, mt)
local mt={} --#CHIEF.MissionTypePerformance
mt.MissionType=AUFTRAG.Type.BAI
mt.Performance=100
table.insert(missiontypes, mt)
local mt={} --#CHIEF.MissionTypePerformance
mt.MissionType=AUFTRAG.Type.ARTY
mt.Performance=50
table.insert(missiontypes, mt)
elseif Attribute==GROUP.Attribute.GROUND_EWR then
local mt={} --#CHIEF.MissionTypePerformance
mt.MissionType=AUFTRAG.Type.SEAD
mt.Performance=100
table.insert(missiontypes, mt)
local mt={} --#CHIEF.MissionTypePerformance
mt.MissionType=AUFTRAG.Type.BAI
mt.Performance=100
table.insert(missiontypes, mt)
local mt={} --#CHIEF.MissionTypePerformance
mt.MissionType=AUFTRAG.Type.ARTY
mt.Performance=50
table.insert(missiontypes, mt)
for _,_airwing in pairs(self.airwings) do
local airwing=_airwing --Ops.AirWing#AIRWING
-- Get Number of assets that can do this type of missions.
local _,assets=airwing:CanMission(MissionType)
-- Add up airwing resources.
capabilities[MissionType]=capabilities[MissionType]+#assets
end
end
return capabilities
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@ -294,7 +294,7 @@ function COHORT:AddMissionCapability(MissionTypes, Performance)
capability.MissionType=missiontype
capability.Performance=Performance or 50
table.insert(self.missiontypes, capability)
env.info("FF adding mission capability "..tostring(capability.MissionType))
end
end
@ -783,11 +783,11 @@ function COHORT:CountAssets(InStock, MissionTypes, Attributes)
if MissionTypes==nil or self:CheckMissionCapability(MissionTypes, self.missiontypes) then
if Attributes==nil or self:CheckAttribute(Attributes) then
if asset.spawned then
if InStock==true or InStock==nil then
if InStock==false or InStock==nil then
N=N+1 --Spawned but we also count the spawned ones.
end
else
if InStock==false or InStock==nil then
if InStock==true or InStock==nil then
N=N+1 --This is in stock.
end
end

View File

@ -549,7 +549,7 @@ function COMMANDER:onafterMissionCancel(From, Event, To, Mission)
end
--- On after "MissionAssign" event. Mission is added to a LEGION mission queue.
--- On after "TransportAssign" event. Transport is added to a LEGION mission queue.
-- @param #COMMANDER self
-- @param #string From From state.
-- @param #string Event Event.

View File

@ -18,9 +18,18 @@
-- @field #number verbose Verbosity of output.
-- @field Core.Zone#ZONE zone The zone.
-- @field #string zoneName Name of the zone.
-- @field #number zoneRadius Radius of the zone in meters.
-- @field #number ownerCurrent Coalition of the current owner of the zone.
-- @field #number ownerPrevious Coalition of the previous owner of the zone.
-- @field Core.Timer#TIMER timerStatus Timer for calling the status update.
-- @field #number Nred Number of red 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 #table ObjectCategories Object categories for the scan.
-- @field #table UnitCategories Unit categories for the scan.
-- @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 #boolean neutralCanCapture Neutral units can capture. Default `false`.
-- @extends Core.Fsm#FSM
--- Be surprised!
@ -31,24 +40,32 @@
--
-- An OPSZONE is a strategically important area.
--
-- **Restrictions**
--
-- * Since we are using a DCS routine that scans a zone for units or other objects present in the zone and this DCS routine is limited to cicular zones, only those can be used.
--
-- @field #OPSZONE
OPSZONE = {
ClassName = "OPSZONE",
verbose = 3,
verbose = 0,
Nred = 0,
Nblu = 0,
Nnut = 0,
}
--- OPSZONE class version.
-- @field #string version
OPSZONE.version="0.0.1"
OPSZONE.version="0.1.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Can neutrals capture?
-- TODO: Can statics capture or hold a zone?
-- TODO: Pause/unpause evaluations.
-- TODO: Capture time, i.e. time how long a single coalition has to be inside the zone to capture it.
-- TODO: Can neutrals capture? No, since they are _neutral_!
-- TODO: Can statics capture or hold a zone? No, unless explicitly requested by mission designer.
-- TODO: Differentiate between ground attack and boming by air or arty.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -79,33 +96,138 @@ function OPSZONE:New(Zone, CoalitionOwner)
return nil
end
-- Set some string id for output to DCS.log file.
self.lid=string.format("OPSZONE %s | ", Zone:GetName())
-- Set some values.
self.zone=Zone
self.zoneName=Zone:GetName()
self.zoneRadius=Zone:GetRadius()
-- Current and previous owners.
self.ownerCurrent=CoalitionOwner or coalition.side.NEUTRAL
self.ownerPrevious=CoalitionOwner or coalition.side.NEUTRAL
-- Set some string id for output to DCS.log file.
self.lid=string.format("OPSZONE %s | ", Zone:GetName())
-- Set object categories.
self:SetObjectCategories()
self:SetUnitCategories()
-- FMS start state is PLANNED.
-- Status timer.
self.timerStatus=TIMER:New(OPSZONE.Status, self)
-- FMS start state is EMPTY.
self:SetStartState("Empty")
-- Add FSM transitions.
-- From State --> Event --> To State
self:AddTransition("*", "Start", "*") -- Start FSM.
self:AddTransition("*", "Stop", "*") -- Start FSM.
self:AddTransition("*", "Stop", "*") -- Stop FSM.
self:AddTransition("*", "Captured", "Guarded") -- Start FSM.
self:AddTransition("*", "Empty", "Empty") -- Start FSM.
self:AddTransition("*", "Captured", "Guarded") -- Zone was captured.
self:AddTransition("*", "Empty", "Empty") -- No red or blue units inside the zone.
self:AddTransition("*", "Attacked", "Attacked") -- A guarded zone is under attack.
self:AddTransition("*", "Defeated", "Guarded") -- The owning coalition defeated an attack.
------------------------
--- Pseudo Functions ---
------------------------
self.timerStatus=TIMER:New(OPSZONE.Status, self)
--- Triggers the FSM event "Start".
-- @function [parent=#OPSZONE] Start
-- @param #OPSZONE self
--- Triggers the FSM event "Start" after a delay.
-- @function [parent=#OPSZONE] __Start
-- @param #OPSZONE self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "Stop".
-- @param #OPSZONE self
--- Triggers the FSM event "Stop" after a delay.
-- @function [parent=#OPSZONE] __Stop
-- @param #OPSZONE self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "Captured".
-- @function [parent=#OPSZONE] Captured
-- @param #OPSZONE self
-- @param #number Coalition Coalition side that captured the zone.
--- Triggers the FSM event "Captured" after a delay.
-- @function [parent=#OPSZONE] __Captured
-- @param #OPSZONE self
-- @param #number delay Delay in seconds.
-- @param #number Coalition Coalition side that captured the zone.
--- On after "Captured" event.
-- @function [parent=#OPSZONE] OnAfterCaptured
-- @param #OPSZONE self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #number Coalition Coalition side that captured the zone.
--- Triggers the FSM event "Empty".
-- @function [parent=#OPSZONE] Empty
-- @param #OPSZONE self
--- Triggers the FSM event "Empty" after a delay.
-- @function [parent=#OPSZONE] __Empty
-- @param #OPSZONE self
-- @param #number delay Delay in seconds.
--- On after "Empty" event.
-- @function [parent=#OPSZONE] OnAfterEmpty
-- @param #OPSZONE self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
--- Triggers the FSM event "Attacked".
-- @function [parent=#OPSZONE] Attacked
-- @param #OPSZONE self
-- @param #number AttackerCoalition Coalition side that is attacking the zone.
--- Triggers the FSM event "Attacked" after a delay.
-- @function [parent=#OPSZONE] __Attacked
-- @param #OPSZONE self
-- @param #number delay Delay in seconds.
-- @param #number AttackerCoalition Coalition side that is attacking the zone.
--- On after "Attacked" event.
-- @function [parent=#OPSZONE] OnAfterAttacked
-- @param #OPSZONE self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #number AttackerCoalition Coalition side that is attacking the zone.
--- Triggers the FSM event "Defeated".
-- @function [parent=#OPSZONE] Defeated
-- @param #OPSZONE self
-- @param #number DefeatedCoalition Coalition side that was defeated.
--- Triggers the FSM event "Defeated" after a delay.
-- @function [parent=#OPSZONE] __Defeated
-- @param #OPSZONE self
-- @param #number delay Delay in seconds.
-- @param #number DefeatedCoalition Coalition side that was defeated.
--- On after "Defeated" event.
-- @function [parent=#OPSZONE] OnAfterDefeated
-- @param #OPSZONE self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #number DefeatedCoalition Coalition side that was defeated.
return self
end
@ -114,6 +236,58 @@ end
-- User Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Set verbosity level.
-- @param #OPSZONE self
-- @param #number VerbosityLevel Level of output (higher=more). Default 0.
-- @return #OPSZONE self
function OPSZONE:SetVerbosity(VerbosityLevel)
self.verbose=VerbosityLevel or 0
return self
end
--- Set categories of objects that can capture or hold the zone.
-- @param #OPSZONE self
-- @param #table Categories Object categories. Default is `{Object.Category.UNIT, Object.Category.STATIC}`, i.e. UNITs and STATICs.
-- @return #OPSZONE self
function OPSZONE:SetObjectCategories(Categories)
-- Ensure table if something was passed.
if Categories and type(Categories)~="table" then
Categories={Categories}
end
-- Set categories.
self.ObjectCategories=Categories or {Object.Category.UNIT, Object.Category.STATIC}
return self
end
--- Set categories of units that can capture or hold the zone. See [DCS Class Unit](https://wiki.hoggitworld.com/view/DCS_Class_Unit).
-- @param #OPSZONE self
-- @param #table Categories Table of unit categories. Default `{Unit.Category.GROUND_UNIT}`.
-- @return #OPSZONE
function OPSZONE:SetUnitCategories(Categories)
-- Ensure table.
if Categories and type(Categories)~="table" then
Categories={Categories}
end
-- Set categories.
self.UnitCategories=Categories or {Unit.Category.GROUND_UNIT}
return self
end
--- Set whether *neutral* units can capture the zone.
-- @param #OPSZONE self
-- @param #boolean CanCapture If `true`, neutral units can.
-- @return #OPSZONE self
function OPSZONE:SetNeutralCanCapture(CanCapture)
self.neutralCanCapture=CanCapture
return self
end
--- Get current owner of the zone.
-- @param #OPSZONE self
-- @return #number Owner coalition.
@ -128,6 +302,19 @@ function OPSZONE:GetPreviousOwner()
return self.ownerPrevious
end
--- Get duration of the current attack.
-- @param #OPSZONE self
-- @return #number Duration in seconds since when the last attack began. Is `nil` if the zone is not under attack currently.
function OPSZONE:GetAttackDuration()
if self:IsAttacked() and self.Tattacked then
local dT=timer.getAbsTime()-self.Tattacked
return dT
end
return nil
end
--- Check if the red coalition is currently owning the zone.
-- @param #OPSZONE self
@ -156,7 +343,7 @@ end
--- Check if zone is guarded.
-- @param #OPSZONE self
-- @return #boolean If `true`, zone is guarded.
function OPSZONE:IsEmpty()
function OPSZONE:IsGuarded()
local is=self:is("Guarded")
return is
end
@ -186,7 +373,7 @@ end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- FSM Functions
-- Start/Stop and Status Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Start OPSZONE FSM.
@ -199,6 +386,9 @@ function OPSZONE:onafterStart(From, Event, To)
-- Info.
self:I(self.lid..string.format("Starting OPSZONE v%s", OPSZONE.version))
-- Reinit the timer.
self.timerStatus=self.timerStatus or TIMER:New(OPSZONE.Status, self)
-- Status update.
self.timerStatus:Start(1, 60)
@ -211,9 +401,14 @@ function OPSZONE:Status()
-- Current FSM state.
local fsmstate=self:GetState()
-- Get contested.
local contested=tostring(self:IsContested())
-- Info message.
local text=string.format("State %s: Owner %d (previous %d), contested=%s, Nunits: red=%d, blue=%d, neutral=%d", fsmstate, self.ownerCurrent, self.ownerPrevious, tostring(self:IsContested()), 0, 0, 0)
if self.verbose>=1 then
local text=string.format("State %s: Owner %d (previous %d), contested=%s, Nunits: red=%d, blue=%d, neutral=%d", fsmstate, self.ownerCurrent, self.ownerPrevious, contested, self.Nred, self.Nblu, self.Nnut)
self:I(self.lid..text)
end
-- Scanning zone.
self:Scan()
@ -233,8 +428,9 @@ end
function OPSZONE:onafterCaptured(From, Event, To, NewOwnerCoalition)
-- Debug info.
self:I(self.lid..string.format("Zone captured by %d coalition", NewOwnerCoalition))
self:T(self.lid..string.format("Zone captured by coalition=%d", NewOwnerCoalition))
-- Set owners.
self.ownerPrevious=self.ownerCurrent
self.ownerCurrent=NewOwnerCoalition
@ -248,7 +444,7 @@ end
function OPSZONE:onafterEmpty(From, Event, To)
-- Debug info.
self:I(self.lid..string.format("Zone is empty now"))
self:T(self.lid..string.format("Zone is empty now"))
end
@ -261,10 +457,7 @@ end
function OPSZONE:onafterAttacked(From, Event, To, AttackerCoalition)
-- Debug info.
self:I(self.lid..string.format("Zone is being attacked by coalition %s!", tostring(AttackerCoalition)))
-- Time stam when the attack started.
self.Tattacked=timer.getAbsTime()
self:T(self.lid..string.format("Zone is being attacked by coalition=%s!", tostring(AttackerCoalition)))
end
@ -277,7 +470,7 @@ end
function OPSZONE:onafterEmpty(From, Event, To)
-- Debug info.
self:I(self.lid..string.format("Zone is empty now"))
self:T(self.lid..string.format("Zone is empty now"))
end
@ -286,10 +479,11 @@ end
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function OPSZONE:onafterDefeated(From, Event, To)
-- @param #number DefeatedCoalition Coalition side that was defeated.
function OPSZONE:onafterDefeated(From, Event, To, DefeatedCoalition)
-- Debug info.
self:I(self.lid..string.format("Attack on zone has been defeated"))
self:T(self.lid..string.format("Defeated attack on zone by coalition=%d", DefeatedCoalition))
-- Not attacked any more.
self.Tattacked=nil
@ -304,8 +498,9 @@ end
function OPSZONE:onenterGuarded(From, Event, To)
-- Debug info.
self:I(self.lid..string.format("Zone is guarded"))
self:T(self.lid..string.format("Zone is guarded"))
-- Not attacked any more.
self.Tattacked=nil
end
@ -318,9 +513,10 @@ end
function OPSZONE:onenterAttacked(From, Event, To)
-- Debug info.
self:I(self.lid..string.format("Zone is Attacked"))
self:T(self.lid..string.format("Zone is Attacked"))
self.Tattacked=nil
-- Time stamp when the attack started.
self.Tattacked=timer.getAbsTime()
end
@ -334,14 +530,15 @@ end
function OPSZONE:Scan()
-- Debug info.
local text=string.format("Scanning zone %s R=%.1f m", self.zone:GetName(), self.zone:GetRadius())
if self.verbose>=3 then
local text=string.format("Scanning zone %s R=%.1f m", self.zoneName, self.zoneRadius)
self:I(self.lid..text)
end
-- Search.
local SphereSearch={id=world.VolumeType.SPHERE, params={point=self.zone:GetVec3(), radius=self.zone:GetRadius(),}}
local ObjectCategories={Object.Category.UNIT, Object.Category.STATIC}
local SphereSearch={id=world.VolumeType.SPHERE, params={point=self.zone:GetVec3(), radius=self.zoneRadius}}
-- Init number of red, blue and neutral units.
local Nred=0
local Nblu=0
local Nnut=0
@ -362,12 +559,37 @@ function OPSZONE:Scan()
-- UNIT
---
-- This is a DCS unit object.
local DCSUnit=ZoneObject --DCS#Unit
--TODO: only ground units!
--- Function to check if unit category is included.
local function Included()
if not self.UnitCategories then
-- Any unit is included.
return true
else
-- Check if found object is in specified categories.
local CategoryDCSUnit = ZoneObject:getDesc().category
for _,UnitCategory in pairs(self.UnitCategories) do
if UnitCategory==CategoryDCSUnit then
return true
end
end
end
return false
end
if Included() then
-- Get Coalition.
local Coalition=DCSUnit:getCoalition()
-- Increase counter.
if Coalition==coalition.side.RED then
Nred=Nred+1
elseif Coalition==coalition.side.BLUE then
@ -376,10 +598,11 @@ function OPSZONE:Scan()
Nnut=Nnut+1
end
local unit=UNIT:Find(DCSUnit)
env.info(string.format("FF found unit %s", unit:GetName()))
-- Debug info.
if self.verbose>=4 then
self:I(self.lid..string.format("Found unit %s (coalition=%d)", DCSUnit:getName(), Coalition))
end
end
elseif ObjectCategory==Object.Category.STATIC and ZoneObject:isExist() then
@ -387,12 +610,28 @@ function OPSZONE:Scan()
-- STATIC
---
-- This is a DCS static object.
local DCSStatic=ZoneObject --DCS#Static
-- CAREFUL! Downed pilots break routine here without any error thrown.
local unit=STATIC:Find(DCSStatic)
-- Get coalition.
local Coalition=DCSStatic:getCoalition()
--env.info(string.format("FF found static %s", unit:GetName()))
-- CAREFUL! Downed pilots break routine here without any error thrown.
--local unit=STATIC:Find(DCSStatic)
-- Increase counter.
if Coalition==coalition.side.RED then
Nred=Nred+1
elseif Coalition==coalition.side.BLUE then
Nblu=Nblu+1
elseif Coalition==coalition.side.NEUTRAL then
Nnut=Nnut+1
end
-- Debug info
if self.verbose>=4 then
self:I(self.lid..string.format("Found static %s (coalition=%d)", DCSStatic:getName(), Coalition))
end
elseif ObjectCategory==Object.Category.SCENERY then
@ -403,9 +642,8 @@ function OPSZONE:Scan()
local SceneryType = ZoneObject:getTypeName()
local SceneryName = ZoneObject:getName()
local Scenery=SCENERY:Register(SceneryName, ZoneObject)
env.info(string.format("FF found scenery type=%s, name=%s", SceneryType, SceneryName))
-- Debug info.
self:T2(self.lid..string.format("Found scenery type=%s, name=%s", SceneryType, SceneryName))
end
end
@ -414,11 +652,18 @@ function OPSZONE:Scan()
end
-- Search objects.
world.searchObjects(ObjectCategories, SphereSearch, EvaluateZone)
world.searchObjects(self.ObjectCategories, SphereSearch, EvaluateZone)
-- Debug info.
local text=string.format("Scan result Nred=%d, Nblue=%d, Nneutrl=%d", Nred, Nblu, Nnut)
if self.verbose>=3 then
local text=string.format("Scan result Nred=%d, Nblue=%d, Nneutral=%d", Nred, Nblu, Nnut)
self:I(self.lid..text)
end
-- Set values.
self.Nred=Nred
self.Nblu=Nblu
self.Nnut=Nnut
if self:IsRed() then
@ -438,8 +683,10 @@ function OPSZONE:Scan()
self:Captured(coalition.side.NEUTRAL)
else
-- Red zone is now empty (but will remain red).
if not self:IsEmpty() then
self:Empty()
end
end
else
@ -486,8 +733,10 @@ function OPSZONE:Scan()
self:Captured(coalition.side.NEUTRAL)
else
-- Blue zone is empty now.
if not self:IsEmpty() then
self:Empty()
end
end
else
@ -530,7 +779,7 @@ function OPSZONE:Scan()
-- No neutral units in neutral zone any more.
if Nred>0 and Nblu>0 then
env.info("FF neutrals left neutral zone and red and blue are present! What to do?")
env.info(self.lid.."FF neutrals left neutral zone and red and blue are present! What to do?")
-- TODO Contested!
self:Attacked()
self.isContested=true
@ -550,7 +799,7 @@ function OPSZONE:Scan()
--end
else
env.info("FF error")
self:E(self.lid.."ERROR!")
end
return self

View File

@ -31,15 +31,14 @@
-- @field #table elements Table of target elements/units.
-- @field #table casualties Table of dead element names.
-- @field #number prio Priority.
-- @field #number importance Importance
-- @field #number importance Importance.
-- @field #boolean isDestroyed If true, target objects were destroyed.
-- @extends Core.Fsm#FSM
--- **It is far more important to be able to hit the target than it is to haggle over who makes a weapon or who pulls a trigger** -- Dwight D. Eisenhower
--
-- ===
--
-- ![Banner Image](..\Presentations\OPS\Target\_Main.pngs)
--
-- # The TARGET Concept
--
-- Define a target of your mission and monitor its status. Events are triggered when the target is damaged or destroyed.
@ -130,13 +129,13 @@ _TARGETID=0
--- TARGET class version.
-- @field #string version
TARGET.version="0.5.0"
TARGET.version="0.5.1"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: A lot.
-- TODO: Add pseudo functions.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
@ -189,7 +188,7 @@ function TARGET:New(TargetObject)
self:AddTransition("*", "Damaged", "*") -- Target was damaged.
self:AddTransition("*", "Destroyed", "Dead") -- Target was completely destroyed.
self:AddTransition("*", "Dead", "Dead") -- Target was completely destroyed.
self:AddTransition("*", "Dead", "Dead") -- Target is dead. Could be destroyed or despawned.
------------------------
--- Pseudo Functions ---
@ -222,7 +221,6 @@ function TARGET:New(TargetObject)
-- @param #number delay Delay in seconds.
-- Start.
self:__Start(-1)
@ -300,7 +298,7 @@ end
--- Set importance of the target.
-- @param #TARGET self
-- @param #number Priority Priority of the target. Default `nil`.
-- @param #number Importance Importance of the target. Default `nil`.
-- @return #TARGET self
function TARGET:SetImportance(Importance)
self.importance=Importance
@ -311,14 +309,24 @@ end
-- @param #TARGET self
-- @return #boolean If true, target is alive.
function TARGET:IsAlive()
return self:Is("Alive")
local is=self:Is("Alive")
return is
end
--- Check if TARGET is destroyed.
-- @param #TARGET self
-- @return #boolean If true, target is destroyed.
function TARGET:IsDestroyed()
return self.isDestroyed
end
--- Check if TARGET is dead.
-- @param #TARGET self
-- @return #boolean If true, target is dead.
function TARGET:IsDead()
return self:Is("Dead")
local is=self:Is("Dead")
return is
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -469,6 +477,8 @@ function TARGET:onafterObjectDead(From, Event, To, Target)
if self.Ndestroyed==self.Ntargets0 then
self.isDestroyed=true
self:Destroyed()
else
@ -1041,6 +1051,12 @@ function TARGET:GetCoordinate()
return nil
end
--- Get category.
-- @param #TARGET self
-- @return #string Target category. See `TARGET.Category.X`, where `X=AIRCRAFT, GROUND`.
function TARGET:GetCategory()
return self.category
end
--- Get target category.
-- @param #TARGET self