From 2ae2ee64be804bc3a079e613b1ec491987c35bb9 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 1 Oct 2021 12:04:15 +0200 Subject: [PATCH] OPS - Lots of updates and improvements --- Moose Development/Moose/Core/Database.lua | 14 + Moose Development/Moose/Core/Timer.lua | 14 +- Moose Development/Moose/Ops/AirWing.lua | 22 + Moose Development/Moose/Ops/ArmyGroup.lua | 3 + Moose Development/Moose/Ops/Auftrag.lua | 4 +- Moose Development/Moose/Ops/Chief.lua | 727 +++++++++++++------ Moose Development/Moose/Ops/Cohort.lua | 32 +- Moose Development/Moose/Ops/Commander.lua | 141 +++- Moose Development/Moose/Ops/FlightGroup.lua | 88 ++- Moose Development/Moose/Ops/Legion.lua | 13 +- Moose Development/Moose/Ops/OpsGroup.lua | 52 +- Moose Development/Moose/Ops/OpsTransport.lua | 58 ++ Moose Development/Moose/Ops/OpsZone.lua | 192 ++++- 13 files changed, 1041 insertions(+), 319 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 618e34cbf..fe5f5c450 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -860,6 +860,20 @@ function DATABASE:GetGroupTemplateFromUnitName( UnitName ) end end +--- Get group template from unit name. +-- @param #DATABASE self +-- @param #string UnitName Name of the unit. +-- @return #table Group template. +function DATABASE:GetUnitTemplateFromUnitName( UnitName ) + if self.Templates.Units[UnitName] then + return self.Templates.Units[UnitName] + else + self:E("ERROR: Unit template does not exist for unit "..tostring(UnitName)) + return nil + end +end + + --- Get coalition ID from client name. -- @param #DATABASE self -- @param #string ClientName Name of the Client. diff --git a/Moose Development/Moose/Core/Timer.lua b/Moose Development/Moose/Core/Timer.lua index 6bda7b66b..2d01d5c17 100644 --- a/Moose Development/Moose/Core/Timer.lua +++ b/Moose Development/Moose/Core/Timer.lua @@ -107,14 +107,15 @@ _TIMERID=0 --- TIMER class version. -- @field #string version -TIMER.version="0.1.1" +TIMER.version="0.1.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO: Randomization. -- TODO: Pause/unpause. --- TODO: Write docs. +-- DONE: Write docs. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -228,6 +229,15 @@ function TIMER:SetMaxFunctionCalls(Nmax) return self end +--- Set time interval. Can also be set when the timer is already running and is applied after the next function call. +-- @param #TIMER self +-- @param #number dT Time interval in seconds. +-- @return #TIMER self +function TIMER:SetTimeInterval(dT) + self.dT=dT + return self +end + --- Check if the timer has been started and was not stopped. -- @param #TIMER self -- @return #boolean If `true`, the timer is running. diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 4c3e62a66..91311559a 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -139,6 +139,28 @@ AIRWING = { -- @field #number noccupied Number of flights on this patrol point. -- @field Wrapper.Marker#MARKER marker F10 marker. +--- AWACS zone. +-- @type AIRWING.AwacsZone +-- @field Core.Zone#ZONE zone Zone. +-- @field #number altitude Altitude in feet. +-- @field #number heading Heading in degrees. +-- @field #number leg Leg length in NM. +-- @field #number speed Speed in knots. +-- @field #number refuelsystem Refueling system type: `0=Unit.RefuelingSystem.BOOM_AND_RECEPTACLE`, `1=Unit.RefuelingSystem.PROBE_AND_DROGUE`. +-- @field Ops.Auftrag#AUFTRAG mission Mission assigned. +-- @field Wrapper.Marker#MARKER marker F10 marker. + +--- Tanker zone. +-- @type AIRWING.TankerZone +-- @field Core.Point#COORDINATE coord Patrol coordinate. +-- @field #number altitude Altitude in feet. +-- @field #number heading Heading in degrees. +-- @field #number leg Leg length in NM. +-- @field #number speed Speed in knots. +-- @field #number refuelsystem Refueling system type: `0=Unit.RefuelingSystem.BOOM_AND_RECEPTACLE`, `1=Unit.RefuelingSystem.PROBE_AND_DROGUE`. +-- @field Ops.Auftrag#AUFTRAG mission Mission assigned. +-- @field Wrapper.Marker#MARKER marker F10 marker. + --- AIRWING class version. -- @field #string version AIRWING.version="0.9.0" diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 14fa66182..4c27e75ee 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -361,6 +361,7 @@ function ARMYGROUP:Status() -- Is group alive? local alive=self:IsAlive() + -- Check that group EXISTS and is ACTIVE. if alive then -- Update position etc. @@ -396,6 +397,7 @@ function ARMYGROUP:Status() end + -- Check that group EXISTS. if alive~=nil then if self.verbose>=1 then @@ -1376,6 +1378,7 @@ function ARMYGROUP:_InitGroup(Template) for _,unit in pairs(units) do self:_AddElementByName(unit:GetName()) end + -- Init done. self.groupinitialized=true diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index af261cbd2..d6ed68002 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -1130,7 +1130,7 @@ end -- @param Core.Point#COORDINATE Coordinate Where to orbit. Default is the center of the CAS zone. -- @param #number Heading Heading of race-track pattern in degrees. If not specified, a simple circular orbit is performed. -- @param #number Leg Length of race-track in NM. If not specified, a simple circular orbit is performed. --- @param #table TargetTypes (Optional) Table of target types. Default {"Helicopters", "Ground Units", "Light armed ships"}. +-- @param #table TargetTypes (Optional) Table of target types. Default `{"Helicopters", "Ground Units", "Light armed ships"}`. -- @return #AUFTRAG self function AUFTRAG:NewCAS(ZoneCAS, Altitude, Speed, Coordinate, Heading, Leg, TargetTypes) @@ -1718,7 +1718,7 @@ function AUFTRAG:NewONGUARD(Coordinate) mission:_TargetFromObject(Coordinate) mission.optionROE=ENUMS.ROE.OpenFire - mission.optionAlarm=ENUMS.AlarmState.Red + mission.optionAlarm=ENUMS.AlarmState.Auto mission.missionFraction=1.0 diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index 48eaf2b62..be51a22ce 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -49,8 +49,8 @@ CHIEF = { --- Defence condition. -- @type CHIEF.DEFCON --- @field #string GREEN No enemy activities detected. --- @field #string YELLOW Enemy near our border. +-- @field #string GREEN No enemy activities detected in our terretory or conflict zones. +-- @field #string YELLOW Enemy in conflict zones. -- @field #string RED Enemy within our border. CHIEF.DEFCON = { GREEN="Green", @@ -78,6 +78,15 @@ CHIEF.Strategy = { -- @field #string MissionType Mission Type. -- @field #number Performance Performance: a number between 0 and 100, where 100 is best performance. +--- Strategic zone. +-- @type CHIEF.StrategicZone +-- @field Ops.OpsZone#OPSZONE opszone OPS zone. +-- @field #number prio Priority. +-- @field #number importance Importance +-- @field Ops.Auftrag#AUFTRAG missionPatrol Patrol mission. +-- @field Ops.Auftrag#AUFTRAG missionCAS CAS mission. + + --- CHIEF class version. -- @field #string version CHIEF.version="0.0.1" @@ -86,13 +95,16 @@ CHIEF.version="0.0.1" -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Create a good mission, which can be passed on to the COMMANDER. --- TODO: Capture OPSZONEs. --- TODO: Get list of own assets and capabilities. +-- TODO: Tactical overview. +-- DONE: Add event for opsgroups on mission. +-- DONE: Add event for zone captured. +-- TODO: 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. -- DONE: Get list/overview of enemy assets etc. -- DONE: Put all contacts into target list. Then make missions from them. --- TODO: Set of interesting zones. --- TODO: Define A2A and A2G parameters. +-- DONE: Set of interesting zones. -- DONE: Add/remove spawned flightgroups to detection set. -- DONE: Borderzones. -- NOGO: Maybe it's possible to preselect the assets for the mission. @@ -117,7 +129,8 @@ function CHIEF:New(AgentSet, Coalition, Alias) -- Defaults. self:SetBorderZones() - self:SetYellowZones() + self:SetConflictZones() + self:SetAttackZones() self:SetThreatLevelRange() -- Init stuff. @@ -125,24 +138,24 @@ function CHIEF:New(AgentSet, Coalition, Alias) self.strategy=CHIEF.Strategy.DEFENSIVE -- Create a new COMMANDER. - self.commander=COMMANDER:New() + self.commander=COMMANDER:New(Coalition) -- Add FSM transitions. -- From State --> Event --> To State - 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("*", "MissionAssign", "*") -- Assign mission to a COMMANDER. self:AddTransition("*", "MissionCancel", "*") -- Cancel mission. + self:AddTransition("*", "TransportCancel", "*") -- Cancel transport. - self:AddTransition("*", "DefconChange", "*") -- Change defence condition. + self:AddTransition("*", "OpsOnMission", "*") -- An OPSGROUP was send on a Mission (AUFTRAG). - self:AddTransition("*", "StategyChange", "*") -- Change strategy condition. + self:AddTransition("*", "ZoneCaptured", "*") -- + self:AddTransition("*", "ZoneLost", "*") -- + self:AddTransition("*", "ZoneEmpty", "*") -- + self:AddTransition("*", "ZoneAttacked", "*") -- - self:AddTransition("*", "DeclareWar", "*") -- Declare War. Not implemented. + self:AddTransition("*", "DefconChange", "*") -- Change defence condition. + self:AddTransition("*", "StrategyChange", "*") -- Change strategy condition. ------------------------ --- Pseudo Functions --- @@ -217,23 +230,26 @@ function CHIEF:New(AgentSet, Coalition, Alias) -- @param #string Strategy New stragegy. - --- Triggers the FSM event "MissionAssignToAny". - -- @function [parent=#CHIEF] MissionAssignToAny + --- Triggers the FSM event "MissionAssign". + -- @function [parent=#CHIEF] MissionAssign -- @param #CHIEF self + -- @param Ops.Legion#LEGION Legion The legion. -- @param Ops.Auftrag#AUFTRAG Mission The mission. - --- Triggers the FSM event "MissionAssignToAny" after a delay. - -- @function [parent=#CHIEF] __MissionAssignToAny + --- Triggers the FSM event "MissionAssign" after a delay. + -- @function [parent=#CHIEF] __MissionAssign -- @param #CHIEF self -- @param #number delay Delay in seconds. + -- @param Ops.Legion#LEGION Legion The legion. -- @param Ops.Auftrag#AUFTRAG Mission The mission. - --- On after "MissionAssignToAny" event. - -- @function [parent=#CHIEF] OnAfterMissionAssignToAny + --- On after "MissionAssign" event. + -- @function [parent=#CHIEF] OnAfterMissionAssign -- @param #CHIEF self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. + -- @param Ops.Legion#LEGION Legion The legion. -- @param Ops.Auftrag#AUFTRAG Mission The mission. @@ -276,6 +292,106 @@ function CHIEF:New(AgentSet, Coalition, Alias) -- @param #string To To state. -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. + + --- Triggers the FSM event "OpsOnMission". + -- @function [parent=#CHIEF] OpsOnMission + -- @param #CHIEF self + -- @param Ops.OpsGroup#OPSGROUP OpsGroup The OPS group on mission. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + --- Triggers the FSM event "OpsOnMission" after a delay. + -- @function [parent=#CHIEF] __OpsOnMission + -- @param #CHIEF self + -- @param #number delay Delay in seconds. + -- @param Ops.OpsGroup#OPSGROUP OpsGroup The OPS group on mission. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + --- On after "OpsOnMission" event. + -- @function [parent=#CHIEF] OnAfterOpsOnMission + -- @param #CHIEF self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Ops.OpsGroup#OPSGROUP OpsGroup The OPS group on mission. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + + --- Triggers the FSM event "ZoneCaptured". + -- @function [parent=#CHIEF] ZoneCaptured + -- @param #CHIEF self + -- @param Ops.OpsZone#OPSZONE OpsZone Zone that was captured. + + --- Triggers the FSM event "ZoneCaptured" after a delay. + -- @function [parent=#CHIEF] __ZoneCaptured + -- @param #CHIEF self + -- @param #number delay Delay in seconds. + -- @param Ops.OpsZone#OPSZONE OpsZone Zone that was captured. + + --- On after "ZoneCaptured" event. + -- @function [parent=#CHIEF] OnAfterZoneCaptured + -- @param #CHIEF self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Ops.OpsZone#OPSZONE OpsZone Zone that was captured. + + --- Triggers the FSM event "ZoneLost". + -- @function [parent=#CHIEF] ZoneLost + -- @param #CHIEF self + -- @param Ops.OpsZone#OPSZONE OpsZone Zone that was lost. + + --- Triggers the FSM event "ZoneLost" after a delay. + -- @function [parent=#CHIEF] __ZoneLost + -- @param #CHIEF self + -- @param #number delay Delay in seconds. + -- @param Ops.OpsZone#OPSZONE OpsZone Zone that was lost. + + --- On after "ZoneLost" event. + -- @function [parent=#CHIEF] OnAfterZoneLost + -- @param #CHIEF self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Ops.OpsZone#OPSZONE OpsZone Zone that was lost. + + --- Triggers the FSM event "ZoneEmpty". + -- @function [parent=#CHIEF] ZoneEmpty + -- @param #CHIEF self + -- @param Ops.OpsZone#OPSZONE OpsZone Zone that is empty now. + + --- Triggers the FSM event "ZoneEmpty" after a delay. + -- @function [parent=#CHIEF] __ZoneEmpty + -- @param #CHIEF self + -- @param #number delay Delay in seconds. + -- @param Ops.OpsZone#OPSZONE OpsZone Zone that is empty now. + + --- On after "ZoneEmpty" event. + -- @function [parent=#CHIEF] OnAfterZoneEmpty + -- @param #CHIEF self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Ops.OpsZone#OPSZONE OpsZone Zone that is empty now. + + --- Triggers the FSM event "ZoneAttacked". + -- @function [parent=#CHIEF] ZoneAttacked + -- @param #CHIEF self + -- @param Ops.OpsZone#OPSZONE OpsZone Zone that is beeing attacked. + + --- Triggers the FSM event "ZoneAttacked" after a delay. + -- @function [parent=#CHIEF] __ZoneAttacked + -- @param #CHIEF self + -- @param #number delay Delay in seconds. + -- @param Ops.OpsZone#OPSZONE OpsZone Zone that is beeing attacked. + + --- On after "ZoneAttacked" event. + -- @function [parent=#CHIEF] OnAfterZoneAttacked + -- @param #CHIEF self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Ops.OpsZone#OPSZONE OpsZone Zone that is beeing attacked. + return self end @@ -514,11 +630,29 @@ end -- @return #CHIEF self function CHIEF:AddTarget(Target) - table.insert(self.targetqueue, Target) + if not self:IsTarget(Target) then + table.insert(self.targetqueue, Target) + end return self end +--- Check if a TARGET is already in the queue. +-- @param #CHIEF self +-- @param Ops.Target#TARGET Target Target object to be added. +-- @return #boolean If `true`, target exists in the target queue. +function CHIEF:IsTarget(Target) + + for _,_target in pairs(self.targetqueue) do + local target=_target --Ops.Target#TARGET + if target.uid==Target.uid then + return true + end + end + + return false +end + --- Remove target from queue. -- @param #CHIEF self -- @param Ops.Target#TARGET Target The target. @@ -539,24 +673,19 @@ function CHIEF:RemoveTarget(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 - --- Add strategically important zone. -- @param #CHIEF self -- @param Ops.OpsZone#OPSZONE OpsZone OPS zone object. +-- @param #number Priority Priority. +-- @param #number Importance Importance. -- @return #CHIEF self -function CHIEF:AddOpsZone(OpsZone) +function CHIEF:AddStrateticZone(OpsZone, Priority, Importance) + + local stratzone={} --#CHIEF.StrategicZone + + stratzone.opszone=OpsZone + stratzone.prio=Priority or 50 + stratzone.importance=Importance -- Start ops zone. if OpsZone:IsStopped() then @@ -564,7 +693,10 @@ function CHIEF:AddOpsZone(OpsZone) end -- Add to table. - table.insert(self.zonequeue, OpsZone) + table.insert(self.zonequeue, stratzone) + + -- Add chief so we get informed when something happens. + OpsZone:_AddChief(self) return self end @@ -593,8 +725,28 @@ function CHIEF:AddRefuellingZone(RefuellingZone) return supplyzone end +--- Add an AWACS zone. +-- @param #CHIEF self +-- @param Core.Zone#ZONE AwacsZone Zone. +-- @param #number Altitude Orbit altitude in feet. Default is 12,0000 feet. +-- @param #number Speed Orbit speed in KIAS. Default 350 kts. +-- @param #number Heading Heading of race-track pattern in degrees. Default 270 (East to West). +-- @param #number Leg Length of race-track in NM. Default 30 NM. +-- @return Ops.AirWing#AIRWING.AwacsZone The AWACS zone. +function CHIEF:AddAwacsZone(AwacsZone, Altitude, Speed, Heading, Leg) ---- Set border zone set. + -- Hand over to commander. + local awacszone=self.commander:AddAwacsZone(AwacsZone, Altitude, Speed, Heading, Leg) + + return awacszone +end + + +--- Set border zone set, defining your territory. +-- +-- * Detected enemy troops in these zones will trigger defence condition `RED`. +-- * Enemies in these zones will only be engaged if strategy is at least `CHIEF.STRATEGY.DEFENSIVE`. +-- -- @param #CHIEF self -- @param Core.Set#SET_ZONE BorderZoneSet Set of zones, defining our borders. -- @return #CHIEF self @@ -607,41 +759,119 @@ function CHIEF:SetBorderZones(BorderZoneSet) end --- Add a zone defining your territory. +-- +-- * Detected enemy troops in these zones will trigger defence condition `RED`. +-- * Enemies in these zones will only be engaged if strategy is at least `CHIEF.STRATEGY.DEFENSIVE`. +-- -- @param #CHIEF self --- @param Core.Zone#ZONE BorderZone The zone defining the border of your territory. +-- @param Core.Zone#ZONE Zone The zone. -- @return #CHIEF self -function CHIEF:AddBorderZone(BorderZone) +function CHIEF:AddBorderZone(Zone) -- Add a border zone. - self.borderzoneset:AddZone(BorderZone) + self.borderzoneset:AddZone(Zone) return self end ---- Set yellow zone set. Detected enemy troops in this zone will trigger defence condition YELLOW. +--- Set conflict zone set. +-- +-- * Detected enemy troops in these zones will trigger defence condition `YELLOW`. +-- * Enemies in these zones will only be engaged if strategy is at least `CHIEF.STRATEGY.OFFENSIVE`. +-- -- @param #CHIEF self --- @param Core.Set#SET_ZONE YellowZoneSet Set of zones, defining our borders. +-- @param Core.Set#SET_ZONE ZoneSet Set of zones. -- @return #CHIEF self -function CHIEF:SetYellowZones(YellowZoneSet) +function CHIEF:SetConflictZones(ZoneSet) - -- Border zones. - self.yellowzoneset=YellowZoneSet or SET_ZONE:New() + -- Conflict zones. + self.yellowzoneset=ZoneSet or SET_ZONE:New() return self end ---- Add a zone defining an area outside your territory that is monitored for enemy activity. +--- Add a conflict zone. +-- +-- * Detected enemy troops in these zones will trigger defence condition `YELLOW`. +-- * Enemies in these zones will only be engaged if strategy is at least `CHIEF.STRATEGY.OFFENSIVE`. +-- -- @param #CHIEF self --- @param Core.Zone#ZONE YellowZone The zone defining the border of your territory. +-- @param Core.Zone#ZONE Zone The zone to add. -- @return #CHIEF self -function CHIEF:AddYellowZone(YellowZone) +function CHIEF:AddConflictZone(Zone) - -- Add a border zone. - self.yellowzoneset:AddZone(YellowZone) + -- Add a conflict zone. + self.yellowzoneset:AddZone(Zone) return self end +--- Set attack zone set. +-- +-- * Enemies in these zones will only be engaged if strategy is at least `CHIEF.STRATEGY.AGGRESSIVE`. +-- +-- @param #CHIEF self +-- @param Core.Set#SET_ZONE ZoneSet Set of zones. +-- @return #CHIEF self +function CHIEF:SetAttackZones(ZoneSet) + + -- Attacak zones. + self.engagezoneset=ZoneSet or SET_ZONE:New() + + return self +end + +--- Add an attack zone. +-- +-- * Enemies in these zones will only be engaged if strategy is at least `CHIEF.STRATEGY.AGGRESSIVE`. +-- +-- @param #CHIEF self +-- @param Core.Zone#ZONE Zone The zone to add. +-- @return #CHIEF self +function CHIEF:AddAttackZone(Zone) + + -- Add an attack zone. + self.engagezoneset:AddZone(Zone) + + return self +end + +--- Check if current strategy is passive. +-- @param #CHIEF self +-- @return #boolean If `true`, strategy is passive. +function CHIEF:IsPassive() + return self.strategy==CHIEF.Strategy.PASSIVE +end + +--- Check if current strategy is defensive. +-- @param #CHIEF self +-- @return #boolean If `true`, strategy is defensive. +function CHIEF:IsDefensive() + return self.strategy==CHIEF.Strategy.DEFENSIVE +end + +--- Check if current strategy is offensive. +-- @param #CHIEF self +-- @return #boolean If `true`, strategy is offensive. +function CHIEF:IsOffensive() + return self.strategy==CHIEF.Strategy.OFFENSIVE +end + +--- Check if current strategy is aggressive. +-- @param #CHIEF self +-- @return #boolean If `true`, strategy is agressive. +function CHIEF:IsAgressive() + return self.strategy==CHIEF.Strategy.AGGRESSIVE +end + +--- Check if current strategy is total war. +-- @param #CHIEF self +-- @return #boolean If `true`, strategy is total war. +function CHIEF:IsTotalWar() + return self.strategy==CHIEF.Strategy.TOTALWAR +end + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Start & Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -714,22 +944,29 @@ function CHIEF:onafterStatus(From, Event, To) --- -- Create TARGETs for all new contacts. - local Nred=0 ; local Nyellow=0 ; local Nengage=0 + local Nborder=0 ; local Nconflict=0 ; local Nattack=0 for _,_contact in pairs(self.Contacts) do local contact=_contact --Ops.Intelligence#INTEL.Contact local group=contact.group --Wrapper.Group#GROUP - -- Check if contact inside of out border. + -- Check if contact inside of our borders. local inred=self:CheckGroupInBorder(group) if inred then - Nred=Nred+1 + Nborder=Nborder+1 end - -- Check if contact is in the yellow zones. - local inyellow=self:CheckGroupInYellow(group) + -- Check if contact is in the conflict zones. + local inyellow=self:CheckGroupInConflict(group) if inyellow then - Nyellow=Nyellow+1 + Nconflict=Nconflict+1 end + + -- Check if contact is in the attack zones. + local inattack=self:CheckGroupInAttack(group) + if inattack then + Nattack=Nattack+1 + end + -- Check if this is not already a target. if not contact.target then @@ -755,9 +992,9 @@ function CHIEF:onafterStatus(From, Event, To) --- -- TODO: Need to introduce time check to avoid fast oscillation between different defcon states in case groups move in and out of the zones. - if Nred>0 then + if Nborder>0 then self:SetDefcon(CHIEF.DEFCON.RED) - elseif Nyellow>0 then + elseif Nconflict>0 then self:SetDefcon(CHIEF.DEFCON.YELLOW) else self:SetDefcon(CHIEF.DEFCON.GREEN) @@ -788,7 +1025,8 @@ function CHIEF:onafterStatus(From, Event, To) local Ntargets=#self.targetqueue -- Info message - 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) + local text=string.format("Defcon=%s Strategy=%s: Assets=%d, Contacts=%d [Border=%d, Conflict=%d, Attack=%d], Targets=%d, Missions=%d", + self.Defcon, self.strategy, Nassets, Ncontacts, Nborder, Nconflict, Nattack, Ntargets, Nmissions) self:I(self.lid..text) end @@ -851,11 +1089,17 @@ function CHIEF:onafterStatus(From, Event, To) -- Loop over targets. if self.verbose>=4 and #self.zonequeue>0 then local text="Zone queue:" - for i,_opszone in pairs(self.zonequeue) do - local opszone=_opszone --Ops.OpsZone#OPSZONE + for i,_stratzone in pairs(self.zonequeue) do + local stratzone=_stratzone --#CHIEF.StrategicZone - text=text..string.format("\n[%d] %s [%s]: owner=%d [%d] (prio=%d, importance=%s): Blue=%d, Red=%d, Neutral=%d", i, - opszone.zone:GetName(), opszone:GetState(), opszone:GetOwner(), opszone:GetPreviousOwner(), opszone.prio, tostring(opszone.importance), opszone.Nblu, opszone.Nred, opszone.Nnut) + -- OPS zone object. + local opszone=stratzone.opszone + + local owner=UTILS.GetCoalitionName(opszone.ownerCurrent) + local prevowner=UTILS.GetCoalitionName(opszone.ownerPrevious) + + text=text..string.format("\n[%d] %s [%s]: owner=%s [%s] (prio=%d, importance=%s): Blue=%d, Red=%d, Neutral=%d", + i, opszone.zone:GetName(), opszone:GetState(), owner, prevowner, stratzone.prio, tostring(stratzone.importance), opszone.Nblu, opszone.Nred, opszone.Nnut) end self:I(self.lid..text) @@ -871,7 +1115,7 @@ function CHIEF:onafterStatus(From, Event, To) for _,missiontype in pairs(AUFTRAG.Type) do local N=self.commander:CountAssets(nil, missiontype) if N>0 then - text=text..string.format("\n- %s %d", missiontype, N) + text=text..string.format("\n- %s: %d", missiontype, N) end end self:I(self.lid..text) @@ -880,7 +1124,7 @@ function CHIEF:onafterStatus(From, Event, To) for _,attribute in pairs(WAREHOUSE.Attribute) do local N=self.commander:CountAssets(nil, nil, attribute) if N>0 or self.verbose>=10 then - text=text..string.format("\n- %s %d", attribute, N) + text=text..string.format("\n- %s: %d", attribute, N) end end self:I(self.lid..text) @@ -897,49 +1141,15 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. +-- @param Ops.Legion#LEGION Legion The legion. -- @param Ops.Auftrag#AUFTRAG Mission The mission. -function CHIEF:onafterMissionAssignToAny(From, Event, To, Mission) +function CHIEF:onafterMissionAssign(From, Event, To, Legion, 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 "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: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)) - --TODO: Request only ground assets. - self.commander:AddMission(Mission) + Mission.chief=self + Mission.statusChief=AUFTRAG.Status.QUEUED + self.commander:MissionAssign(Legion, Mission) else self:E(self.lid..string.format("Mission cannot be assigned as no COMMANDER is defined!")) end @@ -957,6 +1167,9 @@ function CHIEF:onafterMissionCancel(From, Event, To, Mission) -- Debug info. self:I(self.lid..string.format("Cancelling mission %s (%s) in status %s", Mission.name, Mission.type, Mission.status)) + -- Set status to CANCELLED. + Mission.statusChief=AUFTRAG.Status.CANCELLED + if Mission:IsPlanned() then -- Mission is still in planning stage. Should not have any LEGIONS assigned ==> Just remove it form the COMMANDER queue. @@ -1020,21 +1233,65 @@ function CHIEF:onafterStrategyChange(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 "OpsOnMission". -- @param #CHIEF self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #CHIEF Chief The Chief we declared war on. -function CHIEF:onafterDeclareWar(From, Event, To, Chief) - - if Chief then - self:AddWarOnChief(Chief) - end - +-- @param Ops.OpsGroup#OPSGROUP OpsGroup Ops group on mission +-- @param Ops.Auftrag#AUFTRAG Mission The requested mission. +function CHIEF:onafterOpsOnMission(From, Event, To, OpsGroup, Mission) + -- Debug info. + self:T(self.lid..string.format("Group %s on mission %s [%s]", OpsGroup:GetName(), Mission:GetName(), Mission:GetType())) end + +--- On after "ZoneCaptured". +-- @param #CHIEF self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.OpsZone#OPSZONE OpsZone The zone that was captured by us. +function CHIEF:onafterZoneCaptured(From, Event, To, OpsZone) + -- Debug info. + self:T(self.lid..string.format("Zone %s captured!", OpsZone:GetName())) +end + + +--- On after "ZoneLost". +-- @param #CHIEF self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.OpsZone#OPSZONE OpsZone The zone that was lost. +function CHIEF:onafterZoneLost(From, Event, To, OpsZone) + -- Debug info. + self:T(self.lid..string.format("Zone %s lost!", OpsZone:GetName())) +end + +--- On after "ZoneEmpty". +-- @param #CHIEF self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.OpsZone#OPSZONE OpsZone The zone that is empty now. +function CHIEF:onafterZoneEmpty(From, Event, To, OpsZone) + -- Debug info. + self:T(self.lid..string.format("Zone %s empty!", OpsZone:GetName())) +end + +--- On after "ZoneAttacked". +-- @param #CHIEF self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.OpsZone#OPSZONE OpsZone The zone that beeing attacked. +function CHIEF:onafterZoneAttacked(From, Event, To, OpsZone) + -- Debug info. + self:T(self.lid..string.format("Zone %s attacked!", OpsZone:GetName())) +end + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Target Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1193,7 +1450,7 @@ function CHIEF:CheckTargetQueue() -- Assign mission to legions. for _,Legion in pairs(Legions) do - self.commander:MissionAssign(Legion, mission) + self:MissionAssign(Legion, mission) end -- Only ONE target is assigned per check. @@ -1211,11 +1468,15 @@ end -- Strategic Zone Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - --- Check strategic zone queue. -- @param #CHIEF self function CHIEF:CheckOpsZoneQueue() + -- Passive strategy ==> Do not act. + if self:IsPassive() then + return + end + -- Number of zones. local Nzones=#self.zonequeue @@ -1224,10 +1485,10 @@ function CHIEF:CheckOpsZoneQueue() return nil end - -- Sort results table wrt ?. + -- Sort results table wrt prio. local function _sort(a, b) - local taskA=a --Ops.OpsZone#OPSZONE - local taskB=b --Ops.OpsZone#OPSZONE + local taskA=a --#CHIEF.StrategicZone + local taskB=b --#CHIEF.StrategicZone return (taskA.prio Recruit Patrol zone infantry assets")) + self:T3(self.lid..string.format("Zone is empty ==> Recruit Patrol zone infantry assets")) -- Recruit ground assets that - local recruited=self:RecruitAssetsForZone(opszone, AUFTRAG.Type.PATROLZONE, 1, 3, {Group.Category.GROUND}, {GROUP.Attribute.GROUND_INFANTRY}) + local recruited=self:RecruitAssetsForZone(stratzone, AUFTRAG.Type.ONGUARD, 1, 3, {Group.Category.GROUND}, {GROUP.Attribute.GROUND_INFANTRY}) + + -- Debug info. + self:T(self.lid..string.format("Zone is empty ==> Recruit Patrol zone infantry assets=%s", tostring(recruited))) end else + + --- + -- Zone is NOT EMPTY + -- + -- We first send a CAS flight to eliminate enemy activity. + --- if not hasMissionCAS then - + -- Debug message. - self:T(self.lid..string.format("Zone is NOT empty ==> recruit CAS assets")) - + self:T3(self.lid..string.format("Zone is NOT empty ==> Recruit CAS assets")) + + -- Recruite CAS assets. - local recruited=self:RecruitAssetsForZone(opszone, AUFTRAG.Type.CAS, 1, 3) + local recruited=self:RecruitAssetsForZone(stratzone, AUFTRAG.Type.CAS, 1, 1) + + -- Debug message. + self:T(self.lid..string.format("Zone is NOT empty ==> Recruit CAS assets=%s", tostring(recruited))) end end + end + end + + -- Loop over strategic zone. + for _,_startzone in pairs(self.zonequeue) do + local stratzone=_startzone --#CHIEF.StrategicZone + + -- Current owner of the zone. + local ownercoalition=stratzone.opszone:GetOwner() + + -- Has a patrol mission? + local hasMissionPatrol=stratzone.missionPatrol and stratzone.missionPatrol:IsNotOver() or false + + -- Has a CAS mission? + local hasMissionCAS=stratzone.missionCAS and stratzone.missionCAS:IsNotOver() or false + + if ownercoalition==self.coalition and stratzone.opszone:IsEmpty() and hasMissionCAS then + -- Cancel CAS mission if zone is ours and no enemies are present. + -- TODO: Might want to check if we still have CAS capable assets in stock?! + stratzone.missionCAS:Cancel() end + + end + + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1299,7 +1603,7 @@ end --- Check if group is inside our border. -- @param #CHIEF self -- @param Wrapper.Group#GROUP group The group. --- @return #boolean If true, group is in any zone. +-- @return #boolean If true, group is in any border zone. function CHIEF:CheckGroupInBorder(group) local inside=self:CheckGroupInZones(group, self.borderzoneset) @@ -1307,14 +1611,26 @@ function CHIEF:CheckGroupInBorder(group) return inside end ---- Check if group is near our border (yellow zone). +--- Check if group is in a conflict zone. -- @param #CHIEF self -- @param Wrapper.Group#GROUP group The group. --- @return #boolean If true, group is in any zone. -function CHIEF:CheckGroupInYellow(group) +-- @return #boolean If true, group is in any conflict zone. +function CHIEF:CheckGroupInConflict(group) -- Check inside yellow but not inside our border. - local inside=self:CheckGroupInZones(group, self.yellowzoneset) and not self:CheckGroupInZones(group, self.borderzoneset) + local inside=self:CheckGroupInZones(group, self.yellowzoneset) --and not self:CheckGroupInZones(group, self.borderzoneset) + + return inside +end + +--- Check if group is in a attack zone. +-- @param #CHIEF self +-- @param Wrapper.Group#GROUP group The group. +-- @return #boolean If true, group is in any attack zone. +function CHIEF:CheckGroupInAttack(group) + + -- Check inside yellow but not inside our border. + local inside=self:CheckGroupInZones(group, self.engagezoneset) --and not self:CheckGroupInZones(group, self.borderzoneset) return inside end @@ -1329,7 +1645,7 @@ function CHIEF:CheckGroupInZones(group, zoneset) for _,_zone in pairs(zoneset.Set or {}) do local zone=_zone --Core.Zone#ZONE - if group:IsPartlyOrCompletelyInZone(zone) then + if group:IsInZone(zone) then return true end end @@ -1371,12 +1687,13 @@ function CHIEF:_CreateMissionPerformance(MissionType, Performance) return mp end ---- Create a mission to attack a group. Mission type is automatically chosen from the group category. +--- Get mission performance for a given TARGET. -- @param #CHIEF self -- @param Ops.Target#TARGET Target --- @return #table Mission performances of type #CHIEF.MissionPerformance +-- @return #table Mission performances of type `#CHIEF.MissionPerformance`. function CHIEF:_GetMissionPerformanceFromTarget(Target) + -- Possible target objects. local group=nil --Wrapper.Group#GROUP local airbase=nil --Wrapper.Airbase#AIRBASE local scenery=nil --Wrapper.Scenery#SCENERY @@ -1395,8 +1712,10 @@ function CHIEF:_GetMissionPerformanceFromTarget(Target) scenery=target end + -- Target category. local TargetCategory=Target:GetCategory() + -- Mission performances. local missionperf={} --#CHIEF.MissionPerformance if group then @@ -1458,93 +1777,55 @@ function CHIEF:_GetMissionPerformanceFromTarget(Target) end elseif airbase then - table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BOMBRUNWAY, 100)) + 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.BOMBCARPET, 50)) 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.BOMBCARPET, 50)) table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.ARTY, 30)) end return missionperf end ---- Check if group is inside our border. +--- Get mission performances for a given Group Attribute. -- @param #CHIEF self -- @param #string Attribute Group attibute. --- @return #table Mission types +-- @return #table Mission performances of type `#CHIEF.MissionPerformance`. function CHIEF:_GetMissionTypeForGroupAttribute(Attribute) - local missiontypes={} + local missionperf={} --#CHIEF.MissionPerformance if Attribute==GROUP.Attribute.AIR_ATTACKHELO then - local mt={} --#CHIEF.MissionPerformance - mt.MissionType=AUFTRAG.Type.INTERCEPT - mt.Performance=100 - table.insert(missiontypes, mt) + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.INTERCEPT), 100) elseif Attribute==GROUP.Attribute.GROUND_AAA then - local mt={} --#CHIEF.MissionPerformance - mt.MissionType=AUFTRAG.Type.BAI - mt.Performance=100 - table.insert(missiontypes, mt) - - local mt={} --#CHIEF.MissionPerformance - mt.MissionType=AUFTRAG.Type.BOMBING - mt.Performance=70 - table.insert(missiontypes, mt) - - local mt={} --#CHIEF.MissionPerformance - mt.MissionType=AUFTRAG.Type.BOMBCARPET - mt.Performance=70 - table.insert(missiontypes, mt) - - local mt={} --#CHIEF.MissionPerformance - mt.MissionType=AUFTRAG.Type.ARTY - mt.Performance=30 - table.insert(missiontypes, mt) + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BAI), 100) + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BOMBING), 80) + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BOMBCARPET), 70) + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.ARTY), 30) elseif Attribute==GROUP.Attribute.GROUND_SAM then - local mt={} --#CHIEF.MissionPerformance - mt.MissionType=AUFTRAG.Type.SEAD - mt.Performance=100 - table.insert(missiontypes, mt) - - local mt={} --#CHIEF.MissionPerformance - mt.MissionType=AUFTRAG.Type.BAI - mt.Performance=100 - table.insert(missiontypes, mt) - - local mt={} --#CHIEF.MissionPerformance - mt.MissionType=AUFTRAG.Type.ARTY - mt.Performance=50 - table.insert(missiontypes, mt) + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.SEAD), 100) + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BAI), 90) + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.ARTY), 50) elseif Attribute==GROUP.Attribute.GROUND_EWR then - local mt={} --#CHIEF.MissionPerformance - mt.MissionType=AUFTRAG.Type.SEAD - mt.Performance=100 - table.insert(missiontypes, mt) - - local mt={} --#CHIEF.MissionPerformance - mt.MissionType=AUFTRAG.Type.BAI - mt.Performance=100 - table.insert(missiontypes, mt) - - local mt={} --#CHIEF.MissionPerformance - mt.MissionType=AUFTRAG.Type.ARTY - mt.Performance=50 - table.insert(missiontypes, mt) - + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.SEAD), 100) + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BAI), 100) + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.ARTY), 50) end + return missionperf end --- Recruit assets for a given TARGET. @@ -1589,14 +1870,14 @@ end --- Recruit assets for a given OPS zone. -- @param #CHIEF self --- @param Ops.OpsZone#OPSZONE OpsZone The OPS zone +-- @param #CHIEF.StrategicZone StratZone The stratetic zone. -- @param #string MissionType Mission Type. -- @param #number NassetsMin Min number of required assets. -- @param #number NassetsMax Max number of required assets. -- @param #table Categories Group categories of the assets. -- @param #table Attributes Generalized group attributes. -- @return #boolean If `true` enough assets could be recruited. -function CHIEF:RecruitAssetsForZone(OpsZone, MissionType, NassetsMin, NassetsMax, Categories, Attributes) +function CHIEF:RecruitAssetsForZone(StratZone, MissionType, NassetsMin, NassetsMax, Categories, Attributes) -- Cohorts. local Cohorts={} @@ -1618,14 +1899,14 @@ function CHIEF:RecruitAssetsForZone(OpsZone, MissionType, NassetsMin, NassetsMax end -- Target position. - local TargetVec2=OpsZone.zone:GetVec2() + local TargetVec2=StratZone.opszone.zone:GetVec2() -- Recruite infantry assets. local recruited, assets, legions=LEGION.RecruitCohortAssets(Cohorts, MissionType, nil, NassetsMin, NassetsMax, TargetVec2, nil, nil, nil, nil, Categories, Attributes) if recruited then - if MissionType==AUFTRAG.Type.PATROLZONE then + if MissionType==AUFTRAG.Type.PATROLZONE or MissionType==AUFTRAG.Type.ONGUARD then -- Debug messgage. self:T2(self.lid..string.format("Recruited %d assets from for PATROL mission", #assets)) @@ -1633,18 +1914,28 @@ function CHIEF:RecruitAssetsForZone(OpsZone, MissionType, NassetsMin, NassetsMax local recruitedTrans=true local transport=nil if Attributes and Attributes[1]==GROUP.Attribute.GROUND_INFANTRY then + + -- Categories. Currently only helicopters are allowed due to problems with ground transports (might get stuck, might not be a land connection. + -- TODO: Check if ground transport is possible. For example, by trying land.getPathOnRoad or something. + local Categories={Group.Category.HELICOPTER} + --local Categories={Group.Category.HELICOPTER, Group.Category.GROUND} -- Recruit transport assets for infantry. - recruitedTrans, transport=LEGION.AssignAssetsForTransport(self.commander, self.commander.legions, assets, 1, 1, OpsZone.zone, nil, {Group.Category.HELICOPTER, Group.Category.GROUND}) + recruitedTrans, transport=LEGION.AssignAssetsForTransport(self.commander, self.commander.legions, assets, 1, 1, StratZone.opszone.zone, nil, Categories) end if recruitedTrans then -- Create Patrol zone mission. - local mission=AUFTRAG:NewPATROLZONE(OpsZone.zone) - mission:SetEngageDetected() - + local mission=nil --Ops.Auftrag#AUFTRAG + + if MissionType==AUFTRAG.Type.PATROLZONE then + mission=AUFTRAG:NewPATROLZONE(StratZone.opszone.zone) + elseif MissionType==AUFTRAG.Type.ONGUARD then + mission=AUFTRAG:NewONGUARD(StratZone.opszone.zone:GetRandomCoordinate(), nil, nil, {land.SurfaceType.LAND}) + end + mission:SetEngageDetected() -- Add assets to mission. for _,asset in pairs(assets) do @@ -1657,12 +1948,12 @@ function CHIEF:RecruitAssetsForZone(OpsZone, MissionType, NassetsMin, NassetsMax -- Assign mission to legions. for _,_legion in pairs(legions) do local legion=_legion --Ops.Legion#LEGION - self.commander:MissionAssign(legion, mission) + self:MissionAssign(legion, mission) end -- Attach mission to ops zone. -- TODO: Need a better way! - OpsZone.missionPatrol=mission + StratZone.missionPatrol=mission else LEGION.UnRecruitAssets(assets) @@ -1671,7 +1962,7 @@ function CHIEF:RecruitAssetsForZone(OpsZone, MissionType, NassetsMin, NassetsMax elseif MissionType==AUFTRAG.Type.CAS then -- Create Patrol zone mission. - local mission=AUFTRAG:NewCAS(OpsZone.zone) + local mission=AUFTRAG:NewCAS(StratZone.opszone.zone, 7000) -- Add assets to mission. for _,asset in pairs(assets) do @@ -1681,12 +1972,12 @@ function CHIEF:RecruitAssetsForZone(OpsZone, MissionType, NassetsMin, NassetsMax -- Assign mission to legions. for _,_legion in pairs(legions) do local legion=_legion --Ops.Legion#LEGION - self.commander:MissionAssign(legion, mission) + self:MissionAssign(legion, mission) end -- Attach mission to ops zone. -- TODO: Need a better way! - OpsZone.missionCAS=mission + StratZone.missionCAS=mission end diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index 11ed4dd31..3e6a45508 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -116,12 +116,7 @@ function COHORT:New(TemplateGroupName, Ngroups, CohortName) self:E(self.lid..string.format("ERROR: Template group %s does not exist!", tostring(self.templatename))) return nil end - - -- Defaults. - self.Ngroups=Ngroups or 3 - self:SetMissionRange() - self:SetSkill(AI.Skill.GOOD) - + -- Generalized attribute. self.attribute=self.templategroup:GetAttribute() @@ -130,7 +125,25 @@ function COHORT:New(TemplateGroupName, Ngroups, CohortName) -- Aircraft type. self.aircrafttype=self.templategroup:GetTypeName() + + -- Defaults. + self.Ngroups=Ngroups or 3 + self:SetSkill(AI.Skill.GOOD) + -- Mission range depends on + if self.category==Group.Category.AIRPLANE then + self:SetMissionRange(150) + elseif self.category==Group.Category.HELICOPTER then + self:SetMissionRange(150) + elseif self.category==Group.Category.GROUND then + self:SetMissionRange(75) + elseif self.category==Group.Category.SHIP then + self:SetMissionRange(100) + elseif self.category==Group.Category.TRAIN then + self:SetMissionRange(100) + end + + -- Units. local units=self.templategroup:GetUnits() -- Weight of the whole group. @@ -346,10 +359,10 @@ end --- Set max mission range. Only missions in a circle of this radius around the cohort base are executed. -- @param #COHORT self --- @param #number Range Range in NM. Default 100 NM. +-- @param #number Range Range in NM. Default 150 NM. -- @return #COHORT self function COHORT:SetMissionRange(Range) - self.engageRange=UTILS.NMToMeters(Range or 100) + self.engageRange=UTILS.NMToMeters(Range or 150) return self end @@ -913,6 +926,9 @@ function COHORT:RecruitAssets(MissionType, Npayloads) if flightgroup:IsCargo() or flightgroup:IsBoarding() or flightgroup:IsAwaitingLift() then combatready=false end + + -- Disable this for now as it can cause problems - at least with transport and cargo assets. + combatready=false -- This asset is "combatready". if combatready then diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index 3eacd7030..06f2df9ef 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -17,11 +17,14 @@ -- @field #string ClassName Name of the class. -- @field #number verbose Verbosity level. -- @field #string lid Class id string for output to DCS log file. +-- @field #number coalition Coalition side of the commander. +-- @field #string alias Alias name. -- @field #table legions Table of legions which are commanded. -- @field #table missionqueue Mission queue. -- @field #table transportqueue Transport queue. -- @field #table rearmingZones Rearming zones. Each element is of type `#BRIGADE.SupplyZone`. -- @field #table refuellingZones Refuelling zones. Each element is of type `#BRIGADE.SupplyZone`. +-- @field #table awacsZones AWACS zones. Each element is of type `#AIRWING.AwacsZone`. -- @field Ops.Chief#CHIEF chief Chief of staff. -- @extends Core.Fsm#FSM @@ -38,11 +41,13 @@ COMMANDER = { ClassName = "COMMANDER", verbose = 0, + coalition = nil, legions = {}, missionqueue = {}, transportqueue = {}, rearmingZones = {}, refuellingZones = {}, + awacsZones = {}, } --- COMMANDER class version. @@ -53,9 +58,9 @@ COMMANDER.version="0.1.0" -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Improve legion selection. Mostly done! --- TODO: Find solution for missions, which require a transport. This is not as easy as it sounds since the selected mission assets restrict the possible transport assets. --- TODO: Add ops transports. +-- DONE: Improve legion selection. Mostly done! +-- DONE: Find solution for missions, which require a transport. This is not as easy as it sounds since the selected mission assets restrict the possible transport assets. +-- DONE: Add ops transports. -- DONE: Allow multiple Legions for one mission. -- NOGO: Maybe it's possible to preselect the assets for the mission. @@ -65,14 +70,21 @@ COMMANDER.version="0.1.0" --- Create a new COMMANDER object and start the FSM. -- @param #COMMANDER self +-- @param #number Coalition Coaliton of the commander. +-- @param #string Alias Some name you want the commander to be called. -- @return #COMMANDER self -function COMMANDER:New() +function COMMANDER:New(Coalition, Alias) -- Inherit everything from INTEL class. local self=BASE:Inherit(self, FSM:New()) --#COMMANDER + -- Set coaliton. + self.coalition=Coalition + -- Alias name. + self.alias=Alias or string.format("Jon Doe") + -- Log ID. - self.lid="COMMANDER | " + self.lid=string.format("COMMANDER %s [%s] | ", self.alias, UTILS.GetCoalitionName(self.coalition)) -- Start state. self:SetStartState("NotReadyYet") @@ -89,6 +101,8 @@ function COMMANDER:New() self:AddTransition("*", "TransportAssign", "*") -- Transport is assigned to a or multiple LEGIONs. self:AddTransition("*", "TransportCancel", "*") -- COMMANDER cancels a Transport. + self:AddTransition("*", "OpsOnMission", "*") -- An OPSGROUP was send on a Mission (AUFTRAG). + ------------------------ --- Pseudo Functions --- ------------------------ @@ -207,6 +221,29 @@ function COMMANDER:New() -- @param #string To To state. -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. + + --- Triggers the FSM event "OpsOnMission". + -- @function [parent=#COMMANDER] OpsOnMission + -- @param #COMMANDER self + -- @param Ops.OpsGroup#OPSGROUP OpsGroup The OPS group on mission. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + --- Triggers the FSM event "OpsOnMission" after a delay. + -- @function [parent=#COMMANDER] __OpsOnMission + -- @param #COMMANDER self + -- @param #number delay Delay in seconds. + -- @param Ops.OpsGroup#OPSGROUP OpsGroup The OPS group on mission. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + --- On after "OpsOnMission" event. + -- @function [parent=#COMMANDER] OnAfterOpsOnMission + -- @param #COMMANDER self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Ops.OpsGroup#OPSGROUP OpsGroup The OPS group on mission. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + return self end @@ -223,6 +260,13 @@ function COMMANDER:SetVerbosity(VerbosityLevel) return self end +--- Get coalition. +-- @param #COMMANDER self +-- @return #number Coalition. +function COMMANDER:GetCoalition() + return self.coalition +end + --- Add an AIRWING to the commander. -- @param #COMMANDER self -- @param Ops.AirWing#AIRWING Airwing The airwing to add. @@ -268,11 +312,15 @@ end -- @return #COMMANDER self function COMMANDER:AddMission(Mission) - Mission.commander=self - - Mission.statusCommander=AUFTRAG.Status.PLANNED + if not self:IsMission(Mission) then - table.insert(self.missionqueue, Mission) + Mission.commander=self + + Mission.statusCommander=AUFTRAG.Status.PLANNED + + table.insert(self.missionqueue, Mission) + + end return self end @@ -368,6 +416,47 @@ function COMMANDER:AddRefuellingZone(RefuellingZone) return rearmingzone end +--- Add an AWACS zone. +-- @param #COMMANDER self +-- @param Core.Zone#ZONE AwacsZone Zone. +-- @param #number Altitude Orbit altitude in feet. Default is 12,0000 feet. +-- @param #number Speed Orbit speed in KIAS. Default 350 kts. +-- @param #number Heading Heading of race-track pattern in degrees. Default 270 (East to West). +-- @param #number Leg Length of race-track in NM. Default 30 NM. +-- @return Ops.AirWing#AIRWING.AwacsZone The AWACS zone. +function COMMANDER:AddAwacsZone(AwacsZone, Altitude, Speed, Heading, Leg) + + local awacszone={} --Ops.AirWing#AIRWING.AwacsZone + + awacszone.zone=AwacsZone + awacszone.altitude=Altitude or 12000 + awacszone.heading=Heading or 270 + awacszone.speed=UTILS.KnotsToAltKIAS(Speed or 350, awacszone.altitude) + awacszone.leg=Leg or 30 + awacszone.mission=nil + awacszone.marker=MARKER:New(awacszone.zone:GetCoordinate(), "AWACS Zone"):ToCoalition(self:GetCoalition()) + + table.insert(self.awacsZones, awacszone) + + return awacszone +end + +--- Check if this mission is already in the queue. +-- @param #COMMANDER self +-- @param Ops.Auftrag#AUFTRAG Mission The mission. +-- @return #boolean If `true`, this mission is in the queue. +function COMMANDER:IsMission(Mission) + + for _,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + if mission.auftragsnummer==Mission.auftragsnummer then + return true + end + end + + return false +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Start & Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -420,7 +509,7 @@ function COMMANDER:onafterStatus(From, Event, To) -- Check rearming zones. for _,_rearmingzone in pairs(self.rearmingZones) do - local rearmingzone=_rearmingzone --#BRIGADE.SupplyZone + local rearmingzone=_rearmingzone --Ops.Brigade#BRIGADE.SupplyZone -- Check if mission is nil or over. if (not rearmingzone.mission) or rearmingzone.mission:IsOver() then rearmingzone.mission=AUFTRAG:NewAMMOSUPPLY(rearmingzone.zone) @@ -430,13 +519,24 @@ function COMMANDER:onafterStatus(From, Event, To) -- Check refuelling zones. for _,_supplyzone in pairs(self.refuellingZones) do - local supplyzone=_supplyzone --#BRIGADE.SupplyZone + local supplyzone=_supplyzone --Ops.Brigade#BRIGADE.SupplyZone -- Check if mission is nil or over. if (not supplyzone.mission) or supplyzone.mission:IsOver() then supplyzone.mission=AUFTRAG:NewFUELSUPPLY(supplyzone.zone) self:AddMission(supplyzone.mission) end - end + end + + -- Check AWACS zones. + for _,_awacszone in pairs(self.awacsZones) do + local awacszone=_awacszone --Ops.AirWing#AIRWING.AwacsZone + -- Check if mission is nil or over. + if (not awacszone.mission) or awacszone.mission:IsOver() then + local Coordinate=awacszone.zone:GetCoordinate() + awacszone.mission=AUFTRAG:NewAWACS(Coordinate, awacszone.altitude, awacszone.speed, awacszone.heading, awacszone.leg) + self:AddMission(awacszone.mission) + end + end --- -- LEGIONS @@ -587,7 +687,10 @@ function COMMANDER:onafterMissionAssign(From, Event, To, Legion, Mission) -- Debug info. self:I(self.lid..string.format("Assigning mission %s (%s) to legion %s", Mission.name, Mission.type, Legion.alias)) - + + -- Add mission to queue. + self:AddMission(Mission) + -- Set mission commander status to QUEUED as it is now queued at a legion. Mission.statusCommander=AUFTRAG.Status.QUEUED @@ -696,6 +799,18 @@ function COMMANDER:onafterTransportCancel(From, Event, To, Transport) end +--- On after "OpsOnMission". +-- @param #COMMANDER self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.OpsGroup#OPSGROUP OpsGroup Ops group on mission +-- @param Ops.Auftrag#AUFTRAG Mission The requested mission. +function COMMANDER:onafterOpsOnMission(From, Event, To, OpsGroup, Mission) + -- Debug info. + self:T2(self.lid..string.format("Group %s on %s mission %s", OpsGroup:GetName(), Mission:GetType(), Mission:GetName())) +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Mission Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 16cb5e3ae..46d170ffa 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -794,50 +794,62 @@ function FLIGHTGROUP:Status() local fsmstate=self:GetState() -- Is group alive? - local alive=self:IsAlive() - - -- Update position. - self:_UpdatePosition() - - -- Check if group has detected any units. - self:_CheckDetectedUnits() + local alive=self:IsAlive() - -- Check ammo status. - self:_CheckAmmoStatus() + if alive then + + -- Update position. + self:_UpdatePosition() - -- Check damage. - self:_CheckDamage() - - --- - -- Parking - --- - - -- TODO: _CheckParking() function - - -- Check if flight began to taxi (if it was parking). - if self:IsParking() then - for _,_element in pairs(self.elements) do - local element=_element --Ops.OpsGroup#OPSGROUP.Element - if element.parking then - - -- Get distance to assigned parking spot. - local dist=element.unit:GetCoordinate():Get2DDistance(element.parking.Coordinate) - - -- If distance >10 meters, we consider the unit as taxiing. - -- TODO: Check distance threshold! If element is taxiing, the parking spot is free again. - -- When the next plane is spawned on this spot, collisions should be avoided! - if dist>10 then - if element.status==OPSGROUP.ElementStatus.ENGINEON then - self:ElementTaxiing(element) - end + -- Check if group has detected any units. + self:_CheckDetectedUnits() + + -- Check ammo status. + self:_CheckAmmoStatus() + + -- Check damage. + self:_CheckDamage() + + -- TODO: Check if group is waiting? + if self:IsWaiting() then + if self.Twaiting and self.dTwait then + if timer.getAbsTime()>self.Twaiting+self.dTwait then + --self.Twaiting=nil + --self.dTwait=nil + --self:Cruise() end - - else - --self:E(self.lid..string.format("Element %s is in PARKING queue but has no parking spot assigned!", element.name)) end end + + + -- TODO: _CheckParking() function + + -- Check if flight began to taxi (if it was parking). + if self:IsParking() then + for _,_element in pairs(self.elements) do + local element=_element --Ops.OpsGroup#OPSGROUP.Element + if element.parking then + + -- Get distance to assigned parking spot. + local dist=element.unit:GetCoordinate():Get2DDistance(element.parking.Coordinate) + + -- If distance >10 meters, we consider the unit as taxiing. + -- TODO: Check distance threshold! If element is taxiing, the parking spot is free again. + -- When the next plane is spawned on this spot, collisions should be avoided! + if dist>10 then + if element.status==OPSGROUP.ElementStatus.ENGINEON then + self:ElementTaxiing(element) + end + end + + else + --self:E(self.lid..string.format("Element %s is in PARKING queue but has no parking spot assigned!", element.name)) + end + end + end + end - + --- -- Group --- diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 5a78d0ac0..746f5344e 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -77,7 +77,8 @@ function LEGION:New(WarehouseName, LegionName) self.lid=string.format("LEGION %s | ", self.alias) -- Defaults: - -- TODO: What? + -- TODO: What? + self:SetMarker(false) -- Add FSM transitions. -- From State --> Event --> To State @@ -929,6 +930,16 @@ function LEGION:onafterOpsOnMission(From, Event, To, OpsGroup, Mission) else --TODO: Flotilla end + + -- Trigger event for chief. + if self.chief then + self.chief:OpsOnMission(OpsGroup, Mission) + end + + -- Trigger event for commander. + if self.commander then + self.commander:OpsOnMission(OpsGroup, Mission) + end end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 2a09a1f56..4473b92f9 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -477,13 +477,12 @@ OPSGROUP.version="0.7.5" -- TODO: Invisible/immortal. -- TODO: F10 menu. -- TODO: Add pseudo function. --- TODO: Afterburner restrict +-- TODO: Afterburner restrict. -- TODO: What more options? -- TODO: Damage? -- TODO: Shot events? -- TODO: Marks to add waypoints/tasks on-the-fly. -- DONE: Options EPLRS --- DONE: A lot. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -849,6 +848,7 @@ function OPSGROUP:GetLifePoints(Element) if unit then life=unit:GetLife() life0=unit:GetLife0() + life=math.min(life, life0) -- Some units have more life than life0 returns! end else @@ -5901,6 +5901,33 @@ function OPSGROUP:onafterInUtero(From, Event, To) --TODO: set element status to inutero end +--- On after "Damaged" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSGROUP:onafterDamaged(From, Event, To) + self:T(self.lid..string.format("Group damaged at t=%.3f", timer.getTime())) + + --[[ + local lifemin=nil + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + if element.status~=OPSGROUP.ElementStatus.DEAD and element.status~=OPSGROUP.ElementStatus.INUTERO then + local life, life0=self:GetLifePoints(element) + if lifemin==nil or life repeating boarding call in 10 sec") self:__Board(-10, CarrierGroup, Carrier) - -- Set carrier. As long as the group is not loaded, we only reserve the cargo space.´ + -- Set carrier. As long as the group is not loaded, we only reserve the cargo space.� CarrierGroup:_AddCargobay(self, Carrier, true) end @@ -10767,7 +10800,8 @@ function OPSGROUP:_AddElementByName(unitname) if unit then -- Get unit template. - local unittemplate=unit:GetTemplate() + --local unittemplate=unit:GetTemplate() + local unittemplate=_DATABASE:GetUnitTemplateFromUnitName(unitname) -- Element table. local element={} --#OPSGROUP.Element @@ -10781,7 +10815,7 @@ function OPSGROUP:_AddElementByName(unitname) element.DCSunit=Unit.getByName(unitname) element.gid=element.DCSunit:getNumber() element.uid=element.DCSunit:getID() - element.group=unit:GetGroup() + --element.group=unit:GetGroup() element.opsgroup=self -- Skill etc. diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index d55de1522..e860d50a5 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -171,6 +171,8 @@ OPSTRANSPORT.Status={ -- @field #boolean disembarkActivation If true, troops are spawned in late activated state when disembarked from carrier. -- @field #boolean disembarkInUtero If true, troops are disembarked "in utero". -- @field #boolean assets Cargo assets. +-- @field #number PickupFormation Formation used to pickup. +-- @field #number TransportFormation Formation used to transport. --- Path used for pickup or transport. -- @type OPSTRANSPORT.Path @@ -194,6 +196,7 @@ OPSTRANSPORT.version="0.5.0" -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO: Trains. -- TODO: Special transport cohorts/legions. Similar to mission. -- TODO: Stop/abort transport. -- DONE: Allow multiple pickup/depoly zones. @@ -749,6 +752,61 @@ function OPSTRANSPORT:GetDisembarkInUtero(TransportZoneCombo) return TransportZoneCombo.disembarkInUtero end +--- Set pickup formation. +-- @param #OPSTRANSPORT self +-- @param #number Formation Pickup formation. +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:SetFormationPickup(Formation, TransportZoneCombo) + + -- Use default TZC if no transport zone combo is provided. + TransportZoneCombo=TransportZoneCombo or self.tzcDefault + + TransportZoneCombo.PickupFormation=Formation + + return self +end + +--- Get pickup formation. +-- @param #OPSTRANSPORT self +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. +-- @return #number Formation. +function OPSTRANSPORT:_GetFormationPickup(TransportZoneCombo) + + -- Use default TZC if no transport zone combo is provided. + TransportZoneCombo=TransportZoneCombo or self.tzcDefault + + return TransportZoneCombo.PickupFormation +end + +--- Set transport formation. +-- @param #OPSTRANSPORT self +-- @param #number Formation Pickup formation. +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:SetFormationTransport(Formation, TransportZoneCombo) + + -- Use default TZC if no transport zone combo is provided. + TransportZoneCombo=TransportZoneCombo or self.tzcDefault + + TransportZoneCombo.TransportFormation=Formation + + return self +end + +--- Get transport formation. +-- @param #OPSTRANSPORT self +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. +-- @return #number Formation. +function OPSTRANSPORT:_GetFormationTransport(TransportZoneCombo) + + -- Use default TZC if no transport zone combo is provided. + TransportZoneCombo=TransportZoneCombo or self.tzcDefault + + return TransportZoneCombo.TransportFormation +end + + --- Set required cargo. This is a list of cargo groups that need to be loaded before the **first** transport will start. -- @param #OPSTRANSPORT self diff --git a/Moose Development/Moose/Ops/OpsZone.lua b/Moose Development/Moose/Ops/OpsZone.lua index 284c0a5f4..fee913378 100644 --- a/Moose Development/Moose/Ops/OpsZone.lua +++ b/Moose Development/Moose/Ops/OpsZone.lua @@ -36,8 +36,9 @@ -- @field #boolean neutralCanCapture Neutral units can capture. Default `false`. -- @field #boolean drawZone If `true`, draw the zone on the F10 map. -- @field #boolean markZone If `true`, mark the zone on the F10 map. --- @field #number prio Priority of the zone (for CHIEF queue). --- @field #number importance Importance of the zone (for CHIEF queue). +-- @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. -- @extends Core.Fsm#FSM --- Be surprised! @@ -59,23 +60,24 @@ OPSZONE = { Nred = 0, Nblu = 0, Nnut = 0, + chiefs = {}, } --- OPSZONE class version. -- @field #string version -OPSZONE.version="0.1.0" +OPSZONE.version="0.2.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Capture airbases. -- 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. +-- DONE: Capture airbases. +-- DONE: Can statics capture or hold a zone? No, unless explicitly requested by mission designer. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -136,22 +138,22 @@ function OPSZONE:New(Zone, CoalitionOwner) self.ownerCurrent=CoalitionOwner or coalition.side.NEUTRAL self.ownerPrevious=CoalitionOwner or coalition.side.NEUTRAL + -- Contested. + self.isContested=false + -- We take the airbase coalition. if self.airbase then self.ownerCurrent=self.airbase:GetCoalition() self.ownerPrevious=self.airbase:GetCoalition() end - -- Set priority (default 50) and importance (default nil). - self:SetPriority() - self:SetImportance() - -- Set object categories. self:SetObjectCategories() self:SetUnitCategories() - -- TODO: make input function - self.drawZone=true + -- Draw zone. Default is on. + self:SetDrawZone() + self:SetMarkZone(true) -- Status timer. self.timerStatus=TIMER:New(OPSZONE.Status, self) @@ -307,8 +309,14 @@ function OPSZONE:SetVerbosity(VerbosityLevel) 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 +-- +-- Which units can capture zones can be further refined by `:SetUnitCategories()`. +-- -- @param #OPSZONE self --- @param #table Categories Object categories. Default is `{Object.Category.UNIT, Object.Category.STATIC}`, i.e. UNITs and STATICs. +-- @param #table Categories Object categories. Default is `{Object.Category.UNIT}`. -- @return #OPSZONE self function OPSZONE:SetObjectCategories(Categories) @@ -349,21 +357,43 @@ function OPSZONE:SetNeutralCanCapture(CanCapture) return self end ---- **[CHIEF]** Set mission priority. +--- Set if zone is drawn on the F10 map. Color will change depending on current owning coalition. -- @param #OPSZONE self --- @param #number Prio Priority 1=high, 100=low. Default 50. +-- @param #boolean Switch If `true` or `nil`, draw zone. If `false`, zone is not drawn. -- @return #OPSZONE self -function OPSZONE:SetPriority(Prio) - self.prio=Prio or 50 +function OPSZONE:SetDrawZone(Switch) + if Switch==false then + self.drawZone=false + else + self.drawZone=true + end return self end ---- **[CHIEF]** Set importance. +--- Set if a marker on the F10 map shows the current zone status. -- @param #OPSZONE self --- @param #number Importance Number 1-10. If missions with lower value are in the queue, these have to be finished first. Default is `nil`. +-- @param #boolean Switch If `true`, zone is marked. If `false` or `nil`, zone is not marked. +-- @param #boolean ReadOnly If `true` or `nil` then mark is read only. -- @return #OPSZONE self -function OPSZONE:SetImportance(Importance) - self.importance=Importance +function OPSZONE:SetMarkZone(Switch, ReadOnly) + if Switch then + self.markZone=true + local Coordinate=self:GetCoordinate() + self.markerText=self:_GetMarkerText() + self.marker=self.marker or MARKER:New(Coordinate, self.markerText) + if ReadOnly==false then + self.marker.readonly=false + else + self.marker.readonly=true + end + self.marker:ToAll() + else + if self.marker then + self.marker:Remove() + end + self.marker=nil + self.marker=false + end return self end @@ -375,6 +405,21 @@ function OPSZONE:GetOwner() return self.ownerCurrent end +--- Get coordinate of zone. +-- @param #OPSZONE self +-- @return Core.Point#COORDINATE Coordinate of the zone. +function OPSZONE:GetCoordinate() + local coordinate=self.zone:GetCoordinate() + return coordinate +end + +--- Get name. +-- @param #OPSZONE self +-- @return #string Name of the zone. +function OPSZONE:GetName() + return self.zoneName +end + --- Get previous owner of the zone. -- @param #OPSZONE self -- @return #number Previous owner coalition. @@ -477,7 +522,7 @@ function OPSZONE:onafterStart(From, Event, To) self.timerStatus=self.timerStatus or TIMER:New(OPSZONE.Status, self) -- Status update. - self.timerStatus:Start(1, 60) + self.timerStatus:Start(1, 120) -- Handle base captured event. if self.airbase then @@ -529,6 +574,9 @@ function OPSZONE:Status() -- Evaluate the scan result. self:EvaluateZone() + + -- Update F10 marker (only if enabled). + self:_UpdateMarker() end @@ -550,6 +598,15 @@ function OPSZONE:onafterCaptured(From, Event, To, NewOwnerCoalition) -- Set owners. self.ownerPrevious=self.ownerCurrent self.ownerCurrent=NewOwnerCoalition + + for _,_chief in pairs(self.chiefs) do + local chief=_chief --Ops.Chief#CHIEF + if chief.coalition==self.ownerCurrent then + chief:ZoneCaptured(self) + else + chief:ZoneLost(self) + end + end end @@ -562,6 +619,12 @@ function OPSZONE:onafterEmpty(From, Event, To) -- Debug info. self:T(self.lid..string.format("Zone is empty EVENT")) + + -- Inform chief. + for _,_chief in pairs(self.chiefs) do + local chief=_chief --Ops.Chief#CHIEF + chief:ZoneEmpty(self) + end end @@ -575,6 +638,16 @@ function OPSZONE:onafterAttacked(From, Event, To, AttackerCoalition) -- Debug info. self:T(self.lid..string.format("Zone is being attacked by coalition=%s!", tostring(AttackerCoalition))) + + -- Inform chief. + if AttackerCoalition then + for _,_chief in pairs(self.chiefs) do + local chief=_chief --Ops.Chief#CHIEF + if chief.coalition~=AttackerCoalition then + chief:ZoneAttacked(self) + end + end + end end @@ -630,12 +703,14 @@ function OPSZONE:onenterAttacked(From, Event, To) -- Time stamp when the attack started. self.Tattacked=timer.getAbsTime() + -- Draw zone? if self.drawZone then self.zone:UndrawZone() + -- Color. + local color={1, 204/255, 204/255} - local color={1,204/255,204/255} - + -- Draw zone. self.zone:DrawZone(nil, color, 1.0, color, 0.5) end @@ -947,9 +1022,10 @@ function OPSZONE:EvaluateZone() -- No neutral units in neutral zone any more. if Nred>0 and Nblu>0 then - env.info(self.lid.."FF neutrals left neutral zone and red and blue are present! What to do?") - -- TODO Contested! - self:Attacked() + self:T(self.lid.."FF neutrals left neutral zone and red and blue are present! What to do?") + if not self:IsAttacked() then + self:Attacked() + end self.isContested=true elseif Nred>0 then -- Red captured neutral zone. @@ -964,17 +1040,30 @@ function OPSZONE:EvaluateZone() else -- Neutral zone is empty now. if not self:IsEmpty() then - self:Emtpy() + self:Empty() end end --end else - self:E(self.lid.."ERROR!") + self:E(self.lid.."ERROR: Unknown coaliton!") end + -- Finally, check airbase coalition + if self.airbase then + + -- Current coalition. + local airbasecoalition=self.airbase:GetCoalition() + + if airbasecoalition~=self.ownerCurrent then + self:T(self.lid..string.format("Captured airbase %s: Coaltion %d-->%d", self.airbaseName, self.ownerCurrent, airbasecoalition)) + self:Captured(airbasecoalition) + end + + end + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1062,6 +1151,53 @@ function OPSZONE:_GetZoneColor() return color end +--- Update marker on the F10 map. +-- @param #OPSZONE self +function OPSZONE:_UpdateMarker() + + if self.markZone then + + -- Get marker text. + local text=self:_GetMarkerText() + + -- Chck if marker text changed and if so, update the marker. + if text~=self.markerText then + self.markerText=text + self.marker:UpdateText(self.markerText) + end + + --TODO: Update position if changed. + + end + +end + +--- Get marker text +-- @param #OPSZONE self +-- @return #string Marker text. +function OPSZONE:_GetMarkerText() + + local owner=UTILS.GetCoalitionName(self.ownerCurrent) + local prevowner=UTILS.GetCoalitionName(self.ownerPrevious) + + -- Get marker text. + local text=string.format("%s: Owner=%s [%s]\nState=%s [Contested=%s]\nBlue=%d, Red=%d, Neutral=%d", + self.zoneName, owner, prevowner, self:GetState(), tostring(self:IsContested()), self.Nblu, self.Nred, self.Nnut) + + return text +end + +--- Add a chief that monitors this zone. Chief will be informed about capturing etc. +-- @param #OPSZONE self +-- @param Ops.Chief#CHIEF Chief The chief. +-- @return #table RGB color. +function OPSZONE:_AddChief(Chief) + + -- Add chief. + table.insert(self.chiefs, Chief) + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------