diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index a7d1c9210..9d4ee753f 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -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 diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index de7c143a8..a90297ff0 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index 1896674d0..95c545dfe 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -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 diff --git a/Moose Development/Moose/Ops/OpsZone.lua b/Moose Development/Moose/Ops/OpsZone.lua index 002ac00cc..575bb4919 100644 --- a/Moose Development/Moose/Ops/OpsZone.lua +++ b/Moose Development/Moose/Ops/OpsZone.lua @@ -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()