Merge pull request #1699 from FlightControl-Master/FF/Ops

OPS
This commit is contained in:
Frank 2022-03-26 22:39:44 +01:00 committed by GitHub
commit c3591c1fae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 279 additions and 57 deletions

View File

@ -3492,10 +3492,20 @@ function AUFTRAG:Evaluate()
text=text..string.format("=========================")
self:I(self.lid..text)
end
-- Trigger events.
if failed then
self:I(self.lid..string.format("Mission %d [%s] failed!", self.auftragsnummer, self.type))
if self.chief then
self.chief.Nfailure=self.chief.Nfailure+1
end
self:Failed()
else
self:Success()
self:I(self.lid..string.format("Mission %d [%s] success!", self.auftragsnummer, self.type))
if self.chief then
self.chief.Nsuccess=self.chief.Nsuccess+1
end
self:Success()
end
return self

View File

@ -32,7 +32,8 @@
-- @field #string Defcon Defence condition.
-- @field #string strategy Strategy of the CHIEF.
-- @field Ops.Commander#COMMANDER commander Commander of assigned legions.
-- @field #boolean tacview Tactical overview.
-- @field #number Nsuccess Number of successful missions.
-- @field #number Nfailure Number of failed mission.
-- @extends Ops.Intelligence#INTEL
--- *In preparing for battle I have always found that plans are useless, but planning is indispensable* -- Dwight D Eisenhower
@ -136,6 +137,8 @@ CHIEF = {
yellowzoneset = nil,
engagezoneset = nil,
tacview = false,
Nsuccess = 0,
Nfailure = 0,
}
--- Defence condition.
@ -182,16 +185,17 @@ CHIEF.Strategy = {
--- CHIEF class version.
-- @field #string version
CHIEF.version="0.1.1"
CHIEF.version="0.2.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Tactical overview.
-- TODO: Let user specify amount of resources.
-- DONE: Tactical overview.
-- DONE: Add event for opsgroups on mission.
-- DONE: Add event for zone captured.
-- TODO: Limits of missions?
-- DONE: Limits of missions?
-- DONE: Create a good mission, which can be passed on to the COMMANDER.
-- DONE: Capture OPSZONEs.
-- DONE: Get list of own assets and capabilities.
@ -603,6 +607,16 @@ function CHIEF:GetDefcon(Defcon)
return self.Defcon
end
--- Set limit for number of total or specific missions to be executed simultaniously.
-- @param #CHIEF self
-- @param #number Limit Number of max. mission of this type. Default 10.
-- @param #string MissionType Type of mission, e.g. `AUFTRAG.Type.BAI`. Default `"Total"` for total number of missions.
-- @return #CHIEF self
function CHIEF:SetLimitMission(Limit, MissionType)
self.commander:SetLimitMission(Limit, MissionType)
return self
end
--- Set tactical overview on.
-- @param #CHIEF self
-- @return #CHIEF self
@ -701,6 +715,10 @@ function CHIEF:AddMission(Mission)
Mission.chief=self
Mission.statusChief=AUFTRAG.Status.PLANNED
self:I(self.lid..string.format("Adding mission #%d", Mission.auftragsnummer))
self.commander:AddMission(Mission)
return self
@ -822,6 +840,48 @@ function CHIEF:AddStrategicZone(OpsZone, Priority, Importance)
return self
end
--- Remove strategically important zone. All runing missions are cancelled.
-- @param #CHIEF self
-- @param Ops.OpsZone#OPSZONE OpsZone OPS zone object.
-- @param #number Delay Delay in seconds before the zone is removed. Default immidiately.
-- @return #CHIEF self
function CHIEF:RemoveStrategicZone(OpsZone, Delay)
if Delay and Delay>0 then
-- Delayed call.
self:ScheduleOnce(Delay, CHIEF.RemoveStrategicZone, self, OpsZone)
else
-- Loop over all zones in the queue.
for i=#self.zonequeue,1,-1 do
local stratzone=self.zonequeue[i] --#CHIEF.StrategicZone
if OpsZone.zoneName==stratzone.opszone.zoneName then
-- Debug info.
self:T(self.lid..string.format("Removing OPS zone \"%s\" from queue! All running missions will be cancelled", OpsZone.zoneName))
-- Cancel all running missions.
for _,_entry in pairs(OpsZone.Missions or {}) do
local entry = _entry -- Ops.OpsZone#OPSZONE.MISSION
if entry.Coalition==self.coalition and entry.Mission and entry.Mission:IsNotOver() then
entry.Mission:Cancel()
end
end
-- Remove from table.
table.remove(self.zonequeue, i)
-- Done!
return self
end
end
end
return self
end
--- Add a rearming zone.
-- @param #CHIEF self
-- @param Core.Zone#ZONE RearmingZone Rearming zone.
@ -1502,7 +1562,8 @@ function CHIEF:_TacticalOverview()
local NassetsTotal=self.commander:CountAssets()
local NassetsStock=self.commander:CountAssets(true)
local Ncontacts=#self.Contacts
local Nmissions=#self.commander.missionqueue
local NmissionsTotal=#self.commander.missionqueue
local NmissionsRunni=self.commander:CountMissions(AUFTRAG.Type, true)
local Ntargets=#self.targetqueue
local Nzones=#self.zonequeue
@ -1510,22 +1571,30 @@ function CHIEF:_TacticalOverview()
local text=string.format("Tactical Overview\n")
text=text..string.format("=================\n")
-- Strategy and defcon info.
text=text..string.format("Strategy: %s - Defcon: %s\n", self.strategy, self.Defcon)
-- Contact info.
text=text..string.format("Contacts: %d [Border=%d, Conflict=%d, Attack=%d]\n", Ncontacts, self.Nborder, self.Nconflict, self.Nattack)
-- Asset info.
text=text..string.format("Assets: %d [Active=%d, Stock=%d]\n", NassetsTotal, NassetsTotal-NassetsStock, NassetsStock)
-- Target info.
text=text..string.format("Targets: %d\n", Ntargets)
text=text..string.format("Missions: %d\n", Nmissions)
-- Mission info.
text=text..string.format("Missions: %d [Running=%d/%d - Success=%d, Failure=%d]\n", NmissionsTotal, NmissionsRunni, self:GetMissionLimit("Total"), self.Nsuccess, self.Nfailure)
for _,mtype in pairs(AUFTRAG.Type) do
local n=self.commander:CountMissions(mtype)
if n>0 then
text=text..string.format(" - %s: %d\n", mtype, n)
local N=self.commander:CountMissions(mtype, true)
local limit=self:GetMissionLimit(mtype)
text=text..string.format(" - %s: %d [Running=%d/%d]\n", mtype, n, N, limit)
end
end
text=text..string.format("Assets: %d [Stock %d]\n", NassetsTotal, NassetsStock)
-- Strategic zone info.
text=text..string.format("Strategic Zones: %d\n", Nzones)
for _,_stratzone in pairs(self.zonequeue) do
local stratzone=_stratzone --#CHIEF.StrategicZone
@ -1557,6 +1626,13 @@ function CHIEF:CheckTargetQueue()
if Ntargets==0 then
return nil
end
-- Check if total number of missions is reached.
local NoLimit=self:_CheckMissionLimit("Total")
--env.info("FF chief total nolimit="..tostring(NoLimit))
if NoLimit==false then
return nil
end
-- Sort results table wrt prio and threatlevel.
local function _sort(a, b)
@ -1699,33 +1775,41 @@ function CHIEF:CheckTargetQueue()
for _,_mp in pairs(MissionPerformances) do
local mp=_mp --#CHIEF.MissionPerformance
-- Check mission type limit.
local notlimited=self:_CheckMissionLimit(mp.MissionType)
--env.info(string.format("FF chief %s nolimit=%s", mp.MissionType, tostring(NoLimit)))
if notlimited then
-- Debug info.
self:T2(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, assets, legions=self:RecruitAssetsForTarget(target, mp.MissionType, NassetsMin, NassetsMax)
if recruited then
self:T(self.lid..string.format("Recruited %d assets for mission type %s [performance=%d] of target %s", #assets, mp.MissionType, mp.Performance, target:GetName()))
-- Create a mission.
mission=AUFTRAG:NewFromTarget(target, mp.MissionType)
-- Add asset to mission.
if mission then
for _,_asset in pairs(assets) do
local asset=_asset
mission:AddAsset(asset)
-- Debug info.
self:T2(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, assets, legions=self:RecruitAssetsForTarget(target, mp.MissionType, NassetsMin, NassetsMax)
if recruited then
self:T(self.lid..string.format("Recruited %d assets for mission type %s [performance=%d] of target %s", #assets, mp.MissionType, mp.Performance, target:GetName()))
-- Create a mission.
mission=AUFTRAG:NewFromTarget(target, mp.MissionType)
-- Add asset to mission.
if mission then
for _,_asset in pairs(assets) do
local asset=_asset
mission:AddAsset(asset)
end
Legions=legions
-- We got what we wanted ==> leave loop.
break
end
Legions=legions
-- We got what we wanted ==> leave loop.
break
else
self:T(self.lid..string.format("Could NOT recruit assets for mission type %s [performance=%d] of target %s", mp.MissionType, mp.Performance, target:GetName()))
end
else
self:T(self.lid..string.format("Could NOT recruit assets for mission type %s [performance=%d] of target %s", mp.MissionType, mp.Performance, target:GetName()))
end
end
end
@ -1754,6 +1838,26 @@ function CHIEF:CheckTargetQueue()
end
--- Check if limit of missions has been reached.
-- @param #CHIEF self
-- @param #string MissionType Type of mission.
-- @return #boolean If `true`, mission limit has **not** been reached. If `false`, limit has been reached.
function CHIEF:_CheckMissionLimit(MissionType)
return self.commander:_CheckMissionLimit(MissionType)
end
--- Get mission limit.
-- @param #CHIEF self
-- @param #string MissionType Type of mission.
-- @return #number Limit. Unlimited mission types are returned as 999.
function CHIEF:GetMissionLimit(MissionType)
local l=self.commander.limitMission[MissionType]
if not l then
l=999
end
return l
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Strategic Zone Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -1774,6 +1878,13 @@ function CHIEF:CheckOpsZoneQueue()
if Nzones==0 then
return nil
end
-- Check if total number of missions is reached.
local NoLimit=self:_CheckMissionLimit("Total")
--env.info("FF chief zone total nolimit="..tostring(NoLimit))
if NoLimit==false then
return nil
end
-- Sort results table wrt prio.
local function _sort(a, b)
@ -1802,7 +1913,7 @@ function CHIEF:CheckOpsZoneQueue()
local ownercoalition=stratzone.opszone:GetOwner()
-- Check coalition and importance.
if ownercoalition~=self.coalition and (stratzone.importance==nil or stratzone.importance<=vip) then
if ownercoalition~=self.coalition and (stratzone.importance==nil or stratzone.importance<=vip) and (not stratzone.opszone:IsStopped()) then
-- Has a patrol mission?
local hasMissionPatrol=stratzone.opszone:_FindMissions(self.coalition,AUFTRAG.Type.ONGUARD) or stratzone.opszone:_FindMissions(self.coalition,AUFTRAG.Type.ARMOREDGUARD)
@ -1908,7 +2019,15 @@ function CHIEF:CheckOpsZoneQueue()
end
end
-- Loop over strategic zone and remove stopped zones.
for i=#self.zonequeue, 1, -1 do
local stratzone=self.zonequeue[i] --#CHIEF.StrategicZone
if stratzone.opszone:IsStopped() then
self:RemoveStrategicZone(stratzone.opszone)
end
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@ -29,6 +29,7 @@
-- @field #table awacsZones AWACS zones. Each element is of type `#AIRWING.PatrolZone`.
-- @field #table tankerZones Tanker zones. Each element is of type `#AIRWING.TankerZone`.
-- @field Ops.Chief#CHIEF chief Chief of staff.
-- @field #table limitMission Table of limits for mission types.
-- @extends Core.Fsm#FSM
--- *He who has never leared to obey cannot be a good commander* -- Aristotle
@ -128,6 +129,7 @@ COMMANDER = {
gcicapZones = {},
awacsZones = {},
tankerZones = {},
limitMission = {},
}
--- COMMANDER class version.
@ -359,6 +361,22 @@ function COMMANDER:SetVerbosity(VerbosityLevel)
return self
end
--- Set limit for number of total or specific missions to be executed simultaniously.
-- @param #COMMANDER self
-- @param #number Limit Number of max. mission of this type. Default 10.
-- @param #string MissionType Type of mission, e.g. `AUFTRAG.Type.BAI`. Default `"Total"` for total number of missions.
-- @return #COMMANDER self
function COMMANDER:SetLimitMission(Limit, MissionType)
MissionType=MissionType or "Total"
if MissionType then
self.limitMission[MissionType]=Limit or 10
else
self:E(self.lid.."ERROR: No mission type given for setting limit!")
end
return self
end
--- Get coalition.
-- @param #COMMANDER self
-- @return #number Coalition.
@ -1046,6 +1064,11 @@ function COMMANDER:CheckMissionQueue()
if Nmissions==0 then
return nil
end
local NoLimit=self:_CheckMissionLimit("Total")
if NoLimit==false then
return nil
end
-- Sort results table wrt prio and start time.
local function _sort(a, b)
@ -1070,7 +1093,7 @@ function COMMANDER:CheckMissionQueue()
local mission=_mission --Ops.Auftrag#AUFTRAG
-- We look for PLANNED missions.
if mission:IsPlanned() and mission:IsReadyToGo() and (mission.importance==nil or mission.importance<=vip) then
if mission:IsPlanned() and mission:IsReadyToGo() and (mission.importance==nil or mission.importance<=vip) and self:_CheckMissionLimit(mission.type) then
---
-- PLANNNED Mission
@ -1394,6 +1417,31 @@ end
-- Resources
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Check if limit of missions has been reached.
-- @param #COMMANDER self
-- @param #string MissionType Type of mission.
-- @return #boolean If `true`, mission limit has **not** been reached. If `false`, limit has been reached.
function COMMANDER:_CheckMissionLimit(MissionType)
local limit=self.limitMission[MissionType]
if limit then
if MissionType=="Total" then
MissionType=AUFTRAG.Type
end
local N=self:CountMissions(MissionType, true)
if N>=limit then
return false
end
end
return true
end
--- Count assets of all assigned legions.
-- @param #COMMANDER self
-- @param #boolean InStock If true, only assets that are in the warehouse stock/inventory are counted.
@ -1414,16 +1462,21 @@ end
--- Count assets of all assigned legions.
-- @param #COMMANDER self
-- @param #table MissionTypes (Optional) Count only missions of these types. Default is all types.
-- @param #boolean OnlyRunning If `true`, only count running missions.
-- @return #number Amount missions.
function COMMANDER:CountMissions(MissionTypes)
function COMMANDER:CountMissions(MissionTypes, OnlyRunning)
local N=0
for _,_mission in pairs(self.missionqueue) do
local mission=_mission --Ops.Auftrag#AUFTRAG
if (not OnlyRunning) or (mission.statusCommander~=AUFTRAG.Status.PLANNED) then
-- Check if this mission type is requested.
if AUFTRAG.CheckMissionType(mission.type, MissionTypes) then
N=N+1
-- Check if this mission type is requested.
if AUFTRAG.CheckMissionType(mission.type, MissionTypes) then
N=N+1
end
end
end

View File

@ -39,10 +39,10 @@
-- @field Wrapper.Marker#MARKER marker Marker on the F10 map.
-- @field #string markerText Text shown in the maker.
-- @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.
-- @extends Core.Fsm#FSM
--- Be surprised!
--- *Gentlemen, when the enemy is committed to a mistake we must not interrupt him too soon.* --- Horation Nelson
--
-- ===
--
@ -73,7 +73,7 @@ OPSZONE = {
--- OPSZONE class version.
-- @field #string version
OPSZONE.version="0.2.0"
OPSZONE.version="0.3.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
@ -81,8 +81,8 @@ OPSZONE.version="0.2.0"
-- TODO: Pause/unpause evaluations.
-- TODO: Capture time, i.e. time how long a single coalition has to be inside the zone to capture it.
-- DONE: Can neutrals capture? No, since they are _neutral_!
-- TODO: Differentiate between ground attack and boming by air or arty.
-- DONE: Can neutrals capture? No, since they are _neutral_!
-- DONE: Capture airbases.
-- DONE: Can statics capture or hold a zone? No, unless explicitly requested by mission designer.
@ -166,8 +166,7 @@ function OPSZONE:New(Zone, CoalitionOwner)
-- Status timer.
self.timerStatus=TIMER:New(OPSZONE.Status, self)
-- FMS start state is EMPTY.
-- FMS start state is STOPPED.
self:SetStartState("Stopped")
-- Add FSM transitions.
@ -199,6 +198,7 @@ function OPSZONE:New(Zone, CoalitionOwner)
--- Triggers the FSM event "Stop".
-- @function [parent=#OPSZONE] Stop
-- @param #OPSZONE self
--- Triggers the FSM event "Stop" after a delay.
@ -318,13 +318,13 @@ end
--- Set categories of objects that can capture or hold the zone.
--
-- * Default is {Object.Category.UNIT} so only units can capture and hold zones.
-- * Set to `{Object.Category.UNIT, Object.Category.STATIC}` if static objects can capture and hold zones
-- * Default is {Object.Category.UNIT, Object.Category.STATIC} so units and statics can capture and hold zones.
-- * Set to `{Object.Category.UNIT}` if only units should be able to capture and hold zones
--
-- Which units can capture zones can be further refined by `:SetUnitCategories()`.
--
-- @param #OPSZONE self
-- @param #table Categories Object categories. Default is `{Object.Category.UNIT}`.
-- @param #table Categories Object categories. Default is `{Object.Category.UNIT, Object.Category.STATIC}`.
-- @return #OPSZONE self
function OPSZONE:SetObjectCategories(Categories)
@ -435,6 +435,13 @@ function OPSZONE:GetName()
return self.zoneName
end
--- Get the zone object.
-- @param #OPSZONE self
-- @return Core.Zone#ZONE The zone.
function OPSZONE:GetZone()
return self.zone
end
--- Get previous owner of the zone.
-- @param #OPSZONE self
-- @return #number Previous owner coalition.
@ -480,6 +487,15 @@ function OPSZONE:IsNeutral()
return is
end
--- Check if a certain coalition is currently owning the zone.
-- @param #OPSZONE self
-- @param #number Coalition The Coalition that is supposed to own the zone.
-- @return #boolean If `true`, zone is owned by the given coalition.
function OPSZONE:IsCoalition(Coalition)
local is=self.ownerCurrent==Coalition
return is
end
--- Check if zone is guarded.
-- @param #OPSZONE self
-- @return #boolean If `true`, zone is guarded.
@ -559,9 +575,33 @@ function OPSZONE:onafterStop(From, Event, To)
-- Reinit the timer.
self.timerStatus:Stop()
-- Draw zone.
if self.drawZone then
self.zone:UndrawZone()
end
-- Remove marker.
if self.markZone then
self.marker:Remove()
end
-- Unhandle events.
self:UnHandleEvent(EVENTS.BaseCaptured)
-- Cancel all running missions.
for _,_entry in pairs(self.Missions or {}) do
local entry = _entry -- Ops.OpsZone#OPSZONE.MISSION
if entry.Mission and entry.Mission:IsNotOver() then
entry.Mission:Cancel()
end
end
-- Stop FSM scheduler.
self.CallScheduler:Clear()
if self.Scheduler then
self.Scheduler:Clear()
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -1240,13 +1280,13 @@ function OPSZONE:_GetMissions()
return self.Missions
end
--- Add an entry to the OpsZone mission table
--- Add an entry to the OpsZone mission table.
-- @param #OPSZONE self
-- @param #number Coalition Coalition of type e.g. coalition.side.NEUTRAL
-- @param #string Type Type of mission, e.g. AUFTRAG.Type.CAS
-- @return #boolean found True if we have that kind of mission, else false
-- @return #table Missions Table of Ops.Auftrag#AUFTRAG entries
function OPSZONE:_FindMissions(Coalition,Type)
-- @param #number Coalition Coalition of type e.g. `coalition.side.NEUTRAL`.
-- @param #string Type Type of mission, e.g. `AUFTRAG.Type.CAS`.
-- @return #boolean found True if we have that kind of mission, else false.
-- @return #table Missions Table of `Ops.Auftrag#AUFTRAG` entries.
function OPSZONE:_FindMissions(Coalition, Type)
-- search the table
local foundmissions = {}
local found = false
@ -1260,7 +1300,7 @@ function OPSZONE:_FindMissions(Coalition,Type)
return found, foundmissions
end
--- Housekeeping
--- Clean mission table from missions that are over.
-- @param #OPSZONE self
-- @return #OPSZONE self
function OPSZONE:_CleanMissionTable()